diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index c5f566c999..dd363b5785 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -19,7 +19,7 @@ on: - "journal/api/**" - "provision/api/**" - "readers/api/**" - - "things/api/**" + - "clients/api/**" - "users/api/**" env: @@ -29,7 +29,7 @@ env: USER_SECRET: 12345678 DOMAIN_NAME: demo-test USERS_URL: http://localhost:9002 - THINGS_URL: http://localhost:9000 + CLIENTS_URL: http://localhost:9000 HTTP_ADAPTER_URL: http://localhost:8008 INVITATIONS_URL: http://localhost:9020 AUTH_URL: http://localhost:8189 @@ -65,8 +65,8 @@ jobs: export DOMAIN_ID=$(curl -sSX POST $DOMAINS_URL -H "Content-Type: application/json" -H "Authorization: Bearer $USER_TOKEN" -d "{\"name\":\"$DOMAIN_NAME\",\"alias\":\"$DOMAIN_NAME\"}" | jq -r .id) export USER_TOKEN=$(curl -sSX POST $TOKENS_URL -H "Content-Type: application/json" -d "{\"identity\": \"$USER_IDENTITY\",\"secret\": \"$USER_SECRET\",\"domain_id\": \"$DOMAIN_ID\"}" | jq -r .access_token) echo "USER_TOKEN=$USER_TOKEN" >> $GITHUB_ENV - export THING_SECRET=$(magistrala-cli provision test | /usr/bin/grep -Eo '"secret": "[^"]+"' | awk 'NR % 2 == 0' | sed 's/"secret": "\(.*\)"/\1/') - echo "THING_SECRET=$THING_SECRET" >> $GITHUB_ENV + export CLIENT_SECRET=$(magistrala-cli provision test | /usr/bin/grep -Eo '"secret": "[^"]+"' | awk 'NR % 2 == 0' | sed 's/"secret": "\(.*\)"/\1/') + echo "CLIENT_SECRET=$CLIENT_SECRET" >> $GITHUB_ENV - name: Check for changes in specific paths uses: dorny/paths-filter@v3 @@ -113,10 +113,10 @@ jobs: - "api/openapi/readers.yml" - "readers/api/**" - things: + clients: - ".github/workflows/api-tests.yml" - - "api/openapi/things.yml" - - "things/api/**" + - "api/openapi/clients.yml" + - "clients/api/**" users: - ".github/workflows/api-tests.yml" @@ -133,12 +133,12 @@ jobs: report: false args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links' - - name: Run Things API tests - if: steps.changes.outputs.things == 'true' + - name: Run Clients API tests + if: steps.changes.outputs.clients == 'true' uses: schemathesis/action@v1 with: - schema: api/openapi/things.yml - base-url: ${{ env.THINGS_URL }} + schema: api/openapi/clients.yml + base-url: ${{ env.CLIENTS_URL }} checks: all report: false args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links' @@ -151,7 +151,7 @@ jobs: base-url: ${{ env.HTTP_ADAPTER_URL }} checks: all report: false - args: '--header "Authorization: Thing ${{ env.THING_SECRET }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links' + args: '--header "Authorization: Client ${{ env.CLIENT_SECRET }}" --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links' - name: Run Invitations API tests if: steps.changes.outputs.invitations == 'true' diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index c0ed4cd18f..ed639af93f 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -49,8 +49,8 @@ jobs: - "users/clients.go" - "pkg/clients/clients.go" - "pkg/messaging/pubsub.go" - - "things/postgres/clients.go" - - "things/things.go" + - "clients/postgres/clients.go" + - "clients/clients.go" - "pkg/authz.go" - "pkg/authn.go" - "auth/domains.go" @@ -132,9 +132,9 @@ jobs: mv ./users/mocks/repository.go ./users/mocks/repository.go.tmp mv ./users/mocks/service.go ./users/mocks/service.go.tmp mv ./pkg/messaging/mocks/pubsub.go ./pkg/messaging/mocks/pubsub.go.tmp - mv ./things/mocks/repository.go ./things/mocks/repository.go.tmp - mv ./things/mocks/service.go ./things/mocks/service.go.tmp - mv ./things/mocks/cache.go ./things/mocks/cache.go.tmp + mv ./clients/mocks/repository.go ./clients/mocks/repository.go.tmp + mv ./clients/mocks/service.go ./clients/mocks/service.go.tmp + mv ./clients/mocks/cache.go ./clients/mocks/cache.go.tmp mv ./auth/mocks/authz.go ./auth/mocks/authz.go.tmp mv ./auth/mocks/domains.go ./auth/mocks/domains.go.tmp mv ./auth/mocks/keys.go ./auth/mocks/keys.go.tmp @@ -161,7 +161,7 @@ jobs: mv ./journal/mocks/repository.go ./journal/mocks/repository.go.tmp mv ./journal/mocks/service.go ./journal/mocks/service.go.tmp mv ./auth/mocks/domains_client.go ./auth/mocks/domains_client.go.tmp - mv ./things/mocks/things_client.go ./things/mocks/things_client.go.tmp + mv ./clients/mocks/clients_client.go ./clients/mocks/clients_client.go.tmp mv ./pkg/authz/mocks/authz.go ./pkg/authz/mocks/authz.go.tmp mv ./pkg/authn/mocks/authn.go ./pkg/authn/mocks/authn.go.tmp @@ -183,9 +183,9 @@ jobs: check_mock_changes ./users/mocks/repository.go "Users Repository ./users/mocks/repository.go" check_mock_changes ./users/mocks/service.go "Users Service ./users/mocks/service.go" check_mock_changes ./pkg/messaging/mocks/pubsub.go "PubSub ./pkg/messaging/mocks/pubsub.go" - check_mock_changes ./things/mocks/repository.go "Things Repository ./things/mocks/repository.go" - check_mock_changes ./things/mocks/service.go "Things Service ./things/mocks/service.go" - check_mock_changes ./things/mocks/cache.go "Things Cache ./things/mocks/cache.go" + check_mock_changes ./clients/mocks/repository.go "Clients Repository ./clients/mocks/repository.go" + check_mock_changes ./clients/mocks/service.go "Clients Service ./clients/mocks/service.go" + check_mock_changes ./clients/mocks/cache.go "Clients Cache ./clients/mocks/cache.go" check_mock_changes ./auth/mocks/authz.go "Auth Authz ./auth/mocks/authz.go" check_mock_changes ./auth/mocks/domains.go "Auth Domains ./auth/mocks/domains.go" check_mock_changes ./auth/mocks/keys.go "Auth Keys ./auth/mocks/keys.go" @@ -214,4 +214,4 @@ jobs: check_mock_changes ./journal/mocks/service.go "Journal Service ./journal/mocks/service.go" check_mock_changes ./auth/mocks/domains_client.go "Domains Service Client ./auth/mocks/domains_client.go" check_mock_changes ./auth/mocks/token_client.go "Token Service Client ./auth/mocks/token_client.go" - check_mock_changes ./things/mocks/things_client.go "Things Service Client things/mocks/things_client.go" + check_mock_changes .clients/mocks/clients_client.go "Clients Service Client clients/mocks/clients_client.go" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9d178422a6..74e680b4e8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -111,7 +111,7 @@ jobs: - "cmd/coap/**" - "auth.pb.go" - "auth_grpc.pb.go" - - "things/**" + - "clients/**" - "pkg/messaging/**" consumers: @@ -140,7 +140,7 @@ jobs: - "cmd/http/**" - "auth.pb.go" - "auth_grpc.pb.go" - - "things/**" + - "clients/**" - "pkg/messaging/**" - "logger/**" @@ -163,7 +163,7 @@ jobs: - "cmd/mqtt/**" - "auth.pb.go" - "auth_grpc.pb.go" - - "things/**" + - "clients/**" - "pkg/messaging/**" - "logger/**" - "pkg/events/**" @@ -197,7 +197,7 @@ jobs: - "invitations/**" - "provision/**" - "readers/**" - - "things/**" + - "clients/**" - "users/**" pkg-transformers: @@ -221,12 +221,12 @@ jobs: - "cmd/timescale-reader/**" - "auth.pb.go" - "auth_grpc.pb.go" - - "things/**" + - "clients/**" - "auth/**" - things: - - "things/**" - - "cmd/things/**" + clients: + - "clients/**" + - "cmd/clients/**" - "auth.pb.go" - "auth_grpc.pb.go" - "auth/**" @@ -249,7 +249,7 @@ jobs: - "cmd/ws/**" - "auth.pb.go" - "auth_grpc.pb.go" - - "things/**" + - "clients/**" - "pkg/messaging/**" - name: Create coverage directory @@ -366,10 +366,10 @@ jobs: run: | go test --race -v -count=1 -coverprofile=coverage/readers.out ./readers/... - - name: Run things tests - if: steps.changes.outputs.things == 'true' || steps.changes.outputs.workflow == 'true' + - name: Run clients tests + if: steps.changes.outputs.clients == 'true' || steps.changes.outputs.workflow == 'true' run: | - go test --race -v -count=1 -coverprofile=coverage/things.out ./things/... + go test --race -v -count=1 -coverprofile=coverage/clients.out ./clients/... - name: Run users tests if: steps.changes.outputs.users == 'true' || steps.changes.outputs.workflow == 'true' diff --git a/Makefile b/Makefile index 3819259b0e..d83a7b935d 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,10 @@ # SPDX-License-Identifier: Apache-2.0 MG_DOCKER_IMAGE_NAME_PREFIX ?= magistrala -BUILD_DIR = build -SERVICES = auth users things http coap ws postgres-writer postgres-reader timescale-writer \ +BUILD_DIR ?= build +SERVICES = auth users clients groups channels domains http coap ws postgres-writer postgres-reader timescale-writer \ timescale-reader cli bootstrap mqtt provision certs invitations journal -TEST_API_SERVICES = journal auth bootstrap certs http invitations notifiers provision readers things users +TEST_API_SERVICES = journal auth bootstrap certs http invitations notifiers provision readers clients users TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES)) DOCKERS = $(addprefix docker_,$(SERVICES)) DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES)) @@ -19,10 +19,14 @@ empty:= space:= $(empty) $(empty) # Docker compose project name should follow this guidelines: https://docs.docker.com/compose/reference/#use--p-to-specify-a-project-name DOCKER_PROJECT ?= $(shell echo $(subst $(space),,$(USER_REPO)) | tr -c -s '[:alnum:][=-=]' '_' | tr '[:upper:]' '[:lower:]') -DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config +DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config restart DEFAULT_DOCKER_COMPOSE_COMMAND := up GRPC_MTLS_CERT_FILES_EXISTS = 0 MOCKERY_VERSION=v2.43.2 +INTERNAL_PROTO_GEN_OUT_DIR=internal/grpc +INTERNAL_PROTO_DIR=internal/proto +INTERNAL_PROTO_FILES := $(shell find $(INTERNAL_PROTO_DIR) -name "*.proto" | sed 's|$(INTERNAL_PROTO_DIR)/||') + ifneq ($(MG_MESSAGE_BROKER_TYPE),) MG_MESSAGE_BROKER_TYPE := $(MG_MESSAGE_BROKER_TYPE) else @@ -138,8 +142,8 @@ define test_api_service exit 1; \ fi - @if [ "$(svc)" = "http" ] && [ -z "$(THING_SECRET)" ]; then \ - echo "THING_SECRET is not set"; \ + @if [ "$(svc)" = "http" ] && [ -z "$(CLIENT_SECRET)" ]; then \ + echo "CLIENT_SECRET is not set"; \ echo "Please set it to a valid secret"; \ exit 1; \ fi @@ -148,7 +152,7 @@ define test_api_service st run api/openapi/$(svc).yml \ --checks all \ --base-url $(2) \ - --header "Authorization: Thing $(THING_SECRET)" \ + --header "Authorization: Client $(CLIENT_SECRET)" \ --contrib-openapi-formats-uuid \ --hypothesis-suppress-health-check=filter_too_much \ --stateful=links; \ @@ -164,7 +168,7 @@ define test_api_service endef test_api_users: TEST_API_URL := http://localhost:9002 -test_api_things: TEST_API_URL := http://localhost:9000 +test_api_clients: TEST_API_URL := http://localhost:9000 test_api_http: TEST_API_URL := http://localhost:8008 test_api_invitations: TEST_API_URL := http://localhost:9020 test_api_auth: TEST_API_URL := http://localhost:8189 @@ -179,7 +183,8 @@ $(TEST_API): proto: protoc -I. --go_out=. --go_opt=paths=source_relative pkg/messaging/*.proto - protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./*.proto + mkdir -p $(INTERNAL_PROTO_GEN_OUT_DIR) + protoc -I $(INTERNAL_PROTO_DIR) --go_out=$(INTERNAL_PROTO_GEN_OUT_DIR) --go_opt=paths=source_relative --go-grpc_out=$(INTERNAL_PROTO_GEN_OUT_DIR) --go-grpc_opt=paths=source_relative $(INTERNAL_PROTO_FILES) $(FILTERED_SERVICES): $(call compile_service,$(@)) @@ -218,7 +223,7 @@ rundev: cd scripts && ./run.sh grpc_mtls_certs: - $(MAKE) -C docker/ssl auth_grpc_certs things_grpc_certs + $(MAKE) -C docker/ssl auth_grpc_certs clients_grpc_certs check_tls: ifeq ($(GRPC_TLS),true) @@ -244,7 +249,7 @@ check_certs: check_mtls check_tls ifeq ($(GRPC_MTLS_CERT_FILES_EXISTS),0) ifeq ($(filter true,$(GRPC_MTLS) $(GRPC_TLS)),true) ifeq ($(filter $(DEFAULT_DOCKER_COMPOSE_COMMAND),$(DOCKER_COMPOSE_COMMAND)),$(DEFAULT_DOCKER_COMPOSE_COMMAND)) - $(MAKE) -C docker/ssl auth_grpc_certs things_grpc_certs + $(MAKE) -C docker/ssl auth_grpc_certs clients_grpc_certs endif endif endif @@ -257,3 +262,6 @@ run_addons: check_certs @for SVC in $(RUN_ADDON_ARGS); do \ MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \ done + +run_live: check_certs + GOPATH=$(go env GOPATH) docker compose -f docker/docker-compose.yml -f docker/docker-compose-live.yaml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) diff --git a/README.md b/README.md index 2f0f6a15a5..eda80d8e1f 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,8 @@ You like SuperMQ and you would like to make it your day job? We're always lookin [Apache-2.0](LICENSE) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fabsmach%2Fmagistrala.svg?type=large&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fabsmach%2Fmagistrala?ref=badge_large&issueType=license) +[![FOSSA Status][FOSSA]][FOSSA] + ## Data Collection for SuperMQ SuperMQ is committed to continuously improving its services and ensuring a seamless experience for its users. To achieve this, we collect certain data from your deployments. Rest assured, this data is collected solely for the purpose of enhancing SuperMQ and is not used with any malicious intent. The deployment summary can be found on our [website][callhome]. @@ -189,3 +190,4 @@ By utilizing SuperMQ, you actively contribute to its improvement. Together, we c [rodneyosodo]: https://github.com/rodneyosodo [callhome]: https://deployments.magistrala.abstractmachines.fr/ [contrib]: https://www.github.com/absmach/mg-contrib +[FOSSA]: https://app.fossa.com/api/projects/git%2Bgithub.com%2Fabsmach%2Fsupermq.svg?type=small \ No newline at end of file diff --git a/api/asyncapi/mqtt.yml b/api/asyncapi/mqtt.yml index 4a4d1575ea..fa902a22e8 100644 --- a/api/asyncapi/mqtt.yml +++ b/api/asyncapi/mqtt.yml @@ -17,8 +17,8 @@ info: license: name: Apache 2.0 url: 'https://github.com/absmach/magistrala/blob/main/LICENSE' - - + + defaultContentType: application/json servers: @@ -33,7 +33,7 @@ servers: enum: - '1883' - '8883' - security: + security: - user-password: [] channels: @@ -45,7 +45,7 @@ channels: required: true subtopic: $ref: '#/components/parameters/subtopic' - in: path + in: path required: false publish: @@ -88,7 +88,7 @@ components: parameters: channelID: - description: Channel ID connected to the Thing ID defined in the username. + description: Channel ID connected to the Client ID defined in the username. schema: type: string format: uuid @@ -97,13 +97,13 @@ components: schema: type: string default: '' - + securitySchemes: user-password: type: userPassword description: | - username is thing ID connected to the channel defined in the mqtt topic and - password is thing key corresponding to the thing ID + username is client ID connected to the channel defined in the mqtt topic and + password is client secret corresponding to the client ID operationTraits: mqtt: diff --git a/api/asyncapi/websocket.yml b/api/asyncapi/websocket.yml index 0f514c8ab3..536b4e30bd 100644 --- a/api/asyncapi/websocket.yml +++ b/api/asyncapi/websocket.yml @@ -126,7 +126,7 @@ components: ``` parameters: channelID: - description: Channel ID connected to the Thing ID defined in the username. + description: Channel ID connected to the Client ID defined in the username. schema: type: string format: uuid @@ -141,4 +141,4 @@ components: scheme: bearer bearerFormat: uuid description: | - * Thing access: "Authorization: Thing " + * Client access: "Authorization: Client " diff --git a/api/openapi/auth.yml b/api/openapi/auth.yml index fde5df18ec..06ba97969c 100644 --- a/api/openapi/auth.yml +++ b/api/openapi/auth.yml @@ -31,7 +31,7 @@ tags: externalDocs: description: Find out more about domains url: https://docs.magistrala.abstractmachines.fr/ - + - name: Health description: Service health check endpoint. externalDocs: @@ -549,7 +549,7 @@ components: metadata: type: object example: { "domain": "example.com" } - description: Arbitrary, object-encoded thing's data. + description: Arbitrary, object-encoded client's data. alias: type: string example: domain alias diff --git a/api/openapi/bootstrap.yml b/api/openapi/bootstrap.yml index 4298604215..50ca0123e8 100644 --- a/api/openapi/bootstrap.yml +++ b/api/openapi/bootstrap.yml @@ -5,7 +5,7 @@ openapi: 3.0.1 info: title: Magistrala Bootstrap service description: | - HTTP API for managing platform things configuration. + HTTP API for managing platform clients configuration. Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: @@ -27,7 +27,7 @@ tags: url: https://docs.magistrala.abstractmachines.fr/ paths: - /{domainID}/things/configs: + /{domainID}/clients/configs: post: operationId: createConfig summary: Adds new config @@ -60,7 +60,7 @@ paths: "500": $ref: "#/components/responses/ServiceError" "503": - description: Failed to receive response from the things service. + description: Failed to receive response from the clients service. get: operationId: getConfigs summary: Retrieves managed configs @@ -88,7 +88,7 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/configs/{configId}: + /{domainID}/clients/configs/{configId}: get: operationId: getConfig summary: Retrieves config info (with channels). @@ -118,7 +118,7 @@ paths: description: | Update is performed by replacing the current resource data with values provided in a request payload. Note that the owner, ID, external ID, - external key, Magistrala Thing ID and key cannot be changed. + external key, Magistrala Client ID and key cannot be changed. tags: - configs parameters: @@ -167,7 +167,7 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/configs/certs/{configId}: + /{domainID}/clients/configs/certs/{configId}: patch: operationId: updateConfigCerts summary: Updates certs @@ -199,13 +199,13 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/configs/connections/{configId}: + /{domainID}/clients/configs/connections/{configId}: put: operationId: updateConfigConnections - summary: Updates channels the thing is connected to + summary: Updates channels the client is connected to description: | Update connections performs update of the channel list corresponding - Thing is connected to. + Client is connected to. tags: - configs parameters: @@ -230,7 +230,7 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /things/bootstrap/{externalId}: + /clients/bootstrap/{externalId}: get: operationId: getBootstrapConfig summary: Retrieves configuration. @@ -255,7 +255,7 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /things/bootstrap/secure/{externalId}: + /clients/bootstrap/secure/{externalId}: get: operationId: getSecureBootstrapConfig summary: Retrieves configuration. @@ -281,13 +281,13 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/state/{configId}: + /{domainID}/clients/state/{configId}: put: operationId: updateConfigState summary: Updates Config state. description: | Updating state represents enabling/disabling Config, i.e. connecting - and disconnecting corresponding Magistrala Thing to the list of Channels. + and disconnecting corresponding Magistrala Client to the list of Channels. tags: - configs parameters: @@ -330,14 +330,14 @@ components: Config: type: object properties: - thing_id: + client_id: type: string format: uuid - description: Corresponding Magistrala Thing ID. - magistrala_key: + description: Corresponding Magistrala Client ID. + magistrala_secret: type: string format: uuid - description: Corresponding Magistrala Thing key. + description: Corresponding Magistrala Client key. channels: type: array minItems: 0 @@ -402,14 +402,14 @@ components: BootstrapConfig: type: object properties: - thing_id: + client_id: type: string format: uuid - description: Corresponding Magistrala Thing ID. - thing_key: + description: Corresponding Magistrala Client ID. + client_key: type: string format: uuid - description: Corresponding Magistrala Thing key. + description: Corresponding Magistrala Client key. channels: type: array minItems: 0 @@ -428,17 +428,17 @@ components: type: string description: Issuing CA certificate. required: - - thing_id - - thing_key + - client_id + - client_key - channels - content ConfigUpdateCerts: type: object properties: - thing_id: + client_id: type: string format: uuid - description: Corresponding Magistrala Thing ID. + description: Corresponding Magistrala Client ID. client_cert: type: string description: Client certificate. @@ -449,15 +449,15 @@ components: type: string description: Issuing CA certificate. required: - - thing_id - - thing_key + - client_id + - client_key - channels - content parameters: ConfigId: name: configId - description: Unique Config identifier. It's the ID of the corresponding Thing. + description: Unique Config identifier. It's the ID of the corresponding Client. in: path schema: type: string @@ -519,10 +519,10 @@ components: external_key: type: string description: External key. - thing_id: + client_id: type: string format: uuid - description: ID of the corresponding Magistrala Thing. + description: ID of the corresponding Magistrala Client. channels: type: array minItems: 0 @@ -535,17 +535,17 @@ components: type: string client_cert: type: string - description: Thing Certificate. + description: Client Certificate. client_key: type: string - description: Thing Private Key. + description: Client Private Key. ca_cert: type: string required: - external_id - external_key ConfigUpdateReq: - description: JSON-formatted document describing the updated thing. + description: JSON-formatted document describing the updated client. content: application/json: schema: @@ -559,7 +559,7 @@ components: - content - name ConfigCertUpdateReq: - description: JSON-formatted document describing the updated thing. + description: JSON-formatted document describing the updated client. content: application/json: schema: @@ -572,7 +572,7 @@ components: ca_cert: type: string ConfigConnUpdateReq: - description: Array if IDs the thing is be connected to. + description: Array if IDs the client is be connected to. content: application/json: schema: @@ -603,7 +603,7 @@ components: text/plain: schema: type: string - description: Created configuration's relative URL (i.e. /things/configs/{configId}). + description: Created configuration's relative URL (i.e. /clients/configs/{configId}). ConfigListRes: description: Data retrieved. Configs from this list don't contain channels. content: @@ -673,14 +673,14 @@ components: scheme: bearer bearerFormat: string description: | - * Things access: "Authorization: Thing " + * Clients access: "Authorization: Client " bootstrapEncAuth: type: http scheme: bearer bearerFormat: aes-sha256-uuid description: | - * Things access: "Authorization: Thing " + * Clients access: "Authorization: Client " Hex-encoded configuration external key encrypted using the AES algorithm and SHA256 sum of the external key itself as an encryption key. diff --git a/api/openapi/certs.yml b/api/openapi/certs.yml index b5ced9377b..a7b49baa9c 100644 --- a/api/openapi/certs.yml +++ b/api/openapi/certs.yml @@ -30,8 +30,8 @@ paths: /{domainID}/certs: post: operationId: createCert - summary: Creates a certificate for thing - description: Creates a certificate for thing + summary: Creates a certificate for client + description: Creates a certificate for client tags: - certs parameters: @@ -106,17 +106,17 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /{domainID}/serials/{thingID}: + /{domainID}/serials/{clientID}: get: operationId: getSerials summary: Retrieves certificates' serial IDs description: | - Retrieves a list of certificates' serial IDs for a given thing ID. + Retrieves a list of certificates' serial IDs for a given client ID. tags: - certs parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/ClientID" responses: "200": $ref: "#/components/responses/SerialsPageRes" @@ -147,9 +147,9 @@ paths: components: parameters: - ThingID: - name: thingID - description: Thing ID + ClientID: + name: clientID + description: Client ID in: path schema: type: string @@ -168,10 +168,10 @@ components: Cert: type: object properties: - thing_id: + client_id: type: string format: uuid - description: Corresponding Magistrala Thing ID. + description: Corresponding Magistrala Client ID. client_cert: type: string description: Client Certificate. @@ -240,18 +240,18 @@ components: requestBodies: CertReq: description: | - Issues a certificate that is required for mTLS. To create a certificate for a thing - provide a thing id, data identifying particular thing will be embedded into the Certificate. + Issues a certificate that is required for mTLS. To create a certificate for a client + provide a client id, data identifying particular client will be embedded into the Certificate. x509 and ECC certificates are supported when using when Vault is used as PKI. content: application/json: schema: type: object required: - - thing_id + - client_id - ttl properties: - thing_id: + client_id: type: string format: uuid ttl: @@ -271,7 +271,7 @@ components: serial: operationId: getSerials parameters: - thingID: $response.body#/thing_id + clientID: $response.body#/client_id delete: operationId: revokeCert parameters: diff --git a/api/openapi/things.yml b/api/openapi/clients.yml similarity index 80% rename from api/openapi/things.yml rename to api/openapi/clients.yml index 4550ef4610..37f500bbdd 100644 --- a/api/openapi/things.yml +++ b/api/openapi/clients.yml @@ -3,9 +3,9 @@ openapi: 3.0.3 info: - title: Magistrala Things Service + title: Magistrala Clients Service description: | - This is the Things Server based on the OpenAPI 3.0 specification. It is the HTTP API for managing platform things and channels. You can now help us improve the API whether it's by making changes to the definition itself or to the code. + This is the Clients Server based on the OpenAPI 3.0 specification. It is the HTTP API for managing platform clients and channels. You can now help us improve the API whether it's by making changes to the definition itself or to the code. Some useful links: - [The Magistrala repository](https://github.com/absmach/magistrala) contact: @@ -20,39 +20,39 @@ servers: - url: https://localhost:9000 tags: - - name: Things - description: Everything about your Things + - name: Clients + description: Everyclient about your Clients externalDocs: - description: Find out more about things + description: Find out more about clients url: https://docs.magistrala.abstractmachines.fr/ - name: Channels - description: Everything about your Channels + description: Everyclient about your Channels externalDocs: - description: Find out more about things channels + description: Find out more about clients channels url: https://docs.magistrala.abstractmachines.fr/ - name: Policies - description: Access to things policies + description: Access to clients policies externalDocs: - description: Find out more about things policies + description: Find out more about clients policies url: https://docs.magistrala.abstractmachines.fr/ paths: - /{domainID}/things: + /{domainID}clients: post: - operationId: createThing + operationId: createClient tags: - - Things - summary: Adds new thing + - Clients + summary: Adds new client description: | - Adds new thing to the list of things owned by user identified using + Adds new client to the list of clients owned by user identified using the provided access token. parameters: - $ref: "auth.yml#/components/parameters/DomainID" requestBody: - $ref: "#/components/requestBodies/ThingCreateReq" + $ref: "#/components/requestBodies/ClientCreateReq" responses: "201": - $ref: "#/components/responses/ThingCreateRes" + $ref: "#/components/responses/ClientCreateRes" "400": description: Failed due to malformed JSON. "401": @@ -71,13 +71,13 @@ paths: $ref: "#/components/responses/ServiceError" get: - operationId: listThings + operationId: listClients tags: - - Things - summary: Retrieves things + - Clients + summary: Retrieves clients description: | - Retrieves a list of things. Due to performance concerns, data - is retrieved in subsets. The API things must ensure that the entire + Retrieves a list of clients. Due to performance concerns, data + is retrieved in subsets. The API clients must ensure that the entire dataset is consumed either by making subsequent requests, or by increasing the subset size of the initial request. parameters: @@ -86,13 +86,13 @@ paths: - $ref: "#/components/parameters/Offset" - $ref: "#/components/parameters/Metadata" - $ref: "#/components/parameters/Status" - - $ref: "#/components/parameters/ThingName" + - $ref: "#/components/parameters/ClientName" - $ref: "#/components/parameters/Tags" security: - bearerAuth: [] responses: "200": - $ref: "#/components/responses/ThingPageRes" + $ref: "#/components/responses/ClientPageRes" "400": description: Failed due to malformed query parameters. "401": @@ -107,22 +107,22 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/bulk: + /{domainID}clients/bulk: post: - operationId: bulkCreateThings - summary: Bulk provisions new things + operationId: bulkCreateClients + summary: Bulk provisions new clients description: | - Adds new things to the list of things owned by user identified using + Adds a list og new clients to the list of clients owned by user identified using the provided access token. parameters: - $ref: "auth.yml#/components/parameters/DomainID" tags: - - Things + - Clients requestBody: - $ref: "#/components/requestBodies/ThingsCreateReq" + $ref: "#/components/requestBodiesclientsCreateReq" responses: "200": - $ref: "#/components/responses/ThingPageRes" + $ref: "#/components/responses/ClientPageRes" "400": description: Failed due to malformed JSON. "401": @@ -138,22 +138,22 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/{thingID}: + /{domainID}clients/{clientID}: get: - operationId: getThing - summary: Retrieves thing info + operationId: getClient + summary: Retrieves client info description: | - Retrieves a specific thing that is identifier by the thing ID. + Retrieves a specific client that is identifier by the client ID. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" security: - bearerAuth: [] responses: "200": - $ref: "#/components/responses/ThingRes" + $ref: "#/components/responses/ClientRes" "400": description: Failed due to malformed domain ID. "401": @@ -168,24 +168,24 @@ paths: $ref: "#/components/responses/ServiceError" patch: - operationId: updateThing - summary: Updates name and metadata of the thing. + operationId: updateClient + summary: Updates name and metadata of the client. description: | Update is performed by replacing the current resource data with values - provided in a request payload. Note that the thing's type and ID + provided in a request payload. Note that the client's type and ID cannot be changed. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" requestBody: - $ref: "#/components/requestBodies/ThingUpdateReq" + $ref: "#/components/requestBodies/ClientUpdateReq" security: - bearerAuth: [] responses: "200": - $ref: "#/components/responses/ThingRes" + $ref: "#/components/responses/ClientRes" "400": description: Failed due to malformed JSON. "401": @@ -193,7 +193,7 @@ paths: "403": description: Failed to perform authorization over the entity. "404": - description: Failed due to non existing thing. + description: Failed due to non existing client. "409": description: Failed due to using an existing identity. "415": @@ -203,56 +203,56 @@ paths: "500": $ref: "#/components/responses/ServiceError" delete: - summary: Delete thing for a thing with the given id. + summary: Delete client for a client with the given id. description: | - Delete thing removes a thing with the given id from repo - and removes all the policies related to this thing. + Delete client removes a client with the given id from repo + and removes all the policies related to this client. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" security: - bearerAuth: [] responses: "204": - description: Thing deleted. + description: Client deleted. "400": description: Failed due to malformed domain ID. "401": description: Missing or invalid access token provided. "403": - description: Unauthorized access to thing id. + description: Unauthorized access to client id. "404": - description: Missing thing. + description: Missing client. "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/{thingID}/tags: + /{domainID}clients/{clientID}/tags: patch: - operationId: updateThingTags - summary: Updates tags the thing. + operationId: updateClientTags + summary: Updates tags the client. description: | - Updates tags of the thing with provided ID. Tags is updated using + Updates tags of the client with provided ID. Tags is updated using authorization token and the new tags received in request. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" requestBody: - $ref: "#/components/requestBodies/ThingUpdateTagsReq" + $ref: "#/components/requestBodies/ClientUpdateTagsReq" security: - bearerAuth: [] responses: "200": - $ref: "#/components/responses/ThingRes" + $ref: "#/components/responses/ClientRes" "400": description: Failed due to malformed JSON. "403": description: Failed to perform authorization over the entity. "404": - description: Failed due to non existing thing. + description: Failed due to non existing client. "401": description: Missing or invalid access token provided. "422": @@ -260,25 +260,25 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/{thingID}/secret: + /{domainID}clients/{clientID}/secret: patch: - operationId: updateThingSecret - summary: Updates secret of the identified thing. + operationId: updateClientSecret + summary: Updates Secret of the identified client. description: | - Updates secret of the identified in thing. Secret is updated using + Updates secret of the identified in client. Secret is updated using authorization token and the new received info. Update is performed by replacing current key with a new one. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" requestBody: - $ref: "#/components/requestBodies/ThingUpdateSecretReq" + $ref: "#/components/requestBodies/ClientUpdateSecretReq" security: - bearerAuth: [] responses: "200": - $ref: "#/components/responses/ThingRes" + $ref: "#/components/responses/ClientRes" "400": description: Failed due to malformed JSON. "401": @@ -286,7 +286,7 @@ paths: "403": description: Failed to perform authorization over the entity. "404": - description: Failed due to non existing thing. + description: Failed due to non existing client. "409": description: Specified key already exists. "415": @@ -296,24 +296,24 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/{thingID}/disable: + /{domainID}clients/{clientID}/disable: post: - operationId: disableThing - summary: Disables a thing + operationId: disableClient + summary: Disables a client description: | - Disables a specific thing that is identifier by the thing ID. + Disables a specific client that is identifier by the client ID. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" security: - bearerAuth: [] responses: "200": - $ref: "#/components/responses/ThingRes" + $ref: "#/components/responses/ClientRes" "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -321,30 +321,30 @@ paths: "404": description: A non-existent entity request. "409": - description: Failed due to already disabled thing. + description: Failed due to already disabled client. "422": description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/{thingID}/enable: + /{domainID}clients/{clientID}/enable: post: - operationId: enableThing - summary: Enables a thing + operationId: enableClient + summary: Enables a client description: | - Enables a specific thing that is identifier by the thing ID. + Enables a specific client that is identifier by the client ID. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" security: - bearerAuth: [] responses: "200": - $ref: "#/components/responses/ThingRes" + $ref: "#/components/responses/ClientRes" "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -352,32 +352,32 @@ paths: "404": description: A non-existent entity request. "409": - description: Failed due to already enabled thing. + description: Failed due to already enabled client. "422": description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/{thingID}/share: + /{domainID}clients/{clientID}/share: post: - operationId: shareThing - summary: Shares a thing + operationId: shareClient + summary: Shares a client description: | - Shares a specific thing that is identified by the thing ID. + Shares a specific client that is identifier by the client ID. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" requestBody: - $ref: "#/components/requestBodies/ShareThingReq" + $ref: "#/components/requestBodies/ShareClientReq" security: - bearerAuth: [] responses: "200": - description: Thing shared. + description: Client shared. "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -389,26 +389,26 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/{thingID}/unshare: + /{domainID}clients/{clientID}/unshare: post: - operationId: unshareThing - summary: Unshares a thing + operationId: unshareClient + summary: Unshares a client description: | - Unshares a specific thing that is identified by the thing ID. + Unshares a specific client that is identifier by the client ID. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" requestBody: - $ref: "#/components/requestBodies/ShareThingReq" + $ref: "#/components/requestBodies/ShareClientReq" security: - bearerAuth: [] responses: "200": - description: Thing unshared. + description: Client unshared. "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -420,15 +420,15 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/channels/{chanID}/things: + /{domainID}/channels/{chanID}clients: get: - operationId: listThingsInaChannel - summary: List of things connected to specified channel + operationId: listClientsInaChannel + summary: List of clients connected to specified channel description: | - Retrieves list of things connected to specified channel with pagination + Retrieves list of clients connected to specified channel with pagination metadata. tags: - - Things + - Clients parameters: - $ref: "auth.yml#/components/parameters/DomainID" - $ref: "#/components/parameters/chanID" @@ -437,7 +437,7 @@ paths: - $ref: "#/components/parameters/Connected" responses: "200": - $ref: "#/components/responses/ThingsPageRes" + $ref: "#/components/responsesclientsPageRes" "400": description: Failed due to malformed query parameters. "401": @@ -490,7 +490,7 @@ paths: summary: Lists channels. description: | Retrieves a list of channels. Due to performance concerns, data - is retrieved in subsets. The API things must ensure that the entire + is retrieved in subsets. The API clients must ensure that the entire dataset is consumed either by making subsequent requests, or by increasing the subset size of the initial request. tags: @@ -603,7 +603,7 @@ paths: "401": description: Missing or invalid access token provided. "403": - description: Unauthorized access to thing id. + description: Unauthorized access to client id. "404": description: A non-existent entity request. "500": @@ -688,9 +688,9 @@ paths: - bearerAuth: [] responses: "200": - description: Thing shared. + description: Client shared. "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -719,9 +719,9 @@ paths: - bearerAuth: [] responses: "204": - description: Thing unshared. + description: Client unshared. "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -750,9 +750,9 @@ paths: - bearerAuth: [] responses: "200": - description: Thing shared. + description: Client shared. "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -781,9 +781,9 @@ paths: - bearerAuth: [] responses: "204": - description: Thing unshared. + description: Client unshared. "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -795,18 +795,18 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/things/{thingID}/channels: + /{domainID}clients/{clientID}/channels: get: - operationId: listChannelsConnectedToThing - summary: List of channels connected to specified thing + operationId: listChannelsConnectedToClient + summary: List of channels connected to specified client description: | - Retrieves list of channels connected to specified thing with pagination + Retrieves list of channels connected to specified client with pagination metadata. tags: - Channels parameters: - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/Offset" - $ref: "#/components/parameters/Limit" responses: @@ -819,7 +819,7 @@ paths: "403": description: Failed to perform authorization over the entity. "404": - description: Thing does not exist. + description: Client does not exist. "422": description: Database can't process request. "500": @@ -849,7 +849,7 @@ paths: "403": description: Failed to perform authorization over the entity. "404": - description: Thing does not exist. + description: Client does not exist. "422": description: Database can't process request. "500": @@ -879,7 +879,7 @@ paths: "403": description: Failed to perform authorization over the entity. "404": - description: Thing does not exist. + description: Client does not exist. "422": description: Database can't process request. "500": @@ -887,11 +887,11 @@ paths: /{domainID}/connect: post: - operationId: connectThingsAndChannels - summary: Connects thing and channel. + operationId: connectClientsAndChannels + summary: Connects client and channel. description: | - Connect things specified by IDs to channels specified by IDs. - Channel and thing are owned by user identified using the provided access token. + Connect clients specified by IDs to channels specified by IDs. + Channel and client are owned by user identified using the provided access token. parameters: - $ref: "auth.yml#/components/parameters/DomainID" tags: @@ -920,11 +920,11 @@ paths: /{domainID}/disconnect: post: - operationId: disconnectThingsAndChannels - summary: Disconnect things and channels using lists of IDs. + operationId: disconnectClientsAndChannels + summary: Disconnect clients and channels using lists of IDs. description: | - Disconnect things from channels specified by lists of IDs. - Channels and things are owned by user identified using the provided access token. + Disconnect clients from channels specified by lists of IDs. + Channels and clients are owned by user identified using the provided access token. parameters: - $ref: "auth.yml#/components/parameters/DomainID" tags: @@ -949,23 +949,23 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/channels/{chanID}/things/{thingID}/connect: + /{domainID}/channels/{chanID}clients/{clientID}/connect: post: - operationId: connectThingToChannel - summary: Connects a thing to a channel + operationId: connectClientToChannel + summary: Connects a client to a channel description: | - Connects a specific thing to a channel that is identifier by the channel ID. + Connects a specific client to a channel that is identifier by the channel ID. tags: - Policies parameters: - $ref: "auth.yml#/components/parameters/DomainID" - $ref: "#/components/parameters/chanID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" responses: "200": - description: Thing connected. + description: Client connected. "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -977,23 +977,23 @@ paths: "500": $ref: "#/components/responses/ServiceError" - /{domainID}/channels/{chanID}/things/{thingID}/disconnect: + /{domainID}/channels/{chanID}clients/{clientID}/disconnect: post: - operationId: disconnectThingFromChannel - summary: Disconnects a thing to a channel + operationId: disconnectClientFromChannel + summary: Disconnects a client to a channel description: | - Disconnects a specific thing to a channel that is identifier by the channel ID. + Disconnects a specific client to a channel that is identifier by the channel ID. tags: - Policies parameters: - $ref: "auth.yml#/components/parameters/DomainID" - $ref: "#/components/parameters/chanID" - - $ref: "#/components/parameters/ThingID" + - $ref: "#/components/parameters/clientID" responses: "200": - description: Thing connected. + description: Client connected. "400": - description: Failed due to malformed thing's ID. + description: Failed due to malformed client's ID. "401": description: Missing or invalid access token provided. "403": @@ -1019,27 +1019,27 @@ paths: components: schemas: - ThingReqObj: + ClientReqObj: type: object properties: name: type: string - example: thingName - description: Thing name. + example: clientName + description: Client name. tags: type: array minItems: 0 items: type: string example: ["tag1", "tag2"] - description: Thing tags. + description: Client tags. credentials: type: object properties: identity: type: string - example: "thingidentity" - description: Thing's identity will be used as its unique identifier + example: "clientIDentity" + description: Client's identity will be used as its unique identifier secret: type: string format: password @@ -1049,10 +1049,10 @@ components: metadata: type: object example: { "model": "example" } - description: Arbitrary, object-encoded thing's data. + description: Arbitrary, object-encoded client's data. status: type: string - description: Thing Status + description: Client Status format: string example: enabled required: @@ -1100,9 +1100,12 @@ components: "bb7edb32-2eac-4aad-aebe-ed96fe073879", ] relation: - type: string - example: "editor" - description: Policy relation. + type: array + minItems: 0 + items: + type: string + example: ["m_write", "g_add"] + description: Policy relations. required: - user_ids - relation @@ -1123,7 +1126,7 @@ components: ] relation: type: string - example: "editor" + example: "m_write" description: Policy relations. member_kind: type: string @@ -1150,7 +1153,7 @@ components: ] relation: type: string - example: "editor" + example: "m_write" description: Policy relations. required: - users_ids @@ -1173,48 +1176,48 @@ components: required: - group_ids - Thing: + Client: type: object properties: id: type: string format: uuid example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing unique identifier. + description: Client unique identifier. name: type: string - example: thingName - description: Thing name. + example: clientName + description: Client name. tags: type: array minItems: 0 items: type: string example: ["tag1", "tag2"] - description: Thing tags. + description: Client tags. domain_id: type: string format: uuid example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: ID of the domain to which thing belongs. + description: ID of the domain to which client belongs. credentials: type: object properties: identity: type: string - example: thingidentity - description: Thing Identity for example email address. + example: clientIDentity + description: Client Identity for example email address. secret: type: string example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing secret password. + description: Client secret password. metadata: type: object example: { "model": "example" } - description: Arbitrary, object-encoded thing's data. + description: Arbitrary, object-encoded client's data. status: type: string - description: Thing Status + description: Client Status format: string example: enabled created_at: @@ -1228,50 +1231,50 @@ components: example: "2019-11-26 13:31:52" description: Time when the channel was created. xml: - name: thing + name: client - ThingWithEmptySecret: + ClientWithEmptySecret: type: object properties: id: type: string format: uuid example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing unique identifier. + description: Client unique identifier. name: type: string - example: thingName - description: Thing name. + example: clientName + description: Client name. tags: type: array minItems: 0 items: type: string example: ["tag1", "tag2"] - description: Thing tags. + description: Client tags. domain_id: type: string format: uuid example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: ID of the domain to which thing belongs. + description: ID of the domain to which client belongs. credentials: type: object properties: identity: type: string - example: thingidentity - description: Thing Identity for example email address. + example: clientIDentity + description: Client Identity for example email address. secret: type: string example: "" - description: Thing secret password. + description: Client secret password. metadata: type: object example: { "model": "example" } - description: Arbitrary, object-encoded thing's data. + description: Arbitrary, object-encoded client's data. status: type: string - description: Thing Status + description: Client Status format: string example: enabled created_at: @@ -1285,7 +1288,7 @@ components: example: "2019-11-26 13:31:52" description: Time when the channel was created. xml: - name: thing + name: client Channel: type: object @@ -1383,15 +1386,15 @@ components: xml: name: policy - ThingsPage: + ClientsPage: type: object properties: - things: + clients: type: array minItems: 0 uniqueItems: true items: - $ref: "#/components/schemas/ThingWithEmptySecret" + $ref: "#/components/schemas/ClientWithEmptySecret" total: type: integer example: 1 @@ -1404,7 +1407,7 @@ components: example: 10 description: Maximum number of items to return in one page. required: - - things + - clients - total - offset @@ -1458,40 +1461,40 @@ components: - total - offset - ThingUpdate: + ClientUpdate: type: object properties: name: type: string - example: thingName - description: Thing name. + example: clientName + description: Client name. metadata: type: object example: { "role": "general" } - description: Arbitrary, object-encoded thing's data. + description: Arbitrary, object-encoded client's data. required: - name - metadata - ThingTags: + ClientTags: type: object properties: tags: type: array example: ["tag1", "tag2"] - description: Thing tags. + description: Client tags. minItems: 0 uniqueItems: true items: type: string - ThingSecret: + ClientSecret: type: object properties: secret: type: string example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: New thing secret. + description: New client secret. required: - secret @@ -1525,7 +1528,7 @@ components: example: bb7edb32-2eac-4aad-aebe-ed96fe073879 subjects: type: array - description: Thing IDs + description: Client IDs items: example: bb7edb32-2eac-4aad-aebe-ed96fe073879 permission: @@ -1544,7 +1547,7 @@ components: example: bb7edb32-2eac-4aad-aebe-ed96fe073879 subjects: type: array - description: Thing IDs + description: Client IDs items: example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1575,16 +1578,16 @@ components: description: type: string description: Service description. - example: things service + example: clients service build_time: type: string description: Service build time. example: 1970-01-01_00:00:00 parameters: - ThingID: - name: thingID - description: Unique thing identifier. + clientID: + name: clientID + description: Unique client identifier. in: path schema: type: string @@ -1608,18 +1611,18 @@ components: required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - ThingName: + ClientName: name: name - description: Thing's name. + description: Client's name. in: query schema: type: string required: false - example: "thingName" + example: "clientName" Status: name: status - description: Thing account status. + description: Client account status. in: query schema: type: string @@ -1629,7 +1632,7 @@ components: Tags: name: tags - description: Thing tags. + description: Client tags. in: query schema: type: array @@ -1739,40 +1742,40 @@ components: required: false requestBodies: - ThingCreateReq: - description: JSON-formatted document describing the new thing to be registered + ClientCreateReq: + description: JSON-formatted document describing the new client to be registered required: true content: application/json: schema: - $ref: "#/components/schemas/ThingReqObj" + $ref: "#/components/schemas/ClientReqObj" - ThingUpdateReq: - description: JSON-formated document describing the metadata and name of thing to be update + ClientUpdateReq: + description: JSON-formated document describing the metadata and name of client to be update required: true content: application/json: schema: - $ref: "#/components/schemas/ThingUpdate" + $ref: "#/components/schemas/ClientUpdate" - ThingUpdateTagsReq: - description: JSON-formated document describing the tags of thing to be update + ClientUpdateTagsReq: + description: JSON-formated document describing the tags of client to be update required: true content: application/json: schema: - $ref: "#/components/schemas/ThingTags" + $ref: "#/components/schemas/ClientTags" - ThingUpdateSecretReq: - description: Secret change data. Thing can change its secret. + ClientUpdateSecretReq: + description: Secret change data. Client can change its secret. required: true content: application/json: schema: - $ref: "#/components/schemas/ThingSecret" + $ref: "#/components/schemasclientsecret" - ShareThingReq: - description: JSON-formated document describing the policy related to sharing things + ShareClientReq: + description: JSON-formated document describing the policy related to sharing clients required: true content: application/json: @@ -1819,15 +1822,15 @@ components: schema: $ref: "#/components/schemas/ChannelUpdate" - ThingsCreateReq: - description: JSON-formatted document describing the new things. + ClientsCreateReq: + description: JSON-formatted document describing the new clients. required: true content: application/json: schema: type: array items: - $ref: "#/components/schemas/ThingReqObj" + $ref: "#/components/schemas/ClientReqObj" ConnCreateReq: description: JSON-formatted document describing the new connection. @@ -1846,89 +1849,89 @@ components: $ref: "#/components/schemas/DisConnectionReqSchema" responses: - ThingCreateRes: - description: Registered new thing. + ClientCreateRes: + description: Registered new client. headers: Location: schema: type: string format: url - description: Registered thing relative URL in the format `/things/` + description: Registered client relative URL in the format `clients/` content: application/json: schema: - $ref: "#/components/schemas/Thing" + $ref: "#/components/schemas/Client" links: get: - operationId: getThing + operationId: getClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id get_channels: - operationId: listChannelsConnectedToThing + operationId: listChannelsConnectedToClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id update: - operationId: updateThing + operationId: updateClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id update_tags: - operationId: updateThingTags + operationId: updateClientTags parameters: - thingID: $response.body#/id + clientID: $response.body#/id update_secret: - operationId: updateThingSecret + operationId: updateClientSecret parameters: - thingID: $response.body#/id + clientID: $response.body#/id share: - operationId: shareThing + operationId: shareClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id unsahre: - operationId: unshareThing + operationId: unshareClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id disable: - operationId: disableThing + operationId: disableClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id enable: - operationId: enableThing + operationId: enableClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id - ThingRes: + ClientRes: description: Data retrieved. content: application/json: schema: - $ref: "#/components/schemas/Thing" + $ref: "#/components/schemas/Client" links: get_channels: - operationId: listChannelsConnectedToThing + operationId: listChannelsConnectedToClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id share: - operationId: shareThing + operationId: shareClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id unsahre: - operationId: unshareThing + operationId: unshareClient parameters: - thingID: $response.body#/id + clientID: $response.body#/id - ThingPageRes: + ClientPageRes: description: Data retrieved. content: application/json: schema: - $ref: "#/components/schemas/ThingsPage" + $ref: "#/components/schemasclientsPage" - ThingsPageRes: + ClientsPageRes: description: Data retrieved. content: application/json: schema: - $ref: "#/components/schemas/ThingsPage" + $ref: "#/components/schemasclientsPage" ChannelCreateRes: description: Registered new channel. @@ -1947,8 +1950,8 @@ components: operationId: getChannel parameters: chanID: $response.body#/id - get_things: - operationId: listThingsInaChannel + get_clients: + operationId: listClientsInaChannel parameters: chanID: $response.body#/id get_users: @@ -1995,8 +1998,8 @@ components: schema: $ref: "#/components/schemas/Channel" links: - get_things: - operationId: listThingsInaChannel + get_clients: + operationId: listClientsInaChannel parameters: chanID: $response.body#/id get_users: @@ -2032,14 +2035,14 @@ components: $ref: "#/components/schemas/ChannelsPage" ConnCreateRes: - description: Thing registered. + description: Client registered. content: application/json: schema: $ref: "#/components/schemas/PoliciesPage" DisconnRes: - description: Things disconnected. + description: Clients disconnected. HealthRes: description: Service Health Check. @@ -2061,7 +2064,7 @@ components: scheme: bearer bearerFormat: JWT description: | - * Thing access: "Authorization: Bearer " + * Client access: "Authorization: Bearer " security: - bearerAuth: [] diff --git a/api/openapi/http.yml b/api/openapi/http.yml index f366458bdf..44beb7d33d 100644 --- a/api/openapi/http.yml +++ b/api/openapi/http.yml @@ -169,13 +169,13 @@ components: scheme: bearer bearerFormat: uuid description: | - * Thing access: "Authorization: Thing " + * Client access: "Authorization: Client " basicAuth: type: http scheme: basic description: | - * Things access: "Authorization: Basic " + * Clients access: "Authorization: Basic " security: - bearerAuth: [] diff --git a/api/openapi/journal.yml b/api/openapi/journal.yml index 9cea4a1f10..3d3b7b743a 100644 --- a/api/openapi/journal.yml +++ b/api/openapi/journal.yml @@ -197,13 +197,13 @@ components: entity_type: name: entityType - description: Type of entity, e.g. user, group, thing, etc.entityType + description: Type of entity, e.g. user, group, client, etc.entityType in: path schema: type: string enum: - group - - thing + - client - channel required: true example: group diff --git a/api/openapi/readers.yml b/api/openapi/readers.yml index 8cf7ea5210..13375a245b 100644 --- a/api/openapi/readers.yml +++ b/api/openapi/readers.yml @@ -179,7 +179,7 @@ components: required: false Publisher: name: Publisher - description: Unique thing identifier. + description: Unique client identifier. in: query schema: type: string @@ -302,13 +302,13 @@ components: description: | * Users access: "Authorization: Bearer " - thingAuth: + clientAuth: type: http scheme: bearer bearerFormat: uuid description: | - * Things access: "Authorization: Thing " + * Clients access: "Authorization: Client " security: - bearerAuth: [] - - thingAuth: [] + - clientAuth: [] diff --git a/auth.pb.go b/auth.pb.go deleted file mode 100644 index d76fd94f77..0000000000 --- a/auth.pb.go +++ /dev/null @@ -1,993 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.34.2 -// protoc v5.27.1 -// source: auth.proto - -package magistrala - -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) -) - -// If a token is not carrying any information itself, the type -// field can be used to determine how to validate the token. -// Also, different tokens can be encoded in different ways. -type Token struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` - RefreshToken *string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3,oneof" json:"refresh_token,omitempty"` - AccessType string `protobuf:"bytes,3,opt,name=access_type,json=accessType,proto3" json:"access_type,omitempty"` -} - -func (x *Token) Reset() { - *x = Token{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[0] - 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_auth_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 Token.ProtoReflect.Descriptor instead. -func (*Token) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{0} -} - -func (x *Token) GetAccessToken() string { - if x != nil { - return x.AccessToken - } - return "" -} - -func (x *Token) GetRefreshToken() string { - if x != nil && x.RefreshToken != nil { - return *x.RefreshToken - } - return "" -} - -func (x *Token) GetAccessType() string { - if x != nil { - return x.AccessType - } - return "" -} - -type AuthNReq struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` -} - -func (x *AuthNReq) Reset() { - *x = AuthNReq{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AuthNReq) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuthNReq) ProtoMessage() {} - -func (x *AuthNReq) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 AuthNReq.ProtoReflect.Descriptor instead. -func (*AuthNReq) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{1} -} - -func (x *AuthNReq) GetToken() string { - if x != nil { - return x.Token - } - return "" -} - -type AuthNRes struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // change "id" to "subject", sub in jwt = user + domain id - UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // user id - DomainId string `protobuf:"bytes,3,opt,name=domain_id,json=domainId,proto3" json:"domain_id,omitempty"` // domain id -} - -func (x *AuthNRes) Reset() { - *x = AuthNRes{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AuthNRes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuthNRes) ProtoMessage() {} - -func (x *AuthNRes) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 AuthNRes.ProtoReflect.Descriptor instead. -func (*AuthNRes) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{2} -} - -func (x *AuthNRes) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *AuthNRes) GetUserId() string { - if x != nil { - return x.UserId - } - return "" -} - -func (x *AuthNRes) GetDomainId() string { - if x != nil { - return x.DomainId - } - return "" -} - -type IssueReq struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` -} - -func (x *IssueReq) Reset() { - *x = IssueReq{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[3] - 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_auth_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 IssueReq.ProtoReflect.Descriptor instead. -func (*IssueReq) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{3} -} - -func (x *IssueReq) GetUserId() string { - if x != nil { - return x.UserId - } - return "" -} - -func (x *IssueReq) GetType() uint32 { - if x != nil { - return x.Type - } - return 0 -} - -type RefreshReq struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` -} - -func (x *RefreshReq) Reset() { - *x = RefreshReq{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RefreshReq) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RefreshReq) ProtoMessage() {} - -func (x *RefreshReq) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 RefreshReq.ProtoReflect.Descriptor instead. -func (*RefreshReq) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{4} -} - -func (x *RefreshReq) GetRefreshToken() string { - if x != nil { - return x.RefreshToken - } - return "" -} - -type AuthZReq struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Domain string `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"` // Domain - SubjectType string `protobuf:"bytes,2,opt,name=subject_type,json=subjectType,proto3" json:"subject_type,omitempty"` // Thing or User - SubjectKind string `protobuf:"bytes,3,opt,name=subject_kind,json=subjectKind,proto3" json:"subject_kind,omitempty"` // ID or Token - SubjectRelation string `protobuf:"bytes,4,opt,name=subject_relation,json=subjectRelation,proto3" json:"subject_relation,omitempty"` // Subject relation - Subject string `protobuf:"bytes,5,opt,name=subject,proto3" json:"subject,omitempty"` // Subject value (id or token, depending on kind) - Relation string `protobuf:"bytes,6,opt,name=relation,proto3" json:"relation,omitempty"` // Relation to filter - Permission string `protobuf:"bytes,7,opt,name=permission,proto3" json:"permission,omitempty"` // Action - Object string `protobuf:"bytes,8,opt,name=object,proto3" json:"object,omitempty"` // Object ID - ObjectType string `protobuf:"bytes,9,opt,name=object_type,json=objectType,proto3" json:"object_type,omitempty"` // Thing, User, Group -} - -func (x *AuthZReq) Reset() { - *x = AuthZReq{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AuthZReq) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuthZReq) ProtoMessage() {} - -func (x *AuthZReq) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 AuthZReq.ProtoReflect.Descriptor instead. -func (*AuthZReq) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{5} -} - -func (x *AuthZReq) GetDomain() string { - if x != nil { - return x.Domain - } - return "" -} - -func (x *AuthZReq) GetSubjectType() string { - if x != nil { - return x.SubjectType - } - return "" -} - -func (x *AuthZReq) GetSubjectKind() string { - if x != nil { - return x.SubjectKind - } - return "" -} - -func (x *AuthZReq) GetSubjectRelation() string { - if x != nil { - return x.SubjectRelation - } - return "" -} - -func (x *AuthZReq) GetSubject() string { - if x != nil { - return x.Subject - } - return "" -} - -func (x *AuthZReq) GetRelation() string { - if x != nil { - return x.Relation - } - return "" -} - -func (x *AuthZReq) GetPermission() string { - if x != nil { - return x.Permission - } - return "" -} - -func (x *AuthZReq) GetObject() string { - if x != nil { - return x.Object - } - return "" -} - -func (x *AuthZReq) GetObjectType() string { - if x != nil { - return x.ObjectType - } - return "" -} - -type AuthZRes struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *AuthZRes) Reset() { - *x = AuthZRes{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AuthZRes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuthZRes) ProtoMessage() {} - -func (x *AuthZRes) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 AuthZRes.ProtoReflect.Descriptor instead. -func (*AuthZRes) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{6} -} - -func (x *AuthZRes) GetAuthorized() bool { - if x != nil { - return x.Authorized - } - return false -} - -func (x *AuthZRes) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type DeleteUserRes struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` -} - -func (x *DeleteUserRes) Reset() { - *x = DeleteUserRes{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteUserRes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteUserRes) ProtoMessage() {} - -func (x *DeleteUserRes) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 DeleteUserRes.ProtoReflect.Descriptor instead. -func (*DeleteUserRes) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{7} -} - -func (x *DeleteUserRes) GetDeleted() bool { - if x != nil { - return x.Deleted - } - return false -} - -type DeleteUserReq struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *DeleteUserReq) Reset() { - *x = DeleteUserReq{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteUserReq) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteUserReq) ProtoMessage() {} - -func (x *DeleteUserReq) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 DeleteUserReq.ProtoReflect.Descriptor instead. -func (*DeleteUserReq) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{8} -} - -func (x *DeleteUserReq) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type ThingsAuthzReq struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` - ThingId string `protobuf:"bytes,2,opt,name=thing_id,json=thingId,proto3" json:"thing_id,omitempty"` - ThingKey string `protobuf:"bytes,3,opt,name=thing_key,json=thingKey,proto3" json:"thing_key,omitempty"` - Permission string `protobuf:"bytes,4,opt,name=permission,proto3" json:"permission,omitempty"` -} - -func (x *ThingsAuthzReq) Reset() { - *x = ThingsAuthzReq{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ThingsAuthzReq) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ThingsAuthzReq) ProtoMessage() {} - -func (x *ThingsAuthzReq) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 ThingsAuthzReq.ProtoReflect.Descriptor instead. -func (*ThingsAuthzReq) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{9} -} - -func (x *ThingsAuthzReq) GetChannelId() string { - if x != nil { - return x.ChannelId - } - return "" -} - -func (x *ThingsAuthzReq) GetThingId() string { - if x != nil { - return x.ThingId - } - return "" -} - -func (x *ThingsAuthzReq) GetThingKey() string { - if x != nil { - return x.ThingKey - } - return "" -} - -func (x *ThingsAuthzReq) GetPermission() string { - if x != nil { - return x.Permission - } - return "" -} - -type ThingsAuthzRes struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *ThingsAuthzRes) Reset() { - *x = ThingsAuthzRes{} - if protoimpl.UnsafeEnabled { - mi := &file_auth_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ThingsAuthzRes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ThingsAuthzRes) ProtoMessage() {} - -func (x *ThingsAuthzRes) ProtoReflect() protoreflect.Message { - mi := &file_auth_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 ThingsAuthzRes.ProtoReflect.Descriptor instead. -func (*ThingsAuthzRes) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{10} -} - -func (x *ThingsAuthzRes) GetAuthorized() bool { - if x != nil { - return x.Authorized - } - return false -} - -func (x *ThingsAuthzRes) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -var File_auth_proto protoreflect.FileDescriptor - -var file_auth_proto_rawDesc = []byte{ - 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x6d, 0x61, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x22, 0x87, 0x01, 0x0a, 0x05, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x28, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, - 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x12, - 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, - 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0x20, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x4e, 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, 0x22, 0x50, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x4e, 0x52, 0x65, 0x73, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, - 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x31, 0x0a, 0x0a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, - 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0xa2, 0x02, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x5a, 0x52, 0x65, 0x71, 0x12, - 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, - 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, - 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, - 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x5a, - 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, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x22, 0x29, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, - 0x72, 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, 0x1f, - 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, - 0x87, 0x01, 0x0a, 0x0e, 0x54, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x52, - 0x65, 0x71, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, - 0x74, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x40, 0x0a, 0x0e, 0x54, 0x68, 0x69, - 0x6e, 0x67, 0x73, 0x41, 0x75, 0x74, 0x68, 0x7a, 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, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0x56, 0x0a, 0x0d, 0x54, - 0x68, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x09, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x41, 0x75, 0x74, - 0x68, 0x7a, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x54, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, - 0x73, 0x22, 0x00, 0x32, 0x7a, 0x0a, 0x0c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, - 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, - 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x12, 0x16, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x32, - 0x86, 0x01, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x39, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x14, 0x2e, 0x6d, - 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x5a, 0x52, - 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x41, 0x75, 0x74, 0x68, 0x5a, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x0c, 0x41, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x4e, 0x52, 0x65, 0x71, - 0x1a, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, - 0x74, 0x68, 0x4e, 0x52, 0x65, 0x73, 0x22, 0x00, 0x32, 0x61, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x15, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x73, 0x12, 0x19, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x19, - 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, - 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} - -var ( - file_auth_proto_rawDescOnce sync.Once - file_auth_proto_rawDescData = file_auth_proto_rawDesc -) - -func file_auth_proto_rawDescGZIP() []byte { - file_auth_proto_rawDescOnce.Do(func() { - file_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_auth_proto_rawDescData) - }) - return file_auth_proto_rawDescData -} - -var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 11) -var file_auth_proto_goTypes = []any{ - (*Token)(nil), // 0: magistrala.Token - (*AuthNReq)(nil), // 1: magistrala.AuthNReq - (*AuthNRes)(nil), // 2: magistrala.AuthNRes - (*IssueReq)(nil), // 3: magistrala.IssueReq - (*RefreshReq)(nil), // 4: magistrala.RefreshReq - (*AuthZReq)(nil), // 5: magistrala.AuthZReq - (*AuthZRes)(nil), // 6: magistrala.AuthZRes - (*DeleteUserRes)(nil), // 7: magistrala.DeleteUserRes - (*DeleteUserReq)(nil), // 8: magistrala.DeleteUserReq - (*ThingsAuthzReq)(nil), // 9: magistrala.ThingsAuthzReq - (*ThingsAuthzRes)(nil), // 10: magistrala.ThingsAuthzRes -} -var file_auth_proto_depIdxs = []int32{ - 9, // 0: magistrala.ThingsService.Authorize:input_type -> magistrala.ThingsAuthzReq - 3, // 1: magistrala.TokenService.Issue:input_type -> magistrala.IssueReq - 4, // 2: magistrala.TokenService.Refresh:input_type -> magistrala.RefreshReq - 5, // 3: magistrala.AuthService.Authorize:input_type -> magistrala.AuthZReq - 1, // 4: magistrala.AuthService.Authenticate:input_type -> magistrala.AuthNReq - 8, // 5: magistrala.DomainsService.DeleteUserFromDomains:input_type -> magistrala.DeleteUserReq - 10, // 6: magistrala.ThingsService.Authorize:output_type -> magistrala.ThingsAuthzRes - 0, // 7: magistrala.TokenService.Issue:output_type -> magistrala.Token - 0, // 8: magistrala.TokenService.Refresh:output_type -> magistrala.Token - 6, // 9: magistrala.AuthService.Authorize:output_type -> magistrala.AuthZRes - 2, // 10: magistrala.AuthService.Authenticate:output_type -> magistrala.AuthNRes - 7, // 11: magistrala.DomainsService.DeleteUserFromDomains:output_type -> magistrala.DeleteUserRes - 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_auth_proto_init() } -func file_auth_proto_init() { - if File_auth_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_auth_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Token); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*AuthNReq); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*AuthNRes); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*IssueReq); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*RefreshReq); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*AuthZReq); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*AuthZRes); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*DeleteUserRes); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*DeleteUserReq); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*ThingsAuthzReq); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_auth_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*ThingsAuthzRes); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_auth_proto_msgTypes[0].OneofWrappers = []any{} - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_auth_proto_rawDesc, - NumEnums: 0, - NumMessages: 11, - NumExtensions: 0, - NumServices: 4, - }, - GoTypes: file_auth_proto_goTypes, - DependencyIndexes: file_auth_proto_depIdxs, - MessageInfos: file_auth_proto_msgTypes, - }.Build() - File_auth_proto = out.File - file_auth_proto_rawDesc = nil - file_auth_proto_goTypes = nil - file_auth_proto_depIdxs = nil -} diff --git a/auth.proto b/auth.proto deleted file mode 100644 index 54015f1159..0000000000 --- a/auth.proto +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -syntax = "proto3"; - -package magistrala; -option go_package = "./magistrala"; - -// ThingsService is a service that provides things authorization functionalities -// for magistrala services. -service ThingsService { - // Authorize checks if the thing is authorized to perform - // the action on the channel. - rpc Authorize(ThingsAuthzReq) returns (ThingsAuthzRes) {} -} - -service TokenService { - rpc Issue(IssueReq) returns (Token) {} - rpc Refresh(RefreshReq) returns (Token) {} -} - -// AuthService is a service that provides authentication and authorization -// functionalities for magistrala services. -service AuthService { - rpc Authorize(AuthZReq) returns (AuthZRes) {} - rpc Authenticate(AuthNReq) returns (AuthNRes) {} -} - -// DomainsService is a service that provides access to domains -// functionalities for magistrala services. -service DomainsService { - rpc DeleteUserFromDomains(DeleteUserReq) returns (DeleteUserRes) {} -} - -// If a token is not carrying any information itself, the type -// field can be used to determine how to validate the token. -// Also, different tokens can be encoded in different ways. -message Token { - string access_token = 1; - optional string refresh_token = 2; - string access_type = 3; -} - -message AuthNReq { - string token = 1; -} - -message AuthNRes { - string id = 1; // change "id" to "subject", sub in jwt = user + domain id - string user_id = 2; // user id - string domain_id = 3; // domain id -} - -message IssueReq { - string user_id = 1; - uint32 type = 2; -} - -message RefreshReq { - string refresh_token = 1; -} - -message AuthZReq { - string domain = 1; // Domain - string subject_type = 2; // Thing or User - string subject_kind = 3; // ID or Token - string subject_relation = 4; // Subject relation - string subject = 5; // Subject value (id or token, depending on kind) - string relation = 6; // Relation to filter - string permission = 7; // Action - string object = 8; // Object ID - string object_type = 9; // Thing, User, Group -} - -message AuthZRes { - bool authorized = 1; - string id = 2; -} - -message DeleteUserRes { - bool deleted = 1; -} - -message DeleteUserReq { - string id = 1; -} - -message ThingsAuthzReq { - string channel_id = 1; - string thing_id = 2; - string thing_key = 3; - string permission = 4; -} - -message ThingsAuthzRes { - bool authorized = 1; - string id = 2; -} diff --git a/auth/README.md b/auth/README.md index 4a991e0fb1..3a35172ad6 100644 --- a/auth/README.md +++ b/auth/README.md @@ -1,6 +1,6 @@ # Auth - Authentication and Authorization service -Auth service provides authentication features as an API for managing authentication keys as well as administering groups of entities - `things` and `users`. +Auth service provides authentication features as an API for managing authentication keys as well as administering groups of entities - `clients` and `users`. ## Authentication @@ -25,7 +25,7 @@ Authentication keys are represented and distributed by the corresponding [JWT](j User keys are issued when user logs in. Each user request (other than `registration` and `login`) contains user key that is used to authenticate the user. -API keys are similar to the User keys. The main difference is that API keys have configurable expiration time. If no time is set, the key will never expire. For that reason, API keys are _the only key type that can be revoked_. This also means that, despite being used as a JWT, it requires a query to the database to validate the API key. The user with API key can perform all the same actions as the user with login key (can act on behalf of the user for Thing, Channel, or user profile management), _except issuing new API keys_. +API keys are similar to the User keys. The main difference is that API keys have configurable expiration time. If no time is set, the key will never expire. For that reason, API keys are _the only key type that can be revoked_. This also means that, despite being used as a JWT, it requires a query to the database to validate the API key. The user with API key can perform all the same actions as the user with login key (can act on behalf of the user for Client, Channel, or user profile management), _except issuing new API keys_. Recovery key is the password recovery key. It's short-lived token used for password recovery process. @@ -40,7 +40,7 @@ The following actions are supported: ## Domains -Domains are used to group users and things. Each domain has a unique alias that is used to identify the domain. Domains are used to group users and their entities. +Domains are used to group users and clients. Each domain has a unique alias that is used to identify the domain. Domains are used to group users and their entities. Domain consists of the following fields: diff --git a/auth/api/grpc/auth/client.go b/auth/api/grpc/auth/client.go index f53f4f5734..41f4e9e66a 100644 --- a/auth/api/grpc/auth/client.go +++ b/auth/api/grpc/auth/client.go @@ -7,14 +7,14 @@ import ( "context" "time" - "github.com/absmach/magistrala" grpcapi "github.com/absmach/magistrala/auth/api/grpc" + grpcAuthV1 "github.com/absmach/magistrala/internal/grpc/auth/v1" "github.com/go-kit/kit/endpoint" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc" ) -const authSvcName = "magistrala.AuthService" +const authSvcName = "auth.v1.AuthService" type authGrpcClient struct { authenticate endpoint.Endpoint @@ -22,10 +22,10 @@ type authGrpcClient struct { timeout time.Duration } -var _ magistrala.AuthServiceClient = (*authGrpcClient)(nil) +var _ grpcAuthV1.AuthServiceClient = (*authGrpcClient)(nil) // NewAuthClient returns new auth gRPC client instance. -func NewAuthClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.AuthServiceClient { +func NewAuthClient(conn *grpc.ClientConn, timeout time.Duration) grpcAuthV1.AuthServiceClient { return &authGrpcClient{ authenticate: kitgrpc.NewClient( conn, @@ -33,7 +33,7 @@ func NewAuthClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.Auth "Authenticate", encodeIdentifyRequest, decodeIdentifyResponse, - magistrala.AuthNRes{}, + grpcAuthV1.AuthNRes{}, ).Endpoint(), authorize: kitgrpc.NewClient( conn, @@ -41,35 +41,35 @@ func NewAuthClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.Auth "Authorize", encodeAuthorizeRequest, decodeAuthorizeResponse, - magistrala.AuthZRes{}, + grpcAuthV1.AuthZRes{}, ).Endpoint(), timeout: timeout, } } -func (client authGrpcClient) Authenticate(ctx context.Context, token *magistrala.AuthNReq, _ ...grpc.CallOption) (*magistrala.AuthNRes, error) { +func (client authGrpcClient) Authenticate(ctx context.Context, token *grpcAuthV1.AuthNReq, _ ...grpc.CallOption) (*grpcAuthV1.AuthNRes, error) { ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() res, err := client.authenticate(ctx, authenticateReq{token: token.GetToken()}) if err != nil { - return &magistrala.AuthNRes{}, grpcapi.DecodeError(err) + return &grpcAuthV1.AuthNRes{}, grpcapi.DecodeError(err) } ir := res.(authenticateRes) - return &magistrala.AuthNRes{Id: ir.id, UserId: ir.userID, DomainId: ir.domainID}, nil + return &grpcAuthV1.AuthNRes{Id: ir.id, UserId: ir.userID, DomainId: ir.domainID}, nil } func encodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(authenticateReq) - return &magistrala.AuthNReq{Token: req.token}, nil + return &grpcAuthV1.AuthNReq{Token: req.token}, nil } func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.AuthNRes) + res := grpcRes.(*grpcAuthV1.AuthNRes) return authenticateRes{id: res.GetId(), userID: res.GetUserId(), domainID: res.GetDomainId()}, nil } -func (client authGrpcClient) Authorize(ctx context.Context, req *magistrala.AuthZReq, _ ...grpc.CallOption) (r *magistrala.AuthZRes, err error) { +func (client authGrpcClient) Authorize(ctx context.Context, req *grpcAuthV1.AuthZReq, _ ...grpc.CallOption) (r *grpcAuthV1.AuthZRes, err error) { ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() @@ -84,21 +84,21 @@ func (client authGrpcClient) Authorize(ctx context.Context, req *magistrala.Auth Object: req.GetObject(), }) if err != nil { - return &magistrala.AuthZRes{}, grpcapi.DecodeError(err) + return &grpcAuthV1.AuthZRes{}, grpcapi.DecodeError(err) } ar := res.(authorizeRes) - return &magistrala.AuthZRes{Authorized: ar.authorized, Id: ar.id}, nil + return &grpcAuthV1.AuthZRes{Authorized: ar.authorized, Id: ar.id}, nil } func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.AuthZRes) + res := grpcRes.(*grpcAuthV1.AuthZRes) return authorizeRes{authorized: res.Authorized, id: res.Id}, nil } func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(authReq) - return &magistrala.AuthZReq{ + return &grpcAuthV1.AuthZReq{ Domain: req.Domain, SubjectType: req.SubjectType, Subject: req.Subject, diff --git a/auth/api/grpc/auth/endpoint_test.go b/auth/api/grpc/auth/endpoint_test.go index 4b920617a8..33b4003de5 100644 --- a/auth/api/grpc/auth/endpoint_test.go +++ b/auth/api/grpc/auth/endpoint_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" grpcapi "github.com/absmach/magistrala/auth/api/grpc/auth" + grpcAuthV1 "github.com/absmach/magistrala/internal/grpc/auth/v1" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" @@ -28,7 +28,7 @@ const ( secret = "secret" email = "test@example.com" id = "testID" - thingsType = "things" + clientsType = "clients" usersType = "users" description = "Description" groupName = "mgx" @@ -52,7 +52,7 @@ var ( func startGRPCServer(svc auth.Service, port int) *grpc.Server { listener, _ := net.Listen("tcp", fmt.Sprintf(":%d", port)) server := grpc.NewServer() - magistrala.RegisterAuthServiceServer(server, grpcapi.NewAuthServer(svc)) + grpcAuthV1.RegisterAuthServiceServer(server, grpcapi.NewAuthServer(svc)) go func() { err := server.Serve(listener) assert.Nil(&testing.T{}, err, fmt.Sprintf(`"Unexpected error creating auth server %s"`, err)) @@ -69,34 +69,34 @@ func TestIdentify(t *testing.T) { cases := []struct { desc string token string - idt *magistrala.AuthNRes + idt *grpcAuthV1.AuthNRes svcErr error err error }{ { desc: "authenticate user with valid user token", token: validToken, - idt: &magistrala.AuthNRes{Id: id, UserId: email, DomainId: domainID}, + idt: &grpcAuthV1.AuthNRes{Id: id, UserId: email, DomainId: domainID}, err: nil, }, { desc: "authenticate user with invalid user token", token: "invalid", - idt: &magistrala.AuthNRes{}, + idt: &grpcAuthV1.AuthNRes{}, svcErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { desc: "authenticate user with empty token", token: "", - idt: &magistrala.AuthNRes{}, + idt: &grpcAuthV1.AuthNRes{}, err: apiutil.ErrBearerToken, }, } for _, tc := range cases { svcCall := svc.On("Identify", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{Subject: id, User: email, Domain: domainID}, tc.svcErr) - idt, err := grpcClient.Authenticate(context.Background(), &magistrala.AuthNReq{Token: tc.token}) + idt, err := grpcClient.Authenticate(context.Background(), &grpcAuthV1.AuthNReq{Token: tc.token}) if idt != nil { assert.Equal(t, tc.idt, idt, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.idt, idt)) } @@ -113,14 +113,14 @@ func TestAuthorize(t *testing.T) { cases := []struct { desc string token string - authRequest *magistrala.AuthZReq - authResponse *magistrala.AuthZRes + authRequest *grpcAuthV1.AuthZReq + authResponse *grpcAuthV1.AuthZRes err error }{ { desc: "authorize user with authorized token", token: validToken, - authRequest: &magistrala.AuthZReq{ + authRequest: &grpcAuthV1.AuthZReq{ Subject: id, SubjectType: usersType, Object: authoritiesObj, @@ -128,13 +128,13 @@ func TestAuthorize(t *testing.T) { Relation: memberRelation, Permission: adminpermission, }, - authResponse: &magistrala.AuthZRes{Authorized: true}, + authResponse: &grpcAuthV1.AuthZRes{Authorized: true}, err: nil, }, { desc: "authorize user with unauthorized token", token: inValidToken, - authRequest: &magistrala.AuthZReq{ + authRequest: &grpcAuthV1.AuthZReq{ Subject: id, SubjectType: usersType, Object: authoritiesObj, @@ -142,13 +142,13 @@ func TestAuthorize(t *testing.T) { Relation: memberRelation, Permission: adminpermission, }, - authResponse: &magistrala.AuthZRes{Authorized: false}, + authResponse: &grpcAuthV1.AuthZRes{Authorized: false}, err: svcerr.ErrAuthorization, }, { desc: "authorize user with empty subject", token: validToken, - authRequest: &magistrala.AuthZReq{ + authRequest: &grpcAuthV1.AuthZReq{ Subject: "", SubjectType: usersType, Object: authoritiesObj, @@ -156,13 +156,13 @@ func TestAuthorize(t *testing.T) { Relation: memberRelation, Permission: adminpermission, }, - authResponse: &magistrala.AuthZRes{Authorized: false}, + authResponse: &grpcAuthV1.AuthZRes{Authorized: false}, err: apiutil.ErrMissingPolicySub, }, { desc: "authorize user with empty subject type", token: validToken, - authRequest: &magistrala.AuthZReq{ + authRequest: &grpcAuthV1.AuthZReq{ Subject: id, SubjectType: "", Object: authoritiesObj, @@ -170,13 +170,13 @@ func TestAuthorize(t *testing.T) { Relation: memberRelation, Permission: adminpermission, }, - authResponse: &magistrala.AuthZRes{Authorized: false}, + authResponse: &grpcAuthV1.AuthZRes{Authorized: false}, err: apiutil.ErrMissingPolicySub, }, { desc: "authorize user with empty object", token: validToken, - authRequest: &magistrala.AuthZReq{ + authRequest: &grpcAuthV1.AuthZReq{ Subject: id, SubjectType: usersType, Object: "", @@ -184,13 +184,13 @@ func TestAuthorize(t *testing.T) { Relation: memberRelation, Permission: adminpermission, }, - authResponse: &magistrala.AuthZRes{Authorized: false}, + authResponse: &grpcAuthV1.AuthZRes{Authorized: false}, err: apiutil.ErrMissingPolicyObj, }, { desc: "authorize user with empty object type", token: validToken, - authRequest: &magistrala.AuthZReq{ + authRequest: &grpcAuthV1.AuthZReq{ Subject: id, SubjectType: usersType, Object: authoritiesObj, @@ -198,13 +198,13 @@ func TestAuthorize(t *testing.T) { Relation: memberRelation, Permission: adminpermission, }, - authResponse: &magistrala.AuthZRes{Authorized: false}, + authResponse: &grpcAuthV1.AuthZRes{Authorized: false}, err: apiutil.ErrMissingPolicyObj, }, { desc: "authorize user with empty permission", token: validToken, - authRequest: &magistrala.AuthZReq{ + authRequest: &grpcAuthV1.AuthZReq{ Subject: id, SubjectType: usersType, Object: authoritiesObj, @@ -212,7 +212,7 @@ func TestAuthorize(t *testing.T) { Relation: memberRelation, Permission: "", }, - authResponse: &magistrala.AuthZRes{Authorized: false}, + authResponse: &grpcAuthV1.AuthZRes{Authorized: false}, err: apiutil.ErrMalformedPolicyPer, }, } diff --git a/auth/api/grpc/auth/server.go b/auth/api/grpc/auth/server.go index 491b915db8..af6833cf9a 100644 --- a/auth/api/grpc/auth/server.go +++ b/auth/api/grpc/auth/server.go @@ -6,22 +6,22 @@ package auth import ( "context" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" grpcapi "github.com/absmach/magistrala/auth/api/grpc" + grpcAuthV1 "github.com/absmach/magistrala/internal/grpc/auth/v1" kitgrpc "github.com/go-kit/kit/transport/grpc" ) -var _ magistrala.AuthServiceServer = (*authGrpcServer)(nil) +var _ grpcAuthV1.AuthServiceServer = (*authGrpcServer)(nil) type authGrpcServer struct { - magistrala.UnimplementedAuthServiceServer + grpcAuthV1.UnimplementedAuthServiceServer authorize kitgrpc.Handler authenticate kitgrpc.Handler } // NewAuthServer returns new AuthnServiceServer instance. -func NewAuthServer(svc auth.Service) magistrala.AuthServiceServer { +func NewAuthServer(svc auth.Service) grpcAuthV1.AuthServiceServer { return &authGrpcServer{ authorize: kitgrpc.NewServer( (authorizeEndpoint(svc)), @@ -37,34 +37,34 @@ func NewAuthServer(svc auth.Service) magistrala.AuthServiceServer { } } -func (s *authGrpcServer) Authenticate(ctx context.Context, req *magistrala.AuthNReq) (*magistrala.AuthNRes, error) { +func (s *authGrpcServer) Authenticate(ctx context.Context, req *grpcAuthV1.AuthNReq) (*grpcAuthV1.AuthNRes, error) { _, res, err := s.authenticate.ServeGRPC(ctx, req) if err != nil { return nil, grpcapi.EncodeError(err) } - return res.(*magistrala.AuthNRes), nil + return res.(*grpcAuthV1.AuthNRes), nil } -func (s *authGrpcServer) Authorize(ctx context.Context, req *magistrala.AuthZReq) (*magistrala.AuthZRes, error) { +func (s *authGrpcServer) Authorize(ctx context.Context, req *grpcAuthV1.AuthZReq) (*grpcAuthV1.AuthZRes, error) { _, res, err := s.authorize.ServeGRPC(ctx, req) if err != nil { return nil, grpcapi.EncodeError(err) } - return res.(*magistrala.AuthZRes), nil + return res.(*grpcAuthV1.AuthZRes), nil } func decodeAuthenticateRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.AuthNReq) + req := grpcReq.(*grpcAuthV1.AuthNReq) return authenticateReq{token: req.GetToken()}, nil } func encodeAuthenticateResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { res := grpcRes.(authenticateRes) - return &magistrala.AuthNRes{Id: res.id, UserId: res.userID, DomainId: res.domainID}, nil + return &grpcAuthV1.AuthNRes{Id: res.id, UserId: res.userID, DomainId: res.domainID}, nil } func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.AuthZReq) + req := grpcReq.(*grpcAuthV1.AuthZReq) return authReq{ Domain: req.GetDomain(), SubjectType: req.GetSubjectType(), @@ -79,5 +79,5 @@ func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{} func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { res := grpcRes.(authorizeRes) - return &magistrala.AuthZRes{Authorized: res.authorized, Id: res.id}, nil + return &grpcAuthV1.AuthZRes{Authorized: res.authorized, Id: res.id}, nil } diff --git a/auth/api/grpc/token/client.go b/auth/api/grpc/token/client.go index ffb8247a56..63503d3bac 100644 --- a/auth/api/grpc/token/client.go +++ b/auth/api/grpc/token/client.go @@ -7,15 +7,15 @@ import ( "context" "time" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" grpcapi "github.com/absmach/magistrala/auth/api/grpc" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/go-kit/kit/endpoint" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc" ) -const tokenSvcName = "magistrala.TokenService" +const tokenSvcName = "token.v1.TokenService" type tokenGrpcClient struct { issue endpoint.Endpoint @@ -23,10 +23,10 @@ type tokenGrpcClient struct { timeout time.Duration } -var _ magistrala.TokenServiceClient = (*tokenGrpcClient)(nil) +var _ grpcTokenV1.TokenServiceClient = (*tokenGrpcClient)(nil) // NewAuthClient returns new auth gRPC client instance. -func NewTokenClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.TokenServiceClient { +func NewTokenClient(conn *grpc.ClientConn, timeout time.Duration) grpcTokenV1.TokenServiceClient { return &tokenGrpcClient{ issue: kitgrpc.NewClient( conn, @@ -34,7 +34,7 @@ func NewTokenClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.Tok "Issue", encodeIssueRequest, decodeIssueResponse, - magistrala.Token{}, + grpcTokenV1.Token{}, ).Endpoint(), refresh: kitgrpc.NewClient( conn, @@ -42,13 +42,13 @@ func NewTokenClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.Tok "Refresh", encodeRefreshRequest, decodeRefreshResponse, - magistrala.Token{}, + grpcTokenV1.Token{}, ).Endpoint(), timeout: timeout, } } -func (client tokenGrpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ ...grpc.CallOption) (*magistrala.Token, error) { +func (client tokenGrpcClient) Issue(ctx context.Context, req *grpcTokenV1.IssueReq, _ ...grpc.CallOption) (*grpcTokenV1.Token, error) { ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() @@ -57,14 +57,14 @@ func (client tokenGrpcClient) Issue(ctx context.Context, req *magistrala.IssueRe keyType: auth.KeyType(req.GetType()), }) if err != nil { - return &magistrala.Token{}, grpcapi.DecodeError(err) + return &grpcTokenV1.Token{}, grpcapi.DecodeError(err) } - return res.(*magistrala.Token), nil + return res.(*grpcTokenV1.Token), nil } func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(issueReq) - return &magistrala.IssueReq{ + return &grpcTokenV1.IssueReq{ UserId: req.userID, Type: uint32(req.keyType), }, nil @@ -74,20 +74,20 @@ func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, e return grpcRes, nil } -func (client tokenGrpcClient) Refresh(ctx context.Context, req *magistrala.RefreshReq, _ ...grpc.CallOption) (*magistrala.Token, error) { +func (client tokenGrpcClient) Refresh(ctx context.Context, req *grpcTokenV1.RefreshReq, _ ...grpc.CallOption) (*grpcTokenV1.Token, error) { ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() res, err := client.refresh(ctx, refreshReq{refreshToken: req.GetRefreshToken()}) if err != nil { - return &magistrala.Token{}, grpcapi.DecodeError(err) + return &grpcTokenV1.Token{}, grpcapi.DecodeError(err) } - return res.(*magistrala.Token), nil + return res.(*grpcTokenV1.Token), nil } func encodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(refreshReq) - return &magistrala.RefreshReq{RefreshToken: req.refreshToken}, nil + return &grpcTokenV1.RefreshReq{RefreshToken: req.refreshToken}, nil } func decodeRefreshResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { diff --git a/auth/api/grpc/token/endpoint_test.go b/auth/api/grpc/token/endpoint_test.go index 8e0b8b7a92..c6e96bd6c3 100644 --- a/auth/api/grpc/token/endpoint_test.go +++ b/auth/api/grpc/token/endpoint_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" grpcapi "github.com/absmach/magistrala/auth/api/grpc/token" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" @@ -28,7 +28,7 @@ const ( secret = "secret" email = "test@example.com" id = "testID" - thingsType = "things" + clientsType = "clients" usersType = "users" description = "Description" groupName = "mgx" @@ -52,7 +52,7 @@ var ( func startGRPCServer(svc auth.Service, port int) *grpc.Server { listener, _ := net.Listen("tcp", fmt.Sprintf(":%d", port)) server := grpc.NewServer() - magistrala.RegisterTokenServiceServer(server, grpcapi.NewTokenServer(svc)) + grpcTokenV1.RegisterTokenServiceServer(server, grpcapi.NewTokenServer(svc)) go func() { err := server.Serve(listener) assert.Nil(&testing.T{}, err, fmt.Sprintf(`"Unexpected error creating auth server %s"`, err)) @@ -117,12 +117,10 @@ func TestIssue(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("Issue", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.issueResponse, tc.err) - _, err := grpcClient.Issue(context.Background(), &magistrala.IssueReq{UserId: tc.userId, Type: uint32(tc.kind)}) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - svcCall.Unset() - }) + svcCall := svc.On("Issue", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.issueResponse, tc.err) + _, err := grpcClient.Issue(context.Background(), &grpcTokenV1.IssueReq{UserId: tc.userId, Type: uint32(tc.kind)}) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + svcCall.Unset() } } @@ -161,11 +159,9 @@ func TestRefresh(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("Issue", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.issueResponse, tc.err) - _, err := grpcClient.Refresh(context.Background(), &magistrala.RefreshReq{RefreshToken: tc.token}) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - svcCall.Unset() - }) + svcCall := svc.On("Issue", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.issueResponse, tc.err) + _, err := grpcClient.Refresh(context.Background(), &grpcTokenV1.RefreshReq{RefreshToken: tc.token}) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + svcCall.Unset() } } diff --git a/auth/api/grpc/token/server.go b/auth/api/grpc/token/server.go index a2432b323d..83244b3367 100644 --- a/auth/api/grpc/token/server.go +++ b/auth/api/grpc/token/server.go @@ -6,22 +6,22 @@ package token import ( "context" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" grpcapi "github.com/absmach/magistrala/auth/api/grpc" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" kitgrpc "github.com/go-kit/kit/transport/grpc" ) -var _ magistrala.TokenServiceServer = (*tokenGrpcServer)(nil) +var _ grpcTokenV1.TokenServiceServer = (*tokenGrpcServer)(nil) type tokenGrpcServer struct { - magistrala.UnimplementedTokenServiceServer + grpcTokenV1.UnimplementedTokenServiceServer issue kitgrpc.Handler refresh kitgrpc.Handler } // NewAuthServer returns new AuthnServiceServer instance. -func NewTokenServer(svc auth.Service) magistrala.TokenServiceServer { +func NewTokenServer(svc auth.Service) grpcTokenV1.TokenServiceServer { return &tokenGrpcServer{ issue: kitgrpc.NewServer( (issueEndpoint(svc)), @@ -36,24 +36,24 @@ func NewTokenServer(svc auth.Service) magistrala.TokenServiceServer { } } -func (s *tokenGrpcServer) Issue(ctx context.Context, req *magistrala.IssueReq) (*magistrala.Token, error) { +func (s *tokenGrpcServer) Issue(ctx context.Context, req *grpcTokenV1.IssueReq) (*grpcTokenV1.Token, error) { _, res, err := s.issue.ServeGRPC(ctx, req) if err != nil { return nil, grpcapi.EncodeError(err) } - return res.(*magistrala.Token), nil + return res.(*grpcTokenV1.Token), nil } -func (s *tokenGrpcServer) Refresh(ctx context.Context, req *magistrala.RefreshReq) (*magistrala.Token, error) { +func (s *tokenGrpcServer) Refresh(ctx context.Context, req *grpcTokenV1.RefreshReq) (*grpcTokenV1.Token, error) { _, res, err := s.refresh.ServeGRPC(ctx, req) if err != nil { return nil, grpcapi.EncodeError(err) } - return res.(*magistrala.Token), nil + return res.(*grpcTokenV1.Token), nil } func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.IssueReq) + req := grpcReq.(*grpcTokenV1.IssueReq) return issueReq{ userID: req.GetUserId(), keyType: auth.KeyType(req.GetType()), @@ -61,14 +61,14 @@ func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, er } func decodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.RefreshReq) + req := grpcReq.(*grpcTokenV1.RefreshReq) return refreshReq{refreshToken: req.GetRefreshToken()}, nil } func encodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { res := grpcRes.(issueRes) - return &magistrala.Token{ + return &grpcTokenV1.Token{ AccessToken: res.accessToken, RefreshToken: &res.refreshToken, AccessType: res.accessType, diff --git a/auth/api/http/domains/endpoint.go b/auth/api/http/domains/endpoint.go deleted file mode 100644 index ffb00a36e8..0000000000 --- a/auth/api/http/domains/endpoint.go +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "context" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/go-kit/kit/endpoint" -) - -func createDomainEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createDomainReq) - if err := req.validate(); err != nil { - return nil, err - } - - d := auth.Domain{ - Name: req.Name, - Metadata: req.Metadata, - Tags: req.Tags, - Alias: req.Alias, - } - domain, err := svc.CreateDomain(ctx, req.token, d) - if err != nil { - return nil, err - } - - return createDomainRes{domain}, nil - } -} - -func retrieveDomainEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(retrieveDomainRequest) - if err := req.validate(); err != nil { - return nil, err - } - - domain, err := svc.RetrieveDomain(ctx, req.token, req.domainID) - if err != nil { - return nil, err - } - return retrieveDomainRes{domain}, nil - } -} - -func retrieveDomainPermissionsEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(retrieveDomainPermissionsRequest) - if err := req.validate(); err != nil { - return nil, err - } - - permissions, err := svc.RetrieveDomainPermissions(ctx, req.token, req.domainID) - if err != nil { - return nil, err - } - return retrieveDomainPermissionsRes{Permissions: permissions}, nil - } -} - -func updateDomainEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateDomainReq) - if err := req.validate(); err != nil { - return nil, err - } - - var metadata auth.Metadata - if req.Metadata != nil { - metadata = *req.Metadata - } - d := auth.DomainReq{ - Name: req.Name, - Metadata: &metadata, - Tags: req.Tags, - Alias: req.Alias, - } - domain, err := svc.UpdateDomain(ctx, req.token, req.domainID, d) - if err != nil { - return nil, err - } - - return updateDomainRes{domain}, nil - } -} - -func listDomainsEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listDomainsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - page := auth.Page{ - Offset: req.offset, - Limit: req.limit, - Name: req.name, - Metadata: req.metadata, - Order: req.order, - Dir: req.dir, - Tag: req.tag, - Permission: req.permission, - Status: req.status, - } - dp, err := svc.ListDomains(ctx, req.token, page) - if err != nil { - return nil, err - } - return listDomainsRes{dp}, nil - } -} - -func enableDomainEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(enableDomainReq) - if err := req.validate(); err != nil { - return nil, err - } - - enable := auth.EnabledStatus - d := auth.DomainReq{ - Status: &enable, - } - if _, err := svc.ChangeDomainStatus(ctx, req.token, req.domainID, d); err != nil { - return nil, err - } - return enableDomainRes{}, nil - } -} - -func disableDomainEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(disableDomainReq) - if err := req.validate(); err != nil { - return nil, err - } - - disable := auth.DisabledStatus - d := auth.DomainReq{ - Status: &disable, - } - if _, err := svc.ChangeDomainStatus(ctx, req.token, req.domainID, d); err != nil { - return nil, err - } - return disableDomainRes{}, nil - } -} - -func freezeDomainEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(freezeDomainReq) - if err := req.validate(); err != nil { - return nil, err - } - - freeze := auth.FreezeStatus - d := auth.DomainReq{ - Status: &freeze, - } - if _, err := svc.ChangeDomainStatus(ctx, req.token, req.domainID, d); err != nil { - return nil, err - } - return freezeDomainRes{}, nil - } -} - -func assignDomainUsersEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUsersReq) - if err := req.validate(); err != nil { - return nil, err - } - - if err := svc.AssignUsers(ctx, req.token, req.domainID, req.UserIDs, req.Relation); err != nil { - return nil, err - } - return assignUsersRes{}, nil - } -} - -func unassignDomainUserEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignUserReq) - if err := req.validate(); err != nil { - return nil, err - } - - if err := svc.UnassignUser(ctx, req.token, req.domainID, req.UserID); err != nil { - return nil, err - } - return unassignUsersRes{}, nil - } -} - -func listUserDomainsEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listUserDomainsReq) - if err := req.validate(); err != nil { - return nil, err - } - - page := auth.Page{ - Offset: req.offset, - Limit: req.limit, - Name: req.name, - Metadata: req.metadata, - Order: req.order, - Dir: req.dir, - Tag: req.tag, - Permission: req.permission, - Status: req.status, - } - dp, err := svc.ListUserDomains(ctx, req.token, req.userID, page) - if err != nil { - return nil, err - } - return listUserDomainsRes{dp}, nil - } -} diff --git a/auth/api/http/keys/endpoint_test.go b/auth/api/http/keys/endpoint_test.go index 4ed62a340d..ede95053a8 100644 --- a/auth/api/http/keys/endpoint_test.go +++ b/auth/api/http/keys/endpoint_test.go @@ -69,14 +69,12 @@ func (tr testRequest) make() (*http.Response, error) { func newService() (auth.Service, *mocks.KeyRepository) { krepo := new(mocks.KeyRepository) - drepo := new(mocks.DomainsRepository) idProvider := uuid.NewMock() pService := new(policymocks.Service) pEvaluator := new(policymocks.Evaluator) - t := jwt.New([]byte(secret)) - return auth.New(krepo, drepo, idProvider, t, pEvaluator, pService, loginDuration, refreshDuration, invalidDuration), krepo + return auth.New(krepo, idProvider, t, pEvaluator, pService, loginDuration, refreshDuration, invalidDuration), krepo } func newServer(svc auth.Service) *httptest.Server { diff --git a/auth/api/http/transport.go b/auth/api/http/transport.go index 5e31ee553f..27d0214934 100644 --- a/auth/api/http/transport.go +++ b/auth/api/http/transport.go @@ -8,7 +8,6 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/auth/api/http/domains" "github.com/absmach/magistrala/auth/api/http/keys" "github.com/go-chi/chi/v5" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -19,7 +18,6 @@ func MakeHandler(svc auth.Service, logger *slog.Logger, instanceID string) http. mux := chi.NewRouter() mux = keys.MakeHandler(svc, mux, logger) - mux = domains.MakeHandler(svc, mux, logger) mux.Get("/health", magistrala.Health("auth", instanceID)) mux.Handle("/metrics", promhttp.Handler()) diff --git a/auth/api/logging.go b/auth/api/logging.go index 30182bb4c4..c53bbc4cd9 100644 --- a/auth/api/logging.go +++ b/auth/api/logging.go @@ -124,180 +124,3 @@ func (lm *loggingMiddleware) Authorize(ctx context.Context, pr policies.Policy) }(time.Now()) return lm.svc.Authorize(ctx, pr) } - -func (lm *loggingMiddleware) CreateDomain(ctx context.Context, token string, d auth.Domain) (do auth.Domain, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("domain", - slog.String("id", d.ID), - slog.String("name", d.Name), - ), - } - if err != nil { - args := append(args, slog.String("error", err.Error())) - lm.logger.Warn("Create domain failed", args...) - return - } - lm.logger.Info("Create domain completed successfully", args...) - }(time.Now()) - return lm.svc.CreateDomain(ctx, token, d) -} - -func (lm *loggingMiddleware) RetrieveDomain(ctx context.Context, token, id string) (do auth.Domain, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("domain_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Retrieve domain failed", args...) - return - } - lm.logger.Info("Retrieve domain completed successfully", args...) - }(time.Now()) - return lm.svc.RetrieveDomain(ctx, token, id) -} - -func (lm *loggingMiddleware) RetrieveDomainPermissions(ctx context.Context, token, id string) (permissions policies.Permissions, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("domain_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Retrieve domain permissions failed", args...) - return - } - lm.logger.Info("Retrieve domain permissions completed successfully", args...) - }(time.Now()) - return lm.svc.RetrieveDomainPermissions(ctx, token, id) -} - -func (lm *loggingMiddleware) UpdateDomain(ctx context.Context, token, id string, d auth.DomainReq) (do auth.Domain, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("domain", - slog.String("id", id), - slog.Any("name", d.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update domain failed", args...) - return - } - lm.logger.Info("Update domain completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateDomain(ctx, token, id, d) -} - -func (lm *loggingMiddleware) ChangeDomainStatus(ctx context.Context, token, id string, d auth.DomainReq) (do auth.Domain, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("domain", - slog.String("id", id), - slog.String("name", do.Name), - slog.Any("status", d.Status), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Change domain status failed", args...) - return - } - lm.logger.Info("Change domain status completed successfully", args...) - }(time.Now()) - return lm.svc.ChangeDomainStatus(ctx, token, id, d) -} - -func (lm *loggingMiddleware) ListDomains(ctx context.Context, token string, page auth.Page) (do auth.DomainsPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("page", - slog.Uint64("limit", page.Limit), - slog.Uint64("offset", page.Offset), - slog.Uint64("total", page.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List domains failed", args...) - return - } - lm.logger.Info("List domains completed successfully", args...) - }(time.Now()) - return lm.svc.ListDomains(ctx, token, page) -} - -func (lm *loggingMiddleware) AssignUsers(ctx context.Context, token, id string, userIds []string, relation string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("domain_id", id), - slog.String("relation", relation), - slog.Any("user_ids", userIds), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Assign users to domain failed", args...) - return - } - lm.logger.Info("Assign users to domain completed successfully", args...) - }(time.Now()) - return lm.svc.AssignUsers(ctx, token, id, userIds, relation) -} - -func (lm *loggingMiddleware) UnassignUser(ctx context.Context, token, id, userID string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("domain_id", id), - slog.Any("user_id", userID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Unassign user from domain failed", args...) - return - } - lm.logger.Info("Unassign user from domain completed successfully", args...) - }(time.Now()) - return lm.svc.UnassignUser(ctx, token, id, userID) -} - -func (lm *loggingMiddleware) ListUserDomains(ctx context.Context, token, userID string, page auth.Page) (do auth.DomainsPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("user_id", userID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List user domains failed", args...) - return - } - lm.logger.Info("List user domains completed successfully", args...) - }(time.Now()) - return lm.svc.ListUserDomains(ctx, token, userID, page) -} - -func (lm *loggingMiddleware) DeleteUserFromDomains(ctx context.Context, id string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Delete entity policies failed to complete successfully", args...) - return - } - lm.logger.Info("Delete entity policies completed successfully", args...) - }(time.Now()) - return lm.svc.DeleteUserFromDomains(ctx, id) -} diff --git a/auth/api/metrics.go b/auth/api/metrics.go index 1e2befa8d2..7048ee3ca5 100644 --- a/auth/api/metrics.go +++ b/auth/api/metrics.go @@ -74,83 +74,3 @@ func (ms *metricsMiddleware) Authorize(ctx context.Context, pr policies.Policy) }(time.Now()) return ms.svc.Authorize(ctx, pr) } - -func (ms *metricsMiddleware) CreateDomain(ctx context.Context, token string, d auth.Domain) (auth.Domain, error) { - defer func(begin time.Time) { - ms.counter.With("method", "create_domain").Add(1) - ms.latency.With("method", "create_domain").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.CreateDomain(ctx, token, d) -} - -func (ms *metricsMiddleware) RetrieveDomain(ctx context.Context, token, id string) (auth.Domain, error) { - defer func(begin time.Time) { - ms.counter.With("method", "retrieve_domain").Add(1) - ms.latency.With("method", "retrieve_domain").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.RetrieveDomain(ctx, token, id) -} - -func (ms *metricsMiddleware) RetrieveDomainPermissions(ctx context.Context, token, id string) (policies.Permissions, error) { - defer func(begin time.Time) { - ms.counter.With("method", "retrieve_domain_permissions").Add(1) - ms.latency.With("method", "retrieve_domain_permissions").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.RetrieveDomainPermissions(ctx, token, id) -} - -func (ms *metricsMiddleware) UpdateDomain(ctx context.Context, token, id string, d auth.DomainReq) (auth.Domain, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_domain").Add(1) - ms.latency.With("method", "update_domain").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateDomain(ctx, token, id, d) -} - -func (ms *metricsMiddleware) ChangeDomainStatus(ctx context.Context, token, id string, d auth.DomainReq) (auth.Domain, error) { - defer func(begin time.Time) { - ms.counter.With("method", "change_domain_status").Add(1) - ms.latency.With("method", "change_domain_status").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ChangeDomainStatus(ctx, token, id, d) -} - -func (ms *metricsMiddleware) ListDomains(ctx context.Context, token string, page auth.Page) (auth.DomainsPage, error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_domains").Add(1) - ms.latency.With("method", "list_domains").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListDomains(ctx, token, page) -} - -func (ms *metricsMiddleware) AssignUsers(ctx context.Context, token, id string, userIds []string, relation string) error { - defer func(begin time.Time) { - ms.counter.With("method", "assign_users").Add(1) - ms.latency.With("method", "assign_users").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.AssignUsers(ctx, token, id, userIds, relation) -} - -func (ms *metricsMiddleware) UnassignUser(ctx context.Context, token, id, userID string) error { - defer func(begin time.Time) { - ms.counter.With("method", "unassign_users").Add(1) - ms.latency.With("method", "unassign_users").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UnassignUser(ctx, token, id, userID) -} - -func (ms *metricsMiddleware) ListUserDomains(ctx context.Context, token, userID string, page auth.Page) (auth.DomainsPage, error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_user_domains").Add(1) - ms.latency.With("method", "list_user_domains").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListUserDomains(ctx, token, userID, page) -} - -func (ms *metricsMiddleware) DeleteUserFromDomains(ctx context.Context, id string) error { - defer func(begin time.Time) { - ms.counter.With("method", "delete_user_from_domains").Add(1) - ms.latency.With("method", "delete_user_from_domains").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.DeleteUserFromDomains(ctx, id) -} diff --git a/auth/events/streams.go b/auth/events/streams.go deleted file mode 100644 index 702242cfb0..0000000000 --- a/auth/events/streams.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "context" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/store" - "github.com/absmach/magistrala/pkg/policies" -) - -const streamID = "magistrala.auth" - -var _ auth.Service = (*eventStore)(nil) - -type eventStore struct { - events.Publisher - svc auth.Service -} - -// NewEventStoreMiddleware returns wrapper around auth service that sends -// events to event store. -func NewEventStoreMiddleware(ctx context.Context, svc auth.Service, url string) (auth.Service, error) { - publisher, err := store.NewPublisher(ctx, url, streamID) - if err != nil { - return nil, err - } - - return &eventStore{ - svc: svc, - Publisher: publisher, - }, nil -} - -func (es *eventStore) CreateDomain(ctx context.Context, token string, domain auth.Domain) (auth.Domain, error) { - domain, err := es.svc.CreateDomain(ctx, token, domain) - if err != nil { - return domain, err - } - - event := createDomainEvent{ - domain, - } - - if err := es.Publish(ctx, event); err != nil { - return domain, err - } - - return domain, nil -} - -func (es *eventStore) RetrieveDomain(ctx context.Context, token, id string) (auth.Domain, error) { - domain, err := es.svc.RetrieveDomain(ctx, token, id) - if err != nil { - return domain, err - } - - event := retrieveDomainEvent{ - domain, - } - - if err := es.Publish(ctx, event); err != nil { - return domain, err - } - - return domain, nil -} - -func (es *eventStore) RetrieveDomainPermissions(ctx context.Context, token, id string) (policies.Permissions, error) { - permissions, err := es.svc.RetrieveDomainPermissions(ctx, token, id) - if err != nil { - return permissions, err - } - - event := retrieveDomainPermissionsEvent{ - domainID: id, - permissions: permissions, - } - - if err := es.Publish(ctx, event); err != nil { - return permissions, err - } - - return permissions, nil -} - -func (es *eventStore) UpdateDomain(ctx context.Context, token, id string, d auth.DomainReq) (auth.Domain, error) { - domain, err := es.svc.UpdateDomain(ctx, token, id, d) - if err != nil { - return domain, err - } - - event := updateDomainEvent{ - domain, - } - - if err := es.Publish(ctx, event); err != nil { - return domain, err - } - - return domain, nil -} - -func (es *eventStore) ChangeDomainStatus(ctx context.Context, token, id string, d auth.DomainReq) (auth.Domain, error) { - domain, err := es.svc.ChangeDomainStatus(ctx, token, id, d) - if err != nil { - return domain, err - } - - event := changeDomainStatusEvent{ - domainID: id, - status: domain.Status, - updatedAt: domain.UpdatedAt, - updatedBy: domain.UpdatedBy, - } - - if err := es.Publish(ctx, event); err != nil { - return domain, err - } - - return domain, nil -} - -func (es *eventStore) ListDomains(ctx context.Context, token string, p auth.Page) (auth.DomainsPage, error) { - dp, err := es.svc.ListDomains(ctx, token, p) - if err != nil { - return dp, err - } - - event := listDomainsEvent{ - p, dp.Total, - } - - if err := es.Publish(ctx, event); err != nil { - return dp, err - } - - return dp, nil -} - -func (es *eventStore) AssignUsers(ctx context.Context, token, id string, userIds []string, relation string) error { - err := es.svc.AssignUsers(ctx, token, id, userIds, relation) - if err != nil { - return err - } - - event := assignUsersEvent{ - domainID: id, - userIDs: userIds, - relation: relation, - } - - if err := es.Publish(ctx, event); err != nil { - return err - } - - return nil -} - -func (es *eventStore) UnassignUser(ctx context.Context, token, id, userID string) error { - err := es.svc.UnassignUser(ctx, token, id, userID) - if err != nil { - return err - } - - event := unassignUsersEvent{ - domainID: id, - userID: userID, - } - - if err := es.Publish(ctx, event); err != nil { - return err - } - - return nil -} - -func (es *eventStore) ListUserDomains(ctx context.Context, token, userID string, p auth.Page) (auth.DomainsPage, error) { - dp, err := es.svc.ListUserDomains(ctx, token, userID, p) - if err != nil { - return dp, err - } - - event := listUserDomainsEvent{ - Page: p, - userID: userID, - } - - if err := es.Publish(ctx, event); err != nil { - return dp, err - } - - return dp, nil -} - -func (es *eventStore) Issue(ctx context.Context, token string, key auth.Key) (auth.Token, error) { - return es.svc.Issue(ctx, token, key) -} - -func (es *eventStore) Revoke(ctx context.Context, token, id string) error { - return es.svc.Revoke(ctx, token, id) -} - -func (es *eventStore) RetrieveKey(ctx context.Context, token, id string) (auth.Key, error) { - return es.svc.RetrieveKey(ctx, token, id) -} - -func (es *eventStore) Identify(ctx context.Context, token string) (auth.Key, error) { - return es.svc.Identify(ctx, token) -} - -func (es *eventStore) Authorize(ctx context.Context, pr policies.Policy) error { - return es.svc.Authorize(ctx, pr) -} - -func (es *eventStore) DeleteUserFromDomains(ctx context.Context, id string) error { - return es.svc.DeleteUserFromDomains(ctx, id) -} diff --git a/auth/mocks/domains.go b/auth/mocks/domains.go deleted file mode 100644 index c9bc09c95f..0000000000 --- a/auth/mocks/domains.go +++ /dev/null @@ -1,306 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - auth "github.com/absmach/magistrala/auth" - - mock "github.com/stretchr/testify/mock" -) - -// DomainsRepository is an autogenerated mock type for the DomainsRepository type -type DomainsRepository struct { - mock.Mock -} - -// CheckPolicy provides a mock function with given fields: ctx, pc -func (_m *DomainsRepository) CheckPolicy(ctx context.Context, pc auth.Policy) error { - ret := _m.Called(ctx, pc) - - if len(ret) == 0 { - panic("no return value specified for CheckPolicy") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, auth.Policy) error); ok { - r0 = rf(ctx, pc) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Delete provides a mock function with given fields: ctx, id -func (_m *DomainsRepository) Delete(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// DeletePolicies provides a mock function with given fields: ctx, pcs -func (_m *DomainsRepository) DeletePolicies(ctx context.Context, pcs ...auth.Policy) error { - _va := make([]interface{}, len(pcs)) - for _i := range pcs { - _va[_i] = pcs[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for DeletePolicies") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, ...auth.Policy) error); ok { - r0 = rf(ctx, pcs...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// DeleteUserPolicies provides a mock function with given fields: ctx, id -func (_m *DomainsRepository) DeleteUserPolicies(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for DeleteUserPolicies") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ListDomains provides a mock function with given fields: ctx, pm -func (_m *DomainsRepository) ListDomains(ctx context.Context, pm auth.Page) (auth.DomainsPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for ListDomains") - } - - var r0 auth.DomainsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, auth.Page) (auth.DomainsPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, auth.Page) auth.DomainsPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(auth.DomainsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, auth.Page) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveAllByIDs provides a mock function with given fields: ctx, pm -func (_m *DomainsRepository) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth.DomainsPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAllByIDs") - } - - var r0 auth.DomainsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, auth.Page) (auth.DomainsPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, auth.Page) auth.DomainsPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(auth.DomainsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, auth.Page) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveByID provides a mock function with given fields: ctx, id -func (_m *DomainsRepository) RetrieveByID(ctx context.Context, id string) (auth.Domain, error) { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByID") - } - - var r0 auth.Domain - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (auth.Domain, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string) auth.Domain); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(auth.Domain) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrievePermissions provides a mock function with given fields: ctx, subject, id -func (_m *DomainsRepository) RetrievePermissions(ctx context.Context, subject string, id string) ([]string, error) { - ret := _m.Called(ctx, subject, id) - - if len(ret) == 0 { - panic("no return value specified for RetrievePermissions") - } - - var r0 []string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]string, error)); ok { - return rf(ctx, subject, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) []string); ok { - r0 = rf(ctx, subject, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]string) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, subject, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, d -func (_m *DomainsRepository) Save(ctx context.Context, d auth.Domain) (auth.Domain, error) { - ret := _m.Called(ctx, d) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 auth.Domain - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, auth.Domain) (auth.Domain, error)); ok { - return rf(ctx, d) - } - if rf, ok := ret.Get(0).(func(context.Context, auth.Domain) auth.Domain); ok { - r0 = rf(ctx, d) - } else { - r0 = ret.Get(0).(auth.Domain) - } - - if rf, ok := ret.Get(1).(func(context.Context, auth.Domain) error); ok { - r1 = rf(ctx, d) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// SavePolicies provides a mock function with given fields: ctx, pcs -func (_m *DomainsRepository) SavePolicies(ctx context.Context, pcs ...auth.Policy) error { - _va := make([]interface{}, len(pcs)) - for _i := range pcs { - _va[_i] = pcs[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for SavePolicies") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, ...auth.Policy) error); ok { - r0 = rf(ctx, pcs...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Update provides a mock function with given fields: ctx, id, userID, d -func (_m *DomainsRepository) Update(ctx context.Context, id string, userID string, d auth.DomainReq) (auth.Domain, error) { - ret := _m.Called(ctx, id, userID, d) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 auth.Domain - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) (auth.Domain, error)); ok { - return rf(ctx, id, userID, d) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) auth.Domain); ok { - r0 = rf(ctx, id, userID, d) - } else { - r0 = ret.Get(0).(auth.Domain) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string, auth.DomainReq) error); ok { - r1 = rf(ctx, id, userID, d) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewDomainsRepository creates a new instance of DomainsRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewDomainsRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *DomainsRepository { - mock := &DomainsRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/auth/mocks/service.go b/auth/mocks/service.go index 80ec2714fb..fae27a98be 100644 --- a/auth/mocks/service.go +++ b/auth/mocks/service.go @@ -19,24 +19,6 @@ type Service struct { mock.Mock } -// AssignUsers provides a mock function with given fields: ctx, token, id, userIds, relation -func (_m *Service) AssignUsers(ctx context.Context, token string, id string, userIds []string, relation string) error { - ret := _m.Called(ctx, token, id, userIds, relation) - - if len(ret) == 0 { - panic("no return value specified for AssignUsers") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, []string, string) error); ok { - r0 = rf(ctx, token, id, userIds, relation) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // Authorize provides a mock function with given fields: ctx, pr func (_m *Service) Authorize(ctx context.Context, pr policies.Policy) error { ret := _m.Called(ctx, pr) @@ -55,80 +37,6 @@ func (_m *Service) Authorize(ctx context.Context, pr policies.Policy) error { return r0 } -// ChangeDomainStatus provides a mock function with given fields: ctx, token, id, d -func (_m *Service) ChangeDomainStatus(ctx context.Context, token string, id string, d auth.DomainReq) (auth.Domain, error) { - ret := _m.Called(ctx, token, id, d) - - if len(ret) == 0 { - panic("no return value specified for ChangeDomainStatus") - } - - var r0 auth.Domain - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) (auth.Domain, error)); ok { - return rf(ctx, token, id, d) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) auth.Domain); ok { - r0 = rf(ctx, token, id, d) - } else { - r0 = ret.Get(0).(auth.Domain) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string, auth.DomainReq) error); ok { - r1 = rf(ctx, token, id, d) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CreateDomain provides a mock function with given fields: ctx, token, d -func (_m *Service) CreateDomain(ctx context.Context, token string, d auth.Domain) (auth.Domain, error) { - ret := _m.Called(ctx, token, d) - - if len(ret) == 0 { - panic("no return value specified for CreateDomain") - } - - var r0 auth.Domain - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, auth.Domain) (auth.Domain, error)); ok { - return rf(ctx, token, d) - } - if rf, ok := ret.Get(0).(func(context.Context, string, auth.Domain) auth.Domain); ok { - r0 = rf(ctx, token, d) - } else { - r0 = ret.Get(0).(auth.Domain) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, auth.Domain) error); ok { - r1 = rf(ctx, token, d) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DeleteUserFromDomains provides a mock function with given fields: ctx, id -func (_m *Service) DeleteUserFromDomains(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for DeleteUserFromDomains") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // Identify provides a mock function with given fields: ctx, token func (_m *Service) Identify(ctx context.Context, token string) (auth.Key, error) { ret := _m.Called(ctx, token) @@ -185,120 +93,6 @@ func (_m *Service) Issue(ctx context.Context, token string, key auth.Key) (auth. return r0, r1 } -// ListDomains provides a mock function with given fields: ctx, token, page -func (_m *Service) ListDomains(ctx context.Context, token string, page auth.Page) (auth.DomainsPage, error) { - ret := _m.Called(ctx, token, page) - - if len(ret) == 0 { - panic("no return value specified for ListDomains") - } - - var r0 auth.DomainsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, auth.Page) (auth.DomainsPage, error)); ok { - return rf(ctx, token, page) - } - if rf, ok := ret.Get(0).(func(context.Context, string, auth.Page) auth.DomainsPage); ok { - r0 = rf(ctx, token, page) - } else { - r0 = ret.Get(0).(auth.DomainsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, auth.Page) error); ok { - r1 = rf(ctx, token, page) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListUserDomains provides a mock function with given fields: ctx, token, userID, page -func (_m *Service) ListUserDomains(ctx context.Context, token string, userID string, page auth.Page) (auth.DomainsPage, error) { - ret := _m.Called(ctx, token, userID, page) - - if len(ret) == 0 { - panic("no return value specified for ListUserDomains") - } - - var r0 auth.DomainsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.Page) (auth.DomainsPage, error)); ok { - return rf(ctx, token, userID, page) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.Page) auth.DomainsPage); ok { - r0 = rf(ctx, token, userID, page) - } else { - r0 = ret.Get(0).(auth.DomainsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string, auth.Page) error); ok { - r1 = rf(ctx, token, userID, page) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveDomain provides a mock function with given fields: ctx, token, id -func (_m *Service) RetrieveDomain(ctx context.Context, token string, id string) (auth.Domain, error) { - ret := _m.Called(ctx, token, id) - - if len(ret) == 0 { - panic("no return value specified for RetrieveDomain") - } - - var r0 auth.Domain - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (auth.Domain, error)); ok { - return rf(ctx, token, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) auth.Domain); ok { - r0 = rf(ctx, token, id) - } else { - r0 = ret.Get(0).(auth.Domain) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, token, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveDomainPermissions provides a mock function with given fields: ctx, token, id -func (_m *Service) RetrieveDomainPermissions(ctx context.Context, token string, id string) (policies.Permissions, error) { - ret := _m.Called(ctx, token, id) - - if len(ret) == 0 { - panic("no return value specified for RetrieveDomainPermissions") - } - - var r0 policies.Permissions - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (policies.Permissions, error)); ok { - return rf(ctx, token, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) policies.Permissions); ok { - r0 = rf(ctx, token, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(policies.Permissions) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, token, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // RetrieveKey provides a mock function with given fields: ctx, token, id func (_m *Service) RetrieveKey(ctx context.Context, token string, id string) (auth.Key, error) { ret := _m.Called(ctx, token, id) @@ -345,52 +139,6 @@ func (_m *Service) Revoke(ctx context.Context, token string, id string) error { return r0 } -// UnassignUser provides a mock function with given fields: ctx, token, id, userID -func (_m *Service) UnassignUser(ctx context.Context, token string, id string, userID string) error { - ret := _m.Called(ctx, token, id, userID) - - if len(ret) == 0 { - panic("no return value specified for UnassignUser") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { - r0 = rf(ctx, token, id, userID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UpdateDomain provides a mock function with given fields: ctx, token, id, d -func (_m *Service) UpdateDomain(ctx context.Context, token string, id string, d auth.DomainReq) (auth.Domain, error) { - ret := _m.Called(ctx, token, id, d) - - if len(ret) == 0 { - panic("no return value specified for UpdateDomain") - } - - var r0 auth.Domain - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) (auth.Domain, error)); ok { - return rf(ctx, token, id, d) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string, auth.DomainReq) auth.Domain); ok { - r0 = rf(ctx, token, id, d) - } else { - r0 = ret.Get(0).(auth.Domain) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string, auth.DomainReq) error); ok { - r1 = rf(ctx, token, id, d) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewService(t interface { diff --git a/auth/mocks/token_client.go b/auth/mocks/token_client.go index ae2e03e7fb..dce85cf1b4 100644 --- a/auth/mocks/token_client.go +++ b/auth/mocks/token_client.go @@ -11,9 +11,9 @@ import ( grpc "google.golang.org/grpc" - magistrala "github.com/absmach/magistrala" - mock "github.com/stretchr/testify/mock" + + v1 "github.com/absmach/magistrala/internal/grpc/token/v1" ) // TokenServiceClient is an autogenerated mock type for the TokenServiceClient type @@ -30,7 +30,7 @@ func (_m *TokenServiceClient) EXPECT() *TokenServiceClient_Expecter { } // Issue provides a mock function with given fields: ctx, in, opts -func (_m *TokenServiceClient) Issue(ctx context.Context, in *magistrala.IssueReq, opts ...grpc.CallOption) (*magistrala.Token, error) { +func (_m *TokenServiceClient) Issue(ctx context.Context, in *v1.IssueReq, opts ...grpc.CallOption) (*v1.Token, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -44,20 +44,20 @@ func (_m *TokenServiceClient) Issue(ctx context.Context, in *magistrala.IssueReq panic("no return value specified for Issue") } - var r0 *magistrala.Token + var r0 *v1.Token var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.IssueReq, ...grpc.CallOption) (*magistrala.Token, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *v1.IssueReq, ...grpc.CallOption) (*v1.Token, error)); ok { return rf(ctx, in, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.IssueReq, ...grpc.CallOption) *magistrala.Token); ok { + if rf, ok := ret.Get(0).(func(context.Context, *v1.IssueReq, ...grpc.CallOption) *v1.Token); ok { r0 = rf(ctx, in, opts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.Token) + r0 = ret.Get(0).(*v1.Token) } } - if rf, ok := ret.Get(1).(func(context.Context, *magistrala.IssueReq, ...grpc.CallOption) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *v1.IssueReq, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { r1 = ret.Error(1) @@ -73,14 +73,14 @@ type TokenServiceClient_Issue_Call struct { // Issue is a helper method to define mock.On call // - ctx context.Context -// - in *magistrala.IssueReq +// - in *v1.IssueReq // - opts ...grpc.CallOption func (_e *TokenServiceClient_Expecter) Issue(ctx interface{}, in interface{}, opts ...interface{}) *TokenServiceClient_Issue_Call { return &TokenServiceClient_Issue_Call{Call: _e.mock.On("Issue", append([]interface{}{ctx, in}, opts...)...)} } -func (_c *TokenServiceClient_Issue_Call) Run(run func(ctx context.Context, in *magistrala.IssueReq, opts ...grpc.CallOption)) *TokenServiceClient_Issue_Call { +func (_c *TokenServiceClient_Issue_Call) Run(run func(ctx context.Context, in *v1.IssueReq, opts ...grpc.CallOption)) *TokenServiceClient_Issue_Call { _c.Call.Run(func(args mock.Arguments) { variadicArgs := make([]grpc.CallOption, len(args)-2) for i, a := range args[2:] { @@ -88,23 +88,23 @@ func (_c *TokenServiceClient_Issue_Call) Run(run func(ctx context.Context, in *m variadicArgs[i] = a.(grpc.CallOption) } } - run(args[0].(context.Context), args[1].(*magistrala.IssueReq), variadicArgs...) + run(args[0].(context.Context), args[1].(*v1.IssueReq), variadicArgs...) }) return _c } -func (_c *TokenServiceClient_Issue_Call) Return(_a0 *magistrala.Token, _a1 error) *TokenServiceClient_Issue_Call { +func (_c *TokenServiceClient_Issue_Call) Return(_a0 *v1.Token, _a1 error) *TokenServiceClient_Issue_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *TokenServiceClient_Issue_Call) RunAndReturn(run func(context.Context, *magistrala.IssueReq, ...grpc.CallOption) (*magistrala.Token, error)) *TokenServiceClient_Issue_Call { +func (_c *TokenServiceClient_Issue_Call) RunAndReturn(run func(context.Context, *v1.IssueReq, ...grpc.CallOption) (*v1.Token, error)) *TokenServiceClient_Issue_Call { _c.Call.Return(run) return _c } // Refresh provides a mock function with given fields: ctx, in, opts -func (_m *TokenServiceClient) Refresh(ctx context.Context, in *magistrala.RefreshReq, opts ...grpc.CallOption) (*magistrala.Token, error) { +func (_m *TokenServiceClient) Refresh(ctx context.Context, in *v1.RefreshReq, opts ...grpc.CallOption) (*v1.Token, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -118,20 +118,20 @@ func (_m *TokenServiceClient) Refresh(ctx context.Context, in *magistrala.Refres panic("no return value specified for Refresh") } - var r0 *magistrala.Token + var r0 *v1.Token var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.RefreshReq, ...grpc.CallOption) (*magistrala.Token, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *v1.RefreshReq, ...grpc.CallOption) (*v1.Token, error)); ok { return rf(ctx, in, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.RefreshReq, ...grpc.CallOption) *magistrala.Token); ok { + if rf, ok := ret.Get(0).(func(context.Context, *v1.RefreshReq, ...grpc.CallOption) *v1.Token); ok { r0 = rf(ctx, in, opts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.Token) + r0 = ret.Get(0).(*v1.Token) } } - if rf, ok := ret.Get(1).(func(context.Context, *magistrala.RefreshReq, ...grpc.CallOption) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *v1.RefreshReq, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { r1 = ret.Error(1) @@ -147,14 +147,14 @@ type TokenServiceClient_Refresh_Call struct { // Refresh is a helper method to define mock.On call // - ctx context.Context -// - in *magistrala.RefreshReq +// - in *v1.RefreshReq // - opts ...grpc.CallOption func (_e *TokenServiceClient_Expecter) Refresh(ctx interface{}, in interface{}, opts ...interface{}) *TokenServiceClient_Refresh_Call { return &TokenServiceClient_Refresh_Call{Call: _e.mock.On("Refresh", append([]interface{}{ctx, in}, opts...)...)} } -func (_c *TokenServiceClient_Refresh_Call) Run(run func(ctx context.Context, in *magistrala.RefreshReq, opts ...grpc.CallOption)) *TokenServiceClient_Refresh_Call { +func (_c *TokenServiceClient_Refresh_Call) Run(run func(ctx context.Context, in *v1.RefreshReq, opts ...grpc.CallOption)) *TokenServiceClient_Refresh_Call { _c.Call.Run(func(args mock.Arguments) { variadicArgs := make([]grpc.CallOption, len(args)-2) for i, a := range args[2:] { @@ -162,17 +162,17 @@ func (_c *TokenServiceClient_Refresh_Call) Run(run func(ctx context.Context, in variadicArgs[i] = a.(grpc.CallOption) } } - run(args[0].(context.Context), args[1].(*magistrala.RefreshReq), variadicArgs...) + run(args[0].(context.Context), args[1].(*v1.RefreshReq), variadicArgs...) }) return _c } -func (_c *TokenServiceClient_Refresh_Call) Return(_a0 *magistrala.Token, _a1 error) *TokenServiceClient_Refresh_Call { +func (_c *TokenServiceClient_Refresh_Call) Return(_a0 *v1.Token, _a1 error) *TokenServiceClient_Refresh_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *TokenServiceClient_Refresh_Call) RunAndReturn(run func(context.Context, *magistrala.RefreshReq, ...grpc.CallOption) (*magistrala.Token, error)) *TokenServiceClient_Refresh_Call { +func (_c *TokenServiceClient_Refresh_Call) RunAndReturn(run func(context.Context, *v1.RefreshReq, ...grpc.CallOption) (*v1.Token, error)) *TokenServiceClient_Refresh_Call { _c.Call.Return(run) return _c } diff --git a/auth/postgres/domains_test.go b/auth/postgres/domains_test.go deleted file mode 100644 index 1e1997a90e..0000000000 --- a/auth/postgres/domains_test.go +++ /dev/null @@ -1,1148 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/auth/postgres" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/policies" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - inValid = "invalid" -) - -var ( - domainID = testsutil.GenerateUUID(&testing.T{}) - userID = testsutil.GenerateUUID(&testing.T{}) -) - -func TestAddPolicyCopy(t *testing.T) { - repo := postgres.NewDomainRepository(database) - cases := []struct { - desc string - pc auth.Policy - err error - }{ - { - desc: "add a policy copy", - pc: auth.Policy{ - SubjectType: "unknown", - SubjectID: "unknown", - Relation: "unknown", - ObjectType: "unknown", - ObjectID: "unknown", - }, - err: nil, - }, - { - desc: "add again same policy copy", - pc: auth.Policy{ - SubjectType: "unknown", - SubjectID: "unknown", - Relation: "unknown", - ObjectType: "unknown", - ObjectID: "unknown", - }, - err: repoerr.ErrConflict, - }, - } - - for _, tc := range cases { - err := repo.SavePolicies(context.Background(), tc.pc) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestDeletePolicyCopy(t *testing.T) { - repo := postgres.NewDomainRepository(database) - cases := []struct { - desc string - pc auth.Policy - err error - }{ - { - desc: "delete a policy copy", - pc: auth.Policy{ - SubjectType: "unknown", - SubjectID: "unknown", - Relation: "unknown", - ObjectType: "unknown", - ObjectID: "unknown", - }, - err: nil, - }, - { - desc: "delete a policy with empty relation", - pc: auth.Policy{ - SubjectType: "unknown", - SubjectID: "unknown", - Relation: "", - ObjectType: "unknown", - ObjectID: "unknown", - }, - err: nil, - }, - } - - for _, tc := range cases { - err := repo.DeletePolicies(context.Background(), tc.pc) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestSave(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM domains") - require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) - }) - - repo := postgres.NewDomainRepository(database) - - cases := []struct { - desc string - domain auth.Domain - err error - }{ - { - desc: "add new domain with all fields successfully", - domain: auth.Domain{ - ID: domainID, - Name: "test", - Alias: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - }, - err: nil, - }, - { - desc: "add the same domain again", - domain: auth.Domain{ - ID: domainID, - Name: "test", - Alias: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - }, - err: repoerr.ErrConflict, - }, - { - desc: "add domain with empty ID", - domain: auth.Domain{ - ID: "", - Name: "test1", - Alias: "test1", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - }, - err: nil, - }, - { - desc: "add domain with empty alias", - domain: auth.Domain{ - ID: testsutil.GenerateUUID(&testing.T{}), - Name: "test1", - Alias: "", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "add domain with malformed metadata", - domain: auth.Domain{ - ID: domainID, - Name: "test1", - Alias: "test1", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - }, - err: repoerr.ErrCreateEntity, - }, - } - - for _, tc := range cases { - _, err := repo.Save(context.Background(), tc.domain) - { - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestRetrieveByID(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM domains") - require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) - }) - - repo := postgres.NewDomainRepository(database) - - domain := auth.Domain{ - ID: domainID, - Name: "test", - Alias: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - } - - _, err := repo.Save(context.Background(), domain) - require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) - - cases := []struct { - desc string - domainID string - response auth.Domain - err error - }{ - { - desc: "retrieve existing client", - domainID: domain.ID, - response: domain, - err: nil, - }, - { - desc: "retrieve non-existing client", - domainID: inValid, - response: auth.Domain{}, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve with empty client id", - domainID: "", - response: auth.Domain{}, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - d, err := repo.RetrieveByID(context.Background(), tc.domainID) - assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestRetreivePermissions(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM domains") - require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) - _, err = db.Exec("DELETE FROM policies") - require.Nil(t, err, fmt.Sprintf("clean policies unexpected error: %s", err)) - }) - - repo := postgres.NewDomainRepository(database) - - domain := auth.Domain{ - ID: domainID, - Name: "test", - Alias: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - Permission: "admin", - } - - policy := auth.Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - SubjectRelation: "admin", - Relation: "admin", - ObjectType: policies.DomainType, - ObjectID: domainID, - } - - _, err := repo.Save(context.Background(), domain) - require.Nil(t, err, fmt.Sprintf("failed to save domain %s", domain.ID)) - - err = repo.SavePolicies(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("failed to save policy %s", policy.SubjectID)) - - cases := []struct { - desc string - domainID string - policySubject string - response []string - err error - }{ - { - desc: "retrieve existing permissions with valid domaiinID and policySubject", - domainID: domain.ID, - policySubject: userID, - response: []string{"admin"}, - err: nil, - }, - { - desc: "retreieve permissions with invalid domainID", - domainID: inValid, - policySubject: userID, - response: []string{}, - err: nil, - }, - { - desc: "retreieve permissions with invalid policySubject", - domainID: domain.ID, - policySubject: inValid, - response: []string{}, - err: nil, - }, - } - - for _, tc := range cases { - d, err := repo.RetrievePermissions(context.Background(), tc.policySubject, tc.domainID) - assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestRetrieveAllByIDs(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM domains") - require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) - }) - - repo := postgres.NewDomainRepository(database) - - items := []auth.Domain{} - for i := 0; i < 10; i++ { - domain := auth.Domain{ - ID: testsutil.GenerateUUID(t), - Name: fmt.Sprintf(`"test%d"`, i), - Alias: fmt.Sprintf(`"test%d"`, i), - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - } - if i%5 == 0 { - domain.Status = auth.DisabledStatus - domain.Tags = []string{"test", "admin"} - domain.Metadata = map[string]interface{}{ - "test1": "test1", - } - } - _, err := repo.Save(context.Background(), domain) - require.Nil(t, err, fmt.Sprintf("save domain unexpected error: %s", err)) - items = append(items, domain) - } - - cases := []struct { - desc string - pm auth.Page - response auth.DomainsPage - err error - }{ - { - desc: "retrieve by ids successfully", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{items[1].ID, items[2].ID}, - }, - response: auth.DomainsPage{ - Total: 2, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{items[1], items[2]}, - }, - err: nil, - }, - { - desc: "retrieve by ids with empty ids", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{}, - }, - response: auth.DomainsPage{ - Total: 0, - Offset: 0, - Limit: 0, - }, - err: nil, - }, - { - desc: "retrieve by ids with invalid ids", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{inValid}, - }, - response: auth.DomainsPage{ - Total: 0, - Offset: 0, - Limit: 10, - }, - err: nil, - }, - { - desc: "retrieve by ids and status", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{items[0].ID, items[1].ID}, - Status: auth.DisabledStatus, - }, - response: auth.DomainsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{items[0]}, - }, - }, - { - desc: "retrieve by ids and status with invalid status", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{items[0].ID, items[1].ID}, - Status: 5, - }, - response: auth.DomainsPage{ - Total: 2, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{items[0], items[1]}, - }, - }, - { - desc: "retrieve by ids and tags", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{items[0].ID, items[1].ID}, - Tag: "test", - }, - response: auth.DomainsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{items[1]}, - }, - }, - { - desc: " retrieve by ids and metadata", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{items[1].ID, items[2].ID}, - Metadata: map[string]interface{}{ - "test": "test", - }, - Status: auth.EnabledStatus, - }, - response: auth.DomainsPage{ - Total: 2, - Offset: 0, - Limit: 10, - Domains: items[1:3], - }, - }, - { - desc: "retrieve by ids and metadata with invalid metadata", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{items[1].ID, items[2].ID}, - Metadata: map[string]interface{}{ - "test1": "test1", - }, - Status: auth.EnabledStatus, - }, - response: auth.DomainsPage{ - Total: 0, - Offset: 0, - Limit: 10, - }, - }, - { - desc: "retrieve by ids and malfomed metadata", - pm: auth.Page{ - Offset: 0, - Limit: 10, - IDs: []string{items[1].ID, items[2].ID}, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - Status: auth.EnabledStatus, - }, - response: auth.DomainsPage{}, - err: repoerr.ErrViewEntity, - }, - { - desc: "retrieve all by ids and id", - pm: auth.Page{ - Offset: 0, - Limit: 10, - ID: items[1].ID, - IDs: []string{items[1].ID, items[2].ID}, - }, - response: auth.DomainsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{items[1]}, - }, - }, - { - desc: "retrieve all by ids and id with invalid id", - pm: auth.Page{ - Offset: 0, - Limit: 10, - ID: inValid, - IDs: []string{items[1].ID, items[2].ID}, - }, - response: auth.DomainsPage{ - Total: 0, - Offset: 0, - Limit: 10, - }, - }, - { - desc: "retrieve all by ids and name", - pm: auth.Page{ - Offset: 0, - Limit: 10, - Name: items[1].Name, - IDs: []string{items[1].ID, items[2].ID}, - }, - response: auth.DomainsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{items[1]}, - }, - }, - { - desc: "retrieve all by ids with empty page", - pm: auth.Page{}, - response: auth.DomainsPage{}, - }, - } - - for _, tc := range cases { - d, err := repo.RetrieveAllByIDs(context.Background(), tc.pm) - assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestListDomains(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM domains") - require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) - }) - - repo := postgres.NewDomainRepository(database) - - items := []auth.Domain{} - rDomains := []auth.Domain{} - policyList := []auth.Policy{} - for i := 0; i < 10; i++ { - domain := auth.Domain{ - ID: testsutil.GenerateUUID(t), - Name: fmt.Sprintf(`"test%d"`, i), - Alias: fmt.Sprintf(`"test%d"`, i), - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - } - if i%5 == 0 { - domain.Status = auth.DisabledStatus - domain.Tags = []string{"test", "admin"} - domain.Metadata = map[string]interface{}{ - "test1": "test1", - } - } - policy := auth.Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - SubjectRelation: policies.AdministratorRelation, - Relation: policies.DomainRelation, - ObjectType: policies.DomainType, - ObjectID: domain.ID, - } - _, err := repo.Save(context.Background(), domain) - require.Nil(t, err, fmt.Sprintf("save domain unexpected error: %s", err)) - items = append(items, domain) - policyList = append(policyList, policy) - rDomain := domain - rDomain.Permission = "domain" - rDomains = append(rDomains, rDomain) - } - - err := repo.SavePolicies(context.Background(), policyList...) - require.Nil(t, err, fmt.Sprintf("failed to save policies %s", policyList)) - - cases := []struct { - desc string - pm auth.Page - response auth.DomainsPage - err error - }{ - { - desc: "list all domains successfully", - pm: auth.Page{ - Offset: 0, - Limit: 10, - Status: auth.AllStatus, - }, - response: auth.DomainsPage{ - Total: 10, - Offset: 0, - Limit: 10, - Domains: items, - }, - err: nil, - }, - { - desc: "list domains with empty page", - pm: auth.Page{ - Offset: 0, - Limit: 0, - }, - response: auth.DomainsPage{ - Total: 8, - Offset: 0, - Limit: 0, - }, - err: nil, - }, - { - desc: "list domains with enabled status", - pm: auth.Page{ - Offset: 0, - Limit: 10, - Status: auth.EnabledStatus, - }, - response: auth.DomainsPage{ - Total: 8, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{items[1], items[2], items[3], items[4], items[6], items[7], items[8], items[9]}, - }, - err: nil, - }, - { - desc: "list domains with disabled status", - pm: auth.Page{ - Offset: 0, - Limit: 10, - Status: auth.DisabledStatus, - }, - response: auth.DomainsPage{ - Total: 2, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{items[0], items[5]}, - }, - err: nil, - }, - { - desc: "list domains with subject ID", - pm: auth.Page{ - Offset: 0, - Limit: 10, - SubjectID: userID, - Status: auth.AllStatus, - }, - response: auth.DomainsPage{ - Total: 10, - Offset: 0, - Limit: 10, - Domains: rDomains, - }, - err: nil, - }, - { - desc: "list domains with subject ID and status", - pm: auth.Page{ - Offset: 0, - Limit: 10, - SubjectID: userID, - Status: auth.EnabledStatus, - }, - response: auth.DomainsPage{ - Total: 8, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{rDomains[1], rDomains[2], rDomains[3], rDomains[4], rDomains[6], rDomains[7], rDomains[8], rDomains[9]}, - }, - err: nil, - }, - { - desc: "list domains with subject Id and permission", - pm: auth.Page{ - Offset: 0, - Limit: 10, - SubjectID: userID, - Permission: "domain", - Status: auth.AllStatus, - }, - response: auth.DomainsPage{ - Total: 10, - Offset: 0, - Limit: 10, - Domains: rDomains, - }, - err: nil, - }, - { - desc: "list domains with subject id and tags", - pm: auth.Page{ - Offset: 0, - Limit: 10, - SubjectID: userID, - Tag: "test", - Status: auth.AllStatus, - }, - response: auth.DomainsPage{ - Total: 10, - Offset: 0, - Limit: 10, - Domains: rDomains, - }, - err: nil, - }, - { - desc: "list domains with subject id and metadata", - pm: auth.Page{ - Offset: 0, - Limit: 10, - SubjectID: userID, - Metadata: map[string]interface{}{ - "test": "test", - }, - Status: auth.AllStatus, - }, - response: auth.DomainsPage{ - Total: 8, - Offset: 0, - Limit: 10, - Domains: []auth.Domain{rDomains[1], rDomains[2], rDomains[3], rDomains[4], rDomains[6], rDomains[7], rDomains[8], rDomains[9]}, - }, - }, - { - desc: "list domains with subject id and metadata with malforned metadata", - pm: auth.Page{ - Offset: 0, - Limit: 10, - SubjectID: userID, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - Status: auth.AllStatus, - }, - response: auth.DomainsPage{}, - err: repoerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - d, err := repo.ListDomains(context.Background(), tc.pm) - assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestUpdate(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM domains") - require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) - }) - - updatedName := "test1" - updatedMetadata := auth.Metadata{ - "test1": "test1", - } - updatedTags := []string{"test1"} - updatedStatus := auth.DisabledStatus - updatedAlias := "test1" - - repo := postgres.NewDomainRepository(database) - - domain := auth.Domain{ - ID: domainID, - Name: "test", - Alias: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - } - - _, err := repo.Save(context.Background(), domain) - require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) - - cases := []struct { - desc string - domainID string - d auth.DomainReq - response auth.Domain - err error - }{ - { - desc: "update existing domain name and metadata", - domainID: domain.ID, - d: auth.DomainReq{ - Name: &updatedName, - Metadata: &updatedMetadata, - }, - response: auth.Domain{ - ID: domainID, - Name: "test1", - Alias: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test1": "test1", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - UpdatedAt: time.Now(), - }, - err: nil, - }, - { - desc: "update existing domain name, metadata, tags, status and alias", - domainID: domain.ID, - d: auth.DomainReq{ - Name: &updatedName, - Metadata: &updatedMetadata, - Tags: &updatedTags, - Status: &updatedStatus, - Alias: &updatedAlias, - }, - response: auth.Domain{ - ID: domainID, - Name: "test1", - Alias: "test1", - Tags: []string{"test1"}, - Metadata: map[string]interface{}{ - "test1": "test1", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.DisabledStatus, - UpdatedAt: time.Now(), - }, - err: nil, - }, - { - desc: "update non-existing domain", - domainID: inValid, - d: auth.DomainReq{ - Name: &updatedName, - Metadata: &updatedMetadata, - }, - response: auth.Domain{}, - err: repoerr.ErrFailedOpDB, - }, - { - desc: "update domain with empty ID", - domainID: "", - d: auth.DomainReq{ - Name: &updatedName, - Metadata: &updatedMetadata, - }, - response: auth.Domain{}, - err: repoerr.ErrFailedOpDB, - }, - { - desc: "update domain with malformed metadata", - domainID: domainID, - d: auth.DomainReq{ - Name: &updatedName, - Metadata: &auth.Metadata{"key": make(chan int)}, - }, - response: auth.Domain{}, - err: repoerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - d, err := repo.Update(context.Background(), tc.domainID, userID, tc.d) - d.UpdatedAt = tc.response.UpdatedAt - assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestDelete(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM domains") - require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) - }) - - repo := postgres.NewDomainRepository(database) - - domain := auth.Domain{ - ID: domainID, - Name: "test", - Alias: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - } - - _, err := repo.Save(context.Background(), domain) - require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) - - cases := []struct { - desc string - domainID string - err error - }{ - { - desc: "delete existing domain", - domainID: domain.ID, - err: nil, - }, - { - desc: "delete non-existing domain", - domainID: inValid, - err: repoerr.ErrNotFound, - }, - { - desc: "delete domain with empty ID", - domainID: "", - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - err := repo.Delete(context.Background(), tc.domainID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestCheckPolicy(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM policies") - require.Nil(t, err, fmt.Sprintf("clean policies unexpected error: %s", err)) - }) - - repo := postgres.NewDomainRepository(database) - - policy := auth.Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - SubjectRelation: policies.AdministratorRelation, - Relation: policies.DomainRelation, - ObjectType: policies.DomainType, - ObjectID: domainID, - } - - err := repo.SavePolicies(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("failed to save policy %s", policy.SubjectID)) - - cases := []struct { - desc string - policy auth.Policy - err error - }{ - { - desc: "check valid policy", - policy: policy, - err: nil, - }, - { - desc: "check policy with invalid subject type", - policy: auth.Policy{ - SubjectType: inValid, - SubjectID: userID, - SubjectRelation: policies.AdministratorRelation, - Relation: policies.DomainRelation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "check policy with invalid subject id", - policy: auth.Policy{ - SubjectType: policies.UserType, - SubjectID: inValid, - SubjectRelation: policies.AdministratorRelation, - Relation: policies.DomainRelation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "check policy with invalid subject relation", - policy: auth.Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - SubjectRelation: inValid, - Relation: policies.DomainRelation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "check policy with invalid relation", - policy: auth.Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - SubjectRelation: policies.AdministratorRelation, - Relation: inValid, - ObjectType: policies.DomainType, - ObjectID: domainID, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "check policy with invalid object type", - policy: auth.Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - SubjectRelation: policies.AdministratorRelation, - Relation: policies.DomainRelation, - ObjectType: inValid, - ObjectID: domainID, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "check policy with invalid object id", - policy: auth.Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - SubjectRelation: policies.AdministratorRelation, - Relation: policies.DomainRelation, - ObjectType: policies.DomainType, - ObjectID: inValid, - }, - err: repoerr.ErrNotFound, - }, - } - for _, tc := range cases { - err := repo.CheckPolicy(context.Background(), tc.policy) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestDeleteUserPolicies(t *testing.T) { - repo := postgres.NewDomainRepository(database) - - domain := auth.Domain{ - ID: domainID, - Name: "test", - Alias: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - CreatedBy: userID, - UpdatedBy: userID, - Status: auth.EnabledStatus, - Permission: "admin", - } - - policy := auth.Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - SubjectRelation: "admin", - Relation: "admin", - ObjectType: policies.DomainType, - ObjectID: domainID, - } - - _, err := repo.Save(context.Background(), domain) - require.Nil(t, err, fmt.Sprintf("failed to save domain %s", domain.ID)) - - err = repo.SavePolicies(context.Background(), policy) - require.Nil(t, err, fmt.Sprintf("failed to save policy %s", policy.SubjectID)) - - cases := []struct { - desc string - id string - err error - }{ - { - desc: "delete valid user policy", - id: userID, - err: nil, - }, - { - desc: "delete invalid user policy", - id: inValid, - err: nil, - }, - } - - for _, tc := range cases { - err := repo.DeleteUserPolicies(context.Background(), tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} diff --git a/auth/postgres/init.go b/auth/postgres/init.go index ae69c3a0ca..32e0ab002d 100644 --- a/auth/postgres/init.go +++ b/auth/postgres/init.go @@ -57,6 +57,14 @@ func Migration() *migrate.MemoryMigrationSource { `ALTER TABLE domains ALTER COLUMN alias SET NOT NULL`, }, }, + { + Id: "auth_3", + Up: []string{ + `DROP TABLE IF EXISTS policies; + DROP TABLE IF EXISTS domains; + `, + }, + }, }, } } diff --git a/auth/service.go b/auth/service.go index b5c23672e0..48a1123125 100644 --- a/auth/service.go +++ b/auth/service.go @@ -5,7 +5,6 @@ package auth import ( "context" - "fmt" "strings" "time" @@ -82,14 +81,12 @@ type Authn interface { type Service interface { Authn Authz - Domains } var _ Service = (*service)(nil) type service struct { keys KeyRepository - domains DomainsRepository idProvider magistrala.IDProvider evaluator policies.Evaluator policysvc policies.Service @@ -100,10 +97,9 @@ type service struct { } // New instantiates the auth service implementation. -func New(keys KeyRepository, domains DomainsRepository, idp magistrala.IDProvider, tokenizer Tokenizer, policyEvaluator policies.Evaluator, policyService policies.Service, loginDuration, refreshDuration, invitationDuration time.Duration) Service { +func New(keys KeyRepository, idp magistrala.IDProvider, tokenizer Tokenizer, policyEvaluator policies.Evaluator, policyService policies.Service, loginDuration, refreshDuration, invitationDuration time.Duration) Service { return &service{ tokenizer: tokenizer, - domains: domains, keys: keys, idProvider: idp, evaluator: policyEvaluator, @@ -188,7 +184,7 @@ func (svc service) Authorize(ctx context.Context, pr policies.Policy) error { return errors.Wrap(svcerr.ErrAuthentication, err) } if key.Subject == "" { - if pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ThingType || pr.ObjectType == policies.DomainType { + if pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ClientType || pr.ObjectType == policies.DomainType { return svcerr.ErrDomainAuthorization } return svcerr.ErrAuthentication @@ -203,8 +199,8 @@ func (svc service) Authorize(ctx context.Context, pr policies.Policy) error { } func (svc service) checkPolicy(ctx context.Context, pr policies.Policy) error { - // Domain status is required for if user sent authorization request on things, channels, groups and domains - if pr.SubjectType == policies.UserType && (pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ThingType || pr.ObjectType == policies.DomainType) { + // Domain status is required for if user sent authorization request on clients, channels, groups and domains + if pr.SubjectType == policies.UserType && (pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ClientType || pr.ObjectType == policies.DomainType) { domainID := pr.Domain if domainID == "" { if pr.ObjectType != policies.DomainType { @@ -233,36 +229,12 @@ func (svc service) checkDomain(ctx context.Context, subjectType, subject, domain return svcerr.ErrDomainAuthorization } - d, err := svc.domains.RetrieveByID(ctx, domainID) - if err != nil { - return errors.Wrap(svcerr.ErrViewEntity, err) - } + // ToDo: Add domain status in spiceDB like with new relation called status - switch d.Status { - case EnabledStatus: - case DisabledStatus: - if err := svc.evaluator.CheckPolicy(ctx, policies.Policy{ - Subject: subject, - SubjectType: subjectType, - Permission: policies.AdminPermission, - Object: domainID, - ObjectType: policies.DomainType, - }); err != nil { - return svcerr.ErrDomainAuthorization - } - case FreezeStatus: - if err := svc.evaluator.CheckPolicy(ctx, policies.Policy{ - Subject: subject, - SubjectType: subjectType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err != nil { - return svcerr.ErrDomainAuthorization - } - default: - return svcerr.ErrDomainAuthorization - } + // d, err := svc.domains.RetrieveByID(ctx, domainID) + // if err != nil { + // return errors.Wrap(svcerr.ErrViewEntity, err) + // } return nil } @@ -451,403 +423,6 @@ func SwitchToPermission(relation string) string { } } -func (svc service) CreateDomain(ctx context.Context, token string, d Domain) (do Domain, err error) { - key, err := svc.Identify(ctx, token) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - d.CreatedBy = key.User - - domainID, err := svc.idProvider.ID() - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrCreateEntity, err) - } - d.ID = domainID - - if d.Status != DisabledStatus && d.Status != EnabledStatus { - return Domain{}, svcerr.ErrInvalidStatus - } - - d.CreatedAt = time.Now() - - if err := svc.createDomainPolicy(ctx, key.User, domainID, policies.AdministratorRelation); err != nil { - return Domain{}, errors.Wrap(errCreateDomainPolicy, err) - } - defer func() { - if err != nil { - if errRollBack := svc.createDomainPolicyRollback(ctx, key.User, domainID, policies.AdministratorRelation); errRollBack != nil { - err = errors.Wrap(err, errors.Wrap(errRollbackPolicy, errRollBack)) - } - } - }() - dom, err := svc.domains.Save(ctx, d) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrCreateEntity, err) - } - - return dom, nil -} - -func (svc service) RetrieveDomain(ctx context.Context, token, id string) (Domain, error) { - res, err := svc.Identify(ctx, token) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - domain, err := svc.domains.RetrieveByID(ctx, id) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - if err = svc.Authorize(ctx, policies.Policy{ - Subject: EncodeDomainUserID(id, res.User), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }); err != nil { - return Domain{ID: domain.ID, Name: domain.Name, Alias: domain.Alias}, nil - } - return domain, nil -} - -func (svc service) RetrieveDomainPermissions(ctx context.Context, token, id string) (policies.Permissions, error) { - res, err := svc.Identify(ctx, token) - if err != nil { - return []string{}, err - } - domainUserSubject := EncodeDomainUserID(id, res.User) - if err := svc.Authorize(ctx, policies.Policy{ - Subject: domainUserSubject, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }); err != nil { - return []string{}, err - } - - lp, err := svc.policysvc.ListPermissions(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: domainUserSubject, - Object: id, - ObjectType: policies.DomainType, - }, []string{policies.AdminPermission, policies.EditPermission, policies.ViewPermission, policies.MembershipPermission, policies.CreatePermission}) - if err != nil { - return []string{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - return lp, nil -} - -func (svc service) UpdateDomain(ctx context.Context, token, id string, d DomainReq) (Domain, error) { - key, err := svc.Identify(ctx, token) - if err != nil { - return Domain{}, err - } - if err := svc.Authorize(ctx, policies.Policy{ - Subject: EncodeDomainUserID(id, key.User), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.EditPermission, - }); err != nil { - return Domain{}, err - } - - dom, err := svc.domains.Update(ctx, id, key.User, d) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return dom, nil -} - -func (svc service) ChangeDomainStatus(ctx context.Context, token, id string, d DomainReq) (Domain, error) { - key, err := svc.Identify(ctx, token) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - if err := svc.Authorize(ctx, policies.Policy{ - Subject: EncodeDomainUserID(id, key.User), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }); err != nil { - return Domain{}, err - } - - dom, err := svc.domains.Update(ctx, id, key.User, d) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return dom, nil -} - -func (svc service) ListDomains(ctx context.Context, token string, p Page) (DomainsPage, error) { - key, err := svc.Identify(ctx, token) - if err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - p.SubjectID = key.User - if err := svc.Authorize(ctx, policies.Policy{ - Subject: key.User, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }); err == nil { - p.SubjectID = "" - } - dp, err := svc.domains.ListDomains(ctx, p) - if err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - if p.SubjectID == "" { - for i := range dp.Domains { - dp.Domains[i].Permission = policies.AdministratorRelation - } - } - return dp, nil -} - -func (svc service) AssignUsers(ctx context.Context, token, id string, userIds []string, relation string) error { - res, err := svc.Identify(ctx, token) - if err != nil { - return errors.Wrap(svcerr.ErrAuthentication, err) - } - - domainUserID := EncodeDomainUserID(id, res.User) - if err := svc.Authorize(ctx, policies.Policy{ - Subject: domainUserID, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }); err != nil { - return err - } - - if err := svc.Authorize(ctx, policies.Policy{ - Subject: domainUserID, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: SwitchToPermission(relation), - }); err != nil { - return err - } - - for _, userID := range userIds { - if err := svc.Authorize(ctx, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.MembershipPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err != nil { - return errors.Wrap(svcerr.ErrMalformedEntity, fmt.Errorf("invalid user id : %s ", userID)) - } - } - - return svc.addDomainPolicies(ctx, id, relation, userIds...) -} - -func (svc service) UnassignUser(ctx context.Context, token, id, userID string) error { - res, err := svc.Identify(ctx, token) - if err != nil { - return errors.Wrap(svcerr.ErrAuthentication, err) - } - - domainUserID := EncodeDomainUserID(id, res.User) - pr := policies.Policy{ - Subject: domainUserID, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - } - if err := svc.Authorize(ctx, pr); err != nil { - return err - } - - pr.Permission = policies.AdminPermission - if err := svc.Authorize(ctx, pr); err != nil { - pr.SubjectKind = policies.UsersKind - // User is not admin. - pr.Subject = userID - if err := svc.Authorize(ctx, pr); err == nil { - // Non admin attempts to remove admin. - return errors.Wrap(svcerr.ErrAuthorization, err) - } - } - - if err := svc.policysvc.DeletePolicyFilter(ctx, policies.Policy{ - Subject: EncodeDomainUserID(id, userID), - SubjectType: policies.UserType, - }); err != nil { - return errors.Wrap(errRemovePolicies, err) - } - - pc := Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - ObjectType: policies.DomainType, - ObjectID: id, - } - - if err := svc.domains.DeletePolicies(ctx, pc); err != nil { - return errors.Wrap(errRemovePolicies, err) - } - - return nil -} - -// IMPROVEMENT NOTE: Take decision: Only Patform admin or both Patform and domain admins can see others users domain. -func (svc service) ListUserDomains(ctx context.Context, token, userID string, p Page) (DomainsPage, error) { - res, err := svc.Identify(ctx, token) - if err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - if err := svc.Authorize(ctx, policies.Policy{ - Subject: res.User, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrAuthorization, err) - } - if userID != "" && res.User != userID { - p.SubjectID = userID - } else { - p.SubjectID = res.User - } - dp, err := svc.domains.ListDomains(ctx, p) - if err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - return dp, nil -} - -func (svc service) addDomainPolicies(ctx context.Context, domainID, relation string, userIDs ...string) (err error) { - var prs []policies.Policy - var pcs []Policy - - for _, userID := range userIDs { - prs = append(prs, policies.Policy{ - Subject: EncodeDomainUserID(domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Relation: relation, - Object: domainID, - ObjectType: policies.DomainType, - }) - pcs = append(pcs, Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - Relation: relation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }) - } - if err := svc.policysvc.AddPolicies(ctx, prs); err != nil { - return errors.Wrap(errAddPolicies, err) - } - defer func() { - if err != nil { - if errDel := svc.policysvc.DeletePolicies(ctx, prs); errDel != nil { - err = errors.Wrap(err, errors.Wrap(errRollbackPolicy, errDel)) - } - } - }() - - if err = svc.domains.SavePolicies(ctx, pcs...); err != nil { - return errors.Wrap(errAddPolicies, err) - } - return nil -} - -func (svc service) createDomainPolicy(ctx context.Context, userID, domainID, relation string) (err error) { - prs := []policies.Policy{ - { - Subject: EncodeDomainUserID(domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Relation: relation, - Object: domainID, - ObjectType: policies.DomainType, - }, - { - Subject: policies.MagistralaObject, - SubjectType: policies.PlatformType, - Relation: policies.PlatformRelation, - Object: domainID, - ObjectType: policies.DomainType, - }, - } - if err := svc.policysvc.AddPolicies(ctx, prs); err != nil { - return err - } - defer func() { - if err != nil { - if errDel := svc.policysvc.DeletePolicies(ctx, prs); errDel != nil { - err = errors.Wrap(err, errors.Wrap(errRollbackPolicy, errDel)) - } - } - }() - err = svc.domains.SavePolicies(ctx, Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - Relation: relation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }) - if err != nil { - return errors.Wrap(errCreateDomainPolicy, err) - } - return err -} - -func (svc service) createDomainPolicyRollback(ctx context.Context, userID, domainID, relation string) error { - var err error - prs := []policies.Policy{ - { - Subject: EncodeDomainUserID(domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Relation: relation, - Object: domainID, - ObjectType: policies.DomainType, - }, - { - Subject: policies.MagistralaObject, - SubjectType: policies.PlatformType, - Relation: policies.PlatformRelation, - Object: domainID, - ObjectType: policies.DomainType, - }, - } - if errPolicy := svc.policysvc.DeletePolicies(ctx, prs); errPolicy != nil { - err = errors.Wrap(errRemovePolicyEngine, errPolicy) - } - errPolicyCopy := svc.domains.DeletePolicies(ctx, Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - Relation: relation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }) - if errPolicyCopy != nil { - err = errors.Wrap(err, errors.Wrap(errRemoveLocalPolicy, errPolicyCopy)) - } - return err -} - func EncodeDomainUserID(domainID, userID string) string { if domainID == "" || userID == "" { return "" @@ -872,37 +447,3 @@ func DecodeDomainUserID(domainUserID string) (string, string) { return "", "" } } - -func (svc service) DeleteUserFromDomains(ctx context.Context, id string) (err error) { - domainsPage, err := svc.domains.ListDomains(ctx, Page{SubjectID: id, Limit: defLimit}) - if err != nil { - return err - } - - if domainsPage.Total > defLimit { - for i := defLimit; i < int(domainsPage.Total); i += defLimit { - page := Page{SubjectID: id, Offset: uint64(i), Limit: defLimit} - dp, err := svc.domains.ListDomains(ctx, page) - if err != nil { - return err - } - domainsPage.Domains = append(domainsPage.Domains, dp.Domains...) - } - } - - for _, domain := range domainsPage.Domains { - req := policies.Policy{ - Subject: EncodeDomainUserID(domain.ID, id), - SubjectType: policies.UserType, - } - if err := svc.policysvc.DeletePolicyFilter(ctx, req); err != nil { - return err - } - } - - if err := svc.domains.DeleteUserPolicies(ctx, id); err != nil { - return err - } - - return nil -} diff --git a/auth/service_test.go b/auth/service_test.go index f17c114322..2bb7314750 100644 --- a/auth/service_test.go +++ b/auth/service_test.go @@ -48,28 +48,18 @@ var ( inValidToken = "invalid" inValid = "invalid" valid = "valid" - domain = auth.Domain{ - ID: validID, - Name: groupName, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - Permission: policies.AdminPermission, - CreatedBy: validID, - UpdatedBy: validID, - } - userID = testsutil.GenerateUUID(&testing.T{}) + userID = testsutil.GenerateUUID(&testing.T{}) + domainID = testsutil.GenerateUUID(&testing.T{}) ) var ( krepo *mocks.KeyRepository - drepo *mocks.DomainsRepository pService *policymocks.Service pEvaluator *policymocks.Evaluator ) func newService() (auth.Service, string) { krepo = new(mocks.KeyRepository) - drepo = new(mocks.DomainsRepository) pService = new(policymocks.Service) pEvaluator = new(policymocks.Evaluator) idProvider := uuid.NewMock() @@ -85,7 +75,7 @@ func newService() (auth.Service, string) { } token, _ := t.Issue(key) - return auth.New(krepo, drepo, idProvider, t, pEvaluator, pService, loginDuration, refreshDuration, invalidDuration), token + return auth.New(krepo, idProvider, t, pEvaluator, pService, loginDuration, refreshDuration, invalidDuration), token } func TestIssue(t *testing.T) { @@ -107,10 +97,10 @@ func TestIssue(t *testing.T) { refreshkey := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), - Subject: id, + Subject: validID, Type: auth.RefreshKey, - User: email, - Domain: groupName, + User: userID, + Domain: domainID, } refreshToken, err := n.Issue(refreshkey) assert.Nil(t, err, fmt.Sprintf("Issuing refresh key expected to succeed: %s", err)) @@ -141,7 +131,6 @@ func TestIssue(t *testing.T) { desc string key auth.Key saveResponse auth.Key - retrieveByIDResponse auth.Domain token string saveErr error checkPolicyRequest policies.Policy @@ -153,7 +142,7 @@ func TestIssue(t *testing.T) { err error }{ { - desc: "issue login key", + desc: "issue access key", key: auth.Key{ Type: auth.AccessKey, IssuedAt: time.Now(), @@ -173,7 +162,7 @@ func TestIssue(t *testing.T) { err: nil, }, { - desc: "issue login key with domain", + desc: "issue access key with domain", key: auth.Key{ Type: auth.AccessKey, IssuedAt: time.Now(), @@ -194,11 +183,11 @@ func TestIssue(t *testing.T) { err: nil, }, { - desc: "issue login key with failed check on platform admin", + desc: "issue access key with failed check on platform admin", key: auth.Key{ Type: auth.AccessKey, IssuedAt: time.Now(), - Domain: groupName, + Domain: validID, }, token: accessToken, checkPolicyRequest: policies.Policy{ @@ -211,19 +200,17 @@ func TestIssue(t *testing.T) { SubjectType: policies.UserType, ObjectType: policies.DomainType, Permission: policies.MembershipPermission, - Object: groupName, + Object: validID, }, - checkPolicyErr: repoerr.ErrNotFound, - retrieveByIDResponse: auth.Domain{}, - retreiveByIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, + checkPolicyErr: svcerr.ErrAuthorization, + err: nil, }, { - desc: "issue login key with failed check on platform admin with enabled status", + desc: "issue access key with failed check on platform admin with enabled status", key: auth.Key{ Type: auth.AccessKey, IssuedAt: time.Now(), - Domain: groupName, + Domain: validID, }, token: accessToken, checkPolicyRequest: policies.Policy{ @@ -234,7 +221,7 @@ func TestIssue(t *testing.T) { }, checkPlatformPolicyReq: policies.Policy{ SubjectType: policies.UserType, - Object: groupName, + Object: validID, ObjectType: policies.DomainType, Permission: policies.MembershipPermission, }, @@ -243,13 +230,12 @@ func TestIssue(t *testing.T) { ObjectType: policies.DomainType, Permission: policies.MembershipPermission, }, - checkPolicyErr: svcerr.ErrAuthorization, - checkPolicyErr1: svcerr.ErrAuthorization, - retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, - err: svcerr.ErrAuthorization, + checkPolicyErr: svcerr.ErrAuthorization, + checkPolicyErr1: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { - desc: "issue login key with membership permission", + desc: "issue access key with membership permission", key: auth.Key{ Type: auth.AccessKey, IssuedAt: time.Now(), @@ -273,13 +259,12 @@ func TestIssue(t *testing.T) { ObjectType: policies.DomainType, Permission: policies.MembershipPermission, }, - checkPolicyErr: svcerr.ErrAuthorization, - checkPolicyErr1: svcerr.ErrAuthorization, - retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, - err: svcerr.ErrAuthorization, + checkPolicyErr: svcerr.ErrAuthorization, + checkPolicyErr1: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { - desc: "issue login key with membership permission with failed to authorize", + desc: "issue access key with membership permission with failed to authorize", key: auth.Key{ Type: auth.AccessKey, IssuedAt: time.Now(), @@ -303,27 +288,22 @@ func TestIssue(t *testing.T) { ObjectType: policies.DomainType, Permission: policies.MembershipPermission, }, - checkPolicyErr: svcerr.ErrAuthorization, - checkPolicyErr1: svcerr.ErrAuthorization, - retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, - err: svcerr.ErrAuthorization, + checkPolicyErr: svcerr.ErrAuthorization, + checkPolicyErr1: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, } for _, tc := range cases2 { - t.Run(tc.desc, func(t *testing.T) { - repoCall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, tc.saveErr) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyRequest).Return(tc.checkPolicyErr) - repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPlatformPolicyReq).Return(tc.checkPolicyErr1) - repoCall3 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(tc.retrieveByIDResponse, tc.retreiveByIDErr) - repoCall4 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDomainPolicyReq).Return(tc.checkPolicyErr) - _, err := svc.Issue(context.Background(), tc.token, tc.key) - 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() - repoCall3.Unset() - repoCall4.Unset() - }) + repoCall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, tc.saveErr) + repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyRequest).Return(tc.checkPolicyErr) + repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPlatformPolicyReq).Return(tc.checkPolicyErr1) + repoCall4 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDomainPolicyReq).Return(tc.checkPolicyErr) + _, err := svc.Issue(context.Background(), tc.token, tc.key) + 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() + repoCall4.Unset() } cases3 := []struct { @@ -379,137 +359,261 @@ func TestIssue(t *testing.T) { } cases4 := []struct { - desc string - key auth.Key - token string - checkPolicyRequest policies.Policy - checkDOmainPolicyReq policies.Policy - checkPolicyErr error - retrieveByIDErr error - err error + desc string + key auth.Key + token string + checkPlatformAdminReq policies.Policy + checkDomainMemberReq policies.Policy + checkDomainMemberReq1 policies.Policy + checkPlatformAdminErr error + checkDomainMemberErr error + retrieveByIDErr error + err error }{ { - desc: "issue refresh key", + desc: "issue refresh key without domain", key: auth.Key{ Type: auth.RefreshKey, IssuedAt: time.Now(), + User: validID, }, - checkPolicyRequest: policies.Policy{ - Subject: email, + token: refreshToken, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, SubjectType: policies.UserType, + Permission: policies.AdminPermission, Object: policies.MagistralaObject, ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, }, - token: refreshToken, - err: nil, + checkDomainMemberReq: policies.Policy{}, + err: nil, }, { - desc: "issue refresh token with invalid pService", + desc: "issue refresh key as admin with domain", key: auth.Key{ Type: auth.RefreshKey, IssuedAt: time.Now(), - Domain: groupName, + Domain: validID, + User: userID, }, - checkPolicyRequest: policies.Policy{ - Subject: email, + token: refreshToken, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, SubjectType: policies.UserType, + Permission: policies.AdminPermission, Object: policies.MagistralaObject, ObjectType: policies.PlatformType, + }, + checkPlatformAdminErr: nil, + err: nil, + }, + { + desc: "issue refresh key as non admin with domain", + key: auth.Key{ + Type: auth.RefreshKey, + IssuedAt: time.Now(), + Domain: domainID, + User: userID, + }, + token: refreshToken, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, + SubjectType: policies.UserType, Permission: policies.AdminPermission, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, }, - checkDOmainPolicyReq: policies.Policy{ - Subject: "mgx_test@example.com", + checkDomainMemberReq: policies.Policy{ + Subject: auth.EncodeDomainUserID(domainID, userID), SubjectType: policies.UserType, - Object: groupName, + Permission: policies.MembershipPermission, + Object: domainID, ObjectType: policies.DomainType, + }, + checkDomainMemberReq1: policies.Policy{ + Subject: auth.EncodeDomainUserID(domainID, userID), + SubjectType: policies.UserType, Permission: policies.MembershipPermission, + Object: domainID, + ObjectType: policies.DomainType, }, - token: refreshToken, - checkPolicyErr: svcerr.ErrAuthorization, - retrieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrAuthorization, + checkPlatformAdminErr: svcerr.ErrAuthorization, + checkDomainMemberErr: nil, + err: nil, }, { - desc: "issue refresh key with invalid token", + desc: "issue refresh key as non admin and non domain member", key: auth.Key{ Type: auth.RefreshKey, IssuedAt: time.Now(), + Domain: domainID, + User: userID, }, - checkDOmainPolicyReq: policies.Policy{ - Subject: "mgx_test@example.com", + token: refreshToken, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, + SubjectType: policies.UserType, + Permission: policies.AdminPermission, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, + }, + checkDomainMemberReq: policies.Policy{ + Subject: auth.EncodeDomainUserID(domainID, userID), SubjectType: policies.UserType, - ObjectType: policies.DomainType, Permission: policies.MembershipPermission, + Object: domainID, + ObjectType: policies.DomainType, }, - token: accessToken, - err: errIssueUser, + checkPlatformAdminErr: svcerr.ErrAuthorization, + checkDomainMemberErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, }, { - desc: "issue refresh key with empty token", + desc: "issue refresh key with invalid token", key: auth.Key{ Type: auth.RefreshKey, IssuedAt: time.Now(), + User: validID, }, - checkDOmainPolicyReq: policies.Policy{ - Subject: "mgx_test@example.com", + token: inValidToken, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, + Permission: policies.AdminPermission, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, + }, + checkDomainMemberReq: policies.Policy{}, + err: svcerr.ErrAuthentication, + }, + { + desc: "issue refresh key with empty token", + key: auth.Key{ + Type: auth.RefreshKey, + IssuedAt: time.Now(), + User: validID, }, token: "", - err: errRetrieve, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, + SubjectType: policies.UserType, + Permission: policies.AdminPermission, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, + }, + checkDomainMemberReq: policies.Policy{}, + err: svcerr.ErrAuthentication, }, { - desc: "issue invitation key", + desc: "issue invitation key without domain", key: auth.Key{ Type: auth.InvitationKey, IssuedAt: time.Now(), }, - checkPolicyRequest: policies.Policy{ + checkPlatformAdminReq: policies.Policy{ Subject: email, SubjectType: policies.UserType, Object: policies.MagistralaObject, ObjectType: policies.PlatformType, Permission: policies.AdminPermission, }, - token: "", - err: nil, + err: nil, }, { - desc: "issue invitation key with invalid pService", + desc: "issue invitation key as admin with domain", key: auth.Key{ Type: auth.InvitationKey, IssuedAt: time.Now(), - Domain: groupName, + Domain: validID, + User: userID, }, - checkPolicyRequest: policies.Policy{ + token: refreshToken, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, SubjectType: policies.UserType, + Permission: policies.AdminPermission, Object: policies.MagistralaObject, ObjectType: policies.PlatformType, + }, + checkPlatformAdminErr: nil, + err: nil, + }, + { + desc: "issue invitation key as non admin with domain", + key: auth.Key{ + Type: auth.InvitationKey, + IssuedAt: time.Now(), + Domain: domainID, + User: userID, + }, + token: refreshToken, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, + SubjectType: policies.UserType, Permission: policies.AdminPermission, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, }, - checkDOmainPolicyReq: policies.Policy{ + checkDomainMemberReq: policies.Policy{ + Subject: auth.EncodeDomainUserID(domainID, userID), SubjectType: policies.UserType, - Object: groupName, + Permission: policies.MembershipPermission, + Object: domainID, ObjectType: policies.DomainType, + }, + checkDomainMemberReq1: policies.Policy{ + Subject: auth.EncodeDomainUserID(domainID, userID), + SubjectType: policies.UserType, Permission: policies.MembershipPermission, + Object: domainID, + ObjectType: policies.DomainType, }, - token: refreshToken, - checkPolicyErr: svcerr.ErrAuthorization, - retrieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrDomainAuthorization, + checkPlatformAdminErr: svcerr.ErrAuthorization, + checkDomainMemberErr: nil, + err: nil, + }, + { + desc: "issue invitation key as non admin as non domain member", + key: auth.Key{ + Type: auth.InvitationKey, + IssuedAt: time.Now(), + Domain: domainID, + User: userID, + }, + token: refreshToken, + checkPlatformAdminReq: policies.Policy{ + Subject: userID, + SubjectType: policies.UserType, + Permission: policies.AdminPermission, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, + }, + checkDomainMemberReq: policies.Policy{ + Subject: auth.EncodeDomainUserID(domainID, userID), + SubjectType: policies.UserType, + Permission: policies.MembershipPermission, + Object: domainID, + ObjectType: policies.DomainType, + }, + checkDomainMemberReq1: policies.Policy{ + Subject: auth.EncodeDomainUserID(domainID, userID), + SubjectType: policies.UserType, + Permission: policies.MembershipPermission, + Object: domainID, + ObjectType: policies.DomainType, + }, + checkPlatformAdminErr: svcerr.ErrAuthorization, + checkDomainMemberErr: svcerr.ErrAuthorization, + err: svcerr.ErrDomainAuthorization, }, } for _, tc := range cases4 { - repoCall := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyRequest).Return(tc.checkPolicyErr) - repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retrieveByIDErr) - repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDOmainPolicyReq).Return(tc.checkPolicyErr) + repoCall := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPlatformAdminReq).Return(tc.checkPlatformAdminErr) + repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDomainMemberReq).Return(tc.checkDomainMemberErr) _, err := svc.Issue(context.Background(), tc.token, tc.key) 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() } } @@ -559,12 +663,10 @@ func TestRevoke(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(tc.err) - err := svc.Revoke(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() - }) + repocall := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(tc.err) + err := svc.Revoke(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() } } @@ -631,12 +733,10 @@ func TestRetrieve(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{}, tc.err) - _, err := svc.RetrieveKey(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() - }) + repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{}, tc.err) + _, err := svc.RetrieveKey(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() } } @@ -735,15 +835,13 @@ func TestIdentify(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{}, tc.err) - repocall1 := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(tc.err) - idt, err := svc.Identify(context.Background(), tc.key) - 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.idt, idt.Subject, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.idt, idt)) - repocall.Unset() - repocall1.Unset() - }) + repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(auth.Key{}, tc.err) + repocall1 := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(tc.err) + idt, err := svc.Identify(context.Background(), tc.key) + 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.idt, idt.Subject, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.idt, idt)) + repocall.Unset() + repocall1.Unset() } } @@ -782,13 +880,10 @@ func TestAuthorize(t *testing.T) { cases := []struct { desc string policyReq policies.Policy - retrieveDomainRes auth.Domain - checkPolicyReq3 policies.Policy - checkAdminPolicyReq policies.Policy checkDomainPolicyReq policies.Policy + checkPolicyReq policies.Policy checkPolicyErr error - checkPolicyErr1 error - checkPolicyErr2 error + checkDomainPolicyErr error err error }{ { @@ -801,8 +896,7 @@ func TestAuthorize(t *testing.T) { ObjectType: policies.PlatformType, Permission: policies.AdminPermission, }, - checkPolicyReq3: policies.Policy{ - Domain: "", + checkPolicyReq: policies.Policy{ Subject: id, SubjectType: policies.UserType, SubjectKind: policies.TokenKind, @@ -818,6 +912,25 @@ func TestAuthorize(t *testing.T) { }, err: nil, }, + { + desc: "authorize with malformed policy request", + policyReq: policies.Policy{ + Subject: accessToken, + SubjectType: policies.UserType, + SubjectKind: policies.TokenKind, + Object: domainID, + ObjectType: policies.PlatformType, + Permission: policies.AdminPermission, + }, + checkPolicyReq: policies.Policy{}, + checkDomainPolicyReq: policies.Policy{ + Subject: id, + SubjectType: policies.UserType, + ObjectType: policies.DomainType, + Permission: policies.MembershipPermission, + }, + err: svcerr.ErrMalformedEntity, + }, { desc: "authorize token for group type with empty domain", policyReq: policies.Policy{ @@ -828,7 +941,7 @@ func TestAuthorize(t *testing.T) { ObjectType: policies.GroupType, Permission: policies.AdminPermission, }, - checkPolicyReq3: policies.Policy{ + checkPolicyReq: policies.Policy{ Subject: id, SubjectType: policies.UserType, SubjectKind: policies.TokenKind, @@ -836,78 +949,83 @@ func TestAuthorize(t *testing.T) { ObjectType: policies.GroupType, Permission: policies.AdminPermission, }, - checkAdminPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: svcerr.ErrDomainAuthorization, - checkPolicyErr: svcerr.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { desc: "authorize token with disabled domain", policyReq: policies.Policy{ - Subject: emptyDomain, + Subject: accessToken, SubjectType: policies.UserType, SubjectKind: policies.TokenKind, Object: validID, ObjectType: policies.DomainType, Permission: policies.AdminPermission, }, - checkPolicyReq3: policies.Policy{ + checkDomainPolicyReq: policies.Policy{ Subject: id, SubjectType: policies.UserType, Object: validID, ObjectType: policies.DomainType, Permission: policies.MembershipPermission, }, - checkAdminPolicyReq: policies.Policy{ - Subject: id, + checkDomainPolicyErr: svcerr.ErrAuthorization, + err: svcerr.ErrDomainAuthorization, + }, + { + desc: "authorize an expired token", + policyReq: policies.Policy{ + Subject: expSecret.AccessToken, SubjectType: policies.UserType, SubjectKind: policies.TokenKind, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, Permission: policies.AdminPermission, - Object: validID, - ObjectType: policies.DomainType, }, + checkPolicyReq: policies.Policy{}, checkDomainPolicyReq: policies.Policy{ Subject: id, SubjectType: policies.UserType, Object: validID, ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - - retrieveDomainRes: auth.Domain{ - ID: validID, - Name: groupName, - Status: auth.DisabledStatus, + Permission: policies.MembershipPermission, }, - err: nil, + err: svcerr.ErrAuthentication, }, { - desc: "authorize token with disabled domain with failed to authorize", + desc: "authorize a token with an empty subject", policyReq: policies.Policy{ - Subject: emptyDomain, + Subject: emptySubject.AccessToken, SubjectType: policies.UserType, SubjectKind: policies.TokenKind, Object: validID, ObjectType: policies.DomainType, Permission: policies.AdminPermission, }, - checkPolicyReq3: policies.Policy{ + checkPolicyReq: policies.Policy{}, + checkDomainPolicyReq: policies.Policy{ Subject: id, SubjectType: policies.UserType, + Object: validID, ObjectType: policies.DomainType, - Permission: policies.AdminPermission, + Permission: policies.MembershipPermission, }, - checkAdminPolicyReq: policies.Policy{ - Subject: id, + err: svcerr.ErrDomainAuthorization, + }, + { + desc: "authorize a token with an empty subject and invalid type", + policyReq: policies.Policy{ + Subject: emptySubject.AccessToken, SubjectType: policies.UserType, SubjectKind: policies.TokenKind, - Permission: policies.AdminPermission, - Object: validID, + Object: policies.MagistralaObject, ObjectType: policies.DomainType, + Permission: policies.AdminPermission, + }, + checkPolicyReq: policies.Policy{ + SubjectType: policies.UserType, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformKind, + Permission: policies.AdminPermission, }, checkDomainPolicyReq: policies.Policy{ Subject: id, @@ -916,39 +1034,48 @@ func TestAuthorize(t *testing.T) { ObjectType: policies.DomainType, Permission: policies.MembershipPermission, }, - - retrieveDomainRes: auth.Domain{ - ID: validID, - Name: groupName, - Status: auth.DisabledStatus, - }, - checkPolicyErr1: svcerr.ErrDomainAuthorization, - err: svcerr.ErrDomainAuthorization, + err: svcerr.ErrDomainAuthorization, }, { - desc: "authorize token with frozen domain", + desc: "authorize a token with an empty subject and invalid object type", policyReq: policies.Policy{ - Subject: emptyDomain, + Subject: emptySubject.AccessToken, SubjectType: policies.UserType, SubjectKind: policies.TokenKind, Object: validID, - ObjectType: policies.DomainType, + ObjectType: policies.UserType, + Permission: policies.AdminPermission, + }, + checkPolicyReq: policies.Policy{ + SubjectType: policies.UserType, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, Permission: policies.AdminPermission, }, - checkPolicyReq3: policies.Policy{ + checkDomainPolicyReq: policies.Policy{ Subject: id, SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, Object: validID, ObjectType: policies.DomainType, - Permission: policies.AdminPermission, + Permission: policies.MembershipPermission, }, - checkAdminPolicyReq: policies.Policy{ - Subject: id, + err: svcerr.ErrAuthentication, + }, + { + desc: "authorize a user key successfully", + policyReq: policies.Policy{ SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: policies.MagistralaObject, + ObjectType: policies.PlatformType, Permission: policies.AdminPermission, + }, + checkPolicyReq: policies.Policy{ + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, Object: policies.MagistralaObject, ObjectType: policies.PlatformType, + Permission: policies.AdminPermission, }, checkDomainPolicyReq: policies.Policy{ Subject: id, @@ -957,212 +1084,23 @@ func TestAuthorize(t *testing.T) { ObjectType: policies.DomainType, Permission: policies.MembershipPermission, }, - - retrieveDomainRes: auth.Domain{ - ID: validID, - Name: groupName, - Status: auth.FreezeStatus, - }, err: nil, }, { - desc: "authorize token with frozen domain with failed to authorize", + desc: "authorize token with empty subject and domain object type", policyReq: policies.Policy{ - Subject: emptyDomain, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - Subject: id, + Subject: emptySubject.AccessToken, SubjectType: policies.UserType, SubjectKind: policies.TokenKind, - Object: validID, + Object: policies.MagistralaObject, ObjectType: policies.DomainType, Permission: policies.AdminPermission, }, - checkAdminPolicyReq: policies.Policy{ - Subject: id, + checkPolicyReq: policies.Policy{ SubjectType: policies.UserType, - Permission: policies.AdminPermission, Object: policies.MagistralaObject, ObjectType: policies.PlatformType, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - - retrieveDomainRes: auth.Domain{ - ID: validID, - Name: groupName, - Status: auth.FreezeStatus, - }, - checkPolicyErr1: svcerr.ErrDomainAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "authorize token with domain with invalid status", - policyReq: policies.Policy{ - Subject: emptyDomain, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - - retrieveDomainRes: auth.Domain{ - ID: validID, - Name: groupName, - Status: auth.AllStatus, - }, - err: svcerr.ErrDomainAuthorization, - }, - - { - desc: "authorize an expired token", - policyReq: policies.Policy{ - Subject: expSecret.AccessToken, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "authorize a token with an empty subject", - policyReq: policies.Policy{ - Subject: emptySubject.AccessToken, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "authorize a token with an empty secret and invalid type", - policyReq: policies.Policy{ - Subject: emptySubject.AccessToken, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: policies.MagistralaObject, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformKind, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "authorize a user key successfully", - policyReq: policies.Policy{ - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: nil, - }, - { - desc: "authorize token with empty subject and domain object type", - policyReq: policies.Policy{ - Subject: emptySubject.AccessToken, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: policies.MagistralaObject, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, + Permission: policies.AdminPermission, }, checkDomainPolicyReq: policies.Policy{ Subject: id, @@ -1175,43 +1113,14 @@ func TestAuthorize(t *testing.T) { }, } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq3).Return(tc.checkPolicyErr) - repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(tc.retrieveDomainRes, nil) - repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkAdminPolicyReq).Return(tc.checkPolicyErr1) - repoCall3 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDomainPolicyReq).Return(tc.checkPolicyErr1) - repoCall4 := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(nil) - err := svc.Authorize(context.Background(), tc.policyReq) - 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() - repoCall3.Unset() - repoCall4.Unset() - }) - } - cases2 := []struct { - desc string - policyReq policies.Policy - err error - }{ - { - desc: "authorize token with invalid platform validation", - policyReq: policies.Policy{ - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - err: errPlatform, - }, - } - for _, tc := range cases2 { - t.Run(tc.desc, func(t *testing.T) { - err := svc.Authorize(context.Background(), tc.policyReq) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - }) + policyCall := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkPolicyErr) + policyCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDomainPolicyReq).Return(tc.checkDomainPolicyErr) + repoCall := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err := svc.Authorize(context.Background(), tc.policyReq) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + policyCall.Unset() + policyCall1.Unset() + repoCall.Unset() } } @@ -1253,1095 +1162,8 @@ func TestSwitchToPermission(t *testing.T) { }, } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - result := auth.SwitchToPermission(tc.relation) - assert.Equal(t, tc.result, result, fmt.Sprintf("switching to permission expected to succeed: %s", result)) - }) - } -} - -func TestCreateDomain(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - d auth.Domain - token string - userID string - addPolicyErr error - savePolicyErr error - saveDomainErr error - deleteDomainErr error - deletePoliciesErr error - err error - }{ - { - desc: "create domain successfully", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: accessToken, - err: nil, - }, - { - desc: "create domain with invalid token", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: inValidToken, - err: svcerr.ErrAuthentication, - }, - { - desc: "create domain with invalid status", - d: auth.Domain{ - Status: auth.AllStatus, - }, - token: accessToken, - err: svcerr.ErrInvalidStatus, - }, - { - desc: "create domain with failed policy request", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: accessToken, - addPolicyErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "create domain with failed save policyrequest", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: accessToken, - savePolicyErr: errors.ErrMalformedEntity, - err: errCreateDomainPolicy, - }, - { - desc: "create domain with failed save domain request", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: accessToken, - saveDomainErr: errors.ErrMalformedEntity, - err: svcerr.ErrCreateEntity, - }, - { - desc: "create domain with rollback error", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: accessToken, - savePolicyErr: errors.ErrMalformedEntity, - deleteDomainErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "create domain with rollback error and failed to delete policies", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: accessToken, - savePolicyErr: errors.ErrMalformedEntity, - deleteDomainErr: errors.ErrMalformedEntity, - deletePoliciesErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "create domain with failed to create and failed rollback", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: accessToken, - saveDomainErr: errors.ErrMalformedEntity, - deletePoliciesErr: errors.ErrMalformedEntity, - err: errRollbackPolicy, - }, - { - desc: "create domain with failed to create and failed rollback", - d: auth.Domain{ - Status: auth.EnabledStatus, - }, - token: accessToken, - saveDomainErr: errors.ErrMalformedEntity, - deleteDomainErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pService.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPolicyErr) - repoCall1 := drepo.On("SavePolicies", mock.Anything, mock.Anything).Return(tc.savePolicyErr) - repoCall2 := pService.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) - repoCall3 := drepo.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deleteDomainErr) - repoCall4 := drepo.On("Save", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.saveDomainErr) - _, err := svc.CreateDomain(context.Background(), tc.token, tc.d) - 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() - repoCall3.Unset() - repoCall4.Unset() - }) - } -} - -func TestRetrieveDomain(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - domainRepoErr error - domainRepoErr1 error - checkPolicyErr error - err error - }{ - { - desc: "retrieve domain successfully", - token: accessToken, - domainID: validID, - err: nil, - }, - { - desc: "retrieve domain with invalid token", - token: inValidToken, - domainID: validID, - err: svcerr.ErrAuthentication, - }, - { - desc: "retrieve domain with empty domain id", - token: accessToken, - domainID: "", - err: svcerr.ErrViewEntity, - domainRepoErr1: repoerr.ErrNotFound, - }, - { - desc: "retrieve non-existing domain", - token: accessToken, - domainID: inValid, - domainRepoErr: repoerr.ErrNotFound, - err: svcerr.ErrViewEntity, - domainRepoErr1: repoerr.ErrNotFound, - }, - { - desc: "retrieve domain with failed to retrieve by id", - token: accessToken, - domainID: validID, - domainRepoErr1: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := drepo.On("RetrieveByID", mock.Anything, groupName).Return(auth.Domain{}, tc.domainRepoErr) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall2 := drepo.On("RetrieveByID", mock.Anything, tc.domainID).Return(auth.Domain{}, tc.domainRepoErr1) - _, err := svc.RetrieveDomain(context.Background(), tc.token, tc.domainID) - 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() - }) - } -} - -func TestRetrieveDomainPermissions(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - retreivePermissionsErr error - retreiveByIDErr error - checkPolicyErr error - err error - }{ - { - desc: "retrieve domain permissions successfully", - token: accessToken, - domainID: validID, - err: nil, - }, - { - desc: "retrieve domain permissions with invalid token", - token: inValidToken, - domainID: validID, - err: svcerr.ErrAuthentication, - }, - { - desc: "retrieve domain permissions with empty domainID", - token: accessToken, - domainID: "", - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "retrieve domain permissions with failed to retrieve permissions", - token: accessToken, - domainID: validID, - retreivePermissionsErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "retrieve domain permissions with failed to retrieve by id", - token: accessToken, - domainID: validID, - retreiveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pService.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(policies.Permissions{}, tc.retreivePermissionsErr) - repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retreiveByIDErr) - repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - _, err := svc.RetrieveDomainPermissions(context.Background(), tc.token, tc.domainID) - 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() - }) - } -} - -func TestUpdateDomain(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - domReq auth.DomainReq - checkPolicyErr error - retrieveByIDErr error - updateErr error - err error - }{ - { - desc: "update domain successfully", - token: accessToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - err: nil, - }, - { - desc: "update domain with invalid token", - token: inValidToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "update domain with empty domainID", - token: accessToken, - domainID: "", - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "update domain with failed to retrieve by id", - token: accessToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - retrieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "update domain with failed to update", - token: accessToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - updateErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retrieveByIDErr) - repoCall2 := drepo.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.updateErr) - _, err := svc.UpdateDomain(context.Background(), tc.token, tc.domainID, tc.domReq) - 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() - }) - } -} - -func TestChangeDomainStatus(t *testing.T) { - svc, accessToken := newService() - - disabledStatus := auth.DisabledStatus - - cases := []struct { - desc string - token string - domainID string - domainReq auth.DomainReq - retreieveByIDErr error - checkPolicyErr error - updateErr error - err error - }{ - { - desc: "change domain status successfully", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - err: nil, - }, - { - desc: "change domain status with invalid token", - token: inValidToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "change domain status with empty domainID", - token: accessToken, - domainID: "", - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - retreieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "change domain status with unauthorized domain ID", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "change domain status with repository error on update", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - updateErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retreieveByIDErr) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall2 := drepo.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.updateErr) - _, err := svc.ChangeDomainStatus(context.Background(), tc.token, tc.domainID, tc.domainReq) - 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() - }) - } -} - -func TestListDomains(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - authReq auth.Page - listDomainsRes auth.DomainsPage - retreiveByIDErr error - checkPolicyErr error - listDomainErr error - err error - }{ - { - desc: "list domains successfully", - token: accessToken, - domainID: validID, - authReq: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - Status: auth.EnabledStatus, - }, - listDomainsRes: auth.DomainsPage{ - Domains: []auth.Domain{domain}, - }, - err: nil, - }, - { - desc: "list domains with invalid token", - token: inValidToken, - domainID: validID, - authReq: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - Status: auth.EnabledStatus, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "list domains with repository error on list domains", - token: accessToken, - domainID: validID, - authReq: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - Status: auth.EnabledStatus, - }, - listDomainErr: errors.ErrMalformedEntity, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall1 := drepo.On("ListDomains", mock.Anything, mock.Anything).Return(tc.listDomainsRes, tc.listDomainErr) - _, err := svc.ListDomains(context.Background(), tc.token, auth.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 TestAssignUsers(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - userIDs []string - relation string - checkPolicyReq policies.Policy - checkAdminPolicyReq policies.Policy - checkDomainPolicyReq policies.Policy - checkPolicyReq1 policies.Policy - checkpolicyErr error - checkPolicyErr1 error - checkPolicyErr2 error - addPoliciesErr error - savePoliciesErr error - deletePoliciesErr error - err error - }{ - { - desc: "assign users successfully", - token: accessToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: validID, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.MembershipPermission, - }, - checkPolicyReq1: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: nil, - }, - { - desc: "assign users with invalid token", - token: inValidToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Domain: groupName, - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: validID, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.MembershipPermission, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "assign users with invalid domainID", - token: accessToken, - domainID: inValid, - relation: policies.ContributorRelation, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(inValid, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(inValid, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkPolicyReq1: policies.Policy{ - Subject: auth.EncodeDomainUserID(inValid, userID), - SubjectType: policies.UserType, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr1: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "assign users with invalid userIDs", - token: accessToken, - userIDs: []string{inValid}, - domainID: validID, - relation: policies.ContributorRelation, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: inValid, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.MembershipPermission, - }, - checkPolicyReq1: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr2: svcerr.ErrMalformedEntity, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "assign users with failed to add policies to agent", - token: accessToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: validID, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.MembershipPermission, - }, - checkPolicyReq1: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - addPoliciesErr: svcerr.ErrAuthorization, - err: errAddPolicies, - }, - { - desc: "assign users with failed to save policies to domain", - token: accessToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: validID, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.MembershipPermission, - }, - checkPolicyReq1: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - savePoliciesErr: repoerr.ErrCreateEntity, - err: errAddPolicies, - }, - { - desc: "assign users with failed to save policies to domain and failed to delete", - token: accessToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: validID, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.MembershipPermission, - }, - checkPolicyReq1: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - savePoliciesErr: repoerr.ErrCreateEntity, - deletePoliciesErr: svcerr.ErrDomainAuthorization, - err: errAddPolicies, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, nil) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkpolicyErr) - repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkAdminPolicyReq).Return(tc.checkPolicyErr1) - repoCall3 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDomainPolicyReq).Return(tc.checkPolicyErr2) - repoCall4 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq1).Return(tc.checkPolicyErr2) - repoCall5 := pService.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesErr) - repoCall6 := drepo.On("SavePolicies", mock.Anything, mock.Anything, mock.Anything).Return(tc.savePoliciesErr) - repoCall7 := pService.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) - err := svc.AssignUsers(context.Background(), tc.token, tc.domainID, tc.userIDs, tc.relation) - 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() - repoCall3.Unset() - repoCall4.Unset() - repoCall5.Unset() - repoCall6.Unset() - repoCall7.Unset() - }) - } -} - -func TestUnassignUser(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - userID string - checkPolicyReq policies.Policy - checkAdminPolicyReq policies.Policy - checkDomainPolicyReq policies.Policy - checkPolicyErr error - checkPolicyErr1 error - deletePolicyFilterErr error - deletePoliciesErr error - err error - }{ - { - desc: "unassign user successfully", - token: accessToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - err: nil, - }, - { - desc: "unassign users with invalid token", - token: inValidToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "unassign users with invalid domainID", - token: accessToken, - domainID: inValid, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(inValid, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(inValid, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(inValid, userID), - SubjectType: policies.UserType, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr1: svcerr.ErrAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "unassign users with failed to delete policies from agent", - token: accessToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - deletePolicyFilterErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "unassign users with failed to delete policies from domain", - token: accessToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - deletePoliciesErr: errors.ErrMalformedEntity, - deletePolicyFilterErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "unassign user with failed to delete policies from domain", - token: accessToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: auth.EncodeDomainUserID(validID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - deletePoliciesErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, nil) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkPolicyErr) - repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkAdminPolicyReq).Return(tc.checkPolicyErr1) - repoCall3 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDomainPolicyReq).Return(tc.checkPolicyErr1) - repoCall4 := pService.On("DeletePolicyFilter", mock.Anything, mock.Anything).Return(tc.deletePolicyFilterErr) - repoCall5 := drepo.On("DeletePolicies", mock.Anything, mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) - err := svc.UnassignUser(context.Background(), tc.token, tc.domainID, tc.userID) - 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() - repoCall3.Unset() - repoCall4.Unset() - repoCall5.Unset() - }) - } -} - -func TestListUsersDomains(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - userID string - page auth.Page - retreiveByIDErr error - checkPolicyErr error - listDomainErr error - err error - }{ - { - desc: "list users domains successfully", - token: accessToken, - userID: validID, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - err: nil, - }, - { - desc: "list users domains successfully was admin", - token: accessToken, - userID: email, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - err: nil, - }, - { - desc: "list users domains with invalid token", - token: inValidToken, - userID: validID, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users domains with invalid domainID", - token: accessToken, - userID: inValid, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "list users domains with repository error on list domains", - token: accessToken, - userID: validID, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - listDomainErr: repoerr.ErrNotFound, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall1 := drepo.On("ListDomains", mock.Anything, mock.Anything).Return(auth.DomainsPage{}, tc.listDomainErr) - _, err := svc.ListUserDomains(context.Background(), tc.token, tc.userID, tc.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() - }) + result := auth.SwitchToPermission(tc.relation) + assert.Equal(t, tc.result, result, fmt.Sprintf("switching to permission expected to succeed: %s", result)) } } @@ -2379,10 +1201,8 @@ func TestEncodeDomainUserID(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ar := auth.EncodeDomainUserID(tc.domainID, tc.userID) - assert.Equal(t, tc.response, ar, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.response, ar)) - }) + ar := auth.EncodeDomainUserID(tc.domainID, tc.userID) + assert.Equal(t, tc.response, ar, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.response, ar)) } } @@ -2420,10 +1240,8 @@ func TestDecodeDomainUserID(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ar, er := auth.DecodeDomainUserID(tc.domainUserID) - assert.Equal(t, tc.respUserID, er, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.respUserID, er)) - assert.Equal(t, tc.respDomainID, ar, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.respDomainID, ar)) - }) + ar, er := auth.DecodeDomainUserID(tc.domainUserID) + assert.Equal(t, tc.respUserID, er, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.respUserID, er)) + assert.Equal(t, tc.respDomainID, ar, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.respDomainID, ar)) } } diff --git a/auth/tracing/tracing.go b/auth/tracing/tracing.go index 97b5f1790f..6869398708 100644 --- a/auth/tracing/tracing.go +++ b/auth/tracing/tracing.go @@ -74,84 +74,3 @@ func (tm *tracingMiddleware) Authorize(ctx context.Context, pr policies.Policy) return tm.svc.Authorize(ctx, pr) } - -func (tm *tracingMiddleware) CreateDomain(ctx context.Context, token string, d auth.Domain) (auth.Domain, error) { - ctx, span := tm.tracer.Start(ctx, "create_domain", trace.WithAttributes( - attribute.String("name", d.Name), - )) - defer span.End() - return tm.svc.CreateDomain(ctx, token, d) -} - -func (tm *tracingMiddleware) RetrieveDomain(ctx context.Context, token, id string) (auth.Domain, error) { - ctx, span := tm.tracer.Start(ctx, "view_domain", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - return tm.svc.RetrieveDomain(ctx, token, id) -} - -func (tm *tracingMiddleware) RetrieveDomainPermissions(ctx context.Context, token, id string) (policies.Permissions, error) { - ctx, span := tm.tracer.Start(ctx, "view_domain_permissions", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - return tm.svc.RetrieveDomainPermissions(ctx, token, id) -} - -func (tm *tracingMiddleware) UpdateDomain(ctx context.Context, token, id string, d auth.DomainReq) (auth.Domain, error) { - ctx, span := tm.tracer.Start(ctx, "update_domain", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - return tm.svc.UpdateDomain(ctx, token, id, d) -} - -func (tm *tracingMiddleware) ChangeDomainStatus(ctx context.Context, token, id string, d auth.DomainReq) (auth.Domain, error) { - ctx, span := tm.tracer.Start(ctx, "change_domain_status", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - return tm.svc.ChangeDomainStatus(ctx, token, id, d) -} - -func (tm *tracingMiddleware) ListDomains(ctx context.Context, token string, p auth.Page) (auth.DomainsPage, error) { - ctx, span := tm.tracer.Start(ctx, "list_domains") - defer span.End() - return tm.svc.ListDomains(ctx, token, p) -} - -func (tm *tracingMiddleware) AssignUsers(ctx context.Context, token, id string, userIds []string, relation string) error { - ctx, span := tm.tracer.Start(ctx, "assign_users", trace.WithAttributes( - attribute.String("id", id), - attribute.StringSlice("user_ids", userIds), - attribute.String("relation", relation), - )) - defer span.End() - return tm.svc.AssignUsers(ctx, token, id, userIds, relation) -} - -func (tm *tracingMiddleware) UnassignUser(ctx context.Context, token, id, userID string) error { - ctx, span := tm.tracer.Start(ctx, "unassign_user", trace.WithAttributes( - attribute.String("id", id), - attribute.String("user_id", userID), - )) - defer span.End() - return tm.svc.UnassignUser(ctx, token, id, userID) -} - -func (tm *tracingMiddleware) ListUserDomains(ctx context.Context, token, userID string, p auth.Page) (auth.DomainsPage, error) { - ctx, span := tm.tracer.Start(ctx, "list_user_domains", trace.WithAttributes( - attribute.String("user_id", userID), - )) - defer span.End() - return tm.svc.ListUserDomains(ctx, token, userID, p) -} - -func (tm *tracingMiddleware) DeleteUserFromDomains(ctx context.Context, id string) error { - ctx, span := tm.tracer.Start(ctx, "delete_user_from_domains", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - return tm.svc.DeleteUserFromDomains(ctx, id) -} diff --git a/auth_grpc.pb.go b/auth_grpc.pb.go deleted file mode 100644 index a9bb42ddb2..0000000000 --- a/auth_grpc.pb.go +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.4.0 -// - protoc v5.27.1 -// source: auth.proto - -package magistrala - -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.62.0 or later. -const _ = grpc.SupportPackageIsVersion8 - -const ( - ThingsService_Authorize_FullMethodName = "/magistrala.ThingsService/Authorize" -) - -// ThingsServiceClient is the client API for ThingsService 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. -// -// ThingsService is a service that provides things authorization functionalities -// for magistrala services. -type ThingsServiceClient interface { - // Authorize checks if the thing is authorized to perform - // the action on the channel. - Authorize(ctx context.Context, in *ThingsAuthzReq, opts ...grpc.CallOption) (*ThingsAuthzRes, error) -} - -type thingsServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewThingsServiceClient(cc grpc.ClientConnInterface) ThingsServiceClient { - return &thingsServiceClient{cc} -} - -func (c *thingsServiceClient) Authorize(ctx context.Context, in *ThingsAuthzReq, opts ...grpc.CallOption) (*ThingsAuthzRes, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ThingsAuthzRes) - err := c.cc.Invoke(ctx, ThingsService_Authorize_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ThingsServiceServer is the server API for ThingsService service. -// All implementations must embed UnimplementedThingsServiceServer -// for forward compatibility -// -// ThingsService is a service that provides things authorization functionalities -// for magistrala services. -type ThingsServiceServer interface { - // Authorize checks if the thing is authorized to perform - // the action on the channel. - Authorize(context.Context, *ThingsAuthzReq) (*ThingsAuthzRes, error) - mustEmbedUnimplementedThingsServiceServer() -} - -// UnimplementedThingsServiceServer must be embedded to have forward compatible implementations. -type UnimplementedThingsServiceServer struct { -} - -func (UnimplementedThingsServiceServer) Authorize(context.Context, *ThingsAuthzReq) (*ThingsAuthzRes, error) { - return nil, status.Errorf(codes.Unimplemented, "method Authorize not implemented") -} -func (UnimplementedThingsServiceServer) mustEmbedUnimplementedThingsServiceServer() {} - -// UnsafeThingsServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ThingsServiceServer will -// result in compilation errors. -type UnsafeThingsServiceServer interface { - mustEmbedUnimplementedThingsServiceServer() -} - -func RegisterThingsServiceServer(s grpc.ServiceRegistrar, srv ThingsServiceServer) { - s.RegisterService(&ThingsService_ServiceDesc, srv) -} - -func _ThingsService_Authorize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ThingsAuthzReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ThingsServiceServer).Authorize(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ThingsService_Authorize_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ThingsServiceServer).Authorize(ctx, req.(*ThingsAuthzReq)) - } - return interceptor(ctx, in, info, handler) -} - -// ThingsService_ServiceDesc is the grpc.ServiceDesc for ThingsService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var ThingsService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "magistrala.ThingsService", - HandlerType: (*ThingsServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Authorize", - Handler: _ThingsService_Authorize_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "auth.proto", -} - -const ( - TokenService_Issue_FullMethodName = "/magistrala.TokenService/Issue" - TokenService_Refresh_FullMethodName = "/magistrala.TokenService/Refresh" -) - -// TokenServiceClient is the client API for TokenService 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 TokenServiceClient interface { - Issue(ctx context.Context, in *IssueReq, opts ...grpc.CallOption) (*Token, error) - Refresh(ctx context.Context, in *RefreshReq, opts ...grpc.CallOption) (*Token, error) -} - -type tokenServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewTokenServiceClient(cc grpc.ClientConnInterface) TokenServiceClient { - return &tokenServiceClient{cc} -} - -func (c *tokenServiceClient) Issue(ctx context.Context, in *IssueReq, opts ...grpc.CallOption) (*Token, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(Token) - err := c.cc.Invoke(ctx, TokenService_Issue_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *tokenServiceClient) Refresh(ctx context.Context, in *RefreshReq, opts ...grpc.CallOption) (*Token, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(Token) - err := c.cc.Invoke(ctx, TokenService_Refresh_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// TokenServiceServer is the server API for TokenService service. -// All implementations must embed UnimplementedTokenServiceServer -// for forward compatibility -type TokenServiceServer interface { - Issue(context.Context, *IssueReq) (*Token, error) - Refresh(context.Context, *RefreshReq) (*Token, error) - mustEmbedUnimplementedTokenServiceServer() -} - -// UnimplementedTokenServiceServer must be embedded to have forward compatible implementations. -type UnimplementedTokenServiceServer struct { -} - -func (UnimplementedTokenServiceServer) Issue(context.Context, *IssueReq) (*Token, error) { - return nil, status.Errorf(codes.Unimplemented, "method Issue not implemented") -} -func (UnimplementedTokenServiceServer) Refresh(context.Context, *RefreshReq) (*Token, error) { - return nil, status.Errorf(codes.Unimplemented, "method Refresh not implemented") -} -func (UnimplementedTokenServiceServer) mustEmbedUnimplementedTokenServiceServer() {} - -// UnsafeTokenServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to TokenServiceServer will -// result in compilation errors. -type UnsafeTokenServiceServer interface { - mustEmbedUnimplementedTokenServiceServer() -} - -func RegisterTokenServiceServer(s grpc.ServiceRegistrar, srv TokenServiceServer) { - s.RegisterService(&TokenService_ServiceDesc, srv) -} - -func _TokenService_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.(TokenServiceServer).Issue(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: TokenService_Issue_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TokenServiceServer).Issue(ctx, req.(*IssueReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _TokenService_Refresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RefreshReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TokenServiceServer).Refresh(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: TokenService_Refresh_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TokenServiceServer).Refresh(ctx, req.(*RefreshReq)) - } - return interceptor(ctx, in, info, handler) -} - -// TokenService_ServiceDesc is the grpc.ServiceDesc for TokenService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var TokenService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "magistrala.TokenService", - HandlerType: (*TokenServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Issue", - Handler: _TokenService_Issue_Handler, - }, - { - MethodName: "Refresh", - Handler: _TokenService_Refresh_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "auth.proto", -} - -const ( - AuthService_Authorize_FullMethodName = "/magistrala.AuthService/Authorize" - AuthService_Authenticate_FullMethodName = "/magistrala.AuthService/Authenticate" -) - -// 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. -// -// AuthService is a service that provides authentication and authorization -// functionalities for magistrala services. -type AuthServiceClient interface { - Authorize(ctx context.Context, in *AuthZReq, opts ...grpc.CallOption) (*AuthZRes, error) - Authenticate(ctx context.Context, in *AuthNReq, opts ...grpc.CallOption) (*AuthNRes, error) -} - -type authServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient { - return &authServiceClient{cc} -} - -func (c *authServiceClient) Authorize(ctx context.Context, in *AuthZReq, opts ...grpc.CallOption) (*AuthZRes, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(AuthZRes) - err := c.cc.Invoke(ctx, AuthService_Authorize_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *authServiceClient) Authenticate(ctx context.Context, in *AuthNReq, opts ...grpc.CallOption) (*AuthNRes, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(AuthNRes) - err := c.cc.Invoke(ctx, AuthService_Authenticate_FullMethodName, in, out, cOpts...) - 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 -// -// AuthService is a service that provides authentication and authorization -// functionalities for magistrala services. -type AuthServiceServer interface { - Authorize(context.Context, *AuthZReq) (*AuthZRes, error) - Authenticate(context.Context, *AuthNReq) (*AuthNRes, error) - mustEmbedUnimplementedAuthServiceServer() -} - -// UnimplementedAuthServiceServer must be embedded to have forward compatible implementations. -type UnimplementedAuthServiceServer struct { -} - -func (UnimplementedAuthServiceServer) Authorize(context.Context, *AuthZReq) (*AuthZRes, error) { - return nil, status.Errorf(codes.Unimplemented, "method Authorize not implemented") -} -func (UnimplementedAuthServiceServer) Authenticate(context.Context, *AuthNReq) (*AuthNRes, error) { - return nil, status.Errorf(codes.Unimplemented, "method Authenticate 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(AuthZReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthServiceServer).Authorize(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AuthService_Authorize_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServiceServer).Authorize(ctx, req.(*AuthZReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _AuthService_Authenticate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(AuthNReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthServiceServer).Authenticate(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AuthService_Authenticate_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServiceServer).Authenticate(ctx, req.(*AuthNReq)) - } - 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: "magistrala.AuthService", - HandlerType: (*AuthServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Authorize", - Handler: _AuthService_Authorize_Handler, - }, - { - MethodName: "Authenticate", - Handler: _AuthService_Authenticate_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "auth.proto", -} - -const ( - DomainsService_DeleteUserFromDomains_FullMethodName = "/magistrala.DomainsService/DeleteUserFromDomains" -) - -// DomainsServiceClient is the client API for DomainsService 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. -// -// DomainsService is a service that provides access to domains -// functionalities for magistrala services. -type DomainsServiceClient interface { - DeleteUserFromDomains(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*DeleteUserRes, error) -} - -type domainsServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewDomainsServiceClient(cc grpc.ClientConnInterface) DomainsServiceClient { - return &domainsServiceClient{cc} -} - -func (c *domainsServiceClient) DeleteUserFromDomains(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*DeleteUserRes, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(DeleteUserRes) - err := c.cc.Invoke(ctx, DomainsService_DeleteUserFromDomains_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// DomainsServiceServer is the server API for DomainsService service. -// All implementations must embed UnimplementedDomainsServiceServer -// for forward compatibility -// -// DomainsService is a service that provides access to domains -// functionalities for magistrala services. -type DomainsServiceServer interface { - DeleteUserFromDomains(context.Context, *DeleteUserReq) (*DeleteUserRes, error) - mustEmbedUnimplementedDomainsServiceServer() -} - -// UnimplementedDomainsServiceServer must be embedded to have forward compatible implementations. -type UnimplementedDomainsServiceServer struct { -} - -func (UnimplementedDomainsServiceServer) DeleteUserFromDomains(context.Context, *DeleteUserReq) (*DeleteUserRes, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteUserFromDomains not implemented") -} -func (UnimplementedDomainsServiceServer) mustEmbedUnimplementedDomainsServiceServer() {} - -// UnsafeDomainsServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to DomainsServiceServer will -// result in compilation errors. -type UnsafeDomainsServiceServer interface { - mustEmbedUnimplementedDomainsServiceServer() -} - -func RegisterDomainsServiceServer(s grpc.ServiceRegistrar, srv DomainsServiceServer) { - s.RegisterService(&DomainsService_ServiceDesc, srv) -} - -func _DomainsService_DeleteUserFromDomains_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteUserReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(DomainsServiceServer).DeleteUserFromDomains(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: DomainsService_DeleteUserFromDomains_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(DomainsServiceServer).DeleteUserFromDomains(ctx, req.(*DeleteUserReq)) - } - return interceptor(ctx, in, info, handler) -} - -// DomainsService_ServiceDesc is the grpc.ServiceDesc for DomainsService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var DomainsService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "magistrala.DomainsService", - HandlerType: (*DomainsServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "DeleteUserFromDomains", - Handler: _DomainsService_DeleteUserFromDomains_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "auth.proto", -} diff --git a/bootstrap/README.md b/bootstrap/README.md index 9fb0538877..26a9c009ed 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -2,68 +2,68 @@ New devices need to be configured properly and connected to the Magistrala. Bootstrap service is used in order to accomplish that. This service provides the following features: -1. Creating new Magistrala Things -2. Providing basic configuration for the newly created Things -3. Enabling/disabling Things +1. Creating new Magistrala Clients +2. Providing basic configuration for the newly created Clients +3. Enabling/disabling Clients -Pre-provisioning a new Thing is as simple as sending Configuration data to the Bootstrap service. Once the Thing is online, it sends a request for initial config to Bootstrap service. Bootstrap service provides an API for enabling and disabling Things. Only enabled Things can exchange messages over Magistrala. Bootstrapping does not implicitly enable Things, it has to be done manually. +Pre-provisioning a new Client is as simple as sending Configuration data to the Bootstrap service. Once the Client is online, it sends a request for initial config to Bootstrap service. Bootstrap service provides an API for enabling and disabling Clients. Only enabled Clients can exchange messages over Magistrala. Bootstrapping does not implicitly enable Clients, it has to be done manually. -In order to bootstrap successfully, the Thing needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during the manufacturing process. If the Thing is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Thing will be saved so that it can be provisioned later. +In order to bootstrap successfully, the Client needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during the manufacturing process. If the Client is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Client will be saved so that it can be provisioned later. -## Thing Configuration Entity +## Client Configuration Entity -Thing Configuration consists of two logical parts: the custom configuration that can be interpreted by the Thing itself and Magistrala-related configuration. Magistrala config contains: +Client Configuration consists of two logical parts: the custom configuration that can be interpreted by the Client itself and Magistrala-related configuration. Magistrala config contains: -1. corresponding Magistrala Thing ID -2. corresponding Magistrala Thing key -3. list of the Magistrala channels the Thing is connected to +1. corresponding Magistrala Client ID +2. corresponding Magistrala Client key +3. list of the Magistrala channels the Client is connected to -> Note: list of channels contains IDs of the Magistrala channels. These channels are _pre-provisioned_ on the Magistrala side and, unlike corresponding Magistrala Thing, Bootstrap service is not able to create Magistrala Channels. +> Note: list of channels contains IDs of the Magistrala channels. These channels are _pre-provisioned_ on the Magistrala side and, unlike corresponding Magistrala Client, Bootstrap service is not able to create Magistrala Channels. -Enabling and disabling Thing (adding Thing to/from whitelist) is as simple as connecting corresponding Magistrala Thing to the given list of Channels. Configuration keeps _state_ of the Thing: +Enabling and disabling Client (adding Client to/from whitelist) is as simple as connecting corresponding Magistrala Client to the given list of Channels. Configuration keeps _state_ of the Client: | State | What it means | | -------- | --------------------------------------------- | -| Inactive | Thing is created, but isn't enabled | -| Active | Thing is able to communicate using Magistrala | +| Inactive | Client is created, but isn't enabled | +| Active | Client is able to communicate using Magistrala | -Switching between states `Active` and `Inactive` enables and disables Thing, respectively. +Switching between states `Active` and `Inactive` enables and disables Client, respectively. -Thing configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Thing. For example, a device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure. +Client configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Client. For example, a device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure. ## Configuration The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ----------------------------- | -------------------------------------------------------------------------------- | -------------------------------- | -| MG_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | info | -| MG_BOOTSTRAP_DB_HOST | Database host address | localhost | -| MG_BOOTSTRAP_DB_PORT | Database host port | 5432 | -| MG_BOOTSTRAP_DB_USER | Database user | magistrala | -| MG_BOOTSTRAP_DB_PASS | Database password | magistrala | -| MG_BOOTSTRAP_DB_NAME | Name of the database used by the service | bootstrap | -| MG_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | -| MG_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | -| MG_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | "" | -| MG_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | -| MG_BOOTSTRAP_ENCRYPT_KEY | Secret key for secure bootstrapping encryption | 12345678910111213141516171819202 | -| MG_BOOTSTRAP_HTTP_HOST | Bootstrap service HTTP host | "" | -| MG_BOOTSTRAP_HTTP_PORT | Bootstrap service HTTP port | 9013 | -| MG_BOOTSTRAP_HTTP_SERVER_CERT | Path to server certificate in pem format | "" | -| MG_BOOTSTRAP_HTTP_SERVER_KEY | Path to server key in pem format | "" | -| MG_BOOTSTRAP_EVENT_CONSUMER | Bootstrap service event source consumer name | bootstrap | -| MG_ES_URL | Event store URL | | -| MG_AUTH_GRPC_URL | Auth service Auth gRPC URL | | -| MG_AUTH_GRPC_TIMEOUT | Auth service Auth gRPC request timeout in seconds | 1s | -| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service Auth gRPC client certificate file | "" | -| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service Auth gRPC client key file | "" | -| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server Auth gRPC server trusted CA certificate file | "" | -| MG_THINGS_URL | Base url for Magistrala Things | | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_BOOTSTRAP_INSTANCE_ID | Bootstrap service instance ID | "" | +| Variable | Description | Default | +| ----------------------------- | -------------------------------------------------------------------------------- | --------------------------------- | +| MG_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | info | +| MG_BOOTSTRAP_DB_HOST | Database host address | localhost | +| MG_BOOTSTRAP_DB_PORT | Database host port | 5432 | +| MG_BOOTSTRAP_DB_USER | Database user | magistrala | +| MG_BOOTSTRAP_DB_PASS | Database password | magistrala | +| MG_BOOTSTRAP_DB_NAME | Name of the database used by the service | bootstrap | +| MG_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | +| MG_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | +| MG_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | "" | +| MG_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | +| MG_BOOTSTRAP_ENCRYPT_KEY | Secret key for secure bootstrapping encryption | 12345678910111213141516171819202 | +| MG_BOOTSTRAP_HTTP_HOST | Bootstrap service HTTP host | "" | +| MG_BOOTSTRAP_HTTP_PORT | Bootstrap service HTTP port | 9013 | +| MG_BOOTSTRAP_HTTP_SERVER_CERT | Path to server certificate in pem format | "" | +| MG_BOOTSTRAP_HTTP_SERVER_KEY | Path to server key in pem format | "" | +| MG_BOOTSTRAP_EVENT_CONSUMER | Bootstrap service event source consumer name | bootstrap | +| MG_ES_URL | Event store URL | | +| MG_AUTH_GRPC_URL | Auth service Auth gRPC URL | | +| MG_AUTH_GRPC_TIMEOUT | Auth service Auth gRPC request timeout in seconds | 1s | +| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service Auth gRPC client certificate file | "" | +| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service Auth gRPC client key file | "" | +| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server Auth gRPC server trusted CA certificate file | "" | +| MG_CLIENTS_URL | Base URL for Magistrala Clients | | +| MG_JAEGER_URL | Jaeger server URL | | +| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_BOOTSTRAP_INSTANCE_ID | Bootstrap service instance ID | "" | ## Deployment @@ -105,7 +105,7 @@ MG_AUTH_GRPC_TIMEOUT=1s \ MG_AUTH_GRPC_CLIENT_CERT="" \ MG_AUTH_GRPC_CLIENT_KEY="" \ MG_AUTH_GRPC_SERVER_CERTS="" \ -MG_THINGS_URL=http://localhost:9000 \ +MG_CLIENTS_URL=http://localhost:9000 \ MG_JAEGER_URL=http://localhost:14268/api/traces \ MG_JAEGER_TRACE_RATIO=1.0 \ MG_SEND_TELEMETRY=true \ diff --git a/bootstrap/api/endpoint.go b/bootstrap/api/endpoint.go index 1bf7cf9756..4bc1f5eca6 100644 --- a/bootstrap/api/endpoint.go +++ b/bootstrap/api/endpoint.go @@ -33,9 +33,9 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint { } config := bootstrap.Config{ - ThingID: req.ThingID, + ClientID: req.ClientID, ExternalID: req.ExternalID, - ExternalKey: req.ExternalKey, + ExternalKey: req.ExternalSecret, Channels: channels, Name: req.Name, ClientCert: req.ClientCert, @@ -50,7 +50,7 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint { } res := configRes{ - id: saved.ThingID, + id: saved.ClientID, created: true, } @@ -70,13 +70,13 @@ func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint { return nil, svcerr.ErrAuthorization } - cfg, err := svc.UpdateCert(ctx, session, req.thingID, req.ClientCert, req.ClientKey, req.CACert) + cfg, err := svc.UpdateCert(ctx, session, req.clientID, req.ClientCert, req.ClientKey, req.CACert) if err != nil { return nil, err } res := updateConfigRes{ - ThingID: cfg.ThingID, + ClientID: cfg.ClientID, ClientCert: cfg.ClientCert, CACert: cfg.CACert, ClientKey: cfg.ClientKey, @@ -113,14 +113,14 @@ func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint { } res := viewRes{ - ThingID: config.ThingID, - ThingKey: config.ThingKey, - Channels: channels, - ExternalID: config.ExternalID, - ExternalKey: config.ExternalKey, - Name: config.Name, - Content: config.Content, - State: config.State, + ClientID: config.ClientID, + CLientSecret: config.ClientSecret, + Channels: channels, + ExternalID: config.ExternalID, + ExternalKey: config.ExternalKey, + Name: config.Name, + Content: config.Content, + State: config.State, } return res, nil @@ -140,9 +140,9 @@ func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint { } config := bootstrap.Config{ - ThingID: req.id, - Name: req.Name, - Content: req.Content, + ClientID: req.id, + Name: req.Name, + Content: req.Content, } if err := svc.Update(ctx, session, config); err != nil { @@ -150,7 +150,7 @@ func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint { } res := configRes{ - id: config.ThingID, + id: config.ClientID, created: false, } @@ -217,14 +217,14 @@ func listEndpoint(svc bootstrap.Service) endpoint.Endpoint { } view := viewRes{ - ThingID: cfg.ThingID, - ThingKey: cfg.ThingKey, - Channels: channels, - ExternalID: cfg.ExternalID, - ExternalKey: cfg.ExternalKey, - Name: cfg.Name, - Content: cfg.Content, - State: cfg.State, + ClientID: cfg.ClientID, + CLientSecret: cfg.ClientSecret, + Channels: channels, + ExternalID: cfg.ExternalID, + ExternalKey: cfg.ExternalKey, + Name: cfg.Name, + Content: cfg.Content, + State: cfg.State, } res.Configs = append(res.Configs, view) } diff --git a/bootstrap/api/endpoint_test.go b/bootstrap/api/endpoint_test.go index 02a0d74646..cc2c66b803 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -49,28 +49,28 @@ const ( ) var ( - encKey = []byte("1234567891011121") - metadata = map[string]interface{}{"meta": "data"} - addExternalID = testsutil.GenerateUUID(&testing.T{}) - addExternalKey = testsutil.GenerateUUID(&testing.T{}) - addThingID = testsutil.GenerateUUID(&testing.T{}) - addThingKey = testsutil.GenerateUUID(&testing.T{}) - addReq = struct { - ThingID string `json:"thing_id"` - ThingKey string `json:"thing_key"` - ExternalID string `json:"external_id"` - ExternalKey string `json:"external_key"` - Channels []string `json:"channels"` - Name string `json:"name"` - Content string `json:"content"` + encKey = []byte("1234567891011121") + metadata = map[string]interface{}{"meta": "data"} + addExternalID = testsutil.GenerateUUID(&testing.T{}) + addExternalKey = testsutil.GenerateUUID(&testing.T{}) + addClientID = testsutil.GenerateUUID(&testing.T{}) + addClientSecret = testsutil.GenerateUUID(&testing.T{}) + addReq = struct { + ClientID string `json:"client_id"` + ClinetSecret string `json:"client_secret"` + ExternalID string `json:"external_id"` + ExternalKey string `json:"external_key"` + Channels []string `json:"channels"` + Name string `json:"name"` + Content string `json:"content"` }{ - ThingID: addThingID, - ThingKey: addThingKey, - ExternalID: addExternalID, - ExternalKey: addExternalKey, - Channels: []string{"1"}, - Name: "name", - Content: "config", + ClientID: addClientID, + ClinetSecret: addClientSecret, + ExternalID: addExternalID, + ExternalKey: addExternalKey, + Channels: []string{"1"}, + Name: "name", + Content: "config", } updateReq = struct { @@ -78,7 +78,7 @@ var ( Content string `json:"content,omitempty"` State bootstrap.State `json:"state,omitempty"` ClientCert string `json:"client_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` + ClientKey string `json:"client_secret,omitempty"` CACert string `json:"ca_cert,omitempty"` }{ Channels: []string{"1"}, @@ -108,10 +108,10 @@ type testRequest struct { func newConfig() bootstrap.Config { return bootstrap.Config{ - ThingID: addThingID, - ThingKey: addThingKey, - ExternalID: addExternalID, - ExternalKey: addExternalKey, + ClientID: addClientID, + ClientSecret: addClientSecret, + ExternalID: addExternalID, + ExternalKey: addExternalKey, Channels: []bootstrap.Channel{ { ID: "1", @@ -136,7 +136,7 @@ func (tr testRequest) make() (*http.Response, error) { req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) } if tr.key != "" { - req.Header.Set("Authorization", apiutil.ThingPrefix+tr.key) + req.Header.Set("Authorization", apiutil.ClientPrefix+tr.key) } if tr.contentType != "" { @@ -200,7 +200,7 @@ func TestAdd(t *testing.T) { data := toJSON(addReq) neID := addReq - neID.ThingID = testsutil.GenerateUUID(t) + neID.ClientID = testsutil.GenerateUUID(t) neData := toJSON(neID) invalidChannels := addReq @@ -237,7 +237,7 @@ func TestAdd(t *testing.T) { token: validToken, contentType: contentType, status: http.StatusCreated, - location: "/things/configs/" + c.ThingID, + location: "/clients/configs/" + c.ClientID, err: nil, }, { @@ -332,7 +332,7 @@ func TestAdd(t *testing.T) { req := testRequest{ client: bs.Client(), method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/configs", bs.URL, tc.domainID), + url: fmt.Sprintf("%s/%s/clients/configs", bs.URL, tc.domainID), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.req), @@ -359,14 +359,14 @@ func TestView(t *testing.T) { } data := config{ - ThingID: c.ThingID, - ThingKey: c.ThingKey, - State: c.State, - Channels: channels, - ExternalID: c.ExternalID, - ExternalKey: c.ExternalKey, - Name: c.Name, - Content: c.Content, + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + State: c.State, + Channels: channels, + ExternalID: c.ExternalID, + ExternalKey: c.ExternalKey, + Name: c.Name, + Content: c.Content, } cases := []struct { @@ -382,7 +382,7 @@ func TestView(t *testing.T) { { desc: "view a config with invalid token", token: invalidToken, - id: c.ThingID, + id: c.ClientID, status: http.StatusUnauthorized, res: config{}, authenticateErr: svcerr.ErrAuthentication, @@ -391,7 +391,7 @@ func TestView(t *testing.T) { { desc: "view a config", token: validToken, - id: c.ThingID, + id: c.ClientID, status: http.StatusOK, res: data, err: nil, @@ -407,7 +407,7 @@ func TestView(t *testing.T) { { desc: "view a config with an empty token", token: "", - id: c.ThingID, + id: c.ClientID, status: http.StatusUnauthorized, res: config{}, err: apiutil.ErrBearerToken, @@ -415,7 +415,7 @@ func TestView(t *testing.T) { { desc: "view config without authorization", token: validToken, - id: c.ThingID, + id: c.ClientID, status: http.StatusForbidden, res: config{}, err: svcerr.ErrAuthorization, @@ -432,7 +432,7 @@ func TestView(t *testing.T) { req := testRequest{ client: bs.Client(), method: http.MethodGet, - url: fmt.Sprintf("%s/%s/things/configs/%s", bs.URL, domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/configs/%s", bs.URL, domainID, tc.id), token: tc.token, } res, err := req.make() @@ -476,7 +476,7 @@ func TestUpdate(t *testing.T) { { desc: "update with invalid token", req: data, - id: c.ThingID, + id: c.ClientID, token: invalidToken, contentType: contentType, status: http.StatusUnauthorized, @@ -486,7 +486,7 @@ func TestUpdate(t *testing.T) { { desc: "update with an empty token", req: data, - id: c.ThingID, + id: c.ClientID, token: "", contentType: contentType, status: http.StatusUnauthorized, @@ -495,7 +495,7 @@ func TestUpdate(t *testing.T) { { desc: "update a valid config", req: data, - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: contentType, status: http.StatusOK, @@ -504,7 +504,7 @@ func TestUpdate(t *testing.T) { { desc: "update a config with wrong content type", req: data, - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: "", status: http.StatusUnsupportedMediaType, @@ -522,7 +522,7 @@ func TestUpdate(t *testing.T) { { desc: "update a config with invalid request format", req: "}", - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: contentType, status: http.StatusBadRequest, @@ -530,7 +530,7 @@ func TestUpdate(t *testing.T) { }, { desc: "update a config with an empty request", - id: c.ThingID, + id: c.ClientID, req: "", token: validToken, contentType: contentType, @@ -549,7 +549,7 @@ func TestUpdate(t *testing.T) { req := testRequest{ client: bs.Client(), method: http.MethodPut, - url: fmt.Sprintf("%s/%s/things/configs/%s", bs.URL, domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/configs/%s", bs.URL, domainID, tc.id), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.req), @@ -584,7 +584,7 @@ func TestUpdateCert(t *testing.T) { { desc: "update with invalid token", req: data, - id: c.ThingID, + id: c.ClientID, token: invalidToken, contentType: contentType, status: http.StatusUnauthorized, @@ -594,7 +594,7 @@ func TestUpdateCert(t *testing.T) { { desc: "update with an empty token", req: data, - id: c.ThingID, + id: c.ClientID, token: "", contentType: contentType, status: http.StatusUnauthorized, @@ -603,7 +603,7 @@ func TestUpdateCert(t *testing.T) { { desc: "update a valid config", req: data, - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: contentType, status: http.StatusOK, @@ -612,7 +612,7 @@ func TestUpdateCert(t *testing.T) { { desc: "update a config with wrong content type", req: data, - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: "", status: http.StatusUnsupportedMediaType, @@ -630,7 +630,7 @@ func TestUpdateCert(t *testing.T) { { desc: "update a config with invalid request format", req: "}", - id: c.ThingKey, + id: c.ClientSecret, token: validToken, contentType: contentType, status: http.StatusBadRequest, @@ -638,7 +638,7 @@ func TestUpdateCert(t *testing.T) { }, { desc: "update a config with an empty request", - id: c.ThingID, + id: c.ClientID, req: "", token: validToken, contentType: contentType, @@ -657,7 +657,7 @@ func TestUpdateCert(t *testing.T) { req := testRequest{ client: bs.Client(), method: http.MethodPatch, - url: fmt.Sprintf("%s/%s/things/configs/certs/%s", bs.URL, domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/configs/certs/%s", bs.URL, domainID, tc.id), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.req), @@ -696,7 +696,7 @@ func TestUpdateConnections(t *testing.T) { { desc: "update connections with invalid token", req: data, - id: c.ThingID, + id: c.ClientID, token: invalidToken, contentType: contentType, status: http.StatusUnauthorized, @@ -706,7 +706,7 @@ func TestUpdateConnections(t *testing.T) { { desc: "update connections with an empty token", req: data, - id: c.ThingID, + id: c.ClientID, token: "", contentType: contentType, status: http.StatusUnauthorized, @@ -715,7 +715,7 @@ func TestUpdateConnections(t *testing.T) { { desc: "update connections valid config", req: data, - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: contentType, status: http.StatusOK, @@ -724,7 +724,7 @@ func TestUpdateConnections(t *testing.T) { { desc: "update connections with wrong content type", req: data, - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: "", status: http.StatusUnsupportedMediaType, @@ -742,7 +742,7 @@ func TestUpdateConnections(t *testing.T) { { desc: "update connections with invalid channels", req: wrongData, - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: contentType, status: http.StatusNotFound, @@ -751,7 +751,7 @@ func TestUpdateConnections(t *testing.T) { { desc: "update a config with invalid request format", req: "}", - id: c.ThingID, + id: c.ClientID, token: validToken, contentType: contentType, status: http.StatusBadRequest, @@ -759,7 +759,7 @@ func TestUpdateConnections(t *testing.T) { }, { desc: "update a config with an empty request", - id: c.ThingID, + id: c.ClientID, req: "", token: validToken, contentType: contentType, @@ -778,7 +778,7 @@ func TestUpdateConnections(t *testing.T) { req := testRequest{ client: bs.Client(), method: http.MethodPut, - url: fmt.Sprintf("%s/%s/things/configs/connections/%s", bs.URL, domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/configs/connections/%s", bs.URL, domainID, tc.id), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.req), @@ -800,13 +800,13 @@ func TestList(t *testing.T) { bs, svc, auth := newBootstrapServer() defer bs.Close() - path := fmt.Sprintf("%s/%s/%s", bs.URL, domainID, "things/configs") + path := fmt.Sprintf("%s/%s/%s", bs.URL, domainID, "clients/configs") c := newConfig() for i := 0; i < configNum; i++ { c.ExternalID = strconv.Itoa(i) - c.ThingKey = c.ExternalID + c.ClientSecret = c.ExternalID c.Name = fmt.Sprintf("%s-%d", addName, i) c.ExternalKey = fmt.Sprintf("%s%s", addExternalKey, strconv.Itoa(i)) @@ -815,14 +815,14 @@ func TestList(t *testing.T) { channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata}) } s := config{ - ThingID: c.ThingID, - ThingKey: c.ThingKey, - Channels: channels, - ExternalID: c.ExternalID, - ExternalKey: c.ExternalKey, - Name: c.Name, - Content: c.Content, - State: c.State, + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + Channels: channels, + ExternalID: c.ExternalID, + ExternalKey: c.ExternalKey, + Name: c.Name, + Content: c.Content, + State: c.State, } list[i] = s } @@ -833,7 +833,7 @@ func TestList(t *testing.T) { state = bootstrap.Inactive } svcCall := svc.On("ChangeState", context.Background(), mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - err := svc.ChangeState(context.Background(), mgauthn.Session{}, validToken, list[i].ThingID, state) + err := svc.ChangeState(context.Background(), mgauthn.Session{}, validToken, list[i].ClientID, state) assert.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err)) svcCall.Unset() @@ -1084,7 +1084,7 @@ func TestRemove(t *testing.T) { }{ { desc: "remove with invalid token", - id: c.ThingID, + id: c.ClientID, token: invalidToken, status: http.StatusUnauthorized, authenticateErr: svcerr.ErrAuthentication, @@ -1092,7 +1092,7 @@ func TestRemove(t *testing.T) { }, { desc: "remove with an empty token", - id: c.ThingID, + id: c.ClientID, token: "", status: http.StatusUnauthorized, err: apiutil.ErrBearerToken, @@ -1106,7 +1106,7 @@ func TestRemove(t *testing.T) { }, { desc: "remove config", - id: c.ThingID, + id: c.ClientID, token: validToken, status: http.StatusNoContent, err: nil, @@ -1130,7 +1130,7 @@ func TestRemove(t *testing.T) { req := testRequest{ client: bs.Client(), method: http.MethodDelete, - url: fmt.Sprintf("%s/%s/things/configs/%s", bs.URL, domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/configs/%s", bs.URL, domainID, tc.id), token: tc.token, } res, err := req.make() @@ -1156,21 +1156,21 @@ func TestBootstrap(t *testing.T) { } s := struct { - ThingID string `json:"thing_id"` - ThingKey string `json:"thing_key"` - Channels []channel `json:"channels"` - Content string `json:"content"` - ClientCert string `json:"client_cert"` - ClientKey string `json:"client_key"` - CACert string `json:"ca_cert"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Channels []channel `json:"channels"` + Content string `json:"content"` + ClientCert string `json:"client_cert"` + ClientKey string `json:"client_key"` + CACert string `json:"ca_cert"` }{ - ThingID: c.ThingID, - ThingKey: c.ThingKey, - Channels: channels, - Content: c.Content, - ClientCert: c.ClientCert, - ClientKey: c.ClientKey, - CACert: c.CACert, + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + Channels: channels, + Content: c.Content, + ClientCert: c.ClientCert, + ClientKey: c.ClientKey, + CACert: c.CACert, } data := toJSON(s) @@ -1185,7 +1185,7 @@ func TestBootstrap(t *testing.T) { err error }{ { - desc: "bootstrap a Thing with unknown ID", + desc: "bootstrap a Client with unknown ID", externalID: unknown, externalKey: c.ExternalKey, status: http.StatusNotFound, @@ -1194,7 +1194,7 @@ func TestBootstrap(t *testing.T) { err: bootstrap.ErrBootstrap, }, { - desc: "bootstrap a Thing with an empty ID", + desc: "bootstrap a Client with an empty ID", externalID: "", externalKey: c.ExternalKey, status: http.StatusBadRequest, @@ -1203,7 +1203,7 @@ func TestBootstrap(t *testing.T) { err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrMalformedEntity), }, { - desc: "bootstrap a Thing with unknown key", + desc: "bootstrap a Client with unknown key", externalID: c.ExternalID, externalKey: unknown, status: http.StatusForbidden, @@ -1212,7 +1212,7 @@ func TestBootstrap(t *testing.T) { err: errors.Wrap(bootstrap.ErrExternalKey, errors.New("")), }, { - desc: "bootstrap a Thing with an empty key", + desc: "bootstrap a Client with an empty key", externalID: c.ExternalID, externalKey: "", status: http.StatusBadRequest, @@ -1221,7 +1221,7 @@ func TestBootstrap(t *testing.T) { err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrAuthentication), }, { - desc: "bootstrap known Thing", + desc: "bootstrap known Client", externalID: c.ExternalID, externalKey: c.ExternalKey, status: http.StatusOK, @@ -1255,7 +1255,7 @@ func TestBootstrap(t *testing.T) { req := testRequest{ client: bs.Client(), method: http.MethodGet, - url: fmt.Sprintf("%s/things/bootstrap/%s", bs.URL, tc.externalID), + url: fmt.Sprintf("%s/clients/bootstrap/%s", bs.URL, tc.externalID), key: tc.externalKey, } res, err := req.make() @@ -1296,7 +1296,7 @@ func TestChangeState(t *testing.T) { }{ { desc: "change state with invalid token", - id: c.ThingID, + id: c.ClientID, token: invalidToken, state: active, contentType: contentType, @@ -1306,7 +1306,7 @@ func TestChangeState(t *testing.T) { }, { desc: "change state with an empty token", - id: c.ThingID, + id: c.ClientID, token: "", state: active, contentType: contentType, @@ -1315,7 +1315,7 @@ func TestChangeState(t *testing.T) { }, { desc: "change state with invalid content type", - id: c.ThingID, + id: c.ClientID, token: validToken, state: active, contentType: "", @@ -1324,7 +1324,7 @@ func TestChangeState(t *testing.T) { }, { desc: "change state to active", - id: c.ThingID, + id: c.ClientID, token: validToken, state: active, contentType: contentType, @@ -1333,7 +1333,7 @@ func TestChangeState(t *testing.T) { }, { desc: "change state to inactive", - id: c.ThingID, + id: c.ClientID, token: validToken, state: inactive, contentType: contentType, @@ -1351,7 +1351,7 @@ func TestChangeState(t *testing.T) { }, { desc: "change state to invalid value", - id: c.ThingID, + id: c.ClientID, token: validToken, state: fmt.Sprintf("{\"state\": %d}", -3), contentType: contentType, @@ -1360,7 +1360,7 @@ func TestChangeState(t *testing.T) { }, { desc: "change state with invalid data", - id: c.ThingID, + id: c.ClientID, token: validToken, state: "", contentType: contentType, @@ -1379,7 +1379,7 @@ func TestChangeState(t *testing.T) { req := testRequest{ client: bs.Client(), method: http.MethodPut, - url: fmt.Sprintf("%s/%s/things/state/%s", bs.URL, domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/state/%s", bs.URL, domainID, tc.id), token: tc.token, contentType: tc.contentType, body: strings.NewReader(tc.state), @@ -1400,14 +1400,14 @@ type channel struct { } type config struct { - ThingID string `json:"thing_id,omitempty"` - ThingKey string `json:"thing_key,omitempty"` - Channels []channel `json:"channels,omitempty"` - ExternalID string `json:"external_id"` - ExternalKey string `json:"external_key,omitempty"` - Content string `json:"content,omitempty"` - Name string `json:"name"` - State bootstrap.State `json:"state"` + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + Channels []channel `json:"channels,omitempty"` + ExternalID string `json:"external_id"` + ExternalKey string `json:"external_key,omitempty"` + Content string `json:"content,omitempty"` + Name string `json:"name"` + State bootstrap.State `json:"state"` } type configPage struct { diff --git a/bootstrap/api/requests.go b/bootstrap/api/requests.go index f1279b4420..dd3fb62fed 100644 --- a/bootstrap/api/requests.go +++ b/bootstrap/api/requests.go @@ -11,16 +11,16 @@ import ( const maxLimitSize = 100 type addReq struct { - token string - ThingID string `json:"thing_id"` - ExternalID string `json:"external_id"` - ExternalKey string `json:"external_key"` - Channels []string `json:"channels"` - Name string `json:"name"` - Content string `json:"content"` - ClientCert string `json:"client_cert"` - ClientKey string `json:"client_key"` - CACert string `json:"ca_cert"` + token string + ClientID string `json:"client_id"` + ExternalID string `json:"external_id"` + ExternalSecret string `json:"external_secret"` + Channels []string `json:"channels"` + Name string `json:"name"` + Content string `json:"content"` + ClientCert string `json:"client_cert"` + ClientKey string `json:"client_key"` + CACert string `json:"ca_cert"` } func (req addReq) validate() error { @@ -32,7 +32,7 @@ func (req addReq) validate() error { return apiutil.ErrMissingID } - if req.ExternalKey == "" { + if req.ExternalSecret == "" { return apiutil.ErrBearerKey } @@ -76,14 +76,14 @@ func (req updateReq) validate() error { } type updateCertReq struct { - thingID string + clientID string ClientCert string `json:"client_cert"` ClientKey string `json:"client_key"` CACert string `json:"ca_cert"` } func (req updateCertReq) validate() error { - if req.thingID == "" { + if req.clientID == "" { return apiutil.ErrMissingID } diff --git a/bootstrap/api/requests_test.go b/bootstrap/api/requests_test.go index 73ac1df9df..a24ea78f0f 100644 --- a/bootstrap/api/requests_test.go +++ b/bootstrap/api/requests_test.go @@ -87,10 +87,10 @@ func TestAddReqValidation(t *testing.T) { for _, tc := range cases { req := addReq{ - token: tc.token, - ExternalID: tc.externalID, - ExternalKey: tc.externalKey, - Channels: tc.channels, + token: tc.token, + ExternalID: tc.externalID, + ExternalSecret: tc.externalKey, + Channels: tc.channels, } err := req.validate() @@ -152,19 +152,19 @@ func TestUpdateReqValidation(t *testing.T) { func TestUpdateCertReqValidation(t *testing.T) { cases := []struct { desc string - thingID string + clientID string err error }{ { - desc: "empty thing id", - thingID: "", + desc: "empty client id", + clientID: "", err: apiutil.ErrMissingID, }, } for _, tc := range cases { req := updateCertReq{ - thingID: tc.thingID, + clientID: tc.clientID, } err := req.validate() diff --git a/bootstrap/api/responses.go b/bootstrap/api/responses.go index 59d166f7c3..e628961c8d 100644 --- a/bootstrap/api/responses.go +++ b/bootstrap/api/responses.go @@ -49,7 +49,7 @@ func (res configRes) Code() int { func (res configRes) Headers() map[string]string { if res.created { return map[string]string{ - "Location": fmt.Sprintf("/things/configs/%s", res.id), + "Location": fmt.Sprintf("/clients/configs/%s", res.id), } } @@ -67,16 +67,16 @@ type channelRes struct { } type viewRes struct { - ThingID string `json:"thing_id,omitempty"` - ThingKey string `json:"thing_key,omitempty"` - Channels []channelRes `json:"channels,omitempty"` - ExternalID string `json:"external_id"` - ExternalKey string `json:"external_key,omitempty"` - Content string `json:"content,omitempty"` - Name string `json:"name,omitempty"` - State bootstrap.State `json:"state"` - ClientCert string `json:"client_cert,omitempty"` - CACert string `json:"ca_cert,omitempty"` + ClientID string `json:"client_id,omitempty"` + CLientSecret string `json:"client,_secretomitempty"` + Channels []channelRes `json:"channels,omitempty"` + ExternalID string `json:"external_id"` + ExternalKey string `json:"external_key,omitempty"` + Content string `json:"content,omitempty"` + Name string `json:"name,omitempty"` + State bootstrap.State `json:"state"` + ClientCert string `json:"client_cert,omitempty"` + CACert string `json:"ca_cert,omitempty"` } func (res viewRes) Code() int { @@ -125,9 +125,9 @@ func (res stateRes) Empty() bool { } type updateConfigRes struct { - ThingID string `json:"thing_id,omitempty"` - ClientCert string `json:"client_cert,omitempty"` + ClientID string `json:"client_id,omitempty"` CACert string `json:"ca_cert,omitempty"` + ClientCert string `json:"client_cert,omitempty"` ClientKey string `json:"client_key,omitempty"` } diff --git a/bootstrap/api/transport.go b/bootstrap/api/transport.go index 742ba51e35..24c7c2dbc5 100644 --- a/bootstrap/api/transport.go +++ b/bootstrap/api/transport.go @@ -33,7 +33,7 @@ const ( ) var ( - fullMatch = []string{"state", "external_id", "thing_id", "thing_key"} + fullMatch = []string{"state", "external_id", "client_id", "client_key"} partialMatch = []string{"name"} // ErrBootstrap indicates error in getting bootstrap configuration. ErrBootstrap = errors.New("failed to read bootstrap configuration") @@ -47,7 +47,7 @@ func MakeHandler(svc bootstrap.Service, authn mgauthn.Authentication, reader boo r := chi.NewRouter() - r.Route("/{domainID}/things", func(r chi.Router) { + r.Route("/{domainID}/clients", func(r chi.Router) { r.Group(func(r chi.Router) { r.Use(api.AuthenticateMiddleware(authn, true)) @@ -96,14 +96,14 @@ func MakeHandler(svc bootstrap.Service, authn mgauthn.Authentication, reader boo }) }) - r.With(api.AuthenticateMiddleware(authn, true)).Put("/state/{thingID}", otelhttp.NewHandler(kithttp.NewServer( + r.With(api.AuthenticateMiddleware(authn, true)).Put("/state/{clientID}", otelhttp.NewHandler(kithttp.NewServer( stateEndpoint(svc), decodeStateRequest, api.EncodeResponse, opts...), "update_state").ServeHTTP) }) - r.Route("/things/bootstrap", func(r chi.Router) { + r.Route("/clients/bootstrap", func(r chi.Router) { r.Get("/", otelhttp.NewHandler(kithttp.NewServer( bootstrapEndpoint(svc, reader, false), decodeBootstrapRequest, @@ -163,7 +163,7 @@ func decodeUpdateCertRequest(_ context.Context, r *http.Request) (interface{}, e } req := updateCertReq{ - thingID: chi.URLParam(r, "certID"), + clientID: chi.URLParam(r, "certID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) @@ -216,7 +216,7 @@ func decodeListRequest(_ context.Context, r *http.Request) (interface{}, error) func decodeBootstrapRequest(_ context.Context, r *http.Request) (interface{}, error) { req := bootstrapReq{ id: chi.URLParam(r, "externalID"), - key: apiutil.ExtractThingKey(r), + key: apiutil.ExtractClientSecret(r), } return req, nil @@ -229,7 +229,7 @@ func decodeStateRequest(_ context.Context, r *http.Request) (interface{}, error) req := changeStateReq{ token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), + id: chi.URLParam(r, "clientID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) diff --git a/bootstrap/configs.go b/bootstrap/configs.go index 24c8ecde94..70fde8c9a3 100644 --- a/bootstrap/configs.go +++ b/bootstrap/configs.go @@ -7,30 +7,30 @@ import ( "context" "time" - "github.com/absmach/magistrala/things" + "github.com/absmach/magistrala/clients" ) // Config represents Configuration entity. It wraps information about external entity // as well as info about corresponding Magistrala entities. -// MGThing represents corresponding Magistrala Thing ID. -// MGKey is key of corresponding Magistrala Thing. -// MGChannels is a list of Magistrala Channels corresponding Magistrala Thing connects to. +// MGClient represents corresponding Magistrala Client ID. +// MGKey is key of corresponding Magistrala Client. +// MGChannels is a list of Magistrala Channels corresponding Magistrala Client connects to. type Config struct { - ThingID string `json:"thing_id"` - DomainID string `json:"domain_id,omitempty"` - Name string `json:"name,omitempty"` - ClientCert string `json:"client_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` - CACert string `json:"ca_cert,omitempty"` - ThingKey string `json:"thing_key"` - Channels []Channel `json:"channels,omitempty"` - ExternalID string `json:"external_id"` - ExternalKey string `json:"external_key"` - Content string `json:"content,omitempty"` - State State `json:"state"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + DomainID string `json:"domain_id,omitempty"` + Name string `json:"name,omitempty"` + ClientCert string `json:"client_cert,omitempty"` + ClientKey string `json:"client_key,omitempty"` + CACert string `json:"ca_cert,omitempty"` + Channels []Channel `json:"channels,omitempty"` + ExternalID string `json:"external_id"` + ExternalKey string `json:"external_key"` + Content string `json:"content,omitempty"` + State State `json:"state"` } -// Channel represents Magistrala channel corresponding Magistrala Thing is connected to. +// Channel represents Magistrala channel corresponding Magistrala Client is connected to. type Channel struct { ID string `json:"id"` Name string `json:"name,omitempty"` @@ -41,7 +41,7 @@ type Channel struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at,omitempty"` UpdatedBy string `json:"updated_by,omitempty"` - Status things.Status `json:"status"` + Status clients.Status `json:"status"` } // Filter is used for the search filters. @@ -73,7 +73,7 @@ type ConfigRepository interface { // RetrieveAll retrieves a subset of Configs that are owned // by the specific user, with given filter parameters. - RetrieveAll(ctx context.Context, domainID string, thingIDs []string, filter Filter, offset, limit uint64) ConfigsPage + RetrieveAll(ctx context.Context, domainID string, clientIDs []string, filter Filter, offset, limit uint64) ConfigsPage // RetrieveByExternalID returns Config for given external ID. RetrieveByExternalID(ctx context.Context, externalID string) (Config, error) @@ -84,7 +84,7 @@ type ConfigRepository interface { // UpdateCerts updates and returns an existing Config certificate and domainID. // A non-nil error is returned to indicate operation failure. - UpdateCert(ctx context.Context, domainID, thingID, clientCert, clientKey, caCert string) (Config, error) + UpdateCert(ctx context.Context, domainID, clientID, clientCert, clientKey, caCert string) (Config, error) // UpdateConnections updates a list of Channels the Config is connected to // adding new Channels if needed. @@ -100,11 +100,11 @@ type ConfigRepository interface { // ListExisting retrieves those channels from the given list that exist in DB. ListExisting(ctx context.Context, domainID string, ids []string) ([]Channel, error) - // Methods RemoveThing, UpdateChannel, and RemoveChannel are related to + // Methods RemoveClient, UpdateChannel, and RemoveChannel are related to // event sourcing. That's why these methods surpass ownership check. - // RemoveThing removes Config of the Thing with the given ID. - RemoveThing(ctx context.Context, id string) error + // RemoveClient removes Config of the Client with the given ID. + RemoveClient(ctx context.Context, id string) error // UpdateChannel updates channel with the given ID. UpdateChannel(ctx context.Context, c Channel) error @@ -112,9 +112,9 @@ type ConfigRepository interface { // RemoveChannel removes channel with the given ID. RemoveChannel(ctx context.Context, id string) error - // ConnectThing changes state of the Config when the corresponding Thing is connected to the Channel. - ConnectThing(ctx context.Context, channelID, thingID string) error + // ConnectClient changes state of the Config when the corresponding Client is connected to the Channel. + ConnectClient(ctx context.Context, channelID, clientID string) error - // DisconnectThing changes state of the Config when the corresponding Thing is disconnected from the Channel. - DisconnectThing(ctx context.Context, channelID, thingID string) error + // DisconnectClient changes state of the Config when the corresponding Client is disconnected from the Channel. + DisconnectClient(ctx context.Context, channelID, clientID string) error } diff --git a/bootstrap/events/consumer/events.go b/bootstrap/events/consumer/events.go index a3a0599650..71ded144ac 100644 --- a/bootstrap/events/consumer/events.go +++ b/bootstrap/events/consumer/events.go @@ -19,6 +19,6 @@ type updateChannelEvent struct { // Connection event is either connect or disconnect event. type connectionEvent struct { - thingIDs []string + clientIDs []string channelID string } diff --git a/bootstrap/events/consumer/streams.go b/bootstrap/events/consumer/streams.go index 7c0d5bcbfb..d6031dc08c 100644 --- a/bootstrap/events/consumer/streams.go +++ b/bootstrap/events/consumer/streams.go @@ -13,15 +13,15 @@ import ( ) const ( - thingRemove = "thing.remove" - thingConnect = "group.assign" - thingDisconnect = "group.unassign" + clientRemove = "client.remove" + clientConnect = "group.assign" + clientDisconnect = "group.unassign" - channelPrefix = "group." + channelPrefix = "channels." channelUpdate = channelPrefix + "update" channelRemove = channelPrefix + "remove" - memberKind = "things" + memberKind = "client" relation = "group" ) @@ -43,35 +43,35 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error { } switch msg["operation"] { - case thingRemove: - rte := decodeRemoveThing(msg) + case clientRemove: + rte := decodeRemoveClient(msg) err = es.svc.RemoveConfigHandler(ctx, rte.id) - case thingConnect: - cte := decodeConnectThing(msg) - if cte.channelID == "" || len(cte.thingIDs) == 0 { + case clientConnect: + cte := decodeConnectClient(msg) + if cte.channelID == "" || len(cte.clientIDs) == 0 { return svcerr.ErrMalformedEntity } - for _, thingID := range cte.thingIDs { - if thingID == "" { + for _, clientID := range cte.clientIDs { + if clientID == "" { return svcerr.ErrMalformedEntity } - if err := es.svc.ConnectThingHandler(ctx, cte.channelID, thingID); err != nil { + if err := es.svc.ConnectClientHandler(ctx, cte.channelID, clientID); err != nil { return err } } - case thingDisconnect: - dte := decodeDisconnectThing(msg) - if dte.channelID == "" || len(dte.thingIDs) == 0 { + case clientDisconnect: + dte := decodeDisconnectClient(msg) + if dte.channelID == "" || len(dte.clientIDs) == 0 { return svcerr.ErrMalformedEntity } - for _, thingID := range dte.thingIDs { - if thingID == "" { + for _, clientID := range dte.clientIDs { + if clientID == "" { return svcerr.ErrMalformedEntity } } - for _, thingID := range dte.thingIDs { - if err = es.svc.DisconnectThingHandler(ctx, dte.channelID, thingID); err != nil { + for _, c := range dte.clientIDs { + if err = es.svc.DisconnectClientHandler(ctx, dte.channelID, c); err != nil { return err } } @@ -89,7 +89,7 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error { return nil } -func decodeRemoveThing(event map[string]interface{}) removeEvent { +func decodeRemoveClient(event map[string]interface{}) removeEvent { return removeEvent{ id: events.Read(event, "id", ""), } @@ -113,25 +113,25 @@ func decodeRemoveChannel(event map[string]interface{}) removeEvent { } } -func decodeConnectThing(event map[string]interface{}) connectionEvent { +func decodeConnectClient(event map[string]interface{}) connectionEvent { if events.Read(event, "memberKind", "") != memberKind && events.Read(event, "relation", "") != relation { return connectionEvent{} } return connectionEvent{ channelID: events.Read(event, "group_id", ""), - thingIDs: events.ReadStringSlice(event, "member_ids"), + clientIDs: events.ReadStringSlice(event, "member_ids"), } } -func decodeDisconnectThing(event map[string]interface{}) connectionEvent { +func decodeDisconnectClient(event map[string]interface{}) connectionEvent { if events.Read(event, "memberKind", "") != memberKind && events.Read(event, "relation", "") != relation { return connectionEvent{} } return connectionEvent{ channelID: events.Read(event, "group_id", ""), - thingIDs: events.ReadStringSlice(event, "member_ids"), + clientIDs: events.ReadStringSlice(event, "member_ids"), } } diff --git a/bootstrap/events/producer/events.go b/bootstrap/events/producer/events.go index 86f5c43099..0e99259071 100644 --- a/bootstrap/events/producer/events.go +++ b/bootstrap/events/producer/events.go @@ -17,12 +17,12 @@ const ( configList = configPrefix + "list" configHandlerRemove = configPrefix + "remove_handler" - thingPrefix = "bootstrap.thing." - thingBootstrap = thingPrefix + "bootstrap" - thingStateChange = thingPrefix + "change_state" - thingUpdateConnections = thingPrefix + "update_connections" - thingConnect = thingPrefix + "connect" - thingDisconnect = thingPrefix + "disconnect" + clientPrefix = "bootstrap.client." + clientBootstrap = clientPrefix + "bootstrap" + clientStateChange = clientPrefix + "change_state" + clientUpdateConnections = clientPrefix + "update_connections" + clientConnect = clientPrefix + "connect" + clientDisconnect = clientPrefix + "disconnect" channelPrefix = "bootstrap.channel." channelHandlerRemove = channelPrefix + "remove_handler" @@ -52,8 +52,8 @@ func (ce configEvent) Encode() (map[string]interface{}, error) { "state": ce.State.String(), "operation": ce.operation, } - if ce.ThingID != "" { - val["thing_id"] = ce.ThingID + if ce.ClientID != "" { + val["client_id"] = ce.ClientID } if ce.Content != "" { val["content"] = ce.Content @@ -91,12 +91,12 @@ func (ce configEvent) Encode() (map[string]interface{}, error) { } type removeConfigEvent struct { - mgThing string + client string } func (rce removeConfigEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "thing_id": rce.mgThing, + "client_id": rce.client, "operation": configRemove, }, nil } @@ -134,11 +134,11 @@ func (be bootstrapEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ "external_id": be.externalID, "success": be.success, - "operation": thingBootstrap, + "operation": clientBootstrap, } - if be.ThingID != "" { - val["thing_id"] = be.ThingID + if be.ClientID != "" { + val["client_id"] = be.ClientID } if be.Content != "" { val["content"] = be.Content @@ -175,38 +175,41 @@ func (be bootstrapEvent) Encode() (map[string]interface{}, error) { } type changeStateEvent struct { - mgThing string - state bootstrap.State + mgClient string + state bootstrap.State } func (cse changeStateEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "thing_id": cse.mgThing, + "client_id": cse.mgClient, "state": cse.state.String(), - "operation": thingStateChange, + "operation": clientStateChange, }, nil } type updateConnectionsEvent struct { - mgThing string + mgClient string mgChannels []string } func (uce updateConnectionsEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "thing_id": uce.mgThing, + "client_id": uce.mgClient, "channels": uce.mgChannels, - "operation": thingUpdateConnections, + "operation": clientUpdateConnections, }, nil } type updateCertEvent struct { - thingKey, clientCert, clientKey, caCert string + clientID string + clientCert string + clientKey string + caCert string } func (uce updateCertEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "thing_key": uce.thingKey, + "client_id": uce.clientID, "client_cert": uce.clientCert, "client_key": uce.clientKey, "ca_cert": uce.caCert, @@ -247,28 +250,28 @@ func (uche updateChannelHandlerEvent) Encode() (map[string]interface{}, error) { return val, nil } -type connectThingEvent struct { - thingID string +type connectClientEvent struct { + clientID string channelID string } -func (cte connectThingEvent) Encode() (map[string]interface{}, error) { +func (cte connectClientEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "thing_id": cte.thingID, + "client_id": cte.clientID, "channel_id": cte.channelID, - "operation": thingConnect, + "operation": clientConnect, }, nil } -type disconnectThingEvent struct { - thingID string +type disconnectClientEvent struct { + clientID string channelID string } -func (dte disconnectThingEvent) Encode() (map[string]interface{}, error) { +func (dte disconnectClientEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "thing_id": dte.thingID, + "client_id": dte.clientID, "channel_id": dte.channelID, - "operation": thingDisconnect, + "operation": clientDisconnect, }, nil } diff --git a/bootstrap/events/producer/streams.go b/bootstrap/events/producer/streams.go index 6202c168e7..5501c00d72 100644 --- a/bootstrap/events/producer/streams.go +++ b/bootstrap/events/producer/streams.go @@ -72,14 +72,14 @@ func (es *eventStore) Update(ctx context.Context, session mgauthn.Session, cfg b return es.Publish(ctx, ev) } -func (es eventStore) UpdateCert(ctx context.Context, session mgauthn.Session, thingKey, clientCert, clientKey, caCert string) (bootstrap.Config, error) { - cfg, err := es.svc.UpdateCert(ctx, session, thingKey, clientCert, clientKey, caCert) +func (es eventStore) UpdateCert(ctx context.Context, session mgauthn.Session, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) { + cfg, err := es.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert) if err != nil { return cfg, err } ev := updateCertEvent{ - thingKey: thingKey, + clientID: clientID, clientCert: clientCert, clientKey: clientKey, caCert: caCert, @@ -98,7 +98,7 @@ func (es *eventStore) UpdateConnections(ctx context.Context, session mgauthn.Ses } ev := updateConnectionsEvent{ - mgThing: id, + mgClient: id, mgChannels: connections, } @@ -131,7 +131,7 @@ func (es *eventStore) Remove(ctx context.Context, session mgauthn.Session, id st } ev := removeConfigEvent{ - mgThing: id, + client: id, } return es.Publish(ctx, ev) @@ -163,8 +163,8 @@ func (es *eventStore) ChangeState(ctx context.Context, session mgauthn.Session, } ev := changeStateEvent{ - mgThing: id, - state: state, + mgClient: id, + state: state, } return es.Publish(ctx, ev) @@ -208,26 +208,26 @@ func (es *eventStore) UpdateChannelHandler(ctx context.Context, channel bootstra return es.Publish(ctx, ev) } -func (es *eventStore) ConnectThingHandler(ctx context.Context, channelID, thingID string) error { - if err := es.svc.ConnectThingHandler(ctx, channelID, thingID); err != nil { +func (es *eventStore) ConnectClientHandler(ctx context.Context, channelID, clientID string) error { + if err := es.svc.ConnectClientHandler(ctx, channelID, clientID); err != nil { return err } - ev := connectThingEvent{ - thingID: thingID, + ev := connectClientEvent{ + clientID: clientID, channelID: channelID, } return es.Publish(ctx, ev) } -func (es *eventStore) DisconnectThingHandler(ctx context.Context, channelID, thingID string) error { - if err := es.svc.DisconnectThingHandler(ctx, channelID, thingID); err != nil { +func (es *eventStore) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error { + if err := es.svc.DisconnectClientHandler(ctx, channelID, clientID); err != nil { return err } - ev := disconnectThingEvent{ - thingID: thingID, + ev := disconnectClientEvent{ + clientID: clientID, channelID: channelID, } diff --git a/bootstrap/events/producer/streams_test.go b/bootstrap/events/producer/streams_test.go index aa5f1de866..15bb197f9a 100644 --- a/bootstrap/events/producer/streams_test.go +++ b/bootstrap/events/producer/streams_test.go @@ -11,11 +11,11 @@ import ( "testing" "time" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/bootstrap/events/producer" "github.com/absmach/magistrala/bootstrap/mocks" "github.com/absmach/magistrala/internal/testsutil" + "github.com/absmach/magistrala/pkg/authn" mgauthn "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -32,13 +32,13 @@ import ( ) const ( - streamID = "magistrala.bootstrap" - email = "user@example.com" - validToken = "validToken" - invalidToken = "invalid" - unknownThingID = "unknown" - channelsNum = 3 - defaultTimout = 5 + streamID = "magistrala.bootstrap" + email = "user@example.com" + validToken = "validToken" + invalidToken = "invalid" + unknownClientID = "unknown" + channelsNum = 3 + defaultTimout = 5 configPrefix = "config." configCreate = configPrefix + "create" @@ -48,12 +48,12 @@ const ( configList = configPrefix + "list" configHandlerRemove = configPrefix + "remove_handler" - thingPrefix = "thing." - thingBootstrap = thingPrefix + "bootstrap" - thingStateChange = thingPrefix + "change_state" - thingUpdateConnections = thingPrefix + "update_connections" - thingConnect = thingPrefix + "connect" - thingDisconnect = thingPrefix + "disconnect" + clientPrefix = "client." + clientBootstrap = clientPrefix + "bootstrap" + clientStateChange = clientPrefix + "change_state" + clientUpdateConnections = clientPrefix + "update_connections" + clientConnect = clientPrefix + "connect" + clientDisconnect = clientPrefix + "disconnect" channelPrefix = "group." channelHandlerRemove = channelPrefix + "remove_handler" @@ -76,12 +76,12 @@ var ( } config = bootstrap.Config{ - ThingID: testsutil.GenerateUUID(&testing.T{}), - ThingKey: testsutil.GenerateUUID(&testing.T{}), - ExternalID: testsutil.GenerateUUID(&testing.T{}), - ExternalKey: testsutil.GenerateUUID(&testing.T{}), - Channels: []bootstrap.Channel{channel}, - Content: "config", + ClientID: testsutil.GenerateUUID(&testing.T{}), + ClientSecret: testsutil.GenerateUUID(&testing.T{}), + ExternalID: testsutil.GenerateUUID(&testing.T{}), + ExternalKey: testsutil.GenerateUUID(&testing.T{}), + Channels: []bootstrap.Channel{channel}, + Content: "config", } ) @@ -125,18 +125,18 @@ func TestAdd(t *testing.T) { invalidConfig.Channels = []bootstrap.Channel{{ID: "empty"}} cases := []struct { - desc string - config bootstrap.Config - token string - session mgauthn.Session - id string - domainID string - thingErr error - channel []bootstrap.Channel - listErr error - saveErr error - err error - event map[string]interface{} + desc string + config bootstrap.Config + token string + session mgauthn.Session + id string + domainID string + clientErr error + channel []bootstrap.Channel + listErr error + saveErr error + err error + event map[string]interface{} }{ { desc: "create config successfully", @@ -146,7 +146,7 @@ func TestAdd(t *testing.T) { domainID: domainID, channel: config.Channels, event: map[string]interface{}{ - "thing_id": "1", + "client_id": "1", "domain_id": domainID, "name": config.Name, "channels": channels, @@ -158,14 +158,14 @@ func TestAdd(t *testing.T) { err: nil, }, { - desc: "create config with failed to fetch thing", - config: config, - token: validToken, - id: validID, - domainID: domainID, - event: nil, - thingErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, + desc: "create config with failed to fetch client", + config: config, + token: validToken, + id: validID, + domainID: domainID, + event: nil, + clientErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, }, { desc: "create config with failed to list existing", @@ -192,7 +192,7 @@ func TestAdd(t *testing.T) { lastID := "0" for _, tc := range cases { tc.session = mgauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} - sdkCall := tv.sdk.On("Thing", tc.config.ThingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.config.ThingID, Credentials: mgsdk.ClientCredentials{Secret: tc.config.ThingKey}}, errors.NewSDKError(tc.thingErr)) + sdkCall := tv.sdk.On("Client", tc.config.ClientID, tc.domainID, tc.token).Return(mgsdk.Client{ID: tc.config.ClientID, Credentials: mgsdk.ClientCredentials{Secret: tc.config.ClientSecret}}, errors.NewSDKError(tc.clientErr)) repoCall := tv.boot.On("ListExisting", context.Background(), domainID, mock.Anything).Return(tc.config.Channels, tc.listErr) repoCall1 := tv.boot.On("Save", context.Background(), mock.Anything, mock.Anything).Return(mock.Anything, tc.saveErr) @@ -226,7 +226,7 @@ func TestView(t *testing.T) { tv := newTestVariable(t, redisURL) nonExisting := config - nonExisting.ThingID = unknownThingID + nonExisting.ClientID = unknownClientID cases := []struct { desc string @@ -247,7 +247,7 @@ func TestView(t *testing.T) { domainID: domainID, err: nil, event: map[string]interface{}{ - "thing_id": config.ThingID, + "client_id": config.ClientID, "domain_id": config.DomainID, "name": config.Name, "channels": config.Channels, @@ -272,8 +272,8 @@ func TestView(t *testing.T) { lastID := "0" for _, tc := range cases { tc.session = mgauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} - repoCall := tv.boot.On("RetrieveByID", context.Background(), tc.domainID, tc.config.ThingID).Return(config, tc.retrieveErr) - _, err := tv.svc.View(context.Background(), tc.session, tc.config.ThingID) + repoCall := tv.boot.On("RetrieveByID", context.Background(), tc.domainID, tc.config.ClientID).Return(config, tc.retrieveErr) + _, err := tv.svc.View(context.Background(), tc.session, tc.config.ClientID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) streams := redisClient.XRead(context.Background(), &redis.XReadArgs{ @@ -316,7 +316,7 @@ func TestUpdate(t *testing.T) { modified.Name = "new name" nonExisting := config - nonExisting.ThingID = unknownThingID + nonExisting.ClientID = unknownClientID channels := []string{modified.Channels[0].ID, modified.Channels[1].ID} @@ -345,7 +345,7 @@ func TestUpdate(t *testing.T) { "operation": configUpdate, "channels": channels, "external_id": modified.ExternalID, - "thing_id": modified.ThingID, + "client_id": modified.ClientID, "domain_id": domainID, "state": "0", "occurred_at": time.Now().UnixNano(), @@ -403,7 +403,7 @@ func TestUpdateConnections(t *testing.T) { token string session mgauthn.Session connections []string - thingErr error + clientErr error channelErr error retrieveErr error listErr error @@ -413,22 +413,22 @@ func TestUpdateConnections(t *testing.T) { }{ { desc: "update connections successfully", - configID: config.ThingID, + configID: config.ClientID, token: validToken, id: validID, domainID: domainID, connections: []string{config.Channels[0].ID}, err: nil, event: map[string]interface{}{ - "thing_id": config.ThingID, + "client_id": config.ClientID, "channels": "2", "timestamp": time.Now().Unix(), - "operation": thingUpdateConnections, + "operation": clientUpdateConnections, }, }, { desc: "update connections with failed channel fetch", - configID: config.ThingID, + configID: config.ClientID, token: validToken, id: validID, domainID: domainID, @@ -439,7 +439,7 @@ func TestUpdateConnections(t *testing.T) { }, { desc: "update connections with failed RetrieveByID", - configID: config.ThingID, + configID: config.ClientID, token: validToken, id: validID, domainID: domainID, @@ -450,7 +450,7 @@ func TestUpdateConnections(t *testing.T) { }, { desc: "update connections with failed ListExisting", - configID: config.ThingID, + configID: config.ClientID, token: validToken, id: validID, domainID: domainID, @@ -461,7 +461,7 @@ func TestUpdateConnections(t *testing.T) { }, { desc: "update connections with failed UpdateConnections", - configID: config.ThingID, + configID: config.ClientID, token: validToken, id: validID, domainID: domainID, @@ -524,7 +524,7 @@ func TestUpdateCert(t *testing.T) { }{ { desc: "update cert successfully", - configID: config.ThingID, + configID: config.ClientID, userID: validID, domainID: domainID, token: validToken, @@ -533,16 +533,16 @@ func TestUpdateCert(t *testing.T) { caCert: "caCert", err: nil, event: map[string]interface{}{ - "thing_key": config.ThingKey, - "client_cert": "clientCert", - "client_key": "clientKey", - "ca_cert": "caCert", - "operation": certUpdate, + "client_secret": config.ClientSecret, + "client_cert": "clientCert", + "client_key": "clientKey", + "ca_cert": "caCert", + "operation": certUpdate, }, }, { desc: "update cert with failed update", - configID: "invalidThingID", + configID: "clientID", token: validToken, userID: validID, domainID: domainID, @@ -555,7 +555,7 @@ func TestUpdateCert(t *testing.T) { }, { desc: "update cert with empty client certificate", - configID: config.ThingID, + configID: config.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -567,7 +567,7 @@ func TestUpdateCert(t *testing.T) { }, { desc: "update cert with empty client key", - configID: config.ThingID, + configID: config.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -579,7 +579,7 @@ func TestUpdateCert(t *testing.T) { }, { desc: "update cert with empty CA certificate", - configID: config.ThingID, + configID: config.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -591,7 +591,7 @@ func TestUpdateCert(t *testing.T) { }, { desc: "successful update without CA certificate", - configID: config.ThingID, + configID: config.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -600,12 +600,12 @@ func TestUpdateCert(t *testing.T) { caCert: "", err: nil, event: map[string]interface{}{ - "thing_key": config.ThingKey, - "client_cert": "clientCert", - "client_key": "clientKey", - "ca_cert": "caCert", - "operation": certUpdate, - "timestamp": time.Now().Unix(), + "client_secret": config.ClientSecret, + "client_cert": "clientCert", + "client_key": "clientKey", + "ca_cert": "caCert", + "operation": certUpdate, + "timestamp": time.Now().Unix(), }, }, } @@ -639,10 +639,10 @@ func TestUpdateCert(t *testing.T) { func TestList(t *testing.T) { tv := newTestVariable(t, redisURL) - numThings := 101 + numClients := 101 var c bootstrap.Config saved := make([]bootstrap.Config, 0) - for i := 0; i < numThings; i++ { + for i := 0; i < numClients; i++ { c := config c.ExternalID = testsutil.GenerateUUID(t) c.ExternalKey = testsutil.GenerateUUID(t) @@ -687,7 +687,7 @@ func TestList(t *testing.T) { listObjectsResponse: policysvc.PolicyPage{}, err: nil, event: map[string]interface{}{ - "thing_id": c.ThingID, + "client_id": c.ClientID, "domain_id": c.DomainID, "name": c.Name, "channels": c.Channels, @@ -715,7 +715,7 @@ func TestList(t *testing.T) { listObjectsResponse: policysvc.PolicyPage{}, err: nil, event: map[string]interface{}{ - "thing_id": c.ThingID, + "client_id": c.ClientID, "domain_id": c.DomainID, "name": c.Name, "channels": c.Channels, @@ -743,7 +743,7 @@ func TestList(t *testing.T) { listObjectsResponse: policysvc.PolicyPage{}, err: nil, event: map[string]interface{}{ - "thing_id": c.ThingID, + "client_id": c.ClientID, "domain_id": c.DomainID, "name": c.Name, "channels": c.Channels, @@ -818,7 +818,7 @@ func TestList(t *testing.T) { SubjectType: policysvc.UserType, Subject: tc.userID, Permission: policysvc.ViewPermission, - ObjectType: policysvc.ThingType, + ObjectType: policysvc.ClientType, }).Return(tc.listObjectsResponse, tc.listObjectsErr) repoCall := tv.boot.On("RetrieveAll", context.Background(), mock.Anything, mock.Anything, tc.filter, tc.offset, tc.limit).Return(tc.config, tc.retrieveErr) @@ -851,7 +851,7 @@ func TestRemove(t *testing.T) { tv := newTestVariable(t, redisURL) nonExisting := config - nonExisting.ThingID = unknownThingID + nonExisting.ClientID = unknownClientID cases := []struct { desc string @@ -866,20 +866,20 @@ func TestRemove(t *testing.T) { }{ { desc: "remove config successfully", - configID: config.ThingID, + configID: config.ClientID, token: validToken, userID: validID, domainID: domainID, err: nil, event: map[string]interface{}{ - "thing_id": config.ThingID, + "client_id": config.ClientID, "timestamp": time.Now().Unix(), "operation": configRemove, }, }, { desc: "remove config with failed removal", - configID: nonExisting.ThingID, + configID: nonExisting.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -936,7 +936,7 @@ func TestBootstrap(t *testing.T) { "external_id": config.ExternalID, "success": "1", "timestamp": time.Now().Unix(), - "operation": thingBootstrap, + "operation": clientBootstrap, }, }, { @@ -949,7 +949,7 @@ func TestBootstrap(t *testing.T) { "external_id": "external_id", "success": "0", "timestamp": time.Now().Unix(), - "operation": thingBootstrap, + "operation": clientBootstrap, }, }, } @@ -990,7 +990,7 @@ func TestChangeState(t *testing.T) { token string session mgauthn.Session state bootstrap.State - authResponse *magistrala.AuthZRes + authResponse authn.Session authorizeErr error connectErr error retrieveErr error @@ -1001,18 +1001,18 @@ func TestChangeState(t *testing.T) { }{ { desc: "change state to active", - id: config.ThingID, + id: config.ClientID, token: validToken, userID: validID, domainID: domainID, state: bootstrap.Active, - authResponse: &magistrala.AuthZRes{Authorized: true}, + authResponse: authn.Session{}, err: nil, event: map[string]interface{}{ - "thing_id": config.ThingID, + "client_id": config.ClientID, "state": bootstrap.Active.String(), "timestamp": time.Now().Unix(), - "operation": thingStateChange, + "operation": clientStateChange, }, }, { @@ -1028,18 +1028,18 @@ func TestChangeState(t *testing.T) { }, { desc: "change state with failed connect", - id: config.ThingID, + id: config.ClientID, token: validToken, userID: validID, domainID: domainID, state: bootstrap.Active, - connectErr: bootstrap.ErrThings, - err: bootstrap.ErrThings, + connectErr: bootstrap.ErrClients, + err: bootstrap.ErrClients, event: nil, }, { desc: "change state unsuccessfully", - id: config.ThingID, + id: config.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -1261,7 +1261,7 @@ func TestRemoveConfigHandler(t *testing.T) { lastID := "0" for _, tc := range cases { - repoCall := tv.boot.On("RemoveThing", context.Background(), mock.Anything).Return(tc.err) + repoCall := tv.boot.On("RemoveClient", context.Background(), mock.Anything).Return(tc.err) err := tv.svc.RemoveConfigHandler(context.Background(), tc.configID) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -1284,7 +1284,7 @@ func TestRemoveConfigHandler(t *testing.T) { } } -func TestConnectThingHandler(t *testing.T) { +func TestConnectClientHandler(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) @@ -1293,41 +1293,41 @@ func TestConnectThingHandler(t *testing.T) { cases := []struct { desc string channelID string - thingID string + clientID string err error event map[string]interface{} }{ { - desc: "connect thing handler successfully", + desc: "connect client handler successfully", channelID: channel.ID, - thingID: "1", + clientID: "1", err: nil, event: map[string]interface{}{ "channel_id": channel.ID, - "thing_id": "1", - "operation": thingConnect, + "client_id": "1", + "operation": clientConnect, "timestamp": time.Now().UnixNano(), "occurred_at": time.Now().UnixNano(), }, }, { - desc: "connect non-existing thing handler", + desc: "connect non-existing client handler", channelID: channel.ID, - thingID: "unknown", + clientID: "unknown", err: nil, event: nil, }, { - desc: "connect thing handler with empty thing ID", + desc: "connect client handler with empty client ID", channelID: channel.ID, - thingID: "", + clientID: "", err: nil, event: nil, }, { - desc: "connect thing handler with empty channel ID", + desc: "connect client handler with empty channel ID", channelID: "", - thingID: "1", + clientID: "1", err: nil, event: nil, }, @@ -1335,8 +1335,8 @@ func TestConnectThingHandler(t *testing.T) { lastID := "0" for _, tc := range cases { - repoCall := tv.boot.On("ConnectThing", context.Background(), mock.Anything, mock.Anything).Return(tc.err) - err := tv.svc.ConnectThingHandler(context.Background(), tc.channelID, tc.thingID) + repoCall := tv.boot.On("ConnectClient", context.Background(), mock.Anything, mock.Anything).Return(tc.err) + err := tv.svc.ConnectClientHandler(context.Background(), tc.channelID, tc.clientID) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) streams := redisClient.XRead(context.Background(), &redis.XReadArgs{ @@ -1358,7 +1358,7 @@ func TestConnectThingHandler(t *testing.T) { } } -func TestDisconnectThingHandler(t *testing.T) { +func TestDisconnectClientHandler(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) @@ -1367,50 +1367,50 @@ func TestDisconnectThingHandler(t *testing.T) { cases := []struct { desc string channelID string - thingID string + clientID string err error event map[string]interface{} }{ { - desc: "disconnect thing handler successfully", + desc: "disconnect client handler successfully", channelID: channel.ID, - thingID: "1", + clientID: "1", err: nil, event: map[string]interface{}{ "channel_id": channel.ID, - "thing_id": "1", - "operation": thingDisconnect, + "client_id": "1", + "operation": clientDisconnect, "timestamp": time.Now().UnixNano(), "occurred_at": time.Now().UnixNano(), }, }, { - desc: "remove non-existing thing handler", + desc: "remove non-existing client handler", channelID: "unknown", err: nil, }, { - desc: "remove thing handler with empty thing ID", + desc: "remove client handler with empty client ID", channelID: channel.ID, - thingID: "", + clientID: "", err: nil, event: nil, }, { - desc: "remove thing handler with empty channel ID", + desc: "remove client handler with empty channel ID", channelID: "", err: nil, event: nil, }, { - desc: "remove thing handler successfully", + desc: "remove client handler successfully", channelID: channel.ID, - thingID: "1", + clientID: "1", err: nil, event: map[string]interface{}{ "channel_id": channel.ID, - "thing_id": "1", - "operation": thingDisconnect, + "client_id": "1", + "operation": clientDisconnect, "timestamp": time.Now().UnixNano(), "occurred_at": time.Now().UnixNano(), }, @@ -1419,8 +1419,8 @@ func TestDisconnectThingHandler(t *testing.T) { lastID := "0" for _, tc := range cases { - repoCall := tv.boot.On("DisconnectThing", context.Background(), tc.channelID, tc.thingID).Return(tc.err) - err := tv.svc.DisconnectThingHandler(context.Background(), tc.channelID, tc.thingID) + repoCall := tv.boot.On("DisconnectClient", context.Background(), tc.channelID, tc.clientID).Return(tc.err) + err := tv.svc.DisconnectClientHandler(context.Background(), tc.channelID, tc.clientID) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) streams := redisClient.XRead(context.Background(), &redis.XReadArgs{ diff --git a/bootstrap/middleware/authorization.go b/bootstrap/middleware/authorization.go index cc14e55a1a..8207477d12 100644 --- a/bootstrap/middleware/authorization.go +++ b/bootstrap/middleware/authorization.go @@ -37,7 +37,7 @@ func (am *authorizationMiddleware) Add(ctx context.Context, session mgauthn.Sess } func (am *authorizationMiddleware) View(ctx context.Context, session mgauthn.Session, id string) (bootstrap.Config, error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.ViewPermission, policies.ThingType, id); err != nil { + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.ViewPermission, policies.ClientType, id); err != nil { return bootstrap.Config{}, err } @@ -45,23 +45,23 @@ func (am *authorizationMiddleware) View(ctx context.Context, session mgauthn.Ses } func (am *authorizationMiddleware) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) error { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ThingType, cfg.ThingID); err != nil { + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ClientType, cfg.ClientID); err != nil { return err } return am.svc.Update(ctx, session, cfg) } -func (am *authorizationMiddleware) UpdateCert(ctx context.Context, session mgauthn.Session, thingID, clientCert, clientKey, caCert string) (bootstrap.Config, error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ThingType, thingID); err != nil { +func (am *authorizationMiddleware) UpdateCert(ctx context.Context, session mgauthn.Session, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) { + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ClientType, clientID); err != nil { return bootstrap.Config{}, err } - return am.svc.UpdateCert(ctx, session, thingID, clientCert, clientKey, caCert) + return am.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert) } func (am *authorizationMiddleware) UpdateConnections(ctx context.Context, session mgauthn.Session, token, id string, connections []string) error { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ThingType, id); err != nil { + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ClientType, id); err != nil { return err } @@ -80,7 +80,7 @@ func (am *authorizationMiddleware) List(ctx context.Context, session mgauthn.Ses } func (am *authorizationMiddleware) Remove(ctx context.Context, session mgauthn.Session, id string) error { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.ThingType, id); err != nil { + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.ClientType, id); err != nil { return err } @@ -107,12 +107,12 @@ func (am *authorizationMiddleware) RemoveChannelHandler(ctx context.Context, id return am.svc.RemoveChannelHandler(ctx, id) } -func (am *authorizationMiddleware) ConnectThingHandler(ctx context.Context, channelID, ThingID string) error { - return am.svc.ConnectThingHandler(ctx, channelID, ThingID) +func (am *authorizationMiddleware) ConnectClientHandler(ctx context.Context, channelID, clientID string) error { + return am.svc.ConnectClientHandler(ctx, channelID, clientID) } -func (am *authorizationMiddleware) DisconnectThingHandler(ctx context.Context, channelID, ThingID string) error { - return am.svc.DisconnectThingHandler(ctx, channelID, ThingID) +func (am *authorizationMiddleware) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error { + return am.svc.DisconnectClientHandler(ctx, channelID, clientID) } func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID string) error { diff --git a/bootstrap/middleware/logging.go b/bootstrap/middleware/logging.go index 362920d84e..36edba6723 100644 --- a/bootstrap/middleware/logging.go +++ b/bootstrap/middleware/logging.go @@ -26,13 +26,13 @@ func LoggingMiddleware(svc bootstrap.Service, logger *slog.Logger) bootstrap.Ser return &loggingMiddleware{logger, svc} } -// Add logs the add request. It logs the thing ID and the time it took to complete the request. +// Add logs the add request. It logs the client ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (saved bootstrap.Config, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", saved.ThingID), + slog.String("client_id", saved.ClientID), } if err != nil { args = append(args, slog.Any("error", err)) @@ -45,33 +45,33 @@ func (lm *loggingMiddleware) Add(ctx context.Context, session mgauthn.Session, t return lm.svc.Add(ctx, session, token, cfg) } -// View logs the view request. It logs the thing ID and the time it took to complete the request. +// View logs the view request. It logs the client ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) View(ctx context.Context, session mgauthn.Session, id string) (saved bootstrap.Config, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", id), + slog.String("client_id", id), } if err != nil { args = append(args, slog.Any("error", err)) - lm.logger.Warn("View thing config failed", args...) + lm.logger.Warn("View client config failed", args...) return } - lm.logger.Info("View thing config completed successfully", args...) + lm.logger.Info("View client config completed successfully", args...) }(time.Now()) return lm.svc.View(ctx, session, id) } -// Update logs the update request. It logs bootstrap thing ID and the time it took to complete the request. +// Update logs the update request. It logs bootstrap client ID and the time it took to complete the request. // If the request fails, it logs the error. func (lm *loggingMiddleware) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.Group("config", - slog.String("thing_id", cfg.ThingID), + slog.String("client_id", cfg.ClientID), slog.String("name", cfg.Name), ), } @@ -86,13 +86,13 @@ func (lm *loggingMiddleware) Update(ctx context.Context, session mgauthn.Session return lm.svc.Update(ctx, session, cfg) } -// UpdateCert logs the update_cert request. It logs thing ID and the time it took to complete the request. +// UpdateCert logs the update_cert request. It logs client ID and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateCert(ctx context.Context, session mgauthn.Session, thingID, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) { +func (lm *loggingMiddleware) UpdateCert(ctx context.Context, session mgauthn.Session, clientID, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", cfg.ThingID), + slog.String("client_id", cfg.ClientID), } if err != nil { args = append(args, slog.Any("error", err)) @@ -102,7 +102,7 @@ func (lm *loggingMiddleware) UpdateCert(ctx context.Context, session mgauthn.Ses lm.logger.Info("Update bootstrap config certificate completed successfully", args...) }(time.Now()) - return lm.svc.UpdateCert(ctx, session, thingID, clientCert, clientKey, caCert) + return lm.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert) } // UpdateConnections logs the update_connections request. It logs bootstrap ID and the time it took to complete the request. @@ -111,7 +111,7 @@ func (lm *loggingMiddleware) UpdateConnections(ctx context.Context, session mgau defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", id), + slog.String("client_id", id), slog.Any("connections", connections), } if err != nil { @@ -155,7 +155,7 @@ func (lm *loggingMiddleware) Remove(ctx context.Context, session mgauthn.Session defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", id), + slog.String("client_id", id), } if err != nil { args = append(args, slog.Any("error", err)) @@ -194,10 +194,10 @@ func (lm *loggingMiddleware) ChangeState(ctx context.Context, session mgauthn.Se } if err != nil { args = append(args, slog.Any("error", err)) - lm.logger.Warn("Change thing state failed", args...) + lm.logger.Warn("Change client state failed", args...) return } - lm.logger.Info("Change thing state completed successfully", args...) + lm.logger.Info("Change client state completed successfully", args...) }(time.Now()) return lm.svc.ChangeState(ctx, session, token, id, state) @@ -258,38 +258,38 @@ func (lm *loggingMiddleware) RemoveChannelHandler(ctx context.Context, id string return lm.svc.RemoveChannelHandler(ctx, id) } -func (lm *loggingMiddleware) ConnectThingHandler(ctx context.Context, channelID, thingID string) (err error) { +func (lm *loggingMiddleware) ConnectClientHandler(ctx context.Context, channelID, clientID string) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.String("channel_id", channelID), - slog.String("thing_id", thingID), + slog.String("client_id", clientID), } if err != nil { args = append(args, slog.Any("error", err)) - lm.logger.Warn("Connect thing handler failed", args...) + lm.logger.Warn("Connect client handler failed", args...) return } - lm.logger.Info("Connect thing handler completed successfully", args...) + lm.logger.Info("Connect client handler completed successfully", args...) }(time.Now()) - return lm.svc.ConnectThingHandler(ctx, channelID, thingID) + return lm.svc.ConnectClientHandler(ctx, channelID, clientID) } -func (lm *loggingMiddleware) DisconnectThingHandler(ctx context.Context, channelID, thingID string) (err error) { +func (lm *loggingMiddleware) DisconnectClientHandler(ctx context.Context, channelID, clientID string) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.String("channel_id", channelID), - slog.String("thing_id", thingID), + slog.String("client_id", clientID), } if err != nil { args = append(args, slog.Any("error", err)) - lm.logger.Warn("Disconnect thing handler failed", args...) + lm.logger.Warn("Disconnect client handler failed", args...) return } - lm.logger.Info("Disconnect thing handler completed successfully", args...) + lm.logger.Info("Disconnect client handler completed successfully", args...) }(time.Now()) - return lm.svc.DisconnectThingHandler(ctx, channelID, thingID) + return lm.svc.DisconnectClientHandler(ctx, channelID, clientID) } diff --git a/bootstrap/middleware/metrics.go b/bootstrap/middleware/metrics.go index cd95e4e6c4..47c8f4422c 100644 --- a/bootstrap/middleware/metrics.go +++ b/bootstrap/middleware/metrics.go @@ -62,13 +62,13 @@ func (mm *metricsMiddleware) Update(ctx context.Context, session mgauthn.Session } // UpdateCert instruments UpdateCert method with metrics. -func (mm *metricsMiddleware) UpdateCert(ctx context.Context, session mgauthn.Session, thingKey, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) { +func (mm *metricsMiddleware) UpdateCert(ctx context.Context, session mgauthn.Session, clientID, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) { defer func(begin time.Time) { mm.counter.With("method", "update_cert").Add(1) mm.latency.With("method", "update_cert").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.UpdateCert(ctx, session, thingKey, clientCert, clientKey, caCert) + return mm.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert) } // UpdateConnections instruments UpdateConnections method with metrics. @@ -151,22 +151,22 @@ func (mm *metricsMiddleware) RemoveChannelHandler(ctx context.Context, id string return mm.svc.RemoveChannelHandler(ctx, id) } -// ConnectThingHandler instruments ConnectThingHandler method with metrics. -func (mm *metricsMiddleware) ConnectThingHandler(ctx context.Context, channelID, thingID string) (err error) { +// ConnectClientHandler instruments ConnectClientHandler method with metrics. +func (mm *metricsMiddleware) ConnectClientHandler(ctx context.Context, channelID, clientID string) (err error) { defer func(begin time.Time) { - mm.counter.With("method", "connect_thing_handler").Add(1) - mm.latency.With("method", "connect_thing_handler").Observe(time.Since(begin).Seconds()) + mm.counter.With("method", "connect_client_handler").Add(1) + mm.latency.With("method", "connect_client_handler").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.ConnectThingHandler(ctx, channelID, thingID) + return mm.svc.ConnectClientHandler(ctx, channelID, clientID) } -// DisconnectThingHandler instruments DisconnectThingHandler method with metrics. -func (mm *metricsMiddleware) DisconnectThingHandler(ctx context.Context, channelID, thingID string) (err error) { +// DisconnectClientHandler instruments DisconnectClientHandler method with metrics. +func (mm *metricsMiddleware) DisconnectClientHandler(ctx context.Context, channelID, clientID string) (err error) { defer func(begin time.Time) { - mm.counter.With("method", "disconnect_thing_handler").Add(1) - mm.latency.With("method", "disconnect_thing_handler").Observe(time.Since(begin).Seconds()) + mm.counter.With("method", "disconnect_client_handler").Add(1) + mm.latency.With("method", "disconnect_client_handler").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.DisconnectThingHandler(ctx, channelID, thingID) + return mm.svc.DisconnectClientHandler(ctx, channelID, clientID) } diff --git a/bootstrap/mocks/configs.go b/bootstrap/mocks/configs.go index d088cb1356..679460c940 100644 --- a/bootstrap/mocks/configs.go +++ b/bootstrap/mocks/configs.go @@ -35,17 +35,17 @@ func (_m *ConfigRepository) ChangeState(ctx context.Context, domainID string, id return r0 } -// ConnectThing provides a mock function with given fields: ctx, channelID, thingID -func (_m *ConfigRepository) ConnectThing(ctx context.Context, channelID string, thingID string) error { - ret := _m.Called(ctx, channelID, thingID) +// ConnectClient provides a mock function with given fields: ctx, channelID, clientID +func (_m *ConfigRepository) ConnectClient(ctx context.Context, channelID string, clientID string) error { + ret := _m.Called(ctx, channelID, clientID) if len(ret) == 0 { - panic("no return value specified for ConnectThing") + panic("no return value specified for ConnectClient") } var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, channelID, thingID) + r0 = rf(ctx, channelID, clientID) } else { r0 = ret.Error(0) } @@ -53,17 +53,17 @@ func (_m *ConfigRepository) ConnectThing(ctx context.Context, channelID string, return r0 } -// DisconnectThing provides a mock function with given fields: ctx, channelID, thingID -func (_m *ConfigRepository) DisconnectThing(ctx context.Context, channelID string, thingID string) error { - ret := _m.Called(ctx, channelID, thingID) +// DisconnectClient provides a mock function with given fields: ctx, channelID, clientID +func (_m *ConfigRepository) DisconnectClient(ctx context.Context, channelID string, clientID string) error { + ret := _m.Called(ctx, channelID, clientID) if len(ret) == 0 { - panic("no return value specified for DisconnectThing") + panic("no return value specified for DisconnectClient") } var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, channelID, thingID) + r0 = rf(ctx, channelID, clientID) } else { r0 = ret.Error(0) } @@ -137,12 +137,12 @@ func (_m *ConfigRepository) RemoveChannel(ctx context.Context, id string) error return r0 } -// RemoveThing provides a mock function with given fields: ctx, id -func (_m *ConfigRepository) RemoveThing(ctx context.Context, id string) error { +// RemoveClient provides a mock function with given fields: ctx, id +func (_m *ConfigRepository) RemoveClient(ctx context.Context, id string) error { ret := _m.Called(ctx, id) if len(ret) == 0 { - panic("no return value specified for RemoveThing") + panic("no return value specified for RemoveClient") } var r0 error @@ -155,9 +155,9 @@ func (_m *ConfigRepository) RemoveThing(ctx context.Context, id string) error { return r0 } -// RetrieveAll provides a mock function with given fields: ctx, domainID, thingIDs, filter, offset, limit -func (_m *ConfigRepository) RetrieveAll(ctx context.Context, domainID string, thingIDs []string, filter bootstrap.Filter, offset uint64, limit uint64) bootstrap.ConfigsPage { - ret := _m.Called(ctx, domainID, thingIDs, filter, offset, limit) +// RetrieveAll provides a mock function with given fields: ctx, domainID, clientIDs, filter, offset, limit +func (_m *ConfigRepository) RetrieveAll(ctx context.Context, domainID string, clientIDs []string, filter bootstrap.Filter, offset uint64, limit uint64) bootstrap.ConfigsPage { + ret := _m.Called(ctx, domainID, clientIDs, filter, offset, limit) if len(ret) == 0 { panic("no return value specified for RetrieveAll") @@ -165,7 +165,7 @@ func (_m *ConfigRepository) RetrieveAll(ctx context.Context, domainID string, th var r0 bootstrap.ConfigsPage if rf, ok := ret.Get(0).(func(context.Context, string, []string, bootstrap.Filter, uint64, uint64) bootstrap.ConfigsPage); ok { - r0 = rf(ctx, domainID, thingIDs, filter, offset, limit) + r0 = rf(ctx, domainID, clientIDs, filter, offset, limit) } else { r0 = ret.Get(0).(bootstrap.ConfigsPage) } @@ -275,9 +275,9 @@ func (_m *ConfigRepository) Update(ctx context.Context, cfg bootstrap.Config) er return r0 } -// UpdateCert provides a mock function with given fields: ctx, domainID, thingID, clientCert, clientKey, caCert -func (_m *ConfigRepository) UpdateCert(ctx context.Context, domainID string, thingID string, clientCert string, clientKey string, caCert string) (bootstrap.Config, error) { - ret := _m.Called(ctx, domainID, thingID, clientCert, clientKey, caCert) +// UpdateCert provides a mock function with given fields: ctx, domainID, clientID, clientCert, clientKey, caCert +func (_m *ConfigRepository) UpdateCert(ctx context.Context, domainID string, clientID string, clientCert string, clientKey string, caCert string) (bootstrap.Config, error) { + ret := _m.Called(ctx, domainID, clientID, clientCert, clientKey, caCert) if len(ret) == 0 { panic("no return value specified for UpdateCert") @@ -286,16 +286,16 @@ func (_m *ConfigRepository) UpdateCert(ctx context.Context, domainID string, thi var r0 bootstrap.Config var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string) (bootstrap.Config, error)); ok { - return rf(ctx, domainID, thingID, clientCert, clientKey, caCert) + return rf(ctx, domainID, clientID, clientCert, clientKey, caCert) } if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string) bootstrap.Config); ok { - r0 = rf(ctx, domainID, thingID, clientCert, clientKey, caCert) + r0 = rf(ctx, domainID, clientID, clientCert, clientKey, caCert) } else { r0 = ret.Get(0).(bootstrap.Config) } if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, string) error); ok { - r1 = rf(ctx, domainID, thingID, clientCert, clientKey, caCert) + r1 = rf(ctx, domainID, clientID, clientCert, clientKey, caCert) } else { r1 = ret.Error(1) } diff --git a/bootstrap/mocks/service.go b/bootstrap/mocks/service.go index 851e6ef160..52ca35b98d 100644 --- a/bootstrap/mocks/service.go +++ b/bootstrap/mocks/service.go @@ -92,17 +92,17 @@ func (_m *Service) ChangeState(ctx context.Context, session authn.Session, token return r0 } -// ConnectThingHandler provides a mock function with given fields: ctx, channelID, ThingID -func (_m *Service) ConnectThingHandler(ctx context.Context, channelID string, ThingID string) error { - ret := _m.Called(ctx, channelID, ThingID) +// ConnectClientHandler provides a mock function with given fields: ctx, channelID, clientID +func (_m *Service) ConnectClientHandler(ctx context.Context, channelID string, clientID string) error { + ret := _m.Called(ctx, channelID, clientID) if len(ret) == 0 { - panic("no return value specified for ConnectThingHandler") + panic("no return value specified for ConnectClientHandler") } var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, channelID, ThingID) + r0 = rf(ctx, channelID, clientID) } else { r0 = ret.Error(0) } @@ -110,17 +110,17 @@ func (_m *Service) ConnectThingHandler(ctx context.Context, channelID string, Th return r0 } -// DisconnectThingHandler provides a mock function with given fields: ctx, channelID, ThingID -func (_m *Service) DisconnectThingHandler(ctx context.Context, channelID string, ThingID string) error { - ret := _m.Called(ctx, channelID, ThingID) +// DisconnectClientHandler provides a mock function with given fields: ctx, channelID, clientID +func (_m *Service) DisconnectClientHandler(ctx context.Context, channelID string, clientID string) error { + ret := _m.Called(ctx, channelID, clientID) if len(ret) == 0 { - panic("no return value specified for DisconnectThingHandler") + panic("no return value specified for DisconnectClientHandler") } var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, channelID, ThingID) + r0 = rf(ctx, channelID, clientID) } else { r0 = ret.Error(0) } @@ -228,9 +228,9 @@ func (_m *Service) Update(ctx context.Context, session authn.Session, cfg bootst return r0 } -// UpdateCert provides a mock function with given fields: ctx, session, thingID, clientCert, clientKey, caCert -func (_m *Service) UpdateCert(ctx context.Context, session authn.Session, thingID string, clientCert string, clientKey string, caCert string) (bootstrap.Config, error) { - ret := _m.Called(ctx, session, thingID, clientCert, clientKey, caCert) +// UpdateCert provides a mock function with given fields: ctx, session, clientID, clientCert, clientKey, caCert +func (_m *Service) UpdateCert(ctx context.Context, session authn.Session, clientID string, clientCert string, clientKey string, caCert string) (bootstrap.Config, error) { + ret := _m.Called(ctx, session, clientID, clientCert, clientKey, caCert) if len(ret) == 0 { panic("no return value specified for UpdateCert") @@ -239,16 +239,16 @@ func (_m *Service) UpdateCert(ctx context.Context, session authn.Session, thingI var r0 bootstrap.Config var r1 error if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string, string) (bootstrap.Config, error)); ok { - return rf(ctx, session, thingID, clientCert, clientKey, caCert) + return rf(ctx, session, clientID, clientCert, clientKey, caCert) } if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string, string) bootstrap.Config); ok { - r0 = rf(ctx, session, thingID, clientCert, clientKey, caCert) + r0 = rf(ctx, session, clientID, clientCert, clientKey, caCert) } else { r0 = ret.Get(0).(bootstrap.Config) } if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, string, string) error); ok { - r1 = rf(ctx, session, thingID, clientCert, clientKey, caCert) + r1 = rf(ctx, session, clientID, clientCert, clientKey, caCert) } else { r1 = ret.Error(1) } diff --git a/bootstrap/postgres/configs.go b/bootstrap/postgres/configs.go index 6c46a3fe1a..0fa8e389e0 100644 --- a/bootstrap/postgres/configs.go +++ b/bootstrap/postgres/configs.go @@ -13,10 +13,10 @@ import ( "time" "github.com/absmach/magistrala/bootstrap" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/pkg/postgres" - "github.com/absmach/magistrala/things" "github.com/jackc/pgerrcode" "github.com/jackc/pgtype" "github.com/jackc/pgx/v5/pgconn" @@ -24,12 +24,12 @@ import ( ) var ( - errSaveChannels = errors.New("failed to insert channels to database") - errSaveConnections = errors.New("failed to insert connections to database") - errUpdateChannels = errors.New("failed to update channels in bootstrap configuration database") - errRemoveChannels = errors.New("failed to remove channels from bootstrap configuration in database") - errConnectThing = errors.New("failed to connect thing in bootstrap configuration in database") - errDisconnectThing = errors.New("failed to disconnect thing in bootstrap configuration in database") + errSaveChannels = errors.New("failed to insert channels to database") + errSaveConnections = errors.New("failed to insert connections to database") + errUpdateChannels = errors.New("failed to update channels in bootstrap configuration database") + errRemoveChannels = errors.New("failed to remove channels from bootstrap configuration in database") + errConnectClient = errors.New("failed to connect client in bootstrap configuration in database") + errDisconnectClient = errors.New("failed to disconnect client in bootstrap configuration in database") ) const cleanupQuery = `DELETE FROM channels ch WHERE NOT EXISTS ( @@ -48,9 +48,9 @@ func NewConfigRepository(db postgres.Database, log *slog.Logger) bootstrap.Confi return &configRepository{db: db, log: log} } -func (cr configRepository) Save(ctx context.Context, cfg bootstrap.Config, chsConnIDs []string) (thingID string, err error) { - q := `INSERT INTO configs (magistrala_thing, domain_id, name, client_cert, client_key, ca_cert, magistrala_key, external_id, external_key, content, state) - VALUES (:magistrala_thing, :domain_id, :name, :client_cert, :client_key, :ca_cert, :magistrala_key, :external_id, :external_key, :content, :state)` +func (cr configRepository) Save(ctx context.Context, cfg bootstrap.Config, chsConnIDs []string) (clientID string, err error) { + q := `INSERT INTO configs (magistrala_client, domain_id, name, client_cert, client_key, ca_cert, magistrala_secret, external_id, external_key, content, state) + VALUES (:magistrala_client, :domain_id, :name, :client_cert, :client_key, :ca_cert, :magistrala_secret, :external_id, :external_key, :content, :state)` tx, err := cr.db.BeginTxx(ctx, nil) if err != nil { @@ -86,16 +86,16 @@ func (cr configRepository) Save(ctx context.Context, cfg bootstrap.Config, chsCo return "", commitErr } - return cfg.ThingID, nil + return cfg.ClientID, nil } func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string) (bootstrap.Config, error) { - q := `SELECT magistrala_thing, magistrala_key, external_id, external_key, name, content, state, client_cert, ca_cert + q := `SELECT magistrala_client, magistrala_secret, external_id, external_key, name, content, state, client_cert, ca_cert FROM configs - WHERE magistrala_thing = :magistrala_thing AND domain_id = :domain_id` + WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id` dbcfg := dbConfig{ - ThingID: id, + ClientID: id, DomainID: domainID, } row, err := cr.db.NamedQueryContext(ctx, q, dbcfg) @@ -118,7 +118,7 @@ func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string q = `SELECT magistrala_channel, name, metadata FROM channels ch INNER JOIN connections conn ON ch.magistrala_channel = conn.channel_id AND ch.domain_id = conn.domain_id - WHERE conn.config_id = :magistrala_thing AND conn.domain_id = :domain_id` + WHERE conn.config_id = :magistrala_client AND conn.domain_id = :domain_id` rows, err := cr.db.NamedQueryContext(ctx, q, dbcfg) if err != nil { @@ -131,7 +131,7 @@ func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string for rows.Next() { dbch := dbChannel{} if err := rows.StructScan(&dbch); err != nil { - cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err)) + cr.log.Error(fmt.Sprintf("Failed to read connected client due to %s", err)) return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } dbch.DomainID = nullString(dbcfg.DomainID) @@ -149,12 +149,12 @@ func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string return cfg, nil } -func (cr configRepository) RetrieveAll(ctx context.Context, domainID string, thingIDs []string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage { - search, params := buildRetrieveQueryParams(domainID, thingIDs, filter) +func (cr configRepository) RetrieveAll(ctx context.Context, domainID string, clientIDs []string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage { + search, params := buildRetrieveQueryParams(domainID, clientIDs, filter) n := len(params) - q := `SELECT magistrala_thing, magistrala_key, external_id, external_key, name, content, state - FROM configs %s ORDER BY magistrala_thing LIMIT $%d OFFSET $%d` + q := `SELECT magistrala_client, magistrala_secret, external_id, external_key, name, content, state + FROM configs %s ORDER BY magistrala_client LIMIT $%d OFFSET $%d` q = fmt.Sprintf(q, search, n+1, n+2) rows, err := cr.db.QueryContext(ctx, q, append(params, limit, offset)...) @@ -169,7 +169,7 @@ func (cr configRepository) RetrieveAll(ctx context.Context, domainID string, thi for rows.Next() { c := bootstrap.Config{DomainID: domainID} - if err := rows.Scan(&c.ThingID, &c.ThingKey, &c.ExternalID, &c.ExternalKey, &name, &content, &c.State); err != nil { + if err := rows.Scan(&c.ClientID, &c.ClientSecret, &c.ExternalID, &c.ExternalKey, &name, &content, &c.State); err != nil { cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err)) return bootstrap.ConfigsPage{} } @@ -196,7 +196,7 @@ func (cr configRepository) RetrieveAll(ctx context.Context, domainID string, thi } func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID string) (bootstrap.Config, error) { - q := `SELECT magistrala_thing, magistrala_key, external_key, domain_id, name, client_cert, client_key, ca_cert, content, state + q := `SELECT magistrala_client, magistrala_secret, external_key, domain_id, name, client_cert, client_key, ca_cert, content, state FROM configs WHERE external_id = :external_id` dbcfg := dbConfig{ @@ -222,7 +222,7 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID q = `SELECT magistrala_channel, name, metadata FROM channels ch INNER JOIN connections conn ON ch.magistrala_channel = conn.channel_id AND ch.domain_id = conn.domain_id - WHERE conn.config_id = :magistrala_thing AND conn.domain_id = :domain_id` + WHERE conn.config_id = :magistrala_client AND conn.domain_id = :domain_id` rows, err := cr.db.NamedQueryContext(ctx, q, dbcfg) if err != nil { @@ -235,7 +235,7 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID for rows.Next() { dbch := dbChannel{} if err := rows.StructScan(&dbch); err != nil { - cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err)) + cr.log.Error(fmt.Sprintf("Failed to read connected client due to %s", err)) return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) } @@ -255,12 +255,12 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID } func (cr configRepository) Update(ctx context.Context, cfg bootstrap.Config) error { - q := `UPDATE configs SET name = :name, content = :content WHERE magistrala_thing = :magistrala_thing AND domain_id = :domain_id ` + q := `UPDATE configs SET name = :name, content = :content WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id ` dbcfg := dbConfig{ Name: nullString(cfg.Name), Content: nullString(cfg.Content), - ThingID: cfg.ThingID, + ClientID: cfg.ClientID, DomainID: cfg.DomainID, } @@ -281,12 +281,12 @@ func (cr configRepository) Update(ctx context.Context, cfg bootstrap.Config) err return nil } -func (cr configRepository) UpdateCert(ctx context.Context, domainID, thingID, clientCert, clientKey, caCert string) (bootstrap.Config, error) { - q := `UPDATE configs SET client_cert = :client_cert, client_key = :client_key, ca_cert = :ca_cert WHERE magistrala_thing = :magistrala_thing AND domain_id = :domain_id - RETURNING magistrala_thing, client_cert, client_key, ca_cert` +func (cr configRepository) UpdateCert(ctx context.Context, domainID, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) { + q := `UPDATE configs SET client_cert = :client_cert, client_key = :client_key, ca_cert = :ca_cert WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id + RETURNING magistrala_client, client_cert, client_key, ca_cert` dbcfg := dbConfig{ - ThingID: thingID, + ClientID: clientID, ClientCert: nullString(clientCert), DomainID: domainID, ClientKey: nullString(clientKey), @@ -345,9 +345,9 @@ func (cr configRepository) UpdateConnections(ctx context.Context, domainID, id s } func (cr configRepository) Remove(ctx context.Context, domainID, id string) error { - q := `DELETE FROM configs WHERE magistrala_thing = :magistrala_thing AND domain_id = :domain_id` + q := `DELETE FROM configs WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id` dbcfg := dbConfig{ - ThingID: id, + ClientID: id, DomainID: domainID, } @@ -363,10 +363,10 @@ func (cr configRepository) Remove(ctx context.Context, domainID, id string) erro } func (cr configRepository) ChangeState(ctx context.Context, domainID, id string, state bootstrap.State) error { - q := `UPDATE configs SET state = :state WHERE magistrala_thing = :magistrala_thing AND domain_id = :domain_id;` + q := `UPDATE configs SET state = :state WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id;` dbcfg := dbConfig{ - ThingID: id, + ClientID: id, State: state, DomainID: domainID, } @@ -424,8 +424,8 @@ func (cr configRepository) ListExisting(ctx context.Context, domainID string, id return channels, nil } -func (cr configRepository) RemoveThing(ctx context.Context, id string) error { - q := `DELETE FROM configs WHERE magistrala_thing = $1` +func (cr configRepository) RemoveClient(ctx context.Context, id string) error { + q := `DELETE FROM configs WHERE magistrala_client = $1` _, err := cr.db.ExecContext(ctx, q, id) if _, err := cr.db.ExecContext(ctx, cleanupQuery); err != nil { @@ -459,14 +459,14 @@ func (cr configRepository) RemoveChannel(ctx context.Context, id string) error { return nil } -func (cr configRepository) ConnectThing(ctx context.Context, channelID, thingID string) error { +func (cr configRepository) ConnectClient(ctx context.Context, channelID, clientID string) error { q := `UPDATE configs SET state = $1 - WHERE magistrala_thing = $2 + WHERE magistrala_client = $2 AND EXISTS (SELECT 1 FROM connections WHERE config_id = $2 AND channel_id = $3)` - result, err := cr.db.ExecContext(ctx, q, bootstrap.Active, thingID, channelID) + result, err := cr.db.ExecContext(ctx, q, bootstrap.Active, clientID, channelID) if err != nil { - return errors.Wrap(errConnectThing, err) + return errors.Wrap(errConnectClient, err) } if rows, _ := result.RowsAffected(); rows == 0 { return repoerr.ErrNotFound @@ -474,23 +474,23 @@ func (cr configRepository) ConnectThing(ctx context.Context, channelID, thingID return nil } -func (cr configRepository) DisconnectThing(ctx context.Context, channelID, thingID string) error { +func (cr configRepository) DisconnectClient(ctx context.Context, channelID, clientID string) error { q := `UPDATE configs SET state = $1 - WHERE magistrala_thing = $2 + WHERE magistrala_client = $2 AND EXISTS (SELECT 1 FROM connections WHERE config_id = $2 AND channel_id = $3)` - _, err := cr.db.ExecContext(ctx, q, bootstrap.Inactive, thingID, channelID) + _, err := cr.db.ExecContext(ctx, q, bootstrap.Inactive, clientID, channelID) if err != nil { - return errors.Wrap(errDisconnectThing, err) + return errors.Wrap(errDisconnectClient, err) } return nil } -func buildRetrieveQueryParams(domainID string, thingIDs []string, filter bootstrap.Filter) (string, []interface{}) { +func buildRetrieveQueryParams(domainID string, clientIDs []string, filter bootstrap.Filter) (string, []interface{}) { params := []interface{}{} queries := []string{} - if len(thingIDs) != 0 { - queries = append(queries, fmt.Sprintf("magistrala_thing IN ('%s')", strings.Join(thingIDs, "','"))) + if len(clientIDs) != 0 { + queries = append(queries, fmt.Sprintf("magistrala_client IN ('%s')", strings.Join(clientIDs, "','"))) } else if domainID != "" { params = append(params, domainID) queries = append(queries, fmt.Sprintf("domain_id = $%d", len(params))) @@ -560,7 +560,7 @@ func insertConnections(_ context.Context, cfg bootstrap.Config, connections []st conns := []dbConnection{} for _, conn := range connections { dbconn := dbConnection{ - Config: cfg.ThingID, + Config: cfg.ClientID, Channel: conn, DomainID: cfg.DomainID, } @@ -644,43 +644,43 @@ func nullTime(t time.Time) sql.NullTime { } type dbConfig struct { - ThingID string `db:"magistrala_thing"` - DomainID string `db:"domain_id"` - Name sql.NullString `db:"name"` - ClientCert sql.NullString `db:"client_cert"` - ClientKey sql.NullString `db:"client_key"` - CaCert sql.NullString `db:"ca_cert"` - ThingKey string `db:"magistrala_key"` - ExternalID string `db:"external_id"` - ExternalKey string `db:"external_key"` - Content sql.NullString `db:"content"` - State bootstrap.State `db:"state"` + DomainID string `db:"domain_id"` + ClientID string `db:"magistrala_client"` + ClientSecret string `db:"magistrala_secret"` + Name sql.NullString `db:"name"` + ClientCert sql.NullString `db:"client_cert"` + ClientKey sql.NullString `db:"client_key"` + CaCert sql.NullString `db:"ca_cert"` + ExternalID string `db:"external_id"` + ExternalKey string `db:"external_key"` + Content sql.NullString `db:"content"` + State bootstrap.State `db:"state"` } func toDBConfig(cfg bootstrap.Config) dbConfig { return dbConfig{ - ThingID: cfg.ThingID, - DomainID: cfg.DomainID, - Name: nullString(cfg.Name), - ClientCert: nullString(cfg.ClientCert), - ClientKey: nullString(cfg.ClientKey), - CaCert: nullString(cfg.CACert), - ThingKey: cfg.ThingKey, - ExternalID: cfg.ExternalID, - ExternalKey: cfg.ExternalKey, - Content: nullString(cfg.Content), - State: cfg.State, + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + DomainID: cfg.DomainID, + Name: nullString(cfg.Name), + ClientCert: nullString(cfg.ClientCert), + ClientKey: nullString(cfg.ClientKey), + CaCert: nullString(cfg.CACert), + ExternalID: cfg.ExternalID, + ExternalKey: cfg.ExternalKey, + Content: nullString(cfg.Content), + State: cfg.State, } } func toConfig(dbcfg dbConfig) bootstrap.Config { cfg := bootstrap.Config{ - ThingID: dbcfg.ThingID, - DomainID: dbcfg.DomainID, - ThingKey: dbcfg.ThingKey, - ExternalID: dbcfg.ExternalID, - ExternalKey: dbcfg.ExternalKey, - State: dbcfg.State, + ClientID: dbcfg.ClientID, + ClientSecret: dbcfg.ClientSecret, + DomainID: dbcfg.DomainID, + ExternalID: dbcfg.ExternalID, + ExternalKey: dbcfg.ExternalKey, + State: dbcfg.State, } if dbcfg.Name.Valid { @@ -715,7 +715,7 @@ type dbChannel struct { CreatedAt time.Time `db:"created_at"` UpdatedAt sql.NullTime `db:"updated_at,omitempty"` UpdatedBy sql.NullString `db:"updated_by,omitempty"` - Status things.Status `db:"status"` + Status clients.Status `db:"status"` } func toDBChannel(domainID string, ch bootstrap.Channel) (dbChannel, error) { diff --git a/bootstrap/postgres/configs_test.go b/bootstrap/postgres/configs_test.go index 584ddd42b6..8293867c11 100644 --- a/bootstrap/postgres/configs_test.go +++ b/bootstrap/postgres/configs_test.go @@ -23,11 +23,11 @@ const numConfigs = 10 var ( config = bootstrap.Config{ - ThingID: "mg-thing", - ThingKey: "mg-key", - ExternalID: "external-id", - ExternalKey: "external-key", - DomainID: testsutil.GenerateUUID(&testing.T{}), + ClientID: "mg-client", + ClientSecret: "mg-key", + ExternalID: "external-id", + ExternalKey: "external-key", + DomainID: testsutil.GenerateUUID(&testing.T{}), Channels: []bootstrap.Channel{ {ID: "1", Name: "name 1", Metadata: map[string]interface{}{"meta": 1.0}}, {ID: "2", Name: "name 2", Metadata: map[string]interface{}{"meta": 2.0}}, @@ -46,20 +46,20 @@ func TestSave(t *testing.T) { diff := "different" - duplicateThing := config - duplicateThing.ExternalID = diff - duplicateThing.ThingKey = diff - duplicateThing.Channels = []bootstrap.Channel{} + duplicateClient := config + duplicateClient.ExternalID = diff + duplicateClient.ClientSecret = diff + duplicateClient.Channels = []bootstrap.Channel{} duplicateExternal := config - duplicateExternal.ThingID = diff - duplicateExternal.ThingKey = diff + duplicateExternal.ClientID = diff + duplicateExternal.ClientSecret = diff duplicateExternal.Channels = []bootstrap.Channel{} duplicateChannels := config duplicateChannels.ExternalID = diff - duplicateChannels.ThingKey = diff - duplicateChannels.ThingID = diff + duplicateChannels.ClientSecret = diff + duplicateChannels.ClientID = diff cases := []struct { desc string @@ -74,8 +74,8 @@ func TestSave(t *testing.T) { err: nil, }, { - desc: "save config with same Thing ID", - config: duplicateThing, + desc: "save config with same Client ID", + config: duplicateClient, connections: nil, err: repoerr.ErrConflict, }, @@ -96,7 +96,7 @@ func TestSave(t *testing.T) { id, err := repo.Save(context.Background(), tc.config, tc.connections) 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, id, tc.config.ThingID, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.config.ThingID, id)) + assert.Equal(t, id, tc.config.ClientID, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.config.ClientID, id)) } } } @@ -110,8 +110,8 @@ func TestRetrieveByID(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() id, err := repo.Save(context.Background(), c, channels) @@ -162,7 +162,7 @@ func TestRetrieveAll(t *testing.T) { err := deleteChannels(context.Background(), repo) require.Nil(t, err, "Channels cleanup expected to succeed.") - thingIDs := make([]string, numConfigs) + clientIDs := make([]string, numConfigs) for i := 0; i < numConfigs; i++ { c := config @@ -172,10 +172,10 @@ func TestRetrieveAll(t *testing.T) { require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) c.ExternalID = uid.String() c.Name = fmt.Sprintf("name %d", i) - c.ThingID = uid.String() - c.ThingKey = uid.String() + c.ClientID = uid.String() + c.ClientSecret = uid.String() - thingIDs[i] = c.ThingID + clientIDs[i] = c.ClientID if i%2 == 0 { c.State = bootstrap.Active @@ -191,7 +191,7 @@ func TestRetrieveAll(t *testing.T) { cases := []struct { desc string domainID string - thingID []string + clientID []string offset uint64 limit uint64 filter bootstrap.Filter @@ -200,7 +200,7 @@ func TestRetrieveAll(t *testing.T) { { desc: "retrieve all configs", domainID: config.DomainID, - thingID: []string{}, + clientID: []string{}, offset: 0, limit: uint64(numConfigs), size: numConfigs, @@ -208,7 +208,7 @@ func TestRetrieveAll(t *testing.T) { { desc: "retrieve a subset of configs", domainID: config.DomainID, - thingID: []string{}, + clientID: []string{}, offset: 5, limit: uint64(numConfigs - 5), size: numConfigs - 5, @@ -216,7 +216,7 @@ func TestRetrieveAll(t *testing.T) { { desc: "retrieve with wrong domain ID ", domainID: "2", - thingID: []string{}, + clientID: []string{}, offset: 0, limit: uint64(numConfigs), size: 0, @@ -224,7 +224,7 @@ func TestRetrieveAll(t *testing.T) { { desc: "retrieve all active configs ", domainID: config.DomainID, - thingID: []string{}, + clientID: []string{}, offset: 0, limit: uint64(numConfigs), filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}}, @@ -233,7 +233,7 @@ func TestRetrieveAll(t *testing.T) { { desc: "retrieve all with partial match filter", domainID: config.DomainID, - thingID: []string{}, + clientID: []string{}, offset: 0, limit: uint64(numConfigs), filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}}, @@ -242,31 +242,31 @@ func TestRetrieveAll(t *testing.T) { { desc: "retrieve search by name", domainID: config.DomainID, - thingID: []string{}, + clientID: []string{}, offset: 0, limit: uint64(numConfigs), filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}}, size: 1, }, { - desc: "retrieve by valid thingIDs", + desc: "retrieve by valid clientIDs", domainID: config.DomainID, - thingID: thingIDs, + clientID: clientIDs, offset: 0, limit: uint64(numConfigs), size: 10, }, { - desc: "retrieve by non-existing thingID", + desc: "retrieve by non-existing clientID", domainID: config.DomainID, - thingID: []string{"non-existing"}, + clientID: []string{"non-existing"}, offset: 0, limit: uint64(numConfigs), size: 0, }, } for _, tc := range cases { - ret := repo.RetrieveAll(context.Background(), tc.domainID, tc.thingID, tc.filter, tc.offset, tc.limit) + ret := repo.RetrieveAll(context.Background(), tc.domainID, tc.clientID, tc.filter, tc.offset, tc.limit) size := len(ret.Configs) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.size, size)) } @@ -281,8 +281,8 @@ func TestRetrieveByExternalID(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() _, err = repo.Save(context.Background(), c, channels) @@ -319,8 +319,8 @@ func TestUpdate(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() _, err = repo.Save(context.Background(), c, channels) @@ -364,8 +364,8 @@ func TestUpdateCert(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() _, err = repo.Save(context.Background(), c, channels) @@ -379,7 +379,7 @@ func TestUpdateCert(t *testing.T) { cases := []struct { desc string - thingID string + clientID string domainID string cert string certKey string @@ -389,7 +389,7 @@ func TestUpdateCert(t *testing.T) { }{ { desc: "update with wrong domain ID ", - thingID: "", + clientID: "", cert: "cert", certKey: "certKey", ca: "", @@ -399,13 +399,13 @@ func TestUpdateCert(t *testing.T) { }, { desc: "update a config", - thingID: c.ThingID, + clientID: c.ClientID, cert: "cert", certKey: "certKey", ca: "ca", domainID: c.DomainID, expectedConfig: bootstrap.Config{ - ThingID: c.ThingID, + ClientID: c.ClientID, ClientCert: "cert", CACert: "ca", ClientKey: "certKey", @@ -415,7 +415,7 @@ func TestUpdateCert(t *testing.T) { }, } for _, tc := range cases { - cfg, err := repo.UpdateCert(context.Background(), tc.domainID, tc.thingID, tc.cert, tc.certKey, tc.ca) + cfg, err := repo.UpdateCert(context.Background(), tc.domainID, tc.clientID, tc.cert, tc.certKey, tc.ca) 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.expectedConfig, cfg, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.expectedConfig, cfg)) } @@ -430,8 +430,8 @@ func TestUpdateConnections(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() _, err = repo.Save(context.Background(), c, channels) @@ -439,8 +439,8 @@ func TestUpdateConnections(t *testing.T) { // Use UUID to prevent conflicts. uid, err = uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() c.Channels = []bootstrap.Channel{} @@ -466,7 +466,7 @@ func TestUpdateConnections(t *testing.T) { { desc: "update connections", domainID: config.DomainID, - id: c.ThingID, + id: c.ClientID, channels: nil, connections: []string{channels[1]}, err: nil, @@ -482,7 +482,7 @@ func TestUpdateConnections(t *testing.T) { { desc: "update connections no channels", domainID: config.DomainID, - id: c.ThingID, + id: c.ClientID, channels: nil, connections: nil, err: nil, @@ -503,8 +503,8 @@ func TestRemove(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() id, err := repo.Save(context.Background(), c, channels) @@ -530,8 +530,8 @@ func TestChangeState(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() saved, err := repo.Save(context.Background(), c, channels) @@ -586,8 +586,8 @@ func TestListExisting(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() _, err = repo.Save(context.Background(), c, channels) @@ -628,7 +628,7 @@ func TestListExisting(t *testing.T) { } } -func TestRemoveThing(t *testing.T) { +func TestRemoveClient(t *testing.T) { repo := postgres.NewConfigRepository(db, testLog) err := deleteChannels(context.Background(), repo) require.Nil(t, err, "Channels cleanup expected to succeed.") @@ -637,14 +637,14 @@ func TestRemoveThing(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() saved, err := repo.Save(context.Background(), c, channels) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) for i := 0; i < 2; i++ { - err := repo.RemoveThing(context.Background(), saved) + err := repo.RemoveClient(context.Background(), saved) assert.Nil(t, err, fmt.Sprintf("an unexpected error occurred: %s\n", err)) } } @@ -658,8 +658,8 @@ func TestUpdateChannel(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() _, err = repo.Save(context.Background(), c, channels) @@ -674,7 +674,7 @@ func TestUpdateChannel(t *testing.T) { err = repo.UpdateChannel(context.Background(), update) assert.Nil(t, err, fmt.Sprintf("updating config expected to succeed: %s.\n", err)) - cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ThingID) + cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID) assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err)) var retreved bootstrap.Channel for _, c := range cfg.Channels { @@ -695,8 +695,8 @@ func TestRemoveChannel(t *testing.T) { c := config uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() _, err = repo.Save(context.Background(), c, channels) @@ -705,12 +705,12 @@ func TestRemoveChannel(t *testing.T) { err = repo.RemoveChannel(context.Background(), c.Channels[0].ID) assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err)) - cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ThingID) + cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID) assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err)) assert.NotContains(t, cfg.Channels, c.Channels[0], fmt.Sprintf("expected to remove channel %s from %s", c.Channels[0], cfg.Channels)) } -func TestConnectThing(t *testing.T) { +func TestConnectClient(t *testing.T) { repo := postgres.NewConfigRepository(db, testLog) err := deleteChannels(context.Background(), repo) require.Nil(t, err, "Channels cleanup expected to succeed.") @@ -719,8 +719,8 @@ func TestConnectThing(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() c.State = bootstrap.Inactive @@ -729,14 +729,14 @@ func TestConnectThing(t *testing.T) { wrongID := testsutil.GenerateUUID(&testing.T{}) - connectedThing := c + connectedClient := c - randomThing := c - randomThingID, _ := uuid.NewV4() - randomThing.ThingID = randomThingID.String() + randomClient := c + randomClientID, _ := uuid.NewV4() + randomClient.ClientID = randomClientID.String() - emptyThing := c - emptyThing.ThingID = "" + emptyClient := c + emptyClient.ClientID = "" cases := []struct { desc string @@ -748,7 +748,7 @@ func TestConnectThing(t *testing.T) { err error }{ { - desc: "connect disconnected thing", + desc: "connect disconnected client", domainID: c.DomainID, id: saved, state: bootstrap.Inactive, @@ -757,16 +757,16 @@ func TestConnectThing(t *testing.T) { err: nil, }, { - desc: "connect already connected thing", + desc: "connect already connected client", domainID: c.DomainID, - id: connectedThing.ThingID, - state: connectedThing.State, + id: connectedClient.ClientID, + state: connectedClient.State, channels: c.Channels, connections: channels, err: nil, }, { - desc: "connect non-existent thing", + desc: "connect non-existent client", domainID: c.DomainID, id: wrongID, channels: c.Channels, @@ -774,17 +774,17 @@ func TestConnectThing(t *testing.T) { err: repoerr.ErrNotFound, }, { - desc: "connect random thing", + desc: "connect random client", domainID: c.DomainID, - id: randomThing.ThingID, + id: randomClient.ClientID, channels: c.Channels, connections: channels, err: repoerr.ErrNotFound, }, { - desc: "connect empty thing", + desc: "connect empty client", domainID: c.DomainID, - id: emptyThing.ThingID, + id: emptyClient.ClientID, channels: c.Channels, connections: channels, err: repoerr.ErrNotFound, @@ -793,23 +793,23 @@ func TestConnectThing(t *testing.T) { for _, tc := range cases { for i, ch := range tc.channels { if i == 0 { - err = repo.ConnectThing(context.Background(), ch.ID, tc.id) + err = repo.ConnectClient(context.Background(), ch.ID, tc.id) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: Expected error: %s, got: %s.\n", tc.desc, tc.err, err)) - cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ThingID) + cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID) assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err)) assert.Equal(t, cfg.State, bootstrap.Active, fmt.Sprintf("expected to be active when a connection is added from %s", cfg)) } else { - _ = repo.ConnectThing(context.Background(), ch.ID, tc.id) + _ = repo.ConnectClient(context.Background(), ch.ID, tc.id) } } - cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ThingID) + cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID) assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err)) assert.Equal(t, cfg.State, bootstrap.Active, fmt.Sprintf("expected to be active when a connection is added from %s", cfg)) } } -func TestDisconnectThing(t *testing.T) { +func TestDisconnectClient(t *testing.T) { repo := postgres.NewConfigRepository(db, testLog) err := deleteChannels(context.Background(), repo) require.Nil(t, err, "Channels cleanup expected to succeed.") @@ -818,8 +818,8 @@ func TestDisconnectThing(t *testing.T) { // Use UUID to prevent conflicts. uid, err := uuid.NewV4() assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ThingKey = uid.String() - c.ThingID = uid.String() + c.ClientSecret = uid.String() + c.ClientID = uid.String() c.ExternalID = uid.String() c.ExternalKey = uid.String() c.State = bootstrap.Inactive @@ -828,14 +828,14 @@ func TestDisconnectThing(t *testing.T) { wrongID := testsutil.GenerateUUID(&testing.T{}) - connectedThing := c + connectedClient := c - randomThing := c - randomThingID, _ := uuid.NewV4() - randomThing.ThingID = randomThingID.String() + randomClient := c + randomClientID, _ := uuid.NewV4() + randomClient.ClientID = randomClientID.String() - emptyThing := c - emptyThing.ThingID = "" + emptyClient := c + emptyClient.ClientID = "" cases := []struct { desc string @@ -847,16 +847,16 @@ func TestDisconnectThing(t *testing.T) { err error }{ { - desc: "disconnect connected thing", + desc: "disconnect connected client", domainID: c.DomainID, - id: connectedThing.ThingID, - state: connectedThing.State, + id: connectedClient.ClientID, + state: connectedClient.State, channels: c.Channels, connections: channels, err: nil, }, { - desc: "disconnect already disconnected thing", + desc: "disconnect already disconnected client", domainID: c.DomainID, id: saved, state: bootstrap.Inactive, @@ -865,7 +865,7 @@ func TestDisconnectThing(t *testing.T) { err: nil, }, { - desc: "disconnect invalid thing", + desc: "disconnect invalid client", domainID: c.DomainID, id: wrongID, channels: c.Channels, @@ -873,17 +873,17 @@ func TestDisconnectThing(t *testing.T) { err: nil, }, { - desc: "disconnect random thing", + desc: "disconnect random client", domainID: c.DomainID, - id: randomThing.ThingID, + id: randomClient.ClientID, channels: c.Channels, connections: channels, err: nil, }, { - desc: "disconnect empty thing", + desc: "disconnect empty client", domainID: c.DomainID, - id: emptyThing.ThingID, + id: emptyClient.ClientID, channels: c.Channels, connections: channels, err: nil, @@ -892,11 +892,11 @@ func TestDisconnectThing(t *testing.T) { for _, tc := range cases { for _, ch := range tc.channels { - err = repo.DisconnectThing(context.Background(), ch.ID, tc.id) + err = repo.DisconnectClient(context.Background(), ch.ID, tc.id) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: Expected error: %s, got: %s.\n", tc.desc, tc.err, err)) } - cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ThingID) + cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID) assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err)) assert.Equal(t, cfg.State, bootstrap.Inactive, fmt.Sprintf("expected to be inactive when a connection is removed from %s", cfg)) } diff --git a/bootstrap/postgres/init.go b/bootstrap/postgres/init.go index f562551ced..5ab55938d7 100644 --- a/bootstrap/postgres/init.go +++ b/bootstrap/postgres/init.go @@ -13,7 +13,7 @@ func Migration() *migrate.MemoryMigrationSource { Id: "configs_1", Up: []string{ `CREATE TABLE IF NOT EXISTS configs ( - mainflux_thing TEXT UNIQUE NOT NULL, + mainflux_client TEXT UNIQUE NOT NULL, owner VARCHAR(254), name TEXT, mainflux_key CHAR(36) UNIQUE NOT NULL, @@ -24,7 +24,7 @@ func Migration() *migrate.MemoryMigrationSource { client_key TEXT, ca_cert TEXT, state BIGINT NOT NULL, - PRIMARY KEY (mainflux_thing, owner) + PRIMARY KEY (mainflux_client, owner) )`, `CREATE TABLE IF NOT EXISTS unknown_configs ( external_id TEXT UNIQUE NOT NULL, @@ -44,7 +44,7 @@ func Migration() *migrate.MemoryMigrationSource { config_id TEXT, config_owner VARCHAR(256), FOREIGN KEY (channel_id, channel_owner) REFERENCES channels (mainflux_channel, owner) ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (config_id, config_owner) REFERENCES configs (mainflux_thing, owner) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (config_id, config_owner) REFERENCES configs (mainflux_client, owner) ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY (channel_id, channel_owner, config_id, config_owner) )`, }, @@ -78,8 +78,8 @@ func Migration() *migrate.MemoryMigrationSource { { Id: "configs_4", Up: []string{ - `ALTER TABLE IF EXISTS configs RENAME COLUMN mainflux_thing TO magistrala_thing`, - `ALTER TABLE IF EXISTS configs RENAME COLUMN mainflux_key TO magistrala_key`, + `ALTER TABLE IF EXISTS configs RENAME COLUMN mainflux_client TO magistrala_client`, + `ALTER TABLE IF EXISTS configs RENAME COLUMN mainflux_key TO magistrala_secret`, `ALTER TABLE IF EXISTS channels RENAME COLUMN mainflux_channel TO magistrala_channel`, }, }, @@ -100,7 +100,7 @@ func Migration() *migrate.MemoryMigrationSource { `ALTER TABLE IF EXISTS connections ADD COLUMN IF NOT EXISTS domain_id VARCHAR(256) NOT NULL`, `ALTER TABLE IF EXISTS connections ADD CONSTRAINT connections_pkey PRIMARY KEY (channel_id, config_id, domain_id)`, `ALTER TABLE IF EXISTS connections ADD FOREIGN KEY (channel_id, domain_id) REFERENCES channels (magistrala_channel, domain_id) ON DELETE CASCADE ON UPDATE CASCADE`, - `ALTER TABLE IF EXISTS connections ADD FOREIGN KEY (config_id, domain_id) REFERENCES configs (magistrala_thing, domain_id) ON DELETE CASCADE ON UPDATE CASCADE`, + `ALTER TABLE IF EXISTS connections ADD FOREIGN KEY (config_id, domain_id) REFERENCES configs (magistrala_client, domain_id) ON DELETE CASCADE ON UPDATE CASCADE`, }, }, }, diff --git a/bootstrap/reader.go b/bootstrap/reader.go index dd4358085d..33d49d19c8 100644 --- a/bootstrap/reader.go +++ b/bootstrap/reader.go @@ -16,13 +16,13 @@ import ( // This is used as a response from ConfigReader and can easily be // replace with any other response format. type bootstrapRes struct { - ThingID string `json:"thing_id"` - ThingKey string `json:"thing_key"` - Channels []channelRes `json:"channels"` - Content string `json:"content,omitempty"` - ClientCert string `json:"client_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` - CACert string `json:"ca_cert,omitempty"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Channels []channelRes `json:"channels"` + Content string `json:"content,omitempty"` + ClientCert string `json:"client_cert,omitempty"` + ClientKey string `json:"client_key,omitempty"` + CACert string `json:"ca_cert,omitempty"` } type channelRes struct { @@ -60,13 +60,13 @@ func (r reader) ReadConfig(cfg Config, secure bool) (interface{}, error) { } res := bootstrapRes{ - ThingKey: cfg.ThingKey, - ThingID: cfg.ThingID, - Channels: channels, - Content: cfg.Content, - ClientCert: cfg.ClientCert, - ClientKey: cfg.ClientKey, - CACert: cfg.CACert, + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + Channels: channels, + Content: cfg.Content, + ClientCert: cfg.ClientCert, + ClientKey: cfg.ClientKey, + CACert: cfg.CACert, } if secure { b, err := json.Marshal(res) diff --git a/bootstrap/reader_test.go b/bootstrap/reader_test.go index c283f3365c..cbe37c188b 100644 --- a/bootstrap/reader_test.go +++ b/bootstrap/reader_test.go @@ -24,13 +24,13 @@ type readChan struct { } type readResp struct { - ThingID string `json:"thing_id"` - ThingKey string `json:"thing_key"` - Channels []readChan `json:"channels"` - Content string `json:"content,omitempty"` - ClientCert string `json:"client_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` - CACert string `json:"ca_cert,omitempty"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Channels []readChan `json:"channels"` + Content string `json:"content,omitempty"` + ClientCert string `json:"client_cert,omitempty"` + ClientKey string `json:"client_key,omitempty"` + CACert string `json:"ca_cert,omitempty"` } func dec(in []byte) ([]byte, error) { @@ -50,11 +50,11 @@ func dec(in []byte) ([]byte, error) { func TestReadConfig(t *testing.T) { cfg := bootstrap.Config{ - ThingID: "mg_id", - ClientCert: "client_cert", - ClientKey: "client_key", - CACert: "ca_cert", - ThingKey: "mg_key", + ClientID: "mg_id", + ClientCert: "client_cert", + ClientKey: "client_key", + CACert: "ca_cert", + ClientSecret: "mg_key", Channels: []bootstrap.Channel{ { ID: "mg_id", @@ -65,8 +65,8 @@ func TestReadConfig(t *testing.T) { Content: "content", } ret := readResp{ - ThingID: "mg_id", - ThingKey: "mg_key", + ClientID: "mg_id", + ClientSecret: "mg_key", Channels: []readChan{ { ID: "mg_id", diff --git a/bootstrap/service.go b/bootstrap/service.go index 91976bd563..4a9ad8e58e 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -19,9 +19,9 @@ import ( ) var ( - // ErrThings indicates failure to communicate with Magistrala Things service. + // ErrClients indicates failure to communicate with Magistrala Clients service. // It can be due to networking error or invalid/unauthenticated request. - ErrThings = errors.New("failed to receive response from Things service") + ErrClients = errors.New("failed to receive response from Clients service") // ErrExternalKey indicates a non-existent bootstrap configuration for given external key. ErrExternalKey = errors.New("failed to get bootstrap configuration for given external key") @@ -44,12 +44,12 @@ var ( errUpdateChannel = errors.New("failed to update channel") errRemoveConfig = errors.New("failed to remove bootstrap configuration") errRemoveChannel = errors.New("failed to remove channel") - errCreateThing = errors.New("failed to create thing") - errConnectThing = errors.New("failed to connect thing") - errDisconnectThing = errors.New("failed to disconnect thing") + errCreateClient = errors.New("failed to create client") + errConnectClient = errors.New("failed to connect client") + errDisconnectClient = errors.New("failed to disconnect client") errCheckChannels = errors.New("failed to check if channels exists") errConnectionChannels = errors.New("failed to check channels connections") - errThingNotFound = errors.New("failed to find thing") + errClientNotFound = errors.New("failed to find client") errUpdateCert = errors.New("failed to update cert") ) @@ -60,10 +60,10 @@ var _ Service = (*bootstrapService)(nil) // //go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" type Service interface { - // Add adds new Thing Config to the user identified by the provided token. + // Add adds new Client Config to the user identified by the provided token. Add(ctx context.Context, session mgauthn.Session, token string, cfg Config) (Config, error) - // View returns Thing Config with given ID belonging to the user identified by the given token. + // View returns Client Config with given ID belonging to the user identified by the given token. View(ctx context.Context, session mgauthn.Session, id string) (Config, error) // Update updates editable fields of the provided Config. @@ -71,7 +71,7 @@ type Service interface { // UpdateCert updates an existing Config certificate and token. // A non-nil error is returned to indicate operation failure. - UpdateCert(ctx context.Context, session mgauthn.Session, thingID, clientCert, clientKey, caCert string) (Config, error) + UpdateCert(ctx context.Context, session mgauthn.Session, clientID, clientCert, clientKey, caCert string) (Config, error) // UpdateConnections updates list of Channels related to given Config. UpdateConnections(ctx context.Context, session mgauthn.Session, token, id string, connections []string) error @@ -83,10 +83,10 @@ type Service interface { // Remove removes Config with specified token that belongs to the user identified by the given token. Remove(ctx context.Context, session mgauthn.Session, id string) error - // Bootstrap returns Config to the Thing with provided external ID using external key. + // Bootstrap returns Config to the Client with provided external ID using external key. Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (Config, error) - // ChangeState changes state of the Thing with given thing ID and domain ID. + // ChangeState changes state of the Client with given client ID and domain ID. ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state State) error // Methods RemoveConfig, UpdateChannel, and RemoveChannel are used as @@ -101,11 +101,11 @@ type Service interface { // RemoveChannelHandler removes Channel with id received from an event. RemoveChannelHandler(ctx context.Context, id string) error - // ConnectThingHandler changes state of the Config to active when connect event occurs. - ConnectThingHandler(ctx context.Context, channelID, ThingID string) error + // ConnectClientHandler changes state of the Config to active when connect event occurs. + ConnectClientHandler(ctx context.Context, channelID, clientID string) error - // DisconnectThingHandler changes state of the Config to inactive when disconnect event occurs. - DisconnectThingHandler(ctx context.Context, channelID, ThingID string) error + // DisconnectClientHandler changes state of the Config to inactive when disconnect event occurs. + DisconnectClientHandler(ctx context.Context, channelID, clientID string) error } // ConfigReader is used to parse Config into format which will be encoded @@ -151,36 +151,36 @@ func (bs bootstrapService) Add(ctx context.Context, session mgauthn.Session, tok return Config{}, errors.Wrap(errConnectionChannels, err) } - id := cfg.ThingID - mgThing, err := bs.thing(session.DomainID, id, token) + id := cfg.ClientID + mgClient, err := bs.client(session.DomainID, id, token) if err != nil { - return Config{}, errors.Wrap(errThingNotFound, err) + return Config{}, errors.Wrap(errClientNotFound, err) } for _, channel := range cfg.Channels { - if channel.DomainID != mgThing.DomainID { + if channel.DomainID != mgClient.DomainID { return Config{}, errors.Wrap(svcerr.ErrMalformedEntity, errNotInSameDomain) } } - cfg.ThingID = mgThing.ID + cfg.ClientID = mgClient.ID cfg.DomainID = session.DomainID cfg.State = Inactive - cfg.ThingKey = mgThing.Credentials.Secret + cfg.ClientSecret = mgClient.Credentials.Secret saved, err := bs.configs.Save(ctx, cfg, toConnect) if err != nil { - // If id is empty, then a new thing has been created function - bs.thing(id, token) - // So, on bootstrap config save error , delete the newly created thing. + // If id is empty, then a new client has been created function - bs.client(id, token) + // So, on bootstrap config save error , delete the newly created client. if id == "" { - if errT := bs.sdk.DeleteThing(cfg.ThingID, cfg.DomainID, token); errT != nil { + if errT := bs.sdk.DeleteClient(cfg.ClientID, cfg.DomainID, token); errT != nil { err = errors.Wrap(err, errT) } } return Config{}, errors.Wrap(ErrAddBootstrap, err) } - cfg.ThingID = saved + cfg.ClientID = saved cfg.Channels = append(cfg.Channels, existing...) return cfg, nil @@ -202,8 +202,8 @@ func (bs bootstrapService) Update(ctx context.Context, session mgauthn.Session, return nil } -func (bs bootstrapService) UpdateCert(ctx context.Context, session mgauthn.Session, thingID, clientCert, clientKey, caCert string) (Config, error) { - cfg, err := bs.configs.UpdateCert(ctx, session.DomainID, thingID, clientCert, clientKey, caCert) +func (bs bootstrapService) UpdateCert(ctx context.Context, session mgauthn.Session, clientID, clientCert, clientKey, caCert string) (Config, error) { + cfg, err := bs.configs.UpdateCert(ctx, session.DomainID, clientID, clientCert, clientKey, caCert) if err != nil { return Config{}, errors.Wrap(errUpdateCert, err) } @@ -238,21 +238,21 @@ func (bs bootstrapService) UpdateConnections(ctx context.Context, session mgauth } for _, c := range disconnect { - if err := bs.sdk.DisconnectThing(id, c, session.DomainID, token); err != nil { + if err := bs.sdk.DisconnectClient(id, c, session.DomainID, token); err != nil { if errors.Contains(err, repoerr.ErrNotFound) { continue } - return ErrThings + return ErrClients } } for _, c := range connect { conIDs := mgsdk.Connection{ ChannelID: c, - ThingID: id, + ClientID: id, } if err := bs.sdk.Connect(conIDs, session.DomainID, token); err != nil { - return ErrThings + return ErrClients } } if err := bs.configs.UpdateConnections(ctx, session.DomainID, id, channels, connections); err != nil { @@ -266,7 +266,7 @@ func (bs bootstrapService) listClientIDs(ctx context.Context, userID string) ([] SubjectType: policies.UserType, Subject: userID, Permission: policies.ViewPermission, - ObjectType: policies.ThingType, + ObjectType: policies.ClientType, }) if err != nil { return nil, errors.Wrap(svcerr.ErrNotFound, err) @@ -280,12 +280,12 @@ func (bs bootstrapService) List(ctx context.Context, session mgauthn.Session, fi } // Handle non-admin users - thingIDs, err := bs.listClientIDs(ctx, session.DomainUserID) + clientIDs, err := bs.listClientIDs(ctx, session.DomainUserID) if err != nil { return ConfigsPage{}, errors.Wrap(svcerr.ErrNotFound, err) } - if len(thingIDs) == 0 { + if len(clientIDs) == 0 { return ConfigsPage{ Total: 0, Offset: offset, @@ -294,7 +294,7 @@ func (bs bootstrapService) List(ctx context.Context, session mgauthn.Session, fi }, nil } - return bs.configs.RetrieveAll(ctx, session.DomainID, thingIDs, filter, offset, limit), nil + return bs.configs.RetrieveAll(ctx, session.DomainID, clientIDs, filter, offset, limit), nil } func (bs bootstrapService) Remove(ctx context.Context, session mgauthn.Session, id string) error { @@ -338,23 +338,23 @@ func (bs bootstrapService) ChangeState(ctx context.Context, session mgauthn.Sess for _, c := range cfg.Channels { conIDs := mgsdk.Connection{ ChannelID: c.ID, - ThingID: cfg.ThingID, + ClientID: cfg.ClientID, } if err := bs.sdk.Connect(conIDs, session.DomainID, token); err != nil { // Ignore conflict errors as they indicate the connection already exists. if errors.Contains(err, svcerr.ErrConflict) { continue } - return ErrThings + return ErrClients } } case Inactive: for _, c := range cfg.Channels { - if err := bs.sdk.DisconnectThing(cfg.ThingID, c.ID, session.DomainID, token); err != nil { + if err := bs.sdk.DisconnectClient(cfg.ClientID, c.ID, session.DomainID, token); err != nil { if errors.Contains(err, repoerr.ErrNotFound) { continue } - return ErrThings + return ErrClients } } } @@ -372,7 +372,7 @@ func (bs bootstrapService) UpdateChannelHandler(ctx context.Context, channel Cha } func (bs bootstrapService) RemoveConfigHandler(ctx context.Context, id string) error { - if err := bs.configs.RemoveThing(ctx, id); err != nil { + if err := bs.configs.RemoveClient(ctx, id); err != nil { return errors.Wrap(errRemoveConfig, err) } return nil @@ -385,41 +385,41 @@ func (bs bootstrapService) RemoveChannelHandler(ctx context.Context, id string) return nil } -func (bs bootstrapService) ConnectThingHandler(ctx context.Context, channelID, thingID string) error { - if err := bs.configs.ConnectThing(ctx, channelID, thingID); err != nil { - return errors.Wrap(errConnectThing, err) +func (bs bootstrapService) ConnectClientHandler(ctx context.Context, channelID, clientID string) error { + if err := bs.configs.ConnectClient(ctx, channelID, clientID); err != nil { + return errors.Wrap(errConnectClient, err) } return nil } -func (bs bootstrapService) DisconnectThingHandler(ctx context.Context, channelID, thingID string) error { - if err := bs.configs.DisconnectThing(ctx, channelID, thingID); err != nil { - return errors.Wrap(errDisconnectThing, err) +func (bs bootstrapService) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error { + if err := bs.configs.DisconnectClient(ctx, channelID, clientID); err != nil { + return errors.Wrap(errDisconnectClient, err) } return nil } -// Method thing retrieves Magistrala Thing creating one if an empty ID is passed. -func (bs bootstrapService) thing(domainID, id, token string) (mgsdk.Thing, error) { - // If Thing ID is not provided, then create new thing. +// Method client retrieves Magistrala Client creating one if an empty ID is passed. +func (bs bootstrapService) client(domainID, id, token string) (mgsdk.Client, error) { + // If Client ID is not provided, then create new client. if id == "" { id, err := bs.idProvider.ID() if err != nil { - return mgsdk.Thing{}, errors.Wrap(errCreateThing, err) + return mgsdk.Client{}, errors.Wrap(errCreateClient, err) } - thing, sdkErr := bs.sdk.CreateThing(mgsdk.Thing{ID: id, Name: "Bootstrapped Thing " + id}, domainID, token) + client, sdkErr := bs.sdk.CreateClient(mgsdk.Client{ID: id, Name: "Bootstrapped Client " + id}, domainID, token) if sdkErr != nil { - return mgsdk.Thing{}, errors.Wrap(errCreateThing, sdkErr) + return mgsdk.Client{}, errors.Wrap(errCreateClient, sdkErr) } - return thing, nil + return client, nil } - // If Thing ID is provided, then retrieve thing - thing, sdkErr := bs.sdk.Thing(id, domainID, token) + // If Client ID is provided, then retrieve client + client, sdkErr := bs.sdk.Client(id, domainID, token) if sdkErr != nil { - return mgsdk.Thing{}, errors.Wrap(ErrThings, sdkErr) + return mgsdk.Client{}, errors.Wrap(ErrClients, sdkErr) } - return thing, nil + return client, nil } func (bs bootstrapService) connectionChannels(channels, existing []string, domainID, token string) ([]Channel, error) { diff --git a/bootstrap/service_test.go b/bootstrap/service_test.go index f2918f2ec9..8eb903b753 100644 --- a/bootstrap/service_test.go +++ b/bootstrap/service_test.go @@ -50,12 +50,12 @@ var ( } config = bootstrap.Config{ - ThingID: testsutil.GenerateUUID(&testing.T{}), - ThingKey: testsutil.GenerateUUID(&testing.T{}), - ExternalID: testsutil.GenerateUUID(&testing.T{}), - ExternalKey: testsutil.GenerateUUID(&testing.T{}), - Channels: []bootstrap.Channel{channel}, - Content: "config", + ClientID: testsutil.GenerateUUID(&testing.T{}), + ClientSecret: testsutil.GenerateUUID(&testing.T{}), + ExternalID: testsutil.GenerateUUID(&testing.T{}), + ExternalKey: testsutil.GenerateUUID(&testing.T{}), + Channels: []bootstrap.Channel{channel}, + Content: "config", } ) @@ -92,7 +92,7 @@ func TestAdd(t *testing.T) { svc := newService() neID := config - neID.ThingID = "non-existent" + neID.ClientID = "non-existent" wrongChannels := config ch := channel @@ -106,12 +106,12 @@ func TestAdd(t *testing.T) { session mgauthn.Session userID string domainID string - thingErr error - createThingErr error + clientErr error + createClientErr error channelErr error listExistingErr error saveErr error - deleteThingErr error + deleteClientErr error err error }{ { @@ -128,7 +128,7 @@ func TestAdd(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - thingErr: errors.NewSDKError(svcerr.ErrNotFound), + clientErr: errors.NewSDKError(svcerr.ErrNotFound), err: svcerr.ErrNotFound, }, { @@ -152,9 +152,9 @@ func TestAdd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { tc.session = mgauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} - repoCall := sdk.On("Thing", tc.config.ThingID, mock.Anything, tc.token).Return(mgsdk.Thing{ID: tc.config.ThingID, Credentials: mgsdk.ClientCredentials{Secret: tc.config.ThingKey}}, tc.thingErr) - repoCall1 := sdk.On("CreateThing", mock.Anything, tc.domainID, tc.token).Return(mgsdk.Thing{}, tc.createThingErr) - repoCall2 := sdk.On("DeleteThing", tc.config.ThingID, tc.domainID, tc.token).Return(tc.deleteThingErr) + repoCall := sdk.On("Client", tc.config.ClientID, mock.Anything, tc.token).Return(mgsdk.Client{ID: tc.config.ClientID, Credentials: mgsdk.ClientCredentials{Secret: tc.config.ClientSecret}}, tc.clientErr) + repoCall1 := sdk.On("CreateClient", mock.Anything, tc.domainID, tc.token).Return(mgsdk.Client{}, tc.createClientErr) + repoCall2 := sdk.On("DeleteClient", tc.config.ClientID, tc.domainID, tc.token).Return(tc.deleteClientErr) repoCall3 := boot.On("ListExisting", context.Background(), tc.domainID, mock.Anything).Return(tc.config.Channels, tc.listExistingErr) repoCall4 := boot.On("Save", context.Background(), mock.Anything, mock.Anything).Return(mock.Anything, tc.saveErr) _, err := svc.Add(context.Background(), tc.session, tc.token, tc.config) @@ -176,19 +176,19 @@ func TestView(t *testing.T) { configID string userID string domain string - thingDomain string + clientDomain string token string session mgauthn.Session retrieveErr error - thingErr error + clientErr error channelErr error err error }{ { desc: "view an existing config", - configID: config.ThingID, + configID: config.ClientID, userID: validID, - thingDomain: domainID, + clientDomain: domainID, domain: domainID, token: validToken, err: nil, @@ -197,7 +197,7 @@ func TestView(t *testing.T) { desc: "view a non-existing config", configID: unknown, userID: validID, - thingDomain: domainID, + clientDomain: domainID, domain: domainID, token: validToken, retrieveErr: svcerr.ErrNotFound, @@ -205,9 +205,9 @@ func TestView(t *testing.T) { }, { desc: "view a config with invalid domain", - configID: config.ThingID, + configID: config.ClientID, userID: validID, - thingDomain: invalidDomainID, + clientDomain: invalidDomainID, domain: invalidDomainID, token: validToken, retrieveErr: svcerr.ErrNotFound, @@ -218,7 +218,7 @@ func TestView(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { tc.session = mgauthn.Session{UserID: tc.userID, DomainID: tc.domain, DomainUserID: validID} - repoCall := boot.On("RetrieveByID", context.Background(), tc.thingDomain, tc.configID).Return(config, tc.retrieveErr) + repoCall := boot.On("RetrieveByID", context.Background(), tc.clientDomain, tc.configID).Return(config, tc.retrieveErr) _, err := svc.View(context.Background(), tc.session, tc.configID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) repoCall.Unset() @@ -239,7 +239,7 @@ func TestUpdate(t *testing.T) { modifiedCreated.Name = "new name" nonExisting := c - nonExisting.ThingID = unknown + nonExisting.ClientID = unknown cases := []struct { desc string @@ -304,7 +304,7 @@ func TestUpdateCert(t *testing.T) { session mgauthn.Session userID string domainID string - thingID string + clientID string clientCert string clientKey string caCert string @@ -318,24 +318,24 @@ func TestUpdateCert(t *testing.T) { desc: "update certs for the valid config", userID: validID, domainID: domainID, - thingID: c.ThingID, + clientID: c.ClientID, clientCert: "newCert", clientKey: "newKey", caCert: "newCert", token: validToken, expectedConfig: bootstrap.Config{ - Name: c.Name, - ThingKey: c.ThingKey, - Channels: c.Channels, - ExternalID: c.ExternalID, - ExternalKey: c.ExternalKey, - Content: c.Content, - State: c.State, - DomainID: c.DomainID, - ThingID: c.ThingID, - ClientCert: "newCert", - CACert: "newCert", - ClientKey: "newKey", + Name: c.Name, + ClientSecret: c.ClientSecret, + Channels: c.Channels, + ExternalID: c.ExternalID, + ExternalKey: c.ExternalKey, + Content: c.Content, + State: c.State, + DomainID: c.DomainID, + ClientID: c.ClientID, + ClientCert: "newCert", + CACert: "newCert", + ClientKey: "newKey", }, err: nil, }, @@ -343,7 +343,7 @@ func TestUpdateCert(t *testing.T) { desc: "update cert for a non-existing config", userID: validID, domainID: domainID, - thingID: "empty", + clientID: "empty", clientCert: "newCert", clientKey: "newKey", caCert: "newCert", @@ -358,7 +358,7 @@ func TestUpdateCert(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { tc.session = mgauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} repoCall := boot.On("UpdateCert", context.Background(), mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.expectedConfig, tc.updateErr) - cfg, err := svc.UpdateCert(context.Background(), tc.session, tc.thingID, tc.clientCert, tc.clientKey, tc.caCert) + cfg, err := svc.UpdateCert(context.Background(), tc.session, tc.clientID, tc.clientCert, tc.clientKey, tc.caCert) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) sort.Slice(cfg.Channels, func(i, j int) bool { return cfg.Channels[i].ID < cfg.Channels[j].ID @@ -393,7 +393,7 @@ func TestUpdateConnections(t *testing.T) { domainID string connections []string updateErr error - thingErr error + clientErr error channelErr error retrieveErr error listErr error @@ -404,7 +404,7 @@ func TestUpdateConnections(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - id: c.ThingID, + id: c.ClientID, state: c.State, connections: []string{ch.ID}, err: nil, @@ -414,7 +414,7 @@ func TestUpdateConnections(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - id: activeConf.ThingID, + id: activeConf.ClientID, state: activeConf.State, connections: []string{ch.ID}, err: nil, @@ -424,7 +424,7 @@ func TestUpdateConnections(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - id: c.ThingID, + id: c.ClientID, connections: []string{"wrong"}, channelErr: errors.NewSDKError(svcerr.ErrNotFound), err: svcerr.ErrNotFound, @@ -451,9 +451,9 @@ func TestUpdateConnections(t *testing.T) { func TestList(t *testing.T) { svc := newService() - numThings := 101 + numClients := 101 var saved []bootstrap.Config - for i := 0; i < numThings; i++ { + for i := 0; i < numClients; i++ { c := config c.ExternalID = testsutil.GenerateUUID(t) c.ExternalKey = testsutil.GenerateUUID(t) @@ -722,7 +722,7 @@ func TestList(t *testing.T) { SubjectType: policysvc.UserType, Subject: tc.userID, Permission: policysvc.ViewPermission, - ObjectType: policysvc.ThingType, + ObjectType: policysvc.ClientType, }).Return(tc.listObjectsResponse, tc.listObjectsErr) repoCall := boot.On("RetrieveAll", context.Background(), mock.Anything, mock.Anything, tc.filter, tc.offset, tc.limit).Return(tc.config, tc.retrieveErr) @@ -752,7 +752,7 @@ func TestRemove(t *testing.T) { }{ { desc: "remove an existing config", - id: c.ThingID, + id: c.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -760,7 +760,7 @@ func TestRemove(t *testing.T) { }, { desc: "remove removed config", - id: c.ThingID, + id: c.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -768,7 +768,7 @@ func TestRemove(t *testing.T) { }, { desc: "remove a config with failed remove", - id: c.ThingID, + id: c.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -889,7 +889,7 @@ func TestChangeState(t *testing.T) { { desc: "change state to Active", state: bootstrap.Active, - id: c.ThingID, + id: c.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -898,7 +898,7 @@ func TestChangeState(t *testing.T) { { desc: "change state to current state", state: bootstrap.Active, - id: c.ThingID, + id: c.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -907,7 +907,7 @@ func TestChangeState(t *testing.T) { { desc: "change state to Inactive", state: bootstrap.Inactive, - id: c.ThingID, + id: c.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -916,17 +916,17 @@ func TestChangeState(t *testing.T) { { desc: "change state with failed Connect", state: bootstrap.Active, - id: c.ThingID, + id: c.ClientID, token: validToken, userID: validID, domainID: domainID, - connectErr: errors.NewSDKError(bootstrap.ErrThings), - err: bootstrap.ErrThings, + connectErr: errors.NewSDKError(bootstrap.ErrClients), + err: bootstrap.ErrClients, }, { desc: "change state with invalid state", state: bootstrap.State(2), - id: c.ThingID, + id: c.ClientID, token: validToken, userID: validID, domainID: domainID, @@ -1026,7 +1026,7 @@ func TestRemoveConfigHandler(t *testing.T) { }{ { desc: "remove an existing config", - id: config.ThingID, + id: config.ClientID, err: nil, }, { @@ -1038,7 +1038,7 @@ func TestRemoveConfigHandler(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - repoCall := boot.On("RemoveThing", context.Background(), mock.Anything).Return(tc.err) + repoCall := boot.On("RemoveClient", context.Background(), mock.Anything).Return(tc.err) err := svc.RemoveConfigHandler(context.Background(), 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() @@ -1046,66 +1046,66 @@ func TestRemoveConfigHandler(t *testing.T) { } } -func TestConnectThingsHandler(t *testing.T) { +func TestConnectClientHandler(t *testing.T) { svc := newService() cases := []struct { desc string - thingID string + clientID string channelID string err error }{ { desc: "connect", channelID: channel.ID, - thingID: config.ThingID, + clientID: config.ClientID, err: nil, }, { desc: "connect connected", channelID: channel.ID, - thingID: config.ThingID, + clientID: config.ClientID, err: svcerr.ErrAddPolicies, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - repoCall := boot.On("ConnectThing", context.Background(), mock.Anything, mock.Anything).Return(tc.err) - err := svc.ConnectThingHandler(context.Background(), tc.channelID, tc.thingID) + repoCall := boot.On("ConnectClient", context.Background(), mock.Anything, mock.Anything).Return(tc.err) + err := svc.ConnectClientHandler(context.Background(), tc.channelID, tc.clientID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) repoCall.Unset() }) } } -func TestDisconnectThingsHandler(t *testing.T) { +func TestDisconnectClientsHandler(t *testing.T) { svc := newService() cases := []struct { desc string - thingID string + clientID string channelID string err error }{ { desc: "disconnect", channelID: channel.ID, - thingID: config.ThingID, + clientID: config.ClientID, err: nil, }, { desc: "disconnect disconnected", channelID: channel.ID, - thingID: config.ThingID, + clientID: config.ClientID, err: nil, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - repoCall := boot.On("DisconnectThing", context.Background(), mock.Anything, mock.Anything).Return(tc.err) - err := svc.DisconnectThingHandler(context.Background(), tc.channelID, tc.thingID) + repoCall := boot.On("DisconnectClient", context.Background(), mock.Anything, mock.Anything).Return(tc.err) + err := svc.DisconnectClientHandler(context.Background(), tc.channelID, tc.clientID) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) repoCall.Unset() }) diff --git a/bootstrap/state.go b/bootstrap/state.go index da8acccbb3..ec230a8dc4 100644 --- a/bootstrap/state.go +++ b/bootstrap/state.go @@ -6,18 +6,18 @@ package bootstrap import "strconv" const ( - // Inactive Thing is created, but not able to exchange messages using Magistrala. + // Inactive Client is created, but not able to exchange messages using Magistrala. Inactive State = iota - // Active Thing is created, configured, and whitelisted. + // Active Client is created, configured, and whitelisted. Active ) -// State represents corresponding Magistrala Thing state. The possible Config States +// State represents corresponding Magistrala Client state. The possible Config States // as well as description of what that State represents are given in the table: // | State | What it means | // |----------+--------------------------------------------------------------------------------| -// | Inactive | Thing is created, but isn't able to communicate over Magistrala | -// | Active | Thing is able to communicate using Magistrala |. +// | Inactive | Client is created, but isn't able to communicate over Magistrala | +// | Active | Client is able to communicate using Magistrala |. type State int // String returns string representation of State. diff --git a/bootstrap/tracing/tracing.go b/bootstrap/tracing/tracing.go index fee7e3547a..8cd6b57eee 100644 --- a/bootstrap/tracing/tracing.go +++ b/bootstrap/tracing/tracing.go @@ -27,7 +27,7 @@ func New(svc bootstrap.Service, tracer trace.Tracer) bootstrap.Service { // Add traces the "Add" operation of the wrapped bootstrap.Service. func (tm *tracingMiddleware) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) { ctx, span := tm.tracer.Start(ctx, "svc_register_user", trace.WithAttributes( - attribute.String("thing_id", cfg.ThingID), + attribute.String("client_id", cfg.ClientID), attribute.String("domain_id ", cfg.DomainID), attribute.String("name", cfg.Name), attribute.String("external_id", cfg.ExternalID), @@ -54,7 +54,7 @@ func (tm *tracingMiddleware) Update(ctx context.Context, session mgauthn.Session ctx, span := tm.tracer.Start(ctx, "svc_update_user", trace.WithAttributes( attribute.String("name", cfg.Name), attribute.String("content", cfg.Content), - attribute.String("thing_id", cfg.ThingID), + attribute.String("client_id", cfg.ClientID), attribute.String("domain_id ", cfg.DomainID), )) defer span.End() @@ -63,13 +63,13 @@ func (tm *tracingMiddleware) Update(ctx context.Context, session mgauthn.Session } // UpdateCert traces the "UpdateCert" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) UpdateCert(ctx context.Context, session mgauthn.Session, thingID, clientCert, clientKey, caCert string) (bootstrap.Config, error) { +func (tm *tracingMiddleware) UpdateCert(ctx context.Context, session mgauthn.Session, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) { ctx, span := tm.tracer.Start(ctx, "svc_update_cert", trace.WithAttributes( - attribute.String("thing_id", thingID), + attribute.String("client_id", clientID), )) defer span.End() - return tm.svc.UpdateCert(ctx, session, thingID, clientCert, clientKey, caCert) + return tm.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert) } // UpdateConnections traces the "UpdateConnections" operation of the wrapped bootstrap.Service. @@ -159,24 +159,24 @@ func (tm *tracingMiddleware) RemoveChannelHandler(ctx context.Context, id string return tm.svc.RemoveChannelHandler(ctx, id) } -// ConnectThingHandler traces the "ConnectThingHandler" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) ConnectThingHandler(ctx context.Context, channelID, thingID string) error { - ctx, span := tm.tracer.Start(ctx, "svc_connect_thing_handler", trace.WithAttributes( +// ConnectClientHandler traces the "ConnectClientHandler" operation of the wrapped bootstrap.Service. +func (tm *tracingMiddleware) ConnectClientHandler(ctx context.Context, channelID, clientID string) error { + ctx, span := tm.tracer.Start(ctx, "svc_connect_client_handler", trace.WithAttributes( attribute.String("channel_id", channelID), - attribute.String("thing_id", thingID), + attribute.String("client_id", clientID), )) defer span.End() - return tm.svc.ConnectThingHandler(ctx, channelID, thingID) + return tm.svc.ConnectClientHandler(ctx, channelID, clientID) } -// DisconnectThingHandler traces the "DisconnectThingHandler" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) DisconnectThingHandler(ctx context.Context, channelID, thingID string) error { - ctx, span := tm.tracer.Start(ctx, "svc_disconnect_thing_handler", trace.WithAttributes( +// DisconnectClientHandler traces the "DisconnectClientHandler" operation of the wrapped bootstrap.Service. +func (tm *tracingMiddleware) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error { + ctx, span := tm.tracer.Start(ctx, "svc_disconnect_client_handler", trace.WithAttributes( attribute.String("channel_id", channelID), - attribute.String("thing_id", thingID), + attribute.String("client_id", clientID), )) defer span.End() - return tm.svc.DisconnectThingHandler(ctx, channelID, thingID) + return tm.svc.DisconnectClientHandler(ctx, channelID, clientID) } diff --git a/certs/README.md b/certs/README.md index b7f2b3cf6a..ee450f1198 100644 --- a/certs/README.md +++ b/certs/README.md @@ -1,6 +1,6 @@ # Certs Service -Issues certificates for things. `Certs` service can create certificates to be used when `Magistrala` is deployed to support mTLS. +Issues certificates for clients. `Certs` service can create certificates to be used when `Magistrala` is deployed to support mTLS. Certificate service can create certificates using PKI mode - where certificates issued by PKI, when you deploy `Vault` as PKI certificate management `cert` service will proxy requests to `Vault` previously checking access rights and saving info on successfully created certificate. ## PKI mode @@ -16,60 +16,60 @@ MG_CERTS_VAULT_HOST= MG_CERTS_VAULT_NAMESPACE= MG_CERTS_VAULT_APPROLE_ROLEID= MG_CERTS_VAULT_APPROLE_SECRET= -MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH= -MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME= +MG_CERTS_VAULT_CLIENTS_CERTS_PKI_PATH= +MG_CERTS_VAULT_CLIENTS_CERTS_PKI_ROLE_NAME= ``` -The certificates can also be revoked using `certs` service. To revoke a certificate you need to provide `thing_id` of the thing for which the certificate was issued. +The certificates can also be revoked using `certs` service. To revoke a certificate you need to provide `client_id` of the client for which the certificate was issued. ```bash -curl -s -S -X DELETE http://localhost:9019/certs/revoke -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"thing_id":"c30b8842-507c-4bcd-973c-74008cef3be5"}' +curl -s -S -X DELETE http://localhost:9019/certs/revoke -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"client_id":"c30b8842-507c-4bcd-973c-74008cef3be5"}' ``` ## Configuration The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| :---------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| MG_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info | -| MG_CERTS_HTTP_HOST | Service Certs host | "" | -| MG_CERTS_HTTP_PORT | Service Certs port | 9019 | -| MG_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| MG_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | -| MG_AUTH_GRPC_URL | Auth service gRPC URL | [localhost:8181](localhost:8181) | -| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | -| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" | -| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" | -| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" | -| MG_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt | -| MG_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key | -| MG_CERTS_VAULT_HOST | Vault host | http://vault:8200 | -| MG_CERTS_VAULT_NAMESPACE | Vault namespace in which pki is present | magistrala | -| MG_CERTS_VAULT_APPROLE_ROLEID | Vault AppRole auth RoleID | magistrala | -| MG_CERTS_VAULT_APPROLE_SECRET | Vault AppRole auth Secret | magistrala | -| MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH | Vault PKI path for issuing Things Certificates | pki_int | -| MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME | Vault PKI Role Name for issuing Things Certificates | magistrala_things_certs | -| MG_CERTS_DB_HOST | Database host | localhost | -| MG_CERTS_DB_PORT | Database port | 5432 | -| MG_CERTS_DB_PASS | Database password | magistrala | -| MG_CERTS_DB_USER | Database user | magistrala | -| MG_CERTS_DB_NAME | Database name | certs | -| MG_CERTS_DB_SSL_MODE | Database SSL mode | disable | -| MG_CERTS_DB_SSL_CERT | Database SSL certificate | "" | -| MG_CERTS_DB_SSL_KEY | Database SSL key | "" | -| MG_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" | -| MG_THINGS_URL | Things service URL | [localhost:9000](localhost:9000) | -| MG_JAEGER_URL | Jaeger server URL | [http://localhost:4318/v1/traces](http://localhost:4318//v1/traces) | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_CERTS_INSTANCE_ID | Service instance ID | "" | +| Variable | Description | Default | +| :----------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| MG_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info | +| MG_CERTS_HTTP_HOST | Service Certs host | "" | +| MG_CERTS_HTTP_PORT | Service Certs port | 9019 | +| MG_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| MG_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | +| MG_AUTH_GRPC_URL | Auth service gRPC URL | [localhost:8181](localhost:8181) | +| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | +| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" | +| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" | +| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" | +| MG_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt | +| MG_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key | +| MG_CERTS_VAULT_HOST | Vault host | http://vault:8200 | +| MG_CERTS_VAULT_NAMESPACE | Vault namespace in which pki is present | magistrala | +| MG_CERTS_VAULT_APPROLE_ROLEID | Vault AppRole auth RoleID | magistrala | +| MG_CERTS_VAULT_APPROLE_SECRET | Vault AppRole auth Secret | magistrala | +| MG_CERTS_VAULT_CLIENTS_CERTS_PKI_PATH | Vault PKI path for issuing Clients Certificates | pki_int | +| MG_CERTS_VAULT_CLIENTS_CERTS_PKI_ROLE_NAME | Vault PKI Role Name for issuing Clients Certificates | magistrala_clients_certs | +| MG_CERTS_DB_HOST | Database host | localhost | +| MG_CERTS_DB_PORT | Database port | 5432 | +| MG_CERTS_DB_PASS | Database password | magistrala | +| MG_CERTS_DB_USER | Database user | magistrala | +| MG_CERTS_DB_NAME | Database name | certs | +| MG_CERTS_DB_SSL_MODE | Database SSL mode | disable | +| MG_CERTS_DB_SSL_CERT | Database SSL certificate | "" | +| MG_CERTS_DB_SSL_KEY | Database SSL key | "" | +| MG_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" | +| MG_CLIENTS_URL | Clients service URL | [localhost:9000](localhost:9000) | +| MG_JAEGER_URL | Jaeger server URL | [http://localhost:4318/v1/traces](http://localhost:4318//v1/traces) | +| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_CERTS_INSTANCE_ID | Service instance ID | "" | ## Deployment The service is distributed as Docker container. Check the [`certs`](https://github.com/absmach/magistrala/blob/main/docker/addons/bootstrap/docker-compose.yml) service section in docker-compose file to see how the service is deployed. -Running this service outside of container requires working instance of the auth service, things service, postgres database, vault and Jaeger server. +Running this service outside of container requires working instance of the auth service, clients service, postgres database, vault and Jaeger server. To start the service outside of the container, execute the following shell script: ```bash @@ -101,8 +101,8 @@ MG_CERTS_VAULT_HOST=http://vault:8200 \ MG_CERTS_VAULT_NAMESPACE=magistrala \ MG_CERTS_VAULT_APPROLE_ROLEID=magistrala \ MG_CERTS_VAULT_APPROLE_SECRET=magistrala \ -MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH=pki_int \ -MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME=magistrala_things_certs \ +MG_CERTS_VAULT_CLIENTS_CERTS_PKI_PATH=pki_int \ +MG_CERTS_VAULT_CLIENTS_CERTS_PKI_ROLE_NAME=magistrala_clients_certs \ MG_CERTS_DB_HOST=localhost \ MG_CERTS_DB_PORT=5432 \ MG_CERTS_DB_PASS=magistrala \ @@ -112,7 +112,7 @@ MG_CERTS_DB_SSL_MODE=disable \ MG_CERTS_DB_SSL_CERT="" \ MG_CERTS_DB_SSL_KEY="" \ MG_CERTS_DB_SSL_ROOT_CERT="" \ -MG_THINGS_URL=localhost:9000 \ +MG_CLIENTS_URL=localhost:9000 \ MG_JAEGER_URL=http://localhost:14268/api/traces \ MG_JAEGER_TRACE_RATIO=1.0 \ MG_SEND_TELEMETRY=true \ diff --git a/certs/api/endpoint.go b/certs/api/endpoint.go index 8e03f47281..764217a422 100644 --- a/certs/api/endpoint.go +++ b/certs/api/endpoint.go @@ -18,14 +18,14 @@ func issueCert(svc certs.Service) endpoint.Endpoint { if err := req.validate(); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - res, err := svc.IssueCert(ctx, req.domainID, req.token, req.ThingID, req.TTL) + res, err := svc.IssueCert(ctx, req.domainID, req.token, req.ClientID, req.TTL) if err != nil { return certsRes{}, errors.Wrap(apiutil.ErrValidation, err) } return certsRes{ SerialNumber: res.SerialNumber, - ThingID: res.ThingID, + ClientID: res.ClientID, Certificate: res.Certificate, ExpiryTime: res.ExpiryTime, Revoked: res.Revoked, @@ -41,7 +41,7 @@ func listSerials(svc certs.Service) endpoint.Endpoint { return nil, errors.Wrap(apiutil.ErrValidation, err) } - page, err := svc.ListSerials(ctx, req.thingID, req.pm) + page, err := svc.ListSerials(ctx, req.clientID, req.pm) if err != nil { return certsPageRes{}, errors.Wrap(apiutil.ErrValidation, err) } @@ -59,7 +59,7 @@ func listSerials(svc certs.Service) endpoint.Endpoint { SerialNumber: cert.SerialNumber, ExpiryTime: cert.ExpiryTime, Revoked: cert.Revoked, - ThingID: cert.ThingID, + ClientID: cert.ClientID, } res.Certs = append(res.Certs, cr) } @@ -80,7 +80,7 @@ func viewCert(svc certs.Service) endpoint.Endpoint { } return certsRes{ - ThingID: cert.ThingID, + ClientID: cert.ClientID, Certificate: cert.Certificate, Key: cert.Key, SerialNumber: cert.SerialNumber, diff --git a/certs/api/endpoint_test.go b/certs/api/endpoint_test.go index 6cc2c143bb..35636dccf7 100644 --- a/certs/api/endpoint_test.go +++ b/certs/api/endpoint_test.go @@ -31,11 +31,11 @@ var ( contentType = "application/json" valid = "valid" invalid = "invalid" - thingID = testsutil.GenerateUUID(&testing.T{}) + clientID = testsutil.GenerateUUID(&testing.T{}) serial = testsutil.GenerateUUID(&testing.T{}) ttl = "1h" cert = certs.Cert{ - ThingID: thingID, + ClientID: clientID, SerialNumber: serial, ExpiryTime: time.Now().Add(time.Hour), } @@ -79,8 +79,8 @@ func TestIssueCert(t *testing.T) { cs, svc, auth := newCertServer() defer cs.Close() - validReqString := `{"thing_id": "%s","ttl": "%s"}` - invalidReqString := `{"thing_id": "%s","ttl": %s}` + validReqString := `{"client_id": "%s","ttl": "%s"}` + invalidReqString := `{"client_id": "%s","ttl": %s}` cases := []struct { desc string @@ -88,7 +88,7 @@ func TestIssueCert(t *testing.T) { token string session mgauthn.Session contentType string - thingID string + clientID string ttl string request string status int @@ -102,9 +102,9 @@ func TestIssueCert(t *testing.T) { token: valid, domainID: valid, contentType: contentType, - thingID: thingID, + clientID: clientID, ttl: ttl, - request: fmt.Sprintf(validReqString, thingID, ttl), + request: fmt.Sprintf(validReqString, clientID, ttl), status: http.StatusCreated, svcRes: certs.Cert{SerialNumber: serial}, svcErr: nil, @@ -115,9 +115,9 @@ func TestIssueCert(t *testing.T) { token: valid, domainID: valid, contentType: contentType, - thingID: thingID, + clientID: clientID, ttl: ttl, - request: fmt.Sprintf(validReqString, thingID, ttl), + request: fmt.Sprintf(validReqString, clientID, ttl), status: http.StatusUnprocessableEntity, svcRes: certs.Cert{}, svcErr: svcerr.ErrCreateEntity, @@ -127,9 +127,9 @@ func TestIssueCert(t *testing.T) { desc: "issue with invalid token", token: invalid, contentType: contentType, - thingID: thingID, + clientID: clientID, ttl: ttl, - request: fmt.Sprintf(validReqString, thingID, ttl), + request: fmt.Sprintf(validReqString, clientID, ttl), status: http.StatusUnauthorized, svcRes: certs.Cert{}, authenticateErr: svcerr.ErrAuthentication, @@ -139,7 +139,7 @@ func TestIssueCert(t *testing.T) { desc: "issue with empty token", domainID: valid, contentType: contentType, - request: fmt.Sprintf(validReqString, thingID, ttl), + request: fmt.Sprintf(validReqString, clientID, ttl), status: http.StatusUnauthorized, svcRes: certs.Cert{}, svcErr: nil, @@ -150,14 +150,14 @@ func TestIssueCert(t *testing.T) { token: valid, domainID: "", contentType: contentType, - request: fmt.Sprintf(validReqString, thingID, ttl), + request: fmt.Sprintf(validReqString, clientID, ttl), status: http.StatusBadRequest, svcRes: certs.Cert{}, svcErr: nil, err: apiutil.ErrMissingDomainID, }, { - desc: "issue with empty thing id", + desc: "issue with empty client id", token: valid, domainID: valid, contentType: contentType, @@ -172,7 +172,7 @@ func TestIssueCert(t *testing.T) { token: valid, domainID: valid, contentType: contentType, - request: fmt.Sprintf(validReqString, thingID, ""), + request: fmt.Sprintf(validReqString, clientID, ""), status: http.StatusBadRequest, svcRes: certs.Cert{}, svcErr: nil, @@ -183,7 +183,7 @@ func TestIssueCert(t *testing.T) { token: valid, domainID: valid, contentType: contentType, - request: fmt.Sprintf(validReqString, thingID, invalid), + request: fmt.Sprintf(validReqString, clientID, invalid), status: http.StatusBadRequest, svcRes: certs.Cert{}, svcErr: nil, @@ -194,7 +194,7 @@ func TestIssueCert(t *testing.T) { token: valid, domainID: valid, contentType: "application/xml", - request: fmt.Sprintf(validReqString, thingID, ttl), + request: fmt.Sprintf(validReqString, clientID, ttl), status: http.StatusUnsupportedMediaType, svcRes: certs.Cert{}, svcErr: nil, @@ -205,7 +205,7 @@ func TestIssueCert(t *testing.T) { token: valid, domainID: valid, contentType: contentType, - request: fmt.Sprintf(invalidReqString, thingID, ttl), + request: fmt.Sprintf(invalidReqString, clientID, ttl), status: http.StatusInternalServerError, svcRes: certs.Cert{}, svcErr: nil, @@ -227,7 +227,7 @@ func TestIssueCert(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("IssueCert", mock.Anything, tc.domainID, tc.token, tc.thingID, tc.ttl).Return(tc.svcRes, tc.svcErr) + svcCall := svc.On("IssueCert", mock.Anything, tc.domainID, tc.token, tc.clientID, tc.ttl).Return(tc.svcRes, tc.svcErr) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var errRes respBody @@ -433,7 +433,7 @@ func TestListSerials(t *testing.T) { token string domainID string session mgauthn.Session - thingID string + clientID string revoked string offset uint64 limit uint64 @@ -448,7 +448,7 @@ func TestListSerials(t *testing.T) { desc: "list certs successfully with default limit", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: revoked, offset: 0, limit: 10, @@ -467,7 +467,7 @@ func TestListSerials(t *testing.T) { desc: "list certs successfully with default revoke", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: revoked, offset: 0, limit: 10, @@ -486,7 +486,7 @@ func TestListSerials(t *testing.T) { desc: "list certs successfully with all certs", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: "all", offset: 0, limit: 10, @@ -505,7 +505,7 @@ func TestListSerials(t *testing.T) { desc: "list certs successfully with limit", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: revoked, offset: 0, limit: 5, @@ -524,7 +524,7 @@ func TestListSerials(t *testing.T) { desc: "list certs successfully with offset", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: revoked, offset: 1, limit: 10, @@ -543,7 +543,7 @@ func TestListSerials(t *testing.T) { desc: "list certs successfully with offset and limit", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: revoked, offset: 1, limit: 5, @@ -562,7 +562,7 @@ func TestListSerials(t *testing.T) { desc: "list with invalid token", domainID: valid, token: invalid, - thingID: thingID, + clientID: clientID, revoked: revoked, offset: 0, limit: 10, @@ -576,7 +576,7 @@ func TestListSerials(t *testing.T) { desc: "list with empty token", domainID: valid, token: "", - thingID: thingID, + clientID: clientID, revoked: revoked, offset: 0, limit: 10, @@ -590,7 +590,7 @@ func TestListSerials(t *testing.T) { desc: "list with limit exceeding max limit", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: revoked, query: "?limit=1000", status: http.StatusBadRequest, @@ -602,7 +602,7 @@ func TestListSerials(t *testing.T) { desc: "list with invalid offset", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: revoked, query: "?offset=invalid", status: http.StatusBadRequest, @@ -614,7 +614,7 @@ func TestListSerials(t *testing.T) { desc: "list with invalid limit", domainID: valid, token: valid, - thingID: thingID, + clientID: clientID, revoked: revoked, query: "?limit=invalid", status: http.StatusBadRequest, @@ -623,10 +623,10 @@ func TestListSerials(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list with invalid thing id", + desc: "list with invalid client id", domainID: valid, token: valid, - thingID: invalid, + clientID: invalid, revoked: revoked, offset: 0, limit: 10, @@ -642,14 +642,14 @@ func TestListSerials(t *testing.T) { req := testRequest{ client: cs.Client(), method: http.MethodGet, - url: fmt.Sprintf("%s/%s/serials/%s", cs.URL, tc.domainID, tc.thingID) + tc.query, + url: fmt.Sprintf("%s/%s/serials/%s", cs.URL, tc.domainID, tc.clientID) + tc.query, token: tc.token, } if tc.token == valid { tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ListSerials", mock.Anything, tc.thingID, certs.PageMetadata{Revoked: tc.revoked, Offset: tc.offset, Limit: tc.limit}).Return(tc.svcRes, tc.svcErr) + svcCall := svc.On("ListSerials", mock.Anything, tc.clientID, certs.PageMetadata{Revoked: tc.revoked, Offset: tc.offset, Limit: tc.limit}).Return(tc.svcRes, tc.svcErr) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var errRes respBody diff --git a/certs/api/logging.go b/certs/api/logging.go index 7a8c3b7d33..1f9a5c2348 100644 --- a/certs/api/logging.go +++ b/certs/api/logging.go @@ -25,13 +25,13 @@ func LoggingMiddleware(svc certs.Service, logger *slog.Logger) certs.Service { return &loggingMiddleware{logger, svc} } -// IssueCert logs the issue_cert request. It logs the ttl, thing ID and the time it took to complete the request. +// IssueCert logs the issue_cert request. It logs the ttl, client ID and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) IssueCert(ctx context.Context, domainID, token, thingID, ttl string) (c certs.Cert, err error) { +func (lm *loggingMiddleware) IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (c certs.Cert, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", thingID), + slog.String("client_id", clientID), slog.String("ttl", ttl), } if err != nil { @@ -42,15 +42,15 @@ func (lm *loggingMiddleware) IssueCert(ctx context.Context, domainID, token, thi lm.logger.Info("Issue certificate completed successfully", args...) }(time.Now()) - return lm.svc.IssueCert(ctx, domainID, token, thingID, ttl) + return lm.svc.IssueCert(ctx, domainID, token, clientID, ttl) } -// ListCerts logs the list_certs request. It logs the thing ID and the time it took to complete the request. -func (lm *loggingMiddleware) ListCerts(ctx context.Context, thingID string, pm certs.PageMetadata) (cp certs.CertPage, err error) { +// ListCerts logs the list_certs request. It logs the client ID and the time it took to complete the request. +func (lm *loggingMiddleware) ListCerts(ctx context.Context, clientID string, pm certs.PageMetadata) (cp certs.CertPage, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", thingID), + slog.String("client_id", clientID), slog.Group("page", slog.Uint64("offset", cp.Offset), slog.Uint64("limit", cp.Limit), @@ -65,16 +65,16 @@ func (lm *loggingMiddleware) ListCerts(ctx context.Context, thingID string, pm c lm.logger.Info("List certificates completed successfully", args...) }(time.Now()) - return lm.svc.ListCerts(ctx, thingID, pm) + return lm.svc.ListCerts(ctx, clientID, pm) } -// ListSerials logs the list_serials request. It logs the thing ID and the time it took to complete the request. +// ListSerials logs the list_serials request. It logs the client ID and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) ListSerials(ctx context.Context, thingID string, pm certs.PageMetadata) (cp certs.CertPage, err error) { +func (lm *loggingMiddleware) ListSerials(ctx context.Context, clientID string, pm certs.PageMetadata) (cp certs.CertPage, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", thingID), + slog.String("client_id", clientID), slog.String("revoke", pm.Revoked), slog.Group("page", slog.Uint64("offset", cp.Offset), @@ -90,7 +90,7 @@ func (lm *loggingMiddleware) ListSerials(ctx context.Context, thingID string, pm lm.logger.Info("List certificates serials completed successfully", args...) }(time.Now()) - return lm.svc.ListSerials(ctx, thingID, pm) + return lm.svc.ListSerials(ctx, clientID, pm) } // ViewCert logs the view_cert request. It logs the serial ID and the time it took to complete the request. @@ -112,13 +112,13 @@ func (lm *loggingMiddleware) ViewCert(ctx context.Context, serialID string) (c c return lm.svc.ViewCert(ctx, serialID) } -// RevokeCert logs the revoke_cert request. It logs the thing ID and the time it took to complete the request. +// RevokeCert logs the revoke_cert request. It logs the client ID and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) RevokeCert(ctx context.Context, domainID, token, thingID string) (c certs.Revoke, err error) { +func (lm *loggingMiddleware) RevokeCert(ctx context.Context, domainID, token, clientID string) (c certs.Revoke, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", thingID), + slog.String("client_id", clientID), } if err != nil { args = append(args, slog.Any("error", err)) @@ -128,5 +128,5 @@ func (lm *loggingMiddleware) RevokeCert(ctx context.Context, domainID, token, th lm.logger.Info("Revoke certificate completed successfully", args...) }(time.Now()) - return lm.svc.RevokeCert(ctx, domainID, token, thingID) + return lm.svc.RevokeCert(ctx, domainID, token, clientID) } diff --git a/certs/api/metrics.go b/certs/api/metrics.go index 9f78fd012c..c95d241a54 100644 --- a/certs/api/metrics.go +++ b/certs/api/metrics.go @@ -31,33 +31,33 @@ func MetricsMiddleware(svc certs.Service, counter metrics.Counter, latency metri } // IssueCert instruments IssueCert method with metrics. -func (ms *metricsMiddleware) IssueCert(ctx context.Context, domainID, token, thingID, ttl string) (certs.Cert, error) { +func (ms *metricsMiddleware) IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (certs.Cert, error) { defer func(begin time.Time) { ms.counter.With("method", "issue_cert").Add(1) ms.latency.With("method", "issue_cert").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.IssueCert(ctx, domainID, token, thingID, ttl) + return ms.svc.IssueCert(ctx, domainID, token, clientID, ttl) } // ListCerts instruments ListCerts method with metrics. -func (ms *metricsMiddleware) ListCerts(ctx context.Context, thingID string, pm certs.PageMetadata) (certs.CertPage, error) { +func (ms *metricsMiddleware) ListCerts(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) { defer func(begin time.Time) { ms.counter.With("method", "list_certs").Add(1) ms.latency.With("method", "list_certs").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.ListCerts(ctx, thingID, pm) + return ms.svc.ListCerts(ctx, clientID, pm) } // ListSerials instruments ListSerials method with metrics. -func (ms *metricsMiddleware) ListSerials(ctx context.Context, thingID string, pm certs.PageMetadata) (certs.CertPage, error) { +func (ms *metricsMiddleware) ListSerials(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) { defer func(begin time.Time) { ms.counter.With("method", "list_serials").Add(1) ms.latency.With("method", "list_serials").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.ListSerials(ctx, thingID, pm) + return ms.svc.ListSerials(ctx, clientID, pm) } // ViewCert instruments ViewCert method with metrics. @@ -71,11 +71,11 @@ func (ms *metricsMiddleware) ViewCert(ctx context.Context, serialID string) (cer } // RevokeCert instruments RevokeCert method with metrics. -func (ms *metricsMiddleware) RevokeCert(ctx context.Context, domainID, token, thingID string) (certs.Revoke, error) { +func (ms *metricsMiddleware) RevokeCert(ctx context.Context, domainID, token, clientID string) (certs.Revoke, error) { defer func(begin time.Time) { ms.counter.With("method", "revoke_cert").Add(1) ms.latency.With("method", "revoke_cert").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.RevokeCert(ctx, domainID, token, thingID) + return ms.svc.RevokeCert(ctx, domainID, token, clientID) } diff --git a/certs/api/requests.go b/certs/api/requests.go index 54bea166bd..c149ffe594 100644 --- a/certs/api/requests.go +++ b/certs/api/requests.go @@ -15,7 +15,7 @@ const maxLimitSize = 100 type addCertsReq struct { token string domainID string - ThingID string `json:"thing_id"` + ClientID string `json:"client_id"` TTL string `json:"ttl"` } @@ -28,7 +28,7 @@ func (req addCertsReq) validate() error { return apiutil.ErrMissingDomainID } - if req.ThingID == "" { + if req.ClientID == "" { return apiutil.ErrMissingID } @@ -44,8 +44,8 @@ func (req addCertsReq) validate() error { } type listReq struct { - thingID string - pm certs.PageMetadata + clientID string + pm certs.PageMetadata } func (req *listReq) validate() error { diff --git a/certs/api/responses.go b/certs/api/responses.go index 4b5f15d481..43c363c148 100644 --- a/certs/api/responses.go +++ b/certs/api/responses.go @@ -20,7 +20,7 @@ type certsPageRes struct { } type certsRes struct { - ThingID string `json:"thing_id"` + ClientID string `json:"client_id"` Certificate string `json:"certificate,omitempty"` Key string `json:"key,omitempty"` SerialNumber string `json:"serial_number"` diff --git a/certs/api/transport.go b/certs/api/transport.go index 4d71d1aac9..5811eebe27 100644 --- a/certs/api/transport.go +++ b/certs/api/transport.go @@ -62,7 +62,7 @@ func MakeHandler(svc certs.Service, authn mgauthn.Authentication, logger *slog.L opts..., ), "revoke").ServeHTTP) }) - r.Get("/serials/{thingID}", otelhttp.NewHandler(kithttp.NewServer( + r.Get("/serials/{clientID}", otelhttp.NewHandler(kithttp.NewServer( listSerials(svc), decodeListCerts, api.EncodeResponse, @@ -91,7 +91,7 @@ func decodeListCerts(_ context.Context, r *http.Request) (interface{}, error) { } req := listReq{ - thingID: chi.URLParam(r, "thingID"), + clientID: chi.URLParam(r, "clientID"), pm: certs.PageMetadata{ Offset: o, Limit: l, diff --git a/certs/certs.go b/certs/certs.go index f1d4f1bb6c..de09e77afa 100644 --- a/certs/certs.go +++ b/certs/certs.go @@ -19,7 +19,7 @@ type Cert struct { Key string `json:"key,omitempty"` Revoked bool `json:"revoked"` ExpiryTime time.Time `json:"expiry_time"` - ThingID string `json:"entity_id"` + ClientID string `json:"entity_id"` } type CertPage struct { @@ -33,7 +33,7 @@ type PageMetadata struct { Total uint64 `json:"total,omitempty"` Offset uint64 `json:"offset,omitempty"` Limit uint64 `json:"limit,omitempty"` - ThingID string `json:"thing_id,omitempty"` + ClientID string `json:"client_id,omitempty"` Token string `json:"token,omitempty"` CommonName string `json:"common_name,omitempty"` Revoked string `json:"revoked,omitempty"` diff --git a/certs/mocks/service.go b/certs/mocks/service.go index 864f3e28dd..56db28b733 100644 --- a/certs/mocks/service.go +++ b/certs/mocks/service.go @@ -17,9 +17,9 @@ type Service struct { mock.Mock } -// IssueCert provides a mock function with given fields: ctx, domainID, token, thingID, ttl -func (_m *Service) IssueCert(ctx context.Context, domainID string, token string, thingID string, ttl string) (certs.Cert, error) { - ret := _m.Called(ctx, domainID, token, thingID, ttl) +// IssueCert provides a mock function with given fields: ctx, domainID, token, clientID, ttl +func (_m *Service) IssueCert(ctx context.Context, domainID string, token string, clientID string, ttl string) (certs.Cert, error) { + ret := _m.Called(ctx, domainID, token, clientID, ttl) if len(ret) == 0 { panic("no return value specified for IssueCert") @@ -28,16 +28,16 @@ func (_m *Service) IssueCert(ctx context.Context, domainID string, token string, var r0 certs.Cert var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) (certs.Cert, error)); ok { - return rf(ctx, domainID, token, thingID, ttl) + return rf(ctx, domainID, token, clientID, ttl) } if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) certs.Cert); ok { - r0 = rf(ctx, domainID, token, thingID, ttl) + r0 = rf(ctx, domainID, token, clientID, ttl) } else { r0 = ret.Get(0).(certs.Cert) } if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok { - r1 = rf(ctx, domainID, token, thingID, ttl) + r1 = rf(ctx, domainID, token, clientID, ttl) } else { r1 = ret.Error(1) } @@ -45,9 +45,9 @@ func (_m *Service) IssueCert(ctx context.Context, domainID string, token string, return r0, r1 } -// ListCerts provides a mock function with given fields: ctx, thingID, pm -func (_m *Service) ListCerts(ctx context.Context, thingID string, pm certs.PageMetadata) (certs.CertPage, error) { - ret := _m.Called(ctx, thingID, pm) +// ListCerts provides a mock function with given fields: ctx, clientID, pm +func (_m *Service) ListCerts(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) { + ret := _m.Called(ctx, clientID, pm) if len(ret) == 0 { panic("no return value specified for ListCerts") @@ -56,16 +56,16 @@ func (_m *Service) ListCerts(ctx context.Context, thingID string, pm certs.PageM var r0 certs.CertPage var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) (certs.CertPage, error)); ok { - return rf(ctx, thingID, pm) + return rf(ctx, clientID, pm) } if rf, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) certs.CertPage); ok { - r0 = rf(ctx, thingID, pm) + r0 = rf(ctx, clientID, pm) } else { r0 = ret.Get(0).(certs.CertPage) } if rf, ok := ret.Get(1).(func(context.Context, string, certs.PageMetadata) error); ok { - r1 = rf(ctx, thingID, pm) + r1 = rf(ctx, clientID, pm) } else { r1 = ret.Error(1) } @@ -73,9 +73,9 @@ func (_m *Service) ListCerts(ctx context.Context, thingID string, pm certs.PageM return r0, r1 } -// ListSerials provides a mock function with given fields: ctx, thingID, pm -func (_m *Service) ListSerials(ctx context.Context, thingID string, pm certs.PageMetadata) (certs.CertPage, error) { - ret := _m.Called(ctx, thingID, pm) +// ListSerials provides a mock function with given fields: ctx, clientID, pm +func (_m *Service) ListSerials(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) { + ret := _m.Called(ctx, clientID, pm) if len(ret) == 0 { panic("no return value specified for ListSerials") @@ -84,16 +84,16 @@ func (_m *Service) ListSerials(ctx context.Context, thingID string, pm certs.Pag var r0 certs.CertPage var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) (certs.CertPage, error)); ok { - return rf(ctx, thingID, pm) + return rf(ctx, clientID, pm) } if rf, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) certs.CertPage); ok { - r0 = rf(ctx, thingID, pm) + r0 = rf(ctx, clientID, pm) } else { r0 = ret.Get(0).(certs.CertPage) } if rf, ok := ret.Get(1).(func(context.Context, string, certs.PageMetadata) error); ok { - r1 = rf(ctx, thingID, pm) + r1 = rf(ctx, clientID, pm) } else { r1 = ret.Error(1) } @@ -101,9 +101,9 @@ func (_m *Service) ListSerials(ctx context.Context, thingID string, pm certs.Pag return r0, r1 } -// RevokeCert provides a mock function with given fields: ctx, domainID, token, thingID -func (_m *Service) RevokeCert(ctx context.Context, domainID string, token string, thingID string) (certs.Revoke, error) { - ret := _m.Called(ctx, domainID, token, thingID) +// RevokeCert provides a mock function with given fields: ctx, domainID, token, clientID +func (_m *Service) RevokeCert(ctx context.Context, domainID string, token string, clientID string) (certs.Revoke, error) { + ret := _m.Called(ctx, domainID, token, clientID) if len(ret) == 0 { panic("no return value specified for RevokeCert") @@ -112,16 +112,16 @@ func (_m *Service) RevokeCert(ctx context.Context, domainID string, token string var r0 certs.Revoke var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (certs.Revoke, error)); ok { - return rf(ctx, domainID, token, thingID) + return rf(ctx, domainID, token, clientID) } if rf, ok := ret.Get(0).(func(context.Context, string, string, string) certs.Revoke); ok { - r0 = rf(ctx, domainID, token, thingID) + r0 = rf(ctx, domainID, token, clientID) } else { r0 = ret.Get(0).(certs.Revoke) } if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok { - r1 = rf(ctx, domainID, token, thingID) + r1 = rf(ctx, domainID, token, clientID) } else { r1 = ret.Error(1) } diff --git a/certs/pki/amcerts/am_certs.go b/certs/pki/amcerts/am_certs.go index b5247aecb9..01484d4eff 100644 --- a/certs/pki/amcerts/am_certs.go +++ b/certs/pki/amcerts/am_certs.go @@ -14,7 +14,7 @@ type Cert struct { Key string `json:"key,omitempty"` Revoked bool `json:"revoked"` ExpiryTime time.Time `json:"expiry_time"` - ThingID string `json:"entity_id"` + ClientID string `json:"entity_id"` DownloadUrl string `json:"-"` } @@ -64,7 +64,7 @@ func (c sdkAgent) Issue(entityId, ttl string, ipAddrs []string) (Cert, error) { Certificate: cert.Certificate, Revoked: cert.Revoked, ExpiryTime: cert.ExpiryTime, - ThingID: cert.EntityID, + ClientID: cert.EntityID, }, nil } @@ -79,7 +79,7 @@ func (c sdkAgent) View(serial string) (Cert, error) { Key: cert.Key, Revoked: cert.Revoked, ExpiryTime: cert.ExpiryTime, - ThingID: cert.EntityID, + ClientID: cert.EntityID, }, nil } @@ -105,7 +105,7 @@ func (c sdkAgent) ListCerts(pm sdk.PageMetadata) (CertPage, error) { Key: c.Key, Revoked: c.Revoked, ExpiryTime: c.ExpiryTime, - ThingID: c.EntityID, + ClientID: c.EntityID, }) } diff --git a/certs/service.go b/certs/service.go index d5e3980537..fe28cd5609 100644 --- a/certs/service.go +++ b/certs/service.go @@ -33,20 +33,20 @@ var _ Service = (*certsService)(nil) // //go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" type Service interface { - // IssueCert issues certificate for given thing id if access is granted with token - IssueCert(ctx context.Context, domainID, token, thingID, ttl string) (Cert, error) + // IssueCert issues certificate for given client id if access is granted with token + IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (Cert, error) - // ListCerts lists certificates issued for a given thing ID - ListCerts(ctx context.Context, thingID string, pm PageMetadata) (CertPage, error) + // ListCerts lists certificates issued for a given client ID + ListCerts(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error) - // ListSerials lists certificate serial IDs issued for a given thing ID - ListSerials(ctx context.Context, thingID string, pm PageMetadata) (CertPage, error) + // ListSerials lists certificate serial IDs issued for a given client ID + ListSerials(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error) // ViewCert retrieves the certificate issued for a given serial ID ViewCert(ctx context.Context, serialID string) (Cert, error) - // RevokeCert revokes a certificate for a given thing ID - RevokeCert(ctx context.Context, domainID, token, thingID string) (Revoke, error) + // RevokeCert revokes a certificate for a given client ID + RevokeCert(ctx context.Context, domainID, token, clientID string) (Revoke, error) } type certsService struct { @@ -67,15 +67,15 @@ type Revoke struct { RevocationTime time.Time `mapstructure:"revocation_time"` } -func (cs *certsService) IssueCert(ctx context.Context, domainID, token, thingID, ttl string) (Cert, error) { +func (cs *certsService) IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (Cert, error) { var err error - thing, err := cs.sdk.Thing(thingID, domainID, token) + client, err := cs.sdk.Client(clientID, domainID, token) if err != nil { return Cert{}, errors.Wrap(ErrFailedCertCreation, err) } - cert, err := cs.pki.Issue(thing.ID, ttl, []string{}) + cert, err := cs.pki.Issue(client.ID, ttl, []string{}) if err != nil { return Cert{}, errors.Wrap(ErrFailedCertCreation, err) } @@ -86,20 +86,20 @@ func (cs *certsService) IssueCert(ctx context.Context, domainID, token, thingID, Key: cert.Key, Revoked: cert.Revoked, ExpiryTime: cert.ExpiryTime, - ThingID: cert.ThingID, + ClientID: cert.ClientID, }, err } -func (cs *certsService) RevokeCert(ctx context.Context, domainID, token, thingID string) (Revoke, error) { +func (cs *certsService) RevokeCert(ctx context.Context, domainID, token, clientID string) (Revoke, error) { var revoke Revoke var err error - thing, err := cs.sdk.Thing(thingID, domainID, token) + client, err := cs.sdk.Client(clientID, domainID, token) if err != nil { return revoke, errors.Wrap(ErrFailedCertRevocation, err) } - cp, err := cs.pki.ListCerts(sdk.PageMetadata{Offset: 0, Limit: 10000, EntityID: thing.ID}) + cp, err := cs.pki.ListCerts(sdk.PageMetadata{Offset: 0, Limit: 10000, EntityID: client.ID}) if err != nil { return revoke, errors.Wrap(ErrFailedCertRevocation, err) } @@ -115,8 +115,8 @@ func (cs *certsService) RevokeCert(ctx context.Context, domainID, token, thingID return revoke, nil } -func (cs *certsService) ListCerts(ctx context.Context, thingID string, pm PageMetadata) (CertPage, error) { - cp, err := cs.pki.ListCerts(sdk.PageMetadata{Offset: pm.Offset, Limit: pm.Limit, EntityID: thingID}) +func (cs *certsService) ListCerts(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error) { + cp, err := cs.pki.ListCerts(sdk.PageMetadata{Offset: pm.Offset, Limit: pm.Limit, EntityID: clientID}) if err != nil { return CertPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } @@ -130,7 +130,7 @@ func (cs *certsService) ListCerts(ctx context.Context, thingID string, pm PageMe Key: c.Key, Revoked: c.Revoked, ExpiryTime: c.ExpiryTime, - ThingID: c.ThingID, + ClientID: c.ClientID, }) } @@ -142,8 +142,8 @@ func (cs *certsService) ListCerts(ctx context.Context, thingID string, pm PageMe }, nil } -func (cs *certsService) ListSerials(ctx context.Context, thingID string, pm PageMetadata) (CertPage, error) { - cp, err := cs.pki.ListCerts(sdk.PageMetadata{Offset: pm.Offset, Limit: pm.Limit, EntityID: thingID}) +func (cs *certsService) ListSerials(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error) { + cp, err := cs.pki.ListCerts(sdk.PageMetadata{Offset: pm.Offset, Limit: pm.Limit, EntityID: clientID}) if err != nil { return CertPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } @@ -153,7 +153,7 @@ func (cs *certsService) ListSerials(ctx context.Context, thingID string, pm Page if (pm.Revoked == "true" && c.Revoked) || (pm.Revoked == "false" && !c.Revoked) || (pm.Revoked == "all") { certs = append(certs, Cert{ SerialNumber: c.SerialNumber, - ThingID: c.ThingID, + ClientID: c.ClientID, ExpiryTime: c.ExpiryTime, Revoked: c.Revoked, }) @@ -180,6 +180,6 @@ func (cs *certsService) ViewCert(ctx context.Context, serialID string) (Cert, er Key: cert.Key, Revoked: cert.Revoked, ExpiryTime: cert.ExpiryTime, - ThingID: cert.ThingID, + ClientID: cert.ClientID, }, nil } diff --git a/certs/service_test.go b/certs/service_test.go index 540885877b..036a6ee00e 100644 --- a/certs/service_test.go +++ b/certs/service_test.go @@ -21,16 +21,16 @@ import ( ) const ( - invalid = "invalid" - email = "user@example.com" - domain = "domain" - token = "token" - thingsNum = 1 - thingKey = "thingKey" - thingID = "1" - ttl = "1h" - certNum = 10 - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + invalid = "invalid" + email = "user@example.com" + domain = "domain" + token = "token" + clientsNum = 1 + clientKey = "clientKey" + clientID = "1" + ttl = "1h" + certNum = 10 + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" ) func newService(_ *testing.T) (certs.Service, *mocks.Agent, *sdkmocks.SDK) { @@ -41,7 +41,7 @@ func newService(_ *testing.T) (certs.Service, *mocks.Agent, *sdkmocks.SDK) { } var cert = mgcrt.Cert{ - ThingID: thingID, + ClientID: clientID, SerialNumber: "Serial", ExpiryTime: time.Now().Add(time.Duration(1000)), Revoked: false, @@ -53,12 +53,12 @@ func TestIssueCert(t *testing.T) { domainID string token string desc string - thingID string + clientID string ttl string ipAddr []string key string cert mgcrt.Cert - thingErr errors.SDKError + clientErr errors.SDKError issueCertErr error err error }{ @@ -66,7 +66,7 @@ func TestIssueCert(t *testing.T) { desc: "issue new cert", domainID: domain, token: token, - thingID: thingID, + clientID: clientID, ttl: ttl, ipAddr: []string{}, cert: cert, @@ -75,40 +75,40 @@ func TestIssueCert(t *testing.T) { desc: "issue new for failed pki", domainID: domain, token: token, - thingID: thingID, + clientID: clientID, ttl: ttl, ipAddr: []string{}, - thingErr: nil, + clientErr: nil, issueCertErr: certs.ErrFailedCertCreation, err: certs.ErrFailedCertCreation, }, { - desc: "issue new cert for non existing thing id", - domainID: domain, - token: token, - thingID: "2", - ttl: ttl, - ipAddr: []string{}, - thingErr: errors.NewSDKError(errors.ErrMalformedEntity), - err: certs.ErrFailedCertCreation, + desc: "issue new cert for non existing client id", + domainID: domain, + token: token, + clientID: "2", + ttl: ttl, + ipAddr: []string{}, + clientErr: errors.NewSDKError(errors.ErrMalformedEntity), + err: certs.ErrFailedCertCreation, }, { - desc: "issue new cert for invalid token", - domainID: domain, - token: invalid, - thingID: thingID, - ttl: ttl, - ipAddr: []string{}, - thingErr: errors.NewSDKError(svcerr.ErrAuthentication), - err: svcerr.ErrAuthentication, + desc: "issue new cert for invalid token", + domainID: domain, + token: invalid, + clientID: clientID, + ttl: ttl, + ipAddr: []string{}, + clientErr: errors.NewSDKError(svcerr.ErrAuthentication), + err: svcerr.ErrAuthentication, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdk.On("Thing", tc.thingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.thingID, Credentials: mgsdk.ClientCredentials{Secret: thingKey}}, tc.thingErr) - agentCall := agent.On("Issue", thingID, tc.ttl, tc.ipAddr).Return(tc.cert, tc.issueCertErr) - resp, err := svc.IssueCert(context.Background(), tc.domainID, tc.token, tc.thingID, tc.ttl) + sdkCall := sdk.On("Client", tc.clientID, tc.domainID, tc.token).Return(mgsdk.Client{ID: tc.clientID, Credentials: mgsdk.ClientCredentials{Secret: clientKey}}, tc.clientErr) + agentCall := agent.On("Issue", clientID, tc.ttl, tc.ipAddr).Return(tc.cert, tc.issueCertErr) + resp, err := svc.IssueCert(context.Background(), tc.domainID, tc.token, tc.clientID, tc.ttl) 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.cert.SerialNumber, resp.SerialNumber, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.cert.SerialNumber, resp.SerialNumber)) sdkCall.Unset() @@ -123,10 +123,10 @@ func TestRevokeCert(t *testing.T) { domainID string token string desc string - thingID string + clientID string page mgcrt.CertPage authErr error - thingErr errors.SDKError + clientErr errors.SDKError revokeErr error listErr error err error @@ -135,32 +135,32 @@ func TestRevokeCert(t *testing.T) { desc: "revoke cert", domainID: domain, token: token, - thingID: thingID, + clientID: clientID, page: mgcrt.CertPage{Limit: 10000, Offset: 0, Total: 1, Certificates: []mgcrt.Cert{cert}}, }, { desc: "revoke cert for failed pki revoke", domainID: domain, token: token, - thingID: thingID, + clientID: clientID, page: mgcrt.CertPage{Limit: 10000, Offset: 0, Total: 1, Certificates: []mgcrt.Cert{cert}}, revokeErr: certs.ErrFailedCertRevocation, err: certs.ErrFailedCertRevocation, }, { - desc: "revoke cert for invalid thing id", - domainID: domain, - token: token, - thingID: "2", - page: mgcrt.CertPage{}, - thingErr: errors.NewSDKError(certs.ErrFailedCertCreation), - err: certs.ErrFailedCertRevocation, + desc: "revoke cert for invalid client id", + domainID: domain, + token: token, + clientID: "2", + page: mgcrt.CertPage{}, + clientErr: errors.NewSDKError(certs.ErrFailedCertCreation), + err: certs.ErrFailedCertRevocation, }, { desc: "revoke cert with failed to list certs", domainID: domain, token: token, - thingID: thingID, + clientID: clientID, page: mgcrt.CertPage{}, listErr: certs.ErrFailedCertRevocation, err: certs.ErrFailedCertRevocation, @@ -169,10 +169,10 @@ func TestRevokeCert(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdk.On("Thing", tc.thingID, tc.domainID, tc.token).Return(mgsdk.Thing{ID: tc.thingID, Credentials: mgsdk.ClientCredentials{Secret: thingKey}}, tc.thingErr) + sdkCall := sdk.On("Client", tc.clientID, tc.domainID, tc.token).Return(mgsdk.Client{ID: tc.clientID, Credentials: mgsdk.ClientCredentials{Secret: clientKey}}, tc.clientErr) agentCall := agent.On("Revoke", mock.Anything).Return(tc.revokeErr) agentCall1 := agent.On("ListCerts", mock.Anything).Return(tc.page, tc.listErr) - _, err := svc.RevokeCert(context.Background(), tc.domainID, tc.token, tc.thingID) + _, err := svc.RevokeCert(context.Background(), tc.domainID, 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)) sdkCall.Unset() agentCall.Unset() @@ -186,7 +186,7 @@ func TestListCerts(t *testing.T) { var mycerts []mgcrt.Cert for i := 0; i < certNum; i++ { c := mgcrt.Cert{ - ThingID: thingID, + ClientID: clientID, SerialNumber: fmt.Sprintf("%d", i), ExpiryTime: time.Now().Add(time.Hour), } @@ -194,40 +194,40 @@ func TestListCerts(t *testing.T) { } cases := []struct { - desc string - thingID string - page mgcrt.CertPage - listErr error - err error + desc string + clientID string + page mgcrt.CertPage + listErr error + err error }{ { - desc: "list all certs successfully", - thingID: thingID, - page: mgcrt.CertPage{Limit: certNum, Offset: 0, Total: certNum, Certificates: mycerts}, + desc: "list all certs successfully", + clientID: clientID, + page: mgcrt.CertPage{Limit: certNum, Offset: 0, Total: certNum, Certificates: mycerts}, }, { - desc: "list all certs with failed pki", - thingID: thingID, - page: mgcrt.CertPage{}, - listErr: svcerr.ErrViewEntity, - err: svcerr.ErrViewEntity, + desc: "list all certs with failed pki", + clientID: clientID, + page: mgcrt.CertPage{}, + listErr: svcerr.ErrViewEntity, + err: svcerr.ErrViewEntity, }, { - desc: "list half certs successfully", - thingID: thingID, - page: mgcrt.CertPage{Limit: certNum, Offset: certNum / 2, Total: certNum / 2, Certificates: mycerts[certNum/2:]}, + desc: "list half certs successfully", + clientID: clientID, + page: mgcrt.CertPage{Limit: certNum, Offset: certNum / 2, Total: certNum / 2, Certificates: mycerts[certNum/2:]}, }, { - desc: "list last cert successfully", - thingID: thingID, - page: mgcrt.CertPage{Limit: certNum, Offset: certNum - 1, Total: 1, Certificates: []mgcrt.Cert{mycerts[certNum-1]}}, + desc: "list last cert successfully", + clientID: clientID, + page: mgcrt.CertPage{Limit: certNum, Offset: certNum - 1, Total: 1, Certificates: []mgcrt.Cert{mycerts[certNum-1]}}, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { agentCall := agent.On("ListCerts", mock.Anything).Return(tc.page, tc.listErr) - page, err := svc.ListCerts(context.Background(), tc.thingID, certs.PageMetadata{Offset: tc.page.Offset, Limit: tc.page.Limit}) + page, err := svc.ListCerts(context.Background(), tc.clientID, certs.PageMetadata{Offset: tc.page.Offset, Limit: tc.page.Limit}) size := uint64(len(page.Certificates)) assert.Equal(t, tc.page.Total, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Total, size)) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -243,7 +243,7 @@ func TestListSerials(t *testing.T) { var issuedCerts []mgcrt.Cert for i := 0; i < certNum; i++ { crt := mgcrt.Cert{ - ThingID: cert.ThingID, + ClientID: cert.ClientID, SerialNumber: cert.SerialNumber, ExpiryTime: cert.ExpiryTime, Revoked: false, @@ -252,55 +252,55 @@ func TestListSerials(t *testing.T) { } cases := []struct { - desc string - thingID string - revoke string - offset uint64 - limit uint64 - certs []mgcrt.Cert - listErr error - err error + desc string + clientID string + revoke string + offset uint64 + limit uint64 + certs []mgcrt.Cert + listErr error + err error }{ { - desc: "list all certs successfully", - thingID: thingID, - revoke: revoke, - offset: 0, - limit: certNum, - certs: issuedCerts, + desc: "list all certs successfully", + clientID: clientID, + revoke: revoke, + offset: 0, + limit: certNum, + certs: issuedCerts, }, { - desc: "list all certs with failed pki", - thingID: thingID, - revoke: revoke, - offset: 0, - limit: certNum, - certs: nil, - listErr: svcerr.ErrViewEntity, - err: svcerr.ErrViewEntity, + desc: "list all certs with failed pki", + clientID: clientID, + revoke: revoke, + offset: 0, + limit: certNum, + certs: nil, + listErr: svcerr.ErrViewEntity, + err: svcerr.ErrViewEntity, }, { - desc: "list half certs successfully", - thingID: thingID, - revoke: revoke, - offset: certNum / 2, - limit: certNum, - certs: issuedCerts[certNum/2:], + desc: "list half certs successfully", + clientID: clientID, + revoke: revoke, + offset: certNum / 2, + limit: certNum, + certs: issuedCerts[certNum/2:], }, { - desc: "list last cert successfully", - thingID: thingID, - revoke: revoke, - offset: certNum - 1, - limit: certNum, - certs: []mgcrt.Cert{issuedCerts[certNum-1]}, + desc: "list last cert successfully", + clientID: clientID, + revoke: revoke, + offset: certNum - 1, + limit: certNum, + certs: []mgcrt.Cert{issuedCerts[certNum-1]}, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { agentCall := agent.On("ListCerts", mock.Anything).Return(mgcrt.CertPage{Certificates: tc.certs}, tc.listErr) - page, err := svc.ListSerials(context.Background(), tc.thingID, certs.PageMetadata{Revoked: tc.revoke, Offset: tc.offset, Limit: tc.limit}) + page, err := svc.ListSerials(context.Background(), tc.clientID, certs.PageMetadata{Revoked: tc.revoke, Offset: tc.offset, Limit: tc.limit}) assert.Equal(t, len(tc.certs), len(page.Certificates), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.certs, page.Certificates)) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) agentCall.Unset() diff --git a/certs/tracing/tracing.go b/certs/tracing/tracing.go index 48a0173dfe..f4a9b56072 100644 --- a/certs/tracing/tracing.go +++ b/certs/tracing/tracing.go @@ -24,38 +24,38 @@ func New(svc certs.Service, tracer trace.Tracer) certs.Service { } // IssueCert traces the "IssueCert" operation of the wrapped certs.Service. -func (tm *tracingMiddleware) IssueCert(ctx context.Context, domainID, token, thingID, ttl string) (certs.Cert, error) { +func (tm *tracingMiddleware) IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (certs.Cert, error) { ctx, span := tm.tracer.Start(ctx, "svc_create_group", trace.WithAttributes( - attribute.String("thing_id", thingID), + attribute.String("client_id", clientID), attribute.String("ttl", ttl), )) defer span.End() - return tm.svc.IssueCert(ctx, domainID, token, thingID, ttl) + return tm.svc.IssueCert(ctx, domainID, token, clientID, ttl) } // ListCerts traces the "ListCerts" operation of the wrapped certs.Service. -func (tm *tracingMiddleware) ListCerts(ctx context.Context, thingID string, pm certs.PageMetadata) (certs.CertPage, error) { +func (tm *tracingMiddleware) ListCerts(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) { ctx, span := tm.tracer.Start(ctx, "svc_list_certs", trace.WithAttributes( - attribute.String("thing_id", thingID), + attribute.String("client_id", clientID), attribute.Int64("offset", int64(pm.Offset)), attribute.Int64("limit", int64(pm.Limit)), )) defer span.End() - return tm.svc.ListCerts(ctx, thingID, pm) + return tm.svc.ListCerts(ctx, clientID, pm) } // ListSerials traces the "ListSerials" operation of the wrapped certs.Service. -func (tm *tracingMiddleware) ListSerials(ctx context.Context, thingID string, pm certs.PageMetadata) (certs.CertPage, error) { +func (tm *tracingMiddleware) ListSerials(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) { ctx, span := tm.tracer.Start(ctx, "svc_list_serials", trace.WithAttributes( - attribute.String("thing_id", thingID), + attribute.String("client_id", clientID), attribute.Int64("offset", int64(pm.Offset)), attribute.Int64("limit", int64(pm.Limit)), )) defer span.End() - return tm.svc.ListSerials(ctx, thingID, pm) + return tm.svc.ListSerials(ctx, clientID, pm) } // ViewCert traces the "ViewCert" operation of the wrapped certs.Service. diff --git a/channels/api/grpc/client.go b/channels/api/grpc/client.go new file mode 100644 index 0000000000..c72692d1ce --- /dev/null +++ b/channels/api/grpc/client.go @@ -0,0 +1,165 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + "fmt" + "time" + + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/go-kit/kit/endpoint" + kitgrpc "github.com/go-kit/kit/transport/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const svcName = "channels.v1.ChannelsService" + +var _ grpcChannelsV1.ChannelsServiceClient = (*grpcClient)(nil) + +type grpcClient struct { + timeout time.Duration + authorize endpoint.Endpoint + removeClientConnections endpoint.Endpoint + unsetParentGroupFromChannels endpoint.Endpoint +} + +// NewClient returns new gRPC client instance. +func NewClient(conn *grpc.ClientConn, timeout time.Duration) grpcChannelsV1.ChannelsServiceClient { + return &grpcClient{ + authorize: kitgrpc.NewClient( + conn, + svcName, + "Authorize", + encodeAuthorizeRequest, + decodeAuthorizeResponse, + grpcChannelsV1.AuthzRes{}, + ).Endpoint(), + removeClientConnections: kitgrpc.NewClient( + conn, + svcName, + "RemoveClientConnections", + encodeRemoveClientConnectionsRequest, + decodeRemoveClientConnectionsResponse, + grpcChannelsV1.RemoveClientConnectionsRes{}, + ).Endpoint(), + unsetParentGroupFromChannels: kitgrpc.NewClient( + conn, + svcName, + "UnsetParentGroupFromChannels", + encodeUnsetParentGroupFromChannelsRequest, + decodeUnsetParentGroupFromChannelsResponse, + grpcChannelsV1.UnsetParentGroupFromChannelsRes{}, + ).Endpoint(), + timeout: timeout, + } +} + +func (client grpcClient) Authorize(ctx context.Context, req *grpcChannelsV1.AuthzReq, _ ...grpc.CallOption) (r *grpcChannelsV1.AuthzRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + res, err := client.authorize(ctx, authorizeReq{ + domainID: req.GetDomainId(), + clientID: req.GetClientId(), + clientType: req.GetClientType(), + channelID: req.GetChannelId(), + connType: connections.ConnType(req.GetType()), + }) + if err != nil { + return &grpcChannelsV1.AuthzRes{}, decodeError(err) + } + + ar := res.(authorizeRes) + + return &grpcChannelsV1.AuthzRes{Authorized: ar.authorized}, nil +} + +func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(authorizeReq) + + return &grpcChannelsV1.AuthzReq{ + DomainId: req.domainID, + ClientId: req.clientID, + ClientType: req.clientType, + ChannelId: req.channelID, + Type: uint32(req.connType), + }, nil +} + +func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(*grpcChannelsV1.AuthzRes) + + return authorizeRes{authorized: res.GetAuthorized()}, nil +} + +func (client grpcClient) RemoveClientConnections(ctx context.Context, req *grpcChannelsV1.RemoveClientConnectionsReq, _ ...grpc.CallOption) (r *grpcChannelsV1.RemoveClientConnectionsRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + if _, err := client.removeClientConnections(ctx, req); err != nil { + return &grpcChannelsV1.RemoveClientConnectionsRes{}, decodeError(err) + } + + return &grpcChannelsV1.RemoveClientConnectionsRes{}, nil +} + +func encodeRemoveClientConnectionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + return grpcReq.(*grpcChannelsV1.RemoveClientConnectionsReq), nil +} + +func decodeRemoveClientConnectionsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + return grpcRes.(*grpcChannelsV1.RemoveClientConnectionsRes), nil +} + +func (client grpcClient) UnsetParentGroupFromChannels(ctx context.Context, req *grpcChannelsV1.UnsetParentGroupFromChannelsReq, _ ...grpc.CallOption) (r *grpcChannelsV1.UnsetParentGroupFromChannelsRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + if _, err := client.unsetParentGroupFromChannels(ctx, req); err != nil { + return &grpcChannelsV1.UnsetParentGroupFromChannelsRes{}, decodeError(err) + } + + return &grpcChannelsV1.UnsetParentGroupFromChannelsRes{}, nil +} + +func encodeUnsetParentGroupFromChannelsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + return grpcReq.(*grpcChannelsV1.UnsetParentGroupFromChannelsReq), nil +} + +func decodeUnsetParentGroupFromChannelsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + return grpcRes.(*grpcChannelsV1.UnsetParentGroupFromChannelsRes), nil +} + +func decodeError(err error) error { + if st, ok := status.FromError(err); ok { + switch st.Code() { + case codes.Unauthenticated: + return errors.Wrap(svcerr.ErrAuthentication, errors.New(st.Message())) + case codes.PermissionDenied: + return errors.Wrap(svcerr.ErrAuthorization, errors.New(st.Message())) + case codes.InvalidArgument: + return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) + case codes.FailedPrecondition: + return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) + case codes.NotFound: + return errors.Wrap(svcerr.ErrNotFound, errors.New(st.Message())) + case codes.AlreadyExists: + return errors.Wrap(svcerr.ErrConflict, errors.New(st.Message())) + case codes.OK: + if msg := st.Message(); msg != "" { + return errors.Wrap(errors.ErrUnidentified, errors.New(msg)) + } + return nil + default: + return errors.Wrap(fmt.Errorf("unexpected gRPC status: %s (status code:%v)", st.Code().String(), st.Code()), errors.New(st.Message())) + } + } + return err +} diff --git a/things/api/grpc/doc.go b/channels/api/grpc/doc.go similarity index 100% rename from things/api/grpc/doc.go rename to channels/api/grpc/doc.go diff --git a/channels/api/grpc/endpoint.go b/channels/api/grpc/endpoint.go new file mode 100644 index 0000000000..97856bf4db --- /dev/null +++ b/channels/api/grpc/endpoint.go @@ -0,0 +1,54 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + + ch "github.com/absmach/magistrala/channels" + channels "github.com/absmach/magistrala/channels/private" + "github.com/go-kit/kit/endpoint" +) + +func authorizeEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(authorizeReq) + + if err := svc.Authorize(ctx, ch.AuthzReq{ + DomainID: req.domainID, + ClientID: req.clientID, + ClientType: req.clientType, + ChannelID: req.channelID, + Type: req.connType, + }); err != nil { + return authorizeRes{}, err + } + + return authorizeRes{authorized: true}, nil + } +} + +func removeClientConnectionsEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(removeClientConnectionsReq) + + if err := svc.RemoveClientConnections(ctx, req.clientID); err != nil { + return removeClientConnectionsRes{}, err + } + + return removeClientConnectionsRes{}, nil + } +} + +func unsetParentGroupFromChannelsEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(unsetParentGroupFromChannelsReq) + + if err := svc.UnsetParentGroupFromChannels(ctx, req.parentGroupID); err != nil { + return unsetParentGroupFromChannelsRes{}, err + } + + return unsetParentGroupFromChannelsRes{}, nil + } +} diff --git a/channels/api/grpc/endpoint_test.go b/channels/api/grpc/endpoint_test.go new file mode 100644 index 0000000000..73e9454f51 --- /dev/null +++ b/channels/api/grpc/endpoint_test.go @@ -0,0 +1,37 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc_test + +import ( + "fmt" + "net" + + grpcapi "github.com/absmach/magistrala/clients/api/grpc" + "github.com/absmach/magistrala/clients/private/mocks" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + "google.golang.org/grpc" +) + +const port = 7000 + +var ( + clientID = "testID" + clientKey = "testKey" + channelID = "testID" + invalid = "invalid" +) + +func startGRPCServer(svc *mocks.Service, port int) { + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(fmt.Sprintf("failed to obtain port: %s", err)) + } + server := grpc.NewServer() + grpcClientsV1.RegisterClientsServiceServer(server, grpcapi.NewServer(svc)) + go func() { + if err := server.Serve(listener); err != nil { + panic(fmt.Sprintf("failed to serve: %s", err)) + } + }() +} diff --git a/channels/api/grpc/request.go b/channels/api/grpc/request.go new file mode 100644 index 0000000000..1be561a865 --- /dev/null +++ b/channels/api/grpc/request.go @@ -0,0 +1,21 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import "github.com/absmach/magistrala/pkg/connections" + +type authorizeReq struct { + domainID string + channelID string + clientID string + clientType string + connType connections.ConnType +} +type removeClientConnectionsReq struct { + clientID string +} + +type unsetParentGroupFromChannelsReq struct { + parentGroupID string +} diff --git a/things/api/grpc/responses.go b/channels/api/grpc/responses.go similarity index 60% rename from things/api/grpc/responses.go rename to channels/api/grpc/responses.go index 8e11f1273f..8f5b5d2c7e 100644 --- a/things/api/grpc/responses.go +++ b/channels/api/grpc/responses.go @@ -4,6 +4,9 @@ package grpc type authorizeRes struct { - id string authorized bool } + +type removeClientConnectionsRes struct{} + +type unsetParentGroupFromChannelsRes struct{} diff --git a/channels/api/grpc/server.go b/channels/api/grpc/server.go new file mode 100644 index 0000000000..d2599ea9f6 --- /dev/null +++ b/channels/api/grpc/server.go @@ -0,0 +1,145 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + + mgauth "github.com/absmach/magistrala/auth" + channels "github.com/absmach/magistrala/channels/private" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + kitgrpc "github.com/go-kit/kit/transport/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var _ grpcChannelsV1.ChannelsServiceServer = (*grpcServer)(nil) + +type grpcServer struct { + grpcChannelsV1.UnimplementedChannelsServiceServer + + authorize kitgrpc.Handler + removeClientConnections kitgrpc.Handler + unsetParentGroupFromChannels kitgrpc.Handler +} + +// NewServer returns new AuthServiceServer instance. +func NewServer(svc channels.Service) grpcChannelsV1.ChannelsServiceServer { + return &grpcServer{ + authorize: kitgrpc.NewServer( + authorizeEndpoint(svc), + decodeAuthorizeRequest, + encodeAuthorizeResponse, + ), + removeClientConnections: kitgrpc.NewServer( + removeClientConnectionsEndpoint(svc), + decodeRemoveClientConnectionsRequest, + encodeRemoveClientConnectionsResponse, + ), + unsetParentGroupFromChannels: kitgrpc.NewServer( + unsetParentGroupFromChannelsEndpoint(svc), + decodeUnsetParentGroupFromChannelsRequest, + encodeUnsetParentGroupFromChannelsResponse, + ), + } +} + +func (s *grpcServer) Authorize(ctx context.Context, req *grpcChannelsV1.AuthzReq) (*grpcChannelsV1.AuthzRes, error) { + _, res, err := s.authorize.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcChannelsV1.AuthzRes), nil +} + +func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcChannelsV1.AuthzReq) + + connType := connections.ConnType(req.GetType()) + if err := connections.CheckConnType(connType); err != nil { + return nil, err + } + return authorizeReq{ + domainID: req.GetDomainId(), + clientID: req.GetClientId(), + clientType: req.GetClientType(), + channelID: req.GetChannelId(), + connType: connType, + }, nil +} + +func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(authorizeRes) + return &grpcChannelsV1.AuthzRes{Authorized: res.authorized}, nil +} + +func (s *grpcServer) RemoveClientConnections(ctx context.Context, req *grpcChannelsV1.RemoveClientConnectionsReq) (*grpcChannelsV1.RemoveClientConnectionsRes, error) { + _, res, err := s.removeClientConnections.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcChannelsV1.RemoveClientConnectionsRes), nil +} + +func decodeRemoveClientConnectionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcChannelsV1.RemoveClientConnectionsReq) + + return removeClientConnectionsReq{ + clientID: req.GetClientId(), + }, nil +} + +func encodeRemoveClientConnectionsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + _ = grpcRes.(removeClientConnectionsRes) + return &grpcChannelsV1.RemoveClientConnectionsRes{}, nil +} + +func (s *grpcServer) UnsetParentGroupFromChannels(ctx context.Context, req *grpcChannelsV1.UnsetParentGroupFromChannelsReq) (*grpcChannelsV1.UnsetParentGroupFromChannelsRes, error) { + _, res, err := s.unsetParentGroupFromChannels.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcChannelsV1.UnsetParentGroupFromChannelsRes), nil +} + +func decodeUnsetParentGroupFromChannelsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcChannelsV1.UnsetParentGroupFromChannelsReq) + + return unsetParentGroupFromChannelsReq{ + parentGroupID: req.GetParentGroupId(), + }, nil +} + +func encodeUnsetParentGroupFromChannelsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + _ = grpcRes.(unsetParentGroupFromChannelsRes) + return &grpcChannelsV1.UnsetParentGroupFromChannelsRes{}, 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.ErrMissingMemberType, + err == apiutil.ErrMissingPolicySub, + err == apiutil.ErrMissingPolicyObj, + err == apiutil.ErrMalformedPolicyAct: + return status.Error(codes.InvalidArgument, err.Error()) + case errors.Contains(err, svcerr.ErrAuthentication), + errors.Contains(err, mgauth.ErrKeyExpired), + err == apiutil.ErrMissingEmail, + err == apiutil.ErrBearerToken: + return status.Error(codes.Unauthenticated, err.Error()) + case errors.Contains(err, svcerr.ErrAuthorization): + return status.Error(codes.PermissionDenied, err.Error()) + default: + return status.Error(codes.Internal, err.Error()) + } +} diff --git a/channels/api/http/decode.go b/channels/api/http/decode.go new file mode 100644 index 0000000000..a8fdaf60de --- /dev/null +++ b/channels/api/http/decode.go @@ -0,0 +1,229 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "context" + "encoding/json" + "net/http" + "strings" + + mgclients "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/errors" + "github.com/go-chi/chi/v5" +) + +func decodeViewChannel(_ context.Context, r *http.Request) (interface{}, error) { + req := viewChannelReq{ + id: chi.URLParam(r, "channelID"), + } + + return req, nil +} + +func decodeCreateChannelReq(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + + req := createChannelReq{} + if err := json.NewDecoder(r.Body).Decode(&req.Channel); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeCreateChannelsReq(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + + req := createChannelsReq{} + if err := json.NewDecoder(r.Body).Decode(&req.Channels); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeListChannels(_ context.Context, r *http.Request) (interface{}, error) { + s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + n, err := apiutil.ReadStringQuery(r, api.NameKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + t, err := apiutil.ReadStringQuery(r, api.TagKey, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + id, err := apiutil.ReadStringQuery(r, api.IDOrder, "") + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + p, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + lp, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + st, err := mgclients.ToStatus(s) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + req := listChannelsReq{ + status: st, + offset: o, + limit: l, + metadata: m, + name: n, + tag: t, + permission: p, + listPerms: lp, + userID: chi.URLParam(r, "userID"), + id: id, + } + return req, nil +} + +func decodeUpdateChannel(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + + req := updateChannelReq{ + id: chi.URLParam(r, "channelID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeUpdateChannelTags(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + + req := updateChannelTagsReq{ + id: chi.URLParam(r, "channelID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeSetChannelParentGroupStatus(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + + req := setChannelParentGroupReq{ + id: chi.URLParam(r, "channelID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func decodeRemoveChannelParentGroupStatus(_ context.Context, r *http.Request) (interface{}, error) { + req := removeChannelParentGroupReq{ + id: chi.URLParam(r, "channelID"), + } + + return req, nil +} + +func decodeChangeChannelStatus(_ context.Context, r *http.Request) (interface{}, error) { + req := changeChannelStatusReq{ + id: chi.URLParam(r, "channelID"), + } + + return req, nil +} + +func decodeDeleteChannelReq(_ context.Context, r *http.Request) (interface{}, error) { + req := deleteChannelReq{ + id: chi.URLParam(r, "channelID"), + } + return req, nil +} + +func decodeConnectChannelClientRequest(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + req := connectChannelClientsRequest{ + channelID: chi.URLParam(r, "channelID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeDisconnectChannelClientsRequest(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + req := disconnectChannelClientsRequest{ + channelID: chi.URLParam(r, "channelID"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeConnectRequest(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + + req := connectRequest{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} + +func decodeDisconnectRequest(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + + req := disconnectRequest{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + + return req, nil +} diff --git a/channels/api/http/endpoints.go b/channels/api/http/endpoints.go new file mode 100644 index 0000000000..93f77ed424 --- /dev/null +++ b/channels/api/http/endpoints.go @@ -0,0 +1,369 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "context" + + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/go-kit/kit/endpoint" +) + +func createChannelEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createChannelReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + channels, err := svc.CreateChannels(ctx, session, req.Channel) + if err != nil { + return nil, err + } + + return createChannelRes{ + Channel: channels[0], + created: true, + }, nil + } +} + +func createChannelsEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createChannelsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + channels, err := svc.CreateChannels(ctx, session, req.Channels...) + if err != nil { + return nil, err + } + + res := channelsPageRes{ + pageRes: pageRes{ + Total: uint64(len(channels)), + }, + Channels: []viewChannelRes{}, + } + for _, c := range channels { + res.Channels = append(res.Channels, viewChannelRes{Channel: c}) + } + + return res, nil + } +} + +func viewChannelEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(viewChannelReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + c, err := svc.ViewChannel(ctx, session, req.id) + if err != nil { + return nil, err + } + + return viewChannelRes{Channel: c}, nil + } +} + +func listChannelsEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listChannelsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + pm := channels.PageMetadata{ + Status: req.status, + Offset: req.offset, + Limit: req.limit, + Name: req.name, + Tag: req.tag, + Permission: req.permission, + Metadata: req.metadata, + ListPerms: req.listPerms, + Id: req.id, + } + page, err := svc.ListChannels(ctx, session, pm) + if err != nil { + return nil, err + } + + res := channelsPageRes{ + pageRes: pageRes{ + Total: page.Total, + Offset: page.Offset, + Limit: page.Limit, + }, + Channels: []viewChannelRes{}, + } + for _, c := range page.Channels { + res.Channels = append(res.Channels, viewChannelRes{Channel: c}) + } + + return res, nil + } +} + +func updateChannelEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateChannelReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + ch := channels.Channel{ + ID: req.id, + Name: req.Name, + Metadata: req.Metadata, + } + ch, err := svc.UpdateChannel(ctx, session, ch) + if err != nil { + return nil, err + } + + return updateChannelRes{Channel: ch}, nil + } +} + +func updateChannelTagsEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateChannelTagsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + ch := channels.Channel{ + ID: req.id, + Tags: req.Tags, + } + ch, err := svc.UpdateChannelTags(ctx, session, ch) + if err != nil { + return nil, err + } + + return updateChannelRes{Channel: ch}, nil + } +} + +func setChannelParentGroupEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(setChannelParentGroupReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.SetParentGroup(ctx, session, req.ParentGroupID, req.id); err != nil { + return nil, err + } + + return setChannelParentGroupRes{}, nil + } +} + +func removeChannelParentGroupEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(removeChannelParentGroupReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.RemoveParentGroup(ctx, session, req.id); err != nil { + return nil, err + } + + return removeChannelParentGroupRes{}, nil + } +} + +func enableChannelEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(changeChannelStatusReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + ch, err := svc.EnableChannel(ctx, session, req.id) + if err != nil { + return nil, err + } + + return changeChannelStatusRes{Channel: ch}, nil + } +} + +func disableChannelEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(changeChannelStatusReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + ch, err := svc.DisableChannel(ctx, session, req.id) + if err != nil { + return nil, err + } + + return changeChannelStatusRes{Channel: ch}, nil + } +} + +func connectChannelClientEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connectChannelClientsRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.Connect(ctx, session, []string{req.channelID}, req.ClientIDs, req.Types); err != nil { + return nil, err + } + + return connectChannelClientsRes{}, nil + } +} + +func disconnectChannelClientsEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(disconnectChannelClientsRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.Disconnect(ctx, session, []string{req.channelID}, req.ClientIds, req.Types); err != nil { + return nil, err + } + + return disconnectChannelClientsRes{}, nil + } +} + +func connectEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connectRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.Connect(ctx, session, req.ChannelIds, req.ClientIds, req.Types); err != nil { + return nil, err + } + + return connectRes{}, nil + } +} + +func disconnectEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(disconnectRequest) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.Disconnect(ctx, session, req.ChannelIds, req.ClientIds, req.Types); err != nil { + return nil, err + } + + return disconnectRes{}, nil + } +} + +func deleteChannelEndpoint(svc channels.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(deleteChannelReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.RemoveChannel(ctx, session, req.id); err != nil { + return nil, err + } + + return deleteChannelRes{}, nil + } +} diff --git a/channels/api/http/requests.go b/channels/api/http/requests.go new file mode 100644 index 0000000000..ffafc35cc2 --- /dev/null +++ b/channels/api/http/requests.go @@ -0,0 +1,303 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "strings" + + "github.com/absmach/magistrala/channels" + mgclients "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/connections" +) + +type createChannelReq struct { + Channel channels.Channel +} + +func (req createChannelReq) validate() error { + if len(req.Channel.Name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + if req.Channel.ID != "" { + if strings.TrimSpace(req.Channel.ID) == "" { + return apiutil.ErrMissingChannelID + } + } + + return nil +} + +type createChannelsReq struct { + Channels []channels.Channel +} + +func (req createChannelsReq) validate() error { + if len(req.Channels) == 0 { + return apiutil.ErrEmptyList + } + for _, channel := range req.Channels { + if channel.ID != "" { + if strings.TrimSpace(channel.ID) == "" { + return apiutil.ErrMissingChannelID + } + } + if len(channel.Name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + } + + return nil +} + +type viewChannelReq struct { + id string +} + +func (req viewChannelReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + return nil +} + +type listChannelsReq struct { + status mgclients.Status + offset uint64 + limit uint64 + name string + tag string + permission string + visibility string + userID string + listPerms bool + metadata mgclients.Metadata + id string +} + +func (req listChannelsReq) validate() error { + if req.limit > api.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 + } + if len(req.name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + + return nil +} + +type updateChannelReq struct { + id string + Name string `json:"name,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +func (req updateChannelReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + if len(req.Name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + + return nil +} + +type updateChannelTagsReq struct { + id string + Tags []string `json:"tags,omitempty"` +} + +func (req updateChannelTagsReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + + return nil +} + +type setChannelParentGroupReq struct { + id string + ParentGroupID string `json:"parent_group_id"` +} + +func (req setChannelParentGroupReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + if req.ParentGroupID == "" { + return apiutil.ErrMissingParentGroupID + } + + return nil +} + +type removeChannelParentGroupReq struct { + id string +} + +func (req removeChannelParentGroupReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + + return nil +} + +type changeChannelStatusReq struct { + id string +} + +func (req changeChannelStatusReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + + return nil +} + +type connectChannelClientsRequest struct { + channelID string + ClientIDs []string `json:"client_ids,omitempty"` + Types []connections.ConnType `json:"types,omitempty"` +} + +func (req *connectChannelClientsRequest) validate() error { + if req.channelID == "" || strings.TrimSpace(req.channelID) == "" { + return apiutil.ErrMissingID + } + + if len(req.ClientIDs) == 0 { + return apiutil.ErrMissingID + } + + for _, tid := range req.ClientIDs { + if err := api.ValidateUUID(tid); err != nil { + return err + } + } + + if len(req.Types) == 0 { + return apiutil.ErrMissingConnectionType + } + + return nil +} + +type disconnectChannelClientsRequest struct { + channelID string + ClientIds []string `json:"client_ids,omitempty"` + Types []connections.ConnType `json:"types,omitempty"` +} + +func (req *disconnectChannelClientsRequest) validate() error { + if req.channelID == "" { + return apiutil.ErrMissingID + } + + if err := api.ValidateUUID(req.channelID); err != nil { + return err + } + + if len(req.ClientIds) == 0 { + return apiutil.ErrMissingID + } + + for _, tid := range req.ClientIds { + if err := api.ValidateUUID(tid); err != nil { + return err + } + } + + if len(req.Types) == 0 { + return apiutil.ErrMissingConnectionType + } + + return nil +} + +type connectRequest struct { + ChannelIds []string `json:"channel_ids,omitempty"` + ClientIds []string `json:"client_ids,omitempty"` + Types []connections.ConnType `json:"types,omitempty"` +} + +func (req *connectRequest) validate() error { + if len(req.ChannelIds) == 0 { + return apiutil.ErrMissingID + } + for _, cid := range req.ChannelIds { + if strings.TrimSpace(cid) == "" { + return apiutil.ErrMissingChannelID + } + } + + if len(req.ClientIds) == 0 { + return apiutil.ErrMissingID + } + + for _, tid := range req.ClientIds { + if strings.TrimSpace(tid) == "" { + return apiutil.ErrMissingChannelID + } + } + + if len(req.Types) == 0 { + return apiutil.ErrMissingConnectionType + } + + return nil +} + +type disconnectRequest struct { + ChannelIds []string `json:"channel_ids,omitempty"` + ClientIds []string `json:"client_ids,omitempty"` + Types []connections.ConnType `json:"types,omitempty"` +} + +func (req *disconnectRequest) validate() error { + if len(req.ChannelIds) == 0 { + return apiutil.ErrMissingID + } + for _, cid := range req.ChannelIds { + if err := api.ValidateUUID(cid); err != nil { + return err + } + } + + if len(req.ClientIds) == 0 { + return apiutil.ErrMissingID + } + + for _, tid := range req.ClientIds { + if err := api.ValidateUUID(tid); err != nil { + return err + } + } + + if len(req.Types) == 0 { + return apiutil.ErrMissingConnectionType + } + + return nil +} + +type deleteChannelReq struct { + id string +} + +func (req deleteChannelReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + return nil +} diff --git a/channels/api/http/responses.go b/channels/api/http/responses.go new file mode 100644 index 0000000000..55c2dd05bd --- /dev/null +++ b/channels/api/http/responses.go @@ -0,0 +1,221 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "fmt" + "net/http" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/channels" +) + +var ( + _ magistrala.Response = (*createChannelRes)(nil) + _ magistrala.Response = (*viewChannelRes)(nil) + _ magistrala.Response = (*channelsPageRes)(nil) + _ magistrala.Response = (*updateChannelRes)(nil) + _ magistrala.Response = (*deleteChannelRes)(nil) + _ magistrala.Response = (*connectChannelClientsRes)(nil) + _ magistrala.Response = (*disconnectChannelClientsRes)(nil) + _ magistrala.Response = (*connectRes)(nil) + _ magistrala.Response = (*disconnectRes)(nil) + _ magistrala.Response = (*changeChannelStatusRes)(nil) +) + +type pageRes struct { + Limit uint64 `json:"limit,omitempty"` + Offset uint64 `json:"offset"` + Total uint64 `json:"total"` +} + +type createChannelRes struct { + channels.Channel + created bool +} + +func (res createChannelRes) Code() int { + if res.created { + return http.StatusCreated + } + + return http.StatusOK +} + +func (res createChannelRes) Headers() map[string]string { + if res.created { + return map[string]string{ + "Location": fmt.Sprintf("/channels/%s", res.ID), + } + } + + return map[string]string{} +} + +func (res createChannelRes) Empty() bool { + return false +} + +type viewChannelRes struct { + channels.Channel +} + +func (res viewChannelRes) Code() int { + return http.StatusOK +} + +func (res viewChannelRes) Headers() map[string]string { + return map[string]string{} +} + +func (res viewChannelRes) Empty() bool { + return false +} + +type channelsPageRes struct { + pageRes + Channels []viewChannelRes `json:"channels"` +} + +func (res channelsPageRes) Code() int { + return http.StatusOK +} + +func (res channelsPageRes) Headers() map[string]string { + return map[string]string{} +} + +func (res channelsPageRes) Empty() bool { + return false +} + +type changeChannelStatusRes struct { + channels.Channel +} + +func (res changeChannelStatusRes) Code() int { + return http.StatusOK +} + +func (res changeChannelStatusRes) Headers() map[string]string { + return map[string]string{} +} + +func (res changeChannelStatusRes) Empty() bool { + return false +} + +type updateChannelRes struct { + channels.Channel +} + +func (res updateChannelRes) Code() int { + return http.StatusOK +} + +func (res updateChannelRes) Headers() map[string]string { + return map[string]string{} +} + +func (res updateChannelRes) Empty() bool { + return false +} + +type setChannelParentGroupRes struct{} + +func (res setChannelParentGroupRes) Code() int { + return http.StatusAccepted +} + +func (res setChannelParentGroupRes) Headers() map[string]string { + return map[string]string{} +} + +func (res setChannelParentGroupRes) Empty() bool { + return true +} + +type removeChannelParentGroupRes struct{} + +func (res removeChannelParentGroupRes) Code() int { + return http.StatusNoContent +} + +func (res removeChannelParentGroupRes) Headers() map[string]string { + return map[string]string{} +} + +func (res removeChannelParentGroupRes) Empty() bool { + return true +} + +type deleteChannelRes struct{} + +func (res deleteChannelRes) Code() int { + return http.StatusNoContent +} + +func (res deleteChannelRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteChannelRes) Empty() bool { + return true +} + +type connectChannelClientsRes struct{} + +func (res connectChannelClientsRes) Code() int { + return http.StatusCreated +} + +func (res connectChannelClientsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res connectChannelClientsRes) Empty() bool { + return true +} + +type disconnectChannelClientsRes struct{} + +func (res disconnectChannelClientsRes) Code() int { + return http.StatusNoContent +} + +func (res disconnectChannelClientsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res disconnectChannelClientsRes) Empty() bool { + return true +} + +type connectRes struct{} + +func (res connectRes) Code() int { + return http.StatusCreated +} + +func (res connectRes) Headers() map[string]string { + return map[string]string{} +} + +func (res connectRes) Empty() bool { + return true +} + +type disconnectRes struct{} + +func (res disconnectRes) Code() int { + return http.StatusNoContent +} + +func (res disconnectRes) Headers() map[string]string { + return map[string]string{} +} + +func (res disconnectRes) Empty() bool { + return true +} diff --git a/channels/api/http/transport.go b/channels/api/http/transport.go new file mode 100644 index 0000000000..86ffa54768 --- /dev/null +++ b/channels/api/http/transport.go @@ -0,0 +1,140 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "log/slog" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/go-chi/chi/v5" + kithttp "github.com/go-kit/kit/transport/http" + "github.com/prometheus/client_golang/prometheus/promhttp" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +// MakeHandler returns a HTTP handler for Channels API endpoints. +func MakeHandler(svc channels.Service, authn mgauthn.Authentication, mux *chi.Mux, logger *slog.Logger, instanceID string) *chi.Mux { + opts := []kithttp.ServerOption{ + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), + } + mux.Route("/{domainID}/channels", func(r chi.Router) { + r.Use(api.AuthenticateMiddleware(authn, true)) + + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + createChannelEndpoint(svc), + decodeCreateChannelReq, + api.EncodeResponse, + opts..., + ), "create_channel").ServeHTTP) + + r.Post("/bulk", otelhttp.NewHandler(kithttp.NewServer( + createChannelsEndpoint(svc), + decodeCreateChannelsReq, + api.EncodeResponse, + opts..., + ), "create_channels").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + listChannelsEndpoint(svc), + decodeListChannels, + api.EncodeResponse, + opts..., + ), "list_channels").ServeHTTP) + + r.Post("/connect", otelhttp.NewHandler(kithttp.NewServer( + connectEndpoint(svc), + decodeConnectRequest, + api.EncodeResponse, + opts..., + ), "connect").ServeHTTP) + + r.Post("/disconnect", otelhttp.NewHandler(kithttp.NewServer( + disconnectEndpoint(svc), + decodeDisconnectRequest, + api.EncodeResponse, + opts..., + ), "disconnect").ServeHTTP) + + r.Route("/{channelID}", func(r chi.Router) { + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + viewChannelEndpoint(svc), + decodeViewChannel, + api.EncodeResponse, + opts..., + ), "view_channel").ServeHTTP) + + r.Patch("/", otelhttp.NewHandler(kithttp.NewServer( + updateChannelEndpoint(svc), + decodeUpdateChannel, + api.EncodeResponse, + opts..., + ), "update_channel_name_and_metadata").ServeHTTP) + + r.Patch("/tags", otelhttp.NewHandler(kithttp.NewServer( + updateChannelTagsEndpoint(svc), + decodeUpdateChannelTags, + api.EncodeResponse, + opts..., + ), "update_channel_tag").ServeHTTP) + + r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( + deleteChannelEndpoint(svc), + decodeDeleteChannelReq, + api.EncodeResponse, + opts..., + ), "delete_channel").ServeHTTP) + + r.Post("/enable", otelhttp.NewHandler(kithttp.NewServer( + enableChannelEndpoint(svc), + decodeChangeChannelStatus, + api.EncodeResponse, + opts..., + ), "enable_channel").ServeHTTP) + + r.Post("/disable", otelhttp.NewHandler(kithttp.NewServer( + disableChannelEndpoint(svc), + decodeChangeChannelStatus, + api.EncodeResponse, + opts..., + ), "disable_channel").ServeHTTP) + + r.Post("/parent", otelhttp.NewHandler(kithttp.NewServer( + setChannelParentGroupEndpoint(svc), + decodeSetChannelParentGroupStatus, + api.EncodeResponse, + opts..., + ), "set_channel_parent_group").ServeHTTP) + + r.Delete("/parent", otelhttp.NewHandler(kithttp.NewServer( + removeChannelParentGroupEndpoint(svc), + decodeRemoveChannelParentGroupStatus, + api.EncodeResponse, + opts..., + ), "remove_channel_parent_group").ServeHTTP) + + r.Post("/connect", otelhttp.NewHandler(kithttp.NewServer( + connectChannelClientEndpoint(svc), + decodeConnectChannelClientRequest, + api.EncodeResponse, + opts..., + ), "connect_channel_client").ServeHTTP) + + r.Post("/disconnect", otelhttp.NewHandler(kithttp.NewServer( + disconnectChannelClientsEndpoint(svc), + decodeDisconnectChannelClientsRequest, + api.EncodeResponse, + opts..., + ), "disconnect_channel_client").ServeHTTP) + }) + }) + + mux.Get("/health", magistrala.Health("channels", instanceID)) + mux.Handle("/metrics", promhttp.Handler()) + + return mux +} diff --git a/channels/channels.go b/channels/channels.go new file mode 100644 index 0000000000..5da7d3b4cc --- /dev/null +++ b/channels/channels.go @@ -0,0 +1,170 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package channels + +import ( + "context" + "time" + + clients "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/roles" +) + +// Channel represents a Mainflux "communication topic". This topic +// contains the clients that can exchange messages between each other. +type Channel struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Tags []string `json:"tags,omitempty"` + ParentGroup string `json:"parent_group_id,omitempty"` + Domain string `json:"domain_id,omitempty"` + Metadata clients.Metadata `json:"metadata,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Status clients.Status `json:"status,omitempty"` // 1 for enabled, 0 for disabled + Permissions []string `json:"permissions,omitempty"` // 1 for enabled, 0 for disabled +} + +type PageMetadata struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` + Name string `json:"name,omitempty"` + Id string `json:"id,omitempty"` + Order string `json:"order,omitempty"` + Dir string `json:"dir,omitempty"` + Metadata clients.Metadata `json:"metadata,omitempty"` + Domain string `json:"domain,omitempty"` + Tag string `json:"tag,omitempty"` + Permission string `json:"permission,omitempty"` + Status clients.Status `json:"status,omitempty"` + IDs []string `json:"ids,omitempty"` + ListPerms bool `json:"-"` + ClientID string `json:"-"` +} + +// ChannelsPage contains page related metadata as well as list of channels that +// belong to this page. +type Page struct { + PageMetadata + Channels []Channel +} + +type Connection struct { + ClientID string + ChannelID string + DomainID string + Type connections.ConnType +} + +type AuthzReq struct { + DomainID string + ChannelID string + ClientID string + ClientType string + Type connections.ConnType +} + +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" +type Service interface { + // CreateChannels adds channels to the user identified by the provided key. + CreateChannels(ctx context.Context, session authn.Session, channels ...Channel) ([]Channel, error) + + // ViewChannel retrieves data about the channel identified by the provided + // ID, that belongs to the user identified by the provided key. + ViewChannel(ctx context.Context, session authn.Session, id string) (Channel, error) + + // UpdateChannel updates the channel identified by the provided ID, that + // belongs to the user identified by the provided key. + UpdateChannel(ctx context.Context, session authn.Session, channel Channel) (Channel, error) + + // UpdateChannelTags updates the channel's tags. + UpdateChannelTags(ctx context.Context, session authn.Session, channel Channel) (Channel, error) + + EnableChannel(ctx context.Context, session authn.Session, id string) (Channel, error) + + DisableChannel(ctx context.Context, session authn.Session, id string) (Channel, error) + + // ListChannels retrieves data about subset of channels that belongs to the + // user identified by the provided key. + ListChannels(ctx context.Context, session authn.Session, pm PageMetadata) (Page, error) + + // ListChannelsByClient retrieves data about subset of channels that have + // specified client connected or not connected to them and belong to the user identified by + // the provided key. + ListChannelsByClient(ctx context.Context, session authn.Session, id string, pm PageMetadata) (Page, error) + + // RemoveChannel removes the client identified by the provided ID, that + // belongs to the user identified by the provided key. + RemoveChannel(ctx context.Context, session authn.Session, id string) error + + // Connect adds clients to the channels list of connected clients. + Connect(ctx context.Context, session authn.Session, chIDs, clIDs []string, connType []connections.ConnType) error + + // Disconnect removes clients from the channels list of connected clients. + Disconnect(ctx context.Context, session authn.Session, chIDs, clIDs []string, connType []connections.ConnType) error + + SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error + + RemoveParentGroup(ctx context.Context, session authn.Session, id string) error + + roles.RoleManager +} + +// ChannelRepository specifies a channel persistence API. +// +//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" +type Repository interface { + // Save persists multiple channels. Channels are saved using a transaction. If one channel + // fails then none will be saved. Successful operation is indicated by non-nil + // error response. + Save(ctx context.Context, chs ...Channel) ([]Channel, error) + + // Update performs an update to the existing channel. + Update(ctx context.Context, c Channel) (Channel, error) + + UpdateTags(ctx context.Context, ch Channel) (Channel, error) + + ChangeStatus(ctx context.Context, channel Channel) (Channel, error) + + // RetrieveByID retrieves the channel having the provided identifier + RetrieveByID(ctx context.Context, id string) (Channel, error) + + // RetrieveAll retrieves the subset of channels. + RetrieveAll(ctx context.Context, pm PageMetadata) (Page, error) + + // Remove removes the channel having the provided identifier + Remove(ctx context.Context, ids ...string) error + + // SetParentGroup set parent group id to a given channel id + SetParentGroup(ctx context.Context, ch Channel) error + + // RemoveParentGroup remove parent group id fr given chanel id + RemoveParentGroup(ctx context.Context, ch Channel) error + + AddConnections(ctx context.Context, conns []Connection) error + + RemoveConnections(ctx context.Context, conns []Connection) error + + CheckConnection(ctx context.Context, conn Connection) error + + ClientAuthorize(ctx context.Context, conn Connection) error + + ChannelConnectionsCount(ctx context.Context, id string) (uint64, error) + + DoesChannelHaveConnections(ctx context.Context, id string) (bool, error) + + RemoveClientConnections(ctx context.Context, clientID string) error + + RemoveChannelConnections(ctx context.Context, channelID string) error + + RetrieveParentGroupChannels(ctx context.Context, parentGroupID string) ([]Channel, error) + + UnsetParentGroupFromChannels(ctx context.Context, parentGroupID string) error + + roles.Repository +} diff --git a/channels/events/doc.go b/channels/events/doc.go new file mode 100644 index 0000000000..d32b58f289 --- /dev/null +++ b/channels/events/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package events provides the domain concept definitions +// needed to support clients events functionality. +package events diff --git a/channels/events/events.go b/channels/events/events.go new file mode 100644 index 0000000000..9a4489fa1a --- /dev/null +++ b/channels/events/events.go @@ -0,0 +1,313 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "time" + + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/events" +) + +const ( + channelPrefix = "channels." + channelCreate = channelPrefix + "create" + channelUpdate = channelPrefix + "update" + channelChangeStatus = channelPrefix + "change_status" + channelRemove = channelPrefix + "remove" + channelView = channelPrefix + "view" + channelList = channelPrefix + "list" + channelConnect = channelPrefix + "connect" + channelDisconnect = channelPrefix + "disconnect" + channelSetParent = channelPrefix + "set_parent" + channelRemoveParent = channelPrefix + "remove_parent" +) + +var ( + _ events.Event = (*createChannelEvent)(nil) + _ events.Event = (*updateChannelEvent)(nil) + _ events.Event = (*changeStatusChannelEvent)(nil) + _ events.Event = (*viewChannelEvent)(nil) + _ events.Event = (*listChannelEvent)(nil) + _ events.Event = (*removeChannelEvent)(nil) + _ events.Event = (*connectEvent)(nil) + _ events.Event = (*disconnectEvent)(nil) +) + +type createChannelEvent struct { + channels.Channel +} + +func (cce createChannelEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": channelCreate, + "id": cce.ID, + "status": cce.Status.String(), + "created_at": cce.CreatedAt, + } + + if cce.Name != "" { + val["name"] = cce.Name + } + if len(cce.Tags) > 0 { + val["tags"] = cce.Tags + } + if cce.Domain != "" { + val["domain"] = cce.Domain + } + if cce.Metadata != nil { + val["metadata"] = cce.Metadata + } + + return val, nil +} + +type updateChannelEvent struct { + channels.Channel + operation string +} + +func (uce updateChannelEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": channelUpdate, + "updated_at": uce.UpdatedAt, + "updated_by": uce.UpdatedBy, + } + if uce.operation != "" { + val["operation"] = channelUpdate + "_" + uce.operation + } + + if uce.ID != "" { + val["id"] = uce.ID + } + if uce.Name != "" { + val["name"] = uce.Name + } + if len(uce.Tags) > 0 { + val["tags"] = uce.Tags + } + if uce.Domain != "" { + val["domain"] = uce.Domain + } + if uce.Metadata != nil { + val["metadata"] = uce.Metadata + } + if !uce.CreatedAt.IsZero() { + val["created_at"] = uce.CreatedAt + } + if uce.Status.String() != "" { + val["status"] = uce.Status.String() + } + + return val, nil +} + +type changeStatusChannelEvent struct { + id string + status string + updatedAt time.Time + updatedBy string +} + +func (rce changeStatusChannelEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": channelChangeStatus, + "id": rce.id, + "status": rce.status, + "updated_at": rce.updatedAt, + "updated_by": rce.updatedBy, + }, nil +} + +type viewChannelEvent struct { + channels.Channel +} + +func (vce viewChannelEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": channelView, + "id": vce.ID, + } + + if vce.Name != "" { + val["name"] = vce.Name + } + if len(vce.Tags) > 0 { + val["tags"] = vce.Tags + } + if vce.Domain != "" { + val["domain"] = vce.Domain + } + if vce.Metadata != nil { + val["metadata"] = vce.Metadata + } + if !vce.CreatedAt.IsZero() { + val["created_at"] = vce.CreatedAt + } + if !vce.UpdatedAt.IsZero() { + val["updated_at"] = vce.UpdatedAt + } + if vce.UpdatedBy != "" { + val["updated_by"] = vce.UpdatedBy + } + if vce.Status.String() != "" { + val["status"] = vce.Status.String() + } + + return val, nil +} + +type listChannelEvent struct { + channels.PageMetadata +} + +func (lce listChannelEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": channelList, + "total": lce.Total, + "offset": lce.Offset, + "limit": lce.Limit, + } + + if lce.Name != "" { + val["name"] = lce.Name + } + if lce.Order != "" { + val["order"] = lce.Order + } + if lce.Dir != "" { + val["dir"] = lce.Dir + } + if lce.Metadata != nil { + val["metadata"] = lce.Metadata + } + if lce.Domain != "" { + val["domain"] = lce.Domain + } + if lce.Tag != "" { + val["tag"] = lce.Tag + } + if lce.Permission != "" { + val["permission"] = lce.Permission + } + if lce.Status.String() != "" { + val["status"] = lce.Status.String() + } + if len(lce.IDs) > 0 { + val["ids"] = lce.IDs + } + + return val, nil +} + +type listChannelByClientEvent struct { + clientID string + channels.PageMetadata +} + +func (lcte listChannelByClientEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": channelList, + "client_id": lcte.clientID, + "total": lcte.Total, + "offset": lcte.Offset, + "limit": lcte.Limit, + } + + if lcte.Name != "" { + val["name"] = lcte.Name + } + if lcte.Order != "" { + val["order"] = lcte.Order + } + if lcte.Dir != "" { + val["dir"] = lcte.Dir + } + if lcte.Metadata != nil { + val["metadata"] = lcte.Metadata + } + if lcte.Domain != "" { + val["domain"] = lcte.Domain + } + if lcte.Tag != "" { + val["tag"] = lcte.Tag + } + if lcte.Permission != "" { + val["permission"] = lcte.Permission + } + if lcte.Status.String() != "" { + val["status"] = lcte.Status.String() + } + if len(lcte.IDs) > 0 { + val["ids"] = lcte.IDs + } + + return val, nil +} + +type removeChannelEvent struct { + id string +} + +func (dce removeChannelEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": channelRemove, + "id": dce.id, + }, nil +} + +type connectEvent struct { + chIDs []string + thIDs []string + types []connections.ConnType +} + +func (ce connectEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": channelConnect, + "client_ids": ce.thIDs, + "channel_ids": ce.chIDs, + "types": ce.types, + }, nil +} + +type disconnectEvent struct { + chIDs []string + thIDs []string + types []connections.ConnType +} + +func (de disconnectEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": channelDisconnect, + "client_ids": de.thIDs, + "channel_ids": de.chIDs, + "types": de.types, + }, nil +} + +type setParentGroupEvent struct { + id string + parentGroupID string +} + +func (spge setParentGroupEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": channelSetParent, + "id": spge.id, + "parent_group_id": spge.parentGroupID, + }, nil +} + +type removeParentGroupEvent struct { + id string +} + +func (rpge removeParentGroupEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": channelRemoveParent, + "id": rpge.id, + }, nil +} diff --git a/channels/events/streams.go b/channels/events/streams.go new file mode 100644 index 0000000000..20f9bf1e49 --- /dev/null +++ b/channels/events/streams.go @@ -0,0 +1,238 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "context" + + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/events" + "github.com/absmach/magistrala/pkg/events/store" + rmEvents "github.com/absmach/magistrala/pkg/roles/rolemanager/events" +) + +const streamID = "magistrala.clients" + +var _ channels.Service = (*eventStore)(nil) + +type eventStore struct { + events.Publisher + svc channels.Service + rmEvents.RoleManagerEventStore +} + +// NewEventStoreMiddleware returns wrapper around clients service that sends +// events to event store. +func NewEventStoreMiddleware(ctx context.Context, svc channels.Service, url string) (channels.Service, error) { + publisher, err := store.NewPublisher(ctx, url, streamID) + if err != nil { + return nil, err + } + + rolesSvcEventStoreMiddleware := rmEvents.NewRoleManagerEventStore("channels", svc, publisher) + return &eventStore{ + svc: svc, + Publisher: publisher, + RoleManagerEventStore: rolesSvcEventStoreMiddleware, + }, nil +} + +func (es *eventStore) CreateChannels(ctx context.Context, session authn.Session, chs ...channels.Channel) ([]channels.Channel, error) { + chs, err := es.svc.CreateChannels(ctx, session, chs...) + if err != nil { + return chs, err + } + + for _, ch := range chs { + event := createChannelEvent{ + ch, + } + if err := es.Publish(ctx, event); err != nil { + return chs, err + } + } + + return chs, nil +} + +func (es *eventStore) UpdateChannel(ctx context.Context, session authn.Session, ch channels.Channel) (channels.Channel, error) { + chann, err := es.svc.UpdateChannel(ctx, session, ch) + if err != nil { + return chann, err + } + + return es.update(ctx, "", chann) +} + +func (es *eventStore) UpdateChannelTags(ctx context.Context, session authn.Session, ch channels.Channel) (channels.Channel, error) { + chann, err := es.svc.UpdateChannelTags(ctx, session, ch) + if err != nil { + return chann, err + } + + return es.update(ctx, "tags", chann) +} + +func (es *eventStore) update(ctx context.Context, operation string, ch channels.Channel) (channels.Channel, error) { + event := updateChannelEvent{ + ch, operation, + } + + if err := es.Publish(ctx, event); err != nil { + return ch, err + } + + return ch, nil +} + +func (es *eventStore) ViewChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + chann, err := es.svc.ViewChannel(ctx, session, id) + if err != nil { + return chann, err + } + + event := viewChannelEvent{ + chann, + } + if err := es.Publish(ctx, event); err != nil { + return chann, err + } + + return chann, nil +} + +func (es *eventStore) ListChannels(ctx context.Context, session authn.Session, pm channels.PageMetadata) (channels.Page, error) { + cp, err := es.svc.ListChannels(ctx, session, pm) + if err != nil { + return cp, err + } + event := listChannelEvent{ + pm, + } + if err := es.Publish(ctx, event); err != nil { + return cp, err + } + + return cp, nil +} + +func (es *eventStore) ListChannelsByClient(ctx context.Context, session authn.Session, clientID string, pm channels.PageMetadata) (channels.Page, error) { + cp, err := es.svc.ListChannelsByClient(ctx, session, clientID, pm) + if err != nil { + return cp, err + } + event := listChannelByClientEvent{ + clientID, + pm, + } + if err := es.Publish(ctx, event); err != nil { + return cp, err + } + + return cp, nil +} + +func (es *eventStore) EnableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + cli, err := es.svc.EnableChannel(ctx, session, id) + if err != nil { + return cli, err + } + + return es.changeStatus(ctx, cli) +} + +func (es *eventStore) DisableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + cli, err := es.svc.DisableChannel(ctx, session, id) + if err != nil { + return cli, err + } + + return es.changeStatus(ctx, cli) +} + +func (es *eventStore) changeStatus(ctx context.Context, ch channels.Channel) (channels.Channel, error) { + event := changeStatusChannelEvent{ + id: ch.ID, + updatedAt: ch.UpdatedAt, + updatedBy: ch.UpdatedBy, + status: ch.Status.String(), + } + if err := es.Publish(ctx, event); err != nil { + return ch, err + } + + return ch, nil +} + +func (es *eventStore) RemoveChannel(ctx context.Context, session authn.Session, id string) error { + if err := es.svc.RemoveChannel(ctx, session, id); err != nil { + return err + } + + event := removeChannelEvent{id} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} + +func (es *eventStore) Connect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error { + if err := es.svc.Connect(ctx, session, chIDs, thIDs, connTypes); err != nil { + return err + } + + event := connectEvent{chIDs, thIDs, connTypes} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} + +func (es *eventStore) Disconnect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error { + if err := es.svc.Disconnect(ctx, session, chIDs, thIDs, connTypes); err != nil { + return err + } + + event := disconnectEvent{chIDs, thIDs, connTypes} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} + +func (es *eventStore) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (err error) { + if err := es.svc.SetParentGroup(ctx, session, parentGroupID, id); err != nil { + return err + } + + event := setParentGroupEvent{parentGroupID: parentGroupID, id: id} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} + +func (es *eventStore) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (err error) { + if err := es.svc.RemoveParentGroup(ctx, session, id); err != nil { + return err + } + + event := removeParentGroupEvent{id: id} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} diff --git a/channels/middleware/authorization.go b/channels/middleware/authorization.go new file mode 100644 index 0000000000..15a2e5ddf2 --- /dev/null +++ b/channels/middleware/authorization.go @@ -0,0 +1,348 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "context" + "fmt" + + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/authz" + mgauthz "github.com/absmach/magistrala/pkg/authz" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" + "github.com/absmach/magistrala/pkg/svcutil" +) + +var ( + errView = errors.New("not authorized to view channel") + errUpdate = errors.New("not authorized to update channel") + errUpdateTags = errors.New("not authorized to update channel tags") + errEnable = errors.New("not authorized to enable channel") + errDisable = errors.New("not authorized to disable channel") + errDelete = errors.New("not authorized to delete channel") + errConnect = errors.New("not authorized to connect to channel") + errDisconnect = errors.New("not authorized to disconnect from channel") + errSetParentGroup = errors.New("not authorized to set parent group to channel") + errRemoveParentGroup = errors.New("not authorized to remove parent group from channel") + errDomainCreateChannels = errors.New("not authorized to create channel in domain") + errGroupSetChildChannels = errors.New("not authorized to set child channel for group") + errGroupRemoveChildChannels = errors.New("not authorized to remove child channel for group") + errClientDisConnectChannels = errors.New("not authorized to disconnect channel for client") + errClientConnectChannels = errors.New("not authorized to connect channel for client") +) + +var _ channels.Service = (*authorizationMiddleware)(nil) + +type authorizationMiddleware struct { + svc channels.Service + repo channels.Repository + authz mgauthz.Authorization + opp svcutil.OperationPerm + extOpp svcutil.ExternalOperationPerm + rmMW.RoleManagerAuthorizationMiddleware +} + +// AuthorizationMiddleware adds authorization to the channels service. +func AuthorizationMiddleware(svc channels.Service, repo channels.Repository, authz mgauthz.Authorization, channelsOpPerm, rolesOpPerm map[svcutil.Operation]svcutil.Permission, extOpPerm map[svcutil.ExternalOperation]svcutil.Permission) (channels.Service, error) { + opp := channels.NewOperationPerm() + if err := opp.AddOperationPermissionMap(channelsOpPerm); err != nil { + return nil, err + } + if err := opp.Validate(); err != nil { + return nil, err + } + ram, err := rmMW.NewRoleManagerAuthorizationMiddleware(policies.ChannelType, svc, authz, rolesOpPerm) + if err != nil { + return nil, err + } + extOpp := channels.NewExternalOperationPerm() + if err := extOpp.AddOperationPermissionMap(extOpPerm); err != nil { + return nil, err + } + if err := extOpp.Validate(); err != nil { + return nil, err + } + return &authorizationMiddleware{ + svc: svc, + repo: repo, + authz: authz, + RoleManagerAuthorizationMiddleware: ram, + opp: opp, + extOpp: extOpp, + }, nil +} + +func (am *authorizationMiddleware) CreateChannels(ctx context.Context, session authn.Session, chs ...channels.Channel) ([]channels.Channel, error) { + // If domain is disabled , then this authorization will fail for all non-admin domain users + if err := am.extAuthorize(ctx, channels.DomainOpCreateChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.DomainType, + Object: session.DomainID, + }); err != nil { + return []channels.Channel{}, errors.Wrap(err, errDomainCreateChannels) + } + + for _, ch := range chs { + if ch.ParentGroup != "" { + if err := am.extAuthorize(ctx, channels.GroupOpSetChildChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.GroupType, + Object: ch.ParentGroup, + }); err != nil { + return []channels.Channel{}, errors.Wrap(err, errors.Wrap(errGroupSetChildChannels, fmt.Errorf("channel name %s parent group id %s", ch.Name, ch.ParentGroup))) + } + } + } + return am.svc.CreateChannels(ctx, session, chs...) +} + +func (am *authorizationMiddleware) ViewChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + if err := am.authorize(ctx, channels.OpViewChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: id, + }); err != nil { + return channels.Channel{}, errors.Wrap(err, errView) + } + return am.svc.ViewChannel(ctx, session, id) +} + +func (am *authorizationMiddleware) ListChannels(ctx context.Context, session authn.Session, pm channels.PageMetadata) (channels.Page, error) { + if err := am.checkSuperAdmin(ctx, session.UserID); err != nil { + session.SuperAdmin = true + } + return am.svc.ListChannels(ctx, session, pm) +} + +func (am *authorizationMiddleware) ListChannelsByClient(ctx context.Context, session authn.Session, clientID string, pm channels.PageMetadata) (channels.Page, error) { + return am.svc.ListChannelsByClient(ctx, session, clientID, pm) +} + +func (am *authorizationMiddleware) UpdateChannel(ctx context.Context, session authn.Session, channel channels.Channel) (channels.Channel, error) { + if err := am.authorize(ctx, channels.OpUpdateChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: channel.ID, + }); err != nil { + return channels.Channel{}, errors.Wrap(err, errUpdate) + } + return am.svc.UpdateChannel(ctx, session, channel) +} + +func (am *authorizationMiddleware) UpdateChannelTags(ctx context.Context, session authn.Session, channel channels.Channel) (channels.Channel, error) { + if err := am.authorize(ctx, channels.OpUpdateChannelTags, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: channel.ID, + }); err != nil { + return channels.Channel{}, errors.Wrap(err, errUpdateTags) + } + return am.svc.UpdateChannelTags(ctx, session, channel) +} + +func (am *authorizationMiddleware) EnableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + if err := am.authorize(ctx, channels.OpEnableChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: id, + }); err != nil { + return channels.Channel{}, errors.Wrap(err, errEnable) + } + return am.svc.EnableChannel(ctx, session, id) +} + +func (am *authorizationMiddleware) DisableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + if err := am.authorize(ctx, channels.OpDisableChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: id, + }); err != nil { + return channels.Channel{}, errors.Wrap(err, errDisable) + } + return am.svc.DisableChannel(ctx, session, id) +} + +func (am *authorizationMiddleware) RemoveChannel(ctx context.Context, session authn.Session, id string) error { + if err := am.authorize(ctx, channels.OpDeleteChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: id, + }); err != nil { + return errors.Wrap(err, errDelete) + } + return am.svc.RemoveChannel(ctx, session, id) +} + +func (am *authorizationMiddleware) Connect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error { + // ToDo: This authorization will be changed with Bulk Authorization. For this we need to add bulk authorization API in policies. + for _, chID := range chIDs { + if err := am.authorize(ctx, channels.OpConnectClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: chID, + }); err != nil { + return errors.Wrap(err, errConnect) + } + } + + for _, thID := range thIDs { + if err := am.extAuthorize(ctx, channels.ClientsOpConnectChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: thID, + }); err != nil { + return errors.Wrap(err, errClientConnectChannels) + } + } + return am.svc.Connect(ctx, session, chIDs, thIDs, connTypes) +} + +func (am *authorizationMiddleware) Disconnect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error { + // ToDo: This authorization will be changed with Bulk Authorization. For this we need to add bulk authorization API in policies. + for _, chID := range chIDs { + if err := am.authorize(ctx, channels.OpDisconnectClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: chID, + }); err != nil { + return errors.Wrap(err, errDisconnect) + } + } + + for _, thID := range thIDs { + if err := am.extAuthorize(ctx, channels.ClientsOpDisconnectChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: thID, + }); err != nil { + return errors.Wrap(err, errClientDisConnectChannels) + } + } + return am.svc.Disconnect(ctx, session, chIDs, thIDs, connTypes) +} + +func (am *authorizationMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error { + if err := am.authorize(ctx, channels.OpSetParentGroup, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: id, + }); err != nil { + return errors.Wrap(err, errSetParentGroup) + } + + if err := am.extAuthorize(ctx, channels.GroupOpSetChildChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.GroupType, + Object: parentGroupID, + }); err != nil { + return errors.Wrap(err, errGroupSetChildChannels) + } + return am.svc.SetParentGroup(ctx, session, parentGroupID, id) +} + +func (am *authorizationMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + if err := am.authorize(ctx, channels.OpSetParentGroup, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ChannelType, + Object: id, + }); err != nil { + return errors.Wrap(err, errRemoveParentGroup) + } + + ch, err := am.repo.RetrieveByID(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + if ch.ParentGroup != "" { + if err := am.extAuthorize(ctx, channels.GroupOpSetChildChannel, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.GroupType, + Object: ch.ParentGroup, + }); err != nil { + return errors.Wrap(err, errGroupRemoveChildChannels) + } + return am.svc.RemoveParentGroup(ctx, session, id) + } + return nil +} + +func (am *authorizationMiddleware) authorize(ctx context.Context, op svcutil.Operation, req authz.PolicyReq) error { + perm, err := am.opp.GetPermission(op) + if err != nil { + return err + } + + req.Permission = perm.String() + + if err := am.authz.Authorize(ctx, req); err != nil { + return err + } + + return nil +} + +func (am *authorizationMiddleware) extAuthorize(ctx context.Context, extOp svcutil.ExternalOperation, req authz.PolicyReq) error { + perm, err := am.extOpp.GetPermission(extOp) + if err != nil { + return err + } + + req.Permission = perm.String() + + if err := am.authz.Authorize(ctx, req); err != nil { + return err + } + + return nil +} + +func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, userID string) error { + if err := am.authz.Authorize(ctx, mgauthz.PolicyReq{ + SubjectType: policies.UserType, + Subject: userID, + Permission: policies.AdminPermission, + ObjectType: policies.PlatformType, + Object: policies.MagistralaObject, + }); err != nil { + return err + } + return nil +} diff --git a/channels/middleware/logging.go b/channels/middleware/logging.go new file mode 100644 index 0000000000..191d81520d --- /dev/null +++ b/channels/middleware/logging.go @@ -0,0 +1,264 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" +) + +var _ channels.Service = (*loggingMiddleware)(nil) + +type loggingMiddleware struct { + logger *slog.Logger + svc channels.Service + rmMW.RoleManagerLoggingMiddleware +} + +func LoggingMiddleware(svc channels.Service, logger *slog.Logger) channels.Service { + return &loggingMiddleware{logger, svc, rmMW.NewRoleManagerLoggingMiddleware("channels", svc, logger)} +} + +func (lm *loggingMiddleware) CreateChannels(ctx context.Context, session authn.Session, clients ...channels.Channel) (cs []channels.Channel, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(fmt.Sprintf("Create %d channels failed", len(clients)), args...) + return + } + lm.logger.Info(fmt.Sprintf("Create %d channel completed successfully", len(clients)), args...) + }(time.Now()) + return lm.svc.CreateChannels(ctx, session, clients...) +} + +func (lm *loggingMiddleware) ViewChannel(ctx context.Context, session authn.Session, id string) (c channels.Channel, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("channel", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("View channel failed", args...) + return + } + lm.logger.Info("View channel completed successfully", args...) + }(time.Now()) + return lm.svc.ViewChannel(ctx, session, id) +} + +func (lm *loggingMiddleware) ListChannels(ctx context.Context, session authn.Session, pm channels.PageMetadata) (cp channels.Page, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("page", + slog.Uint64("limit", pm.Limit), + slog.Uint64("offset", pm.Offset), + slog.Uint64("total", cp.Total), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("List channels failed", args...) + return + } + lm.logger.Info("List channels completed successfully", args...) + }(time.Now()) + return lm.svc.ListChannels(ctx, session, pm) +} + +func (lm *loggingMiddleware) ListChannelsByClient(ctx context.Context, session authn.Session, clientID string, pm channels.PageMetadata) (cp channels.Page, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("client_id", clientID), + slog.Group("page", + slog.Uint64("limit", pm.Limit), + slog.Uint64("offset", pm.Offset), + slog.Uint64("total", cp.Total), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("List channels by client failed", args...) + return + } + lm.logger.Info("List channels by client completed successfully", args...) + }(time.Now()) + return lm.svc.ListChannelsByClient(ctx, session, clientID, pm) +} + +func (lm *loggingMiddleware) UpdateChannel(ctx context.Context, session authn.Session, client channels.Channel) (c channels.Channel, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("channel", + slog.String("id", client.ID), + slog.String("name", client.Name), + slog.Any("metadata", client.Metadata), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Update channel failed", args...) + return + } + lm.logger.Info("Update channel completed successfully", args...) + }(time.Now()) + return lm.svc.UpdateChannel(ctx, session, client) +} + +func (lm *loggingMiddleware) UpdateChannelTags(ctx context.Context, session authn.Session, client channels.Channel) (c channels.Channel, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("channel", + slog.String("id", c.ID), + slog.String("name", c.Name), + slog.Any("tags", c.Tags), + ), + } + if err != nil { + args := append(args, slog.String("error", err.Error())) + lm.logger.Warn("Update channel tags failed", args...) + return + } + lm.logger.Info("Update channel tags completed successfully", args...) + }(time.Now()) + return lm.svc.UpdateChannelTags(ctx, session, client) +} + +func (lm *loggingMiddleware) EnableChannel(ctx context.Context, session authn.Session, id string) (c channels.Channel, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("channel", + slog.String("id", id), + slog.String("name", c.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Enable channel failed", args...) + return + } + lm.logger.Info("Enable channel completed successfully", args...) + }(time.Now()) + return lm.svc.EnableChannel(ctx, session, id) +} + +func (lm *loggingMiddleware) DisableChannel(ctx context.Context, session authn.Session, id string) (c channels.Channel, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("channel", + slog.String("id", id), + slog.String("name", c.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Disable channel failed", args...) + return + } + lm.logger.Info("Disable channel completed successfully", args...) + }(time.Now()) + return lm.svc.DisableChannel(ctx, session, id) +} + +func (lm *loggingMiddleware) RemoveChannel(ctx context.Context, session authn.Session, id string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", id), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Delete channel failed", args...) + return + } + lm.logger.Info("Delete channel completed successfully", args...) + }(time.Now()) + return lm.svc.RemoveChannel(ctx, session, id) +} + +func (lm *loggingMiddleware) Connect(ctx context.Context, session authn.Session, chIDs, clIDs []string, connTypes []connections.ConnType) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Any("channel_ids", chIDs), + slog.Any("client_ids", clIDs), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Connect channels and clients failed", args...) + return + } + lm.logger.Info("Connect channels and clients completed successfully", args...) + }(time.Now()) + return lm.svc.Connect(ctx, session, chIDs, clIDs, connTypes) +} + +func (lm *loggingMiddleware) Disconnect(ctx context.Context, session authn.Session, chIDs, clIDs []string, connTypes []connections.ConnType) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Any("channel_ids", chIDs), + slog.Any("client_ids", clIDs), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Disconnect channels and clients failed", args...) + return + } + lm.logger.Info("Disconnect channels and clients completed successfully", args...) + }(time.Now()) + return lm.svc.Disconnect(ctx, session, chIDs, clIDs, connTypes) +} + +func (lm *loggingMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("parent_group_id", parentGroupID), + slog.String("channel_id", id), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Set parent group to channel failed", args...) + return + } + lm.logger.Info("Set parent group to channel completed successfully", args...) + }(time.Now()) + return lm.svc.SetParentGroup(ctx, session, parentGroupID, id) +} + +func (lm *loggingMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", id), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Remove parent group from channel failed", args...) + return + } + lm.logger.Info("Remove parent group from channel completed successfully", args...) + }(time.Now()) + return lm.svc.RemoveParentGroup(ctx, session, id) +} diff --git a/channels/middleware/metrics.go b/channels/middleware/metrics.go new file mode 100644 index 0000000000..b347e371ea --- /dev/null +++ b/channels/middleware/metrics.go @@ -0,0 +1,138 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "context" + "time" + + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" + "github.com/go-kit/kit/metrics" +) + +var _ channels.Service = (*metricsMiddleware)(nil) + +type metricsMiddleware struct { + counter metrics.Counter + latency metrics.Histogram + svc channels.Service + rmMW.RoleManagerMetricsMiddleware +} + +// MetricsMiddleware returns a new metrics middleware wrapper. +func MetricsMiddleware(svc channels.Service, counter metrics.Counter, latency metrics.Histogram) channels.Service { + return &metricsMiddleware{ + counter: counter, + latency: latency, + svc: svc, + RoleManagerMetricsMiddleware: rmMW.NewRoleManagerMetricsMiddleware("channels", svc, counter, latency), + } +} + +func (ms *metricsMiddleware) CreateChannels(ctx context.Context, session authn.Session, chs ...channels.Channel) ([]channels.Channel, error) { + defer func(begin time.Time) { + ms.counter.With("method", "register_channels").Add(1) + ms.latency.With("method", "register_channels").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.CreateChannels(ctx, session, chs...) +} + +func (ms *metricsMiddleware) ViewChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + defer func(begin time.Time) { + ms.counter.With("method", "view_channel").Add(1) + ms.latency.With("method", "view_channel").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.ViewChannel(ctx, session, id) +} + +func (ms *metricsMiddleware) ListChannels(ctx context.Context, session authn.Session, pm channels.PageMetadata) (channels.Page, error) { + defer func(begin time.Time) { + ms.counter.With("method", "list_channels").Add(1) + ms.latency.With("method", "list_channels").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.ListChannels(ctx, session, pm) +} + +func (ms *metricsMiddleware) ListChannelsByClient(ctx context.Context, session authn.Session, clientID string, pm channels.PageMetadata) (channels.Page, error) { + defer func(begin time.Time) { + ms.counter.With("method", "list_channels_by_client").Add(1) + ms.latency.With("method", "list_channels_by_client").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.ListChannelsByClient(ctx, session, clientID, pm) +} + +func (ms *metricsMiddleware) UpdateChannel(ctx context.Context, session authn.Session, channel channels.Channel) (channels.Channel, error) { + defer func(begin time.Time) { + ms.counter.With("method", "update_channel").Add(1) + ms.latency.With("method", "update_channel").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.UpdateChannel(ctx, session, channel) +} + +func (ms *metricsMiddleware) UpdateChannelTags(ctx context.Context, session authn.Session, channel channels.Channel) (channels.Channel, error) { + defer func(begin time.Time) { + ms.counter.With("method", "update_channel_tags").Add(1) + ms.latency.With("method", "update_channel_tags").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.UpdateChannelTags(ctx, session, channel) +} + +func (ms *metricsMiddleware) EnableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + defer func(begin time.Time) { + ms.counter.With("method", "enable_channel").Add(1) + ms.latency.With("method", "enable_channel").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.EnableChannel(ctx, session, id) +} + +func (ms *metricsMiddleware) DisableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + defer func(begin time.Time) { + ms.counter.With("method", "disable_channel").Add(1) + ms.latency.With("method", "disable_channel").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.DisableChannel(ctx, session, id) +} + +func (ms *metricsMiddleware) RemoveChannel(ctx context.Context, session authn.Session, id string) error { + defer func(begin time.Time) { + ms.counter.With("method", "delete_channel").Add(1) + ms.latency.With("method", "delete_channel").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.RemoveChannel(ctx, session, id) +} + +func (ms *metricsMiddleware) Connect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error { + defer func(begin time.Time) { + ms.counter.With("method", "connect").Add(1) + ms.latency.With("method", "connect").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.Connect(ctx, session, chIDs, thIDs, connTypes) +} + +func (ms *metricsMiddleware) Disconnect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error { + defer func(begin time.Time) { + ms.counter.With("method", "disconnect").Add(1) + ms.latency.With("method", "disconnect").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.Disconnect(ctx, session, chIDs, thIDs, connTypes) +} + +func (ms *metricsMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (err error) { + defer func(begin time.Time) { + ms.counter.With("method", "set_parent_group").Add(1) + ms.latency.With("method", "set_parent_group").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.SetParentGroup(ctx, session, parentGroupID, id) +} + +func (ms *metricsMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (err error) { + defer func(begin time.Time) { + ms.counter.With("method", "remove_parent_group").Add(1) + ms.latency.With("method", "remove_parent_group").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.RemoveParentGroup(ctx, session, id) +} diff --git a/channels/mocks/channels_client.go b/channels/mocks/channels_client.go new file mode 100644 index 0000000000..240c0e17ad --- /dev/null +++ b/channels/mocks/channels_client.go @@ -0,0 +1,266 @@ +// Copyright (c) Abstract Machines + +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" + + v1 "github.com/absmach/magistrala/internal/grpc/channels/v1" +) + +// ChannelsServiceClient is an autogenerated mock type for the ChannelsServiceClient type +type ChannelsServiceClient struct { + mock.Mock +} + +type ChannelsServiceClient_Expecter struct { + mock *mock.Mock +} + +func (_m *ChannelsServiceClient) EXPECT() *ChannelsServiceClient_Expecter { + return &ChannelsServiceClient_Expecter{mock: &_m.Mock} +} + +// Authorize provides a mock function with given fields: ctx, in, opts +func (_m *ChannelsServiceClient) Authorize(ctx context.Context, in *v1.AuthzReq, opts ...grpc.CallOption) (*v1.AuthzRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Authorize") + } + + var r0 *v1.AuthzRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v1.AuthzReq, ...grpc.CallOption) (*v1.AuthzRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v1.AuthzReq, ...grpc.CallOption) *v1.AuthzRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1.AuthzRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v1.AuthzReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChannelsServiceClient_Authorize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Authorize' +type ChannelsServiceClient_Authorize_Call struct { + *mock.Call +} + +// Authorize is a helper method to define mock.On call +// - ctx context.Context +// - in *v1.AuthzReq +// - opts ...grpc.CallOption +func (_e *ChannelsServiceClient_Expecter) Authorize(ctx interface{}, in interface{}, opts ...interface{}) *ChannelsServiceClient_Authorize_Call { + return &ChannelsServiceClient_Authorize_Call{Call: _e.mock.On("Authorize", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ChannelsServiceClient_Authorize_Call) Run(run func(ctx context.Context, in *v1.AuthzReq, opts ...grpc.CallOption)) *ChannelsServiceClient_Authorize_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*v1.AuthzReq), variadicArgs...) + }) + return _c +} + +func (_c *ChannelsServiceClient_Authorize_Call) Return(_a0 *v1.AuthzRes, _a1 error) *ChannelsServiceClient_Authorize_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ChannelsServiceClient_Authorize_Call) RunAndReturn(run func(context.Context, *v1.AuthzReq, ...grpc.CallOption) (*v1.AuthzRes, error)) *ChannelsServiceClient_Authorize_Call { + _c.Call.Return(run) + return _c +} + +// RemoveClientConnections provides a mock function with given fields: ctx, in, opts +func (_m *ChannelsServiceClient) RemoveClientConnections(ctx context.Context, in *v1.RemoveClientConnectionsReq, opts ...grpc.CallOption) (*v1.RemoveClientConnectionsRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for RemoveClientConnections") + } + + var r0 *v1.RemoveClientConnectionsRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v1.RemoveClientConnectionsReq, ...grpc.CallOption) (*v1.RemoveClientConnectionsRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v1.RemoveClientConnectionsReq, ...grpc.CallOption) *v1.RemoveClientConnectionsRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1.RemoveClientConnectionsRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v1.RemoveClientConnectionsReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChannelsServiceClient_RemoveClientConnections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveClientConnections' +type ChannelsServiceClient_RemoveClientConnections_Call struct { + *mock.Call +} + +// RemoveClientConnections is a helper method to define mock.On call +// - ctx context.Context +// - in *v1.RemoveClientConnectionsReq +// - opts ...grpc.CallOption +func (_e *ChannelsServiceClient_Expecter) RemoveClientConnections(ctx interface{}, in interface{}, opts ...interface{}) *ChannelsServiceClient_RemoveClientConnections_Call { + return &ChannelsServiceClient_RemoveClientConnections_Call{Call: _e.mock.On("RemoveClientConnections", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ChannelsServiceClient_RemoveClientConnections_Call) Run(run func(ctx context.Context, in *v1.RemoveClientConnectionsReq, opts ...grpc.CallOption)) *ChannelsServiceClient_RemoveClientConnections_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*v1.RemoveClientConnectionsReq), variadicArgs...) + }) + return _c +} + +func (_c *ChannelsServiceClient_RemoveClientConnections_Call) Return(_a0 *v1.RemoveClientConnectionsRes, _a1 error) *ChannelsServiceClient_RemoveClientConnections_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ChannelsServiceClient_RemoveClientConnections_Call) RunAndReturn(run func(context.Context, *v1.RemoveClientConnectionsReq, ...grpc.CallOption) (*v1.RemoveClientConnectionsRes, error)) *ChannelsServiceClient_RemoveClientConnections_Call { + _c.Call.Return(run) + return _c +} + +// UnsetParentGroupFromChannels provides a mock function with given fields: ctx, in, opts +func (_m *ChannelsServiceClient) UnsetParentGroupFromChannels(ctx context.Context, in *v1.UnsetParentGroupFromChannelsReq, opts ...grpc.CallOption) (*v1.UnsetParentGroupFromChannelsRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for UnsetParentGroupFromChannels") + } + + var r0 *v1.UnsetParentGroupFromChannelsRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v1.UnsetParentGroupFromChannelsReq, ...grpc.CallOption) (*v1.UnsetParentGroupFromChannelsRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v1.UnsetParentGroupFromChannelsReq, ...grpc.CallOption) *v1.UnsetParentGroupFromChannelsRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1.UnsetParentGroupFromChannelsRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v1.UnsetParentGroupFromChannelsReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChannelsServiceClient_UnsetParentGroupFromChannels_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnsetParentGroupFromChannels' +type ChannelsServiceClient_UnsetParentGroupFromChannels_Call struct { + *mock.Call +} + +// UnsetParentGroupFromChannels is a helper method to define mock.On call +// - ctx context.Context +// - in *v1.UnsetParentGroupFromChannelsReq +// - opts ...grpc.CallOption +func (_e *ChannelsServiceClient_Expecter) UnsetParentGroupFromChannels(ctx interface{}, in interface{}, opts ...interface{}) *ChannelsServiceClient_UnsetParentGroupFromChannels_Call { + return &ChannelsServiceClient_UnsetParentGroupFromChannels_Call{Call: _e.mock.On("UnsetParentGroupFromChannels", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ChannelsServiceClient_UnsetParentGroupFromChannels_Call) Run(run func(ctx context.Context, in *v1.UnsetParentGroupFromChannelsReq, opts ...grpc.CallOption)) *ChannelsServiceClient_UnsetParentGroupFromChannels_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*v1.UnsetParentGroupFromChannelsReq), variadicArgs...) + }) + return _c +} + +func (_c *ChannelsServiceClient_UnsetParentGroupFromChannels_Call) Return(_a0 *v1.UnsetParentGroupFromChannelsRes, _a1 error) *ChannelsServiceClient_UnsetParentGroupFromChannels_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ChannelsServiceClient_UnsetParentGroupFromChannels_Call) RunAndReturn(run func(context.Context, *v1.UnsetParentGroupFromChannelsReq, ...grpc.CallOption) (*v1.UnsetParentGroupFromChannelsRes, error)) *ChannelsServiceClient_UnsetParentGroupFromChannels_Call { + _c.Call.Return(run) + return _c +} + +// NewChannelsServiceClient creates a new instance of ChannelsServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewChannelsServiceClient(t interface { + mock.TestingT + Cleanup(func()) +}) *ChannelsServiceClient { + mock := &ChannelsServiceClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/channels/mocks/repository.go b/channels/mocks/repository.go new file mode 100644 index 0000000000..eef2f28d1c --- /dev/null +++ b/channels/mocks/repository.go @@ -0,0 +1,947 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + channels "github.com/absmach/magistrala/channels" + + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Repository is an autogenerated mock type for the Repository type +type Repository struct { + mock.Mock +} + +// AddConnections provides a mock function with given fields: ctx, conns +func (_m *Repository) AddConnections(ctx context.Context, conns []channels.Connection) error { + ret := _m.Called(ctx, conns) + + if len(ret) == 0 { + panic("no return value specified for AddConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []channels.Connection) error); ok { + r0 = rf(ctx, conns) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AddRoles provides a mock function with given fields: ctx, rps +func (_m *Repository) AddRoles(ctx context.Context, rps []roles.RoleProvision) ([]roles.Role, error) { + ret := _m.Called(ctx, rps) + + if len(ret) == 0 { + panic("no return value specified for AddRoles") + } + + var r0 []roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) ([]roles.Role, error)); ok { + return rf(ctx, rps) + } + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) []roles.Role); ok { + r0 = rf(ctx, rps) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.Role) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []roles.RoleProvision) error); ok { + r1 = rf(ctx, rps) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChangeStatus provides a mock function with given fields: ctx, channel +func (_m *Repository) ChangeStatus(ctx context.Context, channel channels.Channel) (channels.Channel, error) { + ret := _m.Called(ctx, channel) + + if len(ret) == 0 { + panic("no return value specified for ChangeStatus") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, channels.Channel) (channels.Channel, error)); ok { + return rf(ctx, channel) + } + if rf, ok := ret.Get(0).(func(context.Context, channels.Channel) channels.Channel); ok { + r0 = rf(ctx, channel) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, channels.Channel) error); ok { + r1 = rf(ctx, channel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChannelConnectionsCount provides a mock function with given fields: ctx, id +func (_m *Repository) ChannelConnectionsCount(ctx context.Context, id string) (uint64, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for ChannelConnectionsCount") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (uint64, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) uint64); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CheckConnection provides a mock function with given fields: ctx, conn +func (_m *Repository) CheckConnection(ctx context.Context, conn channels.Connection) error { + ret := _m.Called(ctx, conn) + + if len(ret) == 0 { + panic("no return value specified for CheckConnection") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, channels.Connection) error); ok { + r0 = rf(ctx, conn) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ClientAuthorize provides a mock function with given fields: ctx, conn +func (_m *Repository) ClientAuthorize(ctx context.Context, conn channels.Connection) error { + ret := _m.Called(ctx, conn) + + if len(ret) == 0 { + panic("no return value specified for ClientAuthorize") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, channels.Connection) error); ok { + r0 = rf(ctx, conn) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DoesChannelHaveConnections provides a mock function with given fields: ctx, id +func (_m *Repository) DoesChannelHaveConnections(ctx context.Context, id string) (bool, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for DoesChannelHaveConnections") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Remove provides a mock function with given fields: ctx, ids +func (_m *Repository) Remove(ctx context.Context, ids ...string) error { + _va := make([]interface{}, len(ids)) + for _i := range ids { + _va[_i] = ids[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Remove") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok { + r0 = rf(ctx, ids...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveChannelConnections provides a mock function with given fields: ctx, channelID +func (_m *Repository) RemoveChannelConnections(ctx context.Context, channelID string) error { + ret := _m.Called(ctx, channelID) + + if len(ret) == 0 { + panic("no return value specified for RemoveChannelConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, channelID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveClientConnections provides a mock function with given fields: ctx, clientID +func (_m *Repository) RemoveClientConnections(ctx context.Context, clientID string) error { + ret := _m.Called(ctx, clientID) + + if len(ret) == 0 { + panic("no return value specified for RemoveClientConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, clientID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveConnections provides a mock function with given fields: ctx, conns +func (_m *Repository) RemoveConnections(ctx context.Context, conns []channels.Connection) error { + ret := _m.Called(ctx, conns) + + if len(ret) == 0 { + panic("no return value specified for RemoveConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []channels.Connection) error); ok { + r0 = rf(ctx, conns) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, members +func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, members string) error { + ret := _m.Called(ctx, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveParentGroup provides a mock function with given fields: ctx, ch +func (_m *Repository) RemoveParentGroup(ctx context.Context, ch channels.Channel) error { + ret := _m.Called(ctx, ch) + + if len(ret) == 0 { + panic("no return value specified for RemoveParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, channels.Channel) error); ok { + r0 = rf(ctx, ch) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRoles provides a mock function with given fields: ctx, roleIDs +func (_m *Repository) RemoveRoles(ctx context.Context, roleIDs []string) error { + ret := _m.Called(ctx, roleIDs) + + if len(ret) == 0 { + panic("no return value specified for RemoveRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { + r0 = rf(ctx, roleIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAll provides a mock function with given fields: ctx, pm +func (_m *Repository) RetrieveAll(ctx context.Context, pm channels.PageMetadata) (channels.Page, error) { + ret := _m.Called(ctx, pm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAll") + } + + var r0 channels.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, channels.PageMetadata) (channels.Page, error)); ok { + return rf(ctx, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, channels.PageMetadata) channels.Page); ok { + r0 = rf(ctx, pm) + } else { + r0 = ret.Get(0).(channels.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, channels.PageMetadata) error); ok { + r1 = rf(ctx, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, entityID, limit, offset +func (_m *Repository) RetrieveAllRoles(ctx context.Context, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByID provides a mock function with given fields: ctx, id +func (_m *Repository) RetrieveByID(ctx context.Context, id string) (channels.Channel, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByID") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (channels.Channel, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) channels.Channel); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveEntitiesRolesActionsMembers provides a mock function with given fields: ctx, entityIDs +func (_m *Repository) RetrieveEntitiesRolesActionsMembers(ctx context.Context, entityIDs []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error) { + ret := _m.Called(ctx, entityIDs) + + if len(ret) == 0 { + panic("no return value specified for RetrieveEntitiesRolesActionsMembers") + } + + var r0 []roles.EntityActionRole + var r1 []roles.EntityMemberRole + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error)); ok { + return rf(ctx, entityIDs) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []roles.EntityActionRole); ok { + r0 = rf(ctx, entityIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.EntityActionRole) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) []roles.EntityMemberRole); ok { + r1 = rf(ctx, entityIDs) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]roles.EntityMemberRole) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, []string) error); ok { + r2 = rf(ctx, entityIDs) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// RetrieveParentGroupChannels provides a mock function with given fields: ctx, parentGroupID +func (_m *Repository) RetrieveParentGroupChannels(ctx context.Context, parentGroupID string) ([]channels.Channel, error) { + ret := _m.Called(ctx, parentGroupID) + + if len(ret) == 0 { + panic("no return value specified for RetrieveParentGroupChannels") + } + + var r0 []channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]channels.Channel, error)); ok { + return rf(ctx, parentGroupID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []channels.Channel); ok { + r0 = rf(ctx, parentGroupID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]channels.Channel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, parentGroupID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRole provides a mock function with given fields: ctx, roleID +func (_m *Repository) RetrieveRole(ctx context.Context, roleID string) (roles.Role, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (roles.Role, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) roles.Role); ok { + r0 = rf(ctx, roleID) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRoleByEntityIDAndName provides a mock function with given fields: ctx, entityID, roleName +func (_m *Repository) RetrieveRoleByEntityIDAndName(ctx context.Context, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRoleByEntityIDAndName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (roles.Role, error)); ok { + return rf(ctx, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) roles.Role); ok { + r0 = rf(ctx, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleAddActions(ctx context.Context, role roles.Role, actions []string) ([]string, error) { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleAddMembers(ctx context.Context, role roles.Role, members []string) ([]string, error) { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, members) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, roleID, actions +func (_m *Repository) RoleCheckActionsExists(ctx context.Context, roleID string, actions []string) (bool, error) { + ret := _m.Called(ctx, roleID, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, roleID, members +func (_m *Repository) RoleCheckMembersExists(ctx context.Context, roleID string, members []string) (bool, error) { + ret := _m.Called(ctx, roleID, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, members) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, roleID +func (_m *Repository) RoleListActions(ctx context.Context, roleID string) ([]string, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []string); ok { + r0 = rf(ctx, roleID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, roleID, limit, offset +func (_m *Repository) RoleListMembers(ctx context.Context, roleID string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, roleID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, roleID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, roleID, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, roleID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleRemoveActions(ctx context.Context, role roles.Role, actions []string) error { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllActions(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllMembers(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleRemoveMembers(ctx context.Context, role roles.Role, members []string) error { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Save provides a mock function with given fields: ctx, chs +func (_m *Repository) Save(ctx context.Context, chs ...channels.Channel) ([]channels.Channel, error) { + _va := make([]interface{}, len(chs)) + for _i := range chs { + _va[_i] = chs[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Save") + } + + var r0 []channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ...channels.Channel) ([]channels.Channel, error)); ok { + return rf(ctx, chs...) + } + if rf, ok := ret.Get(0).(func(context.Context, ...channels.Channel) []channels.Channel); ok { + r0 = rf(ctx, chs...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]channels.Channel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ...channels.Channel) error); ok { + r1 = rf(ctx, chs...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetParentGroup provides a mock function with given fields: ctx, ch +func (_m *Repository) SetParentGroup(ctx context.Context, ch channels.Channel) error { + ret := _m.Called(ctx, ch) + + if len(ret) == 0 { + panic("no return value specified for SetParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, channels.Channel) error); ok { + r0 = rf(ctx, ch) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnsetParentGroupFromChannels provides a mock function with given fields: ctx, parentGroupID +func (_m *Repository) UnsetParentGroupFromChannels(ctx context.Context, parentGroupID string) error { + ret := _m.Called(ctx, parentGroupID) + + if len(ret) == 0 { + panic("no return value specified for UnsetParentGroupFromChannels") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, parentGroupID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: ctx, c +func (_m *Repository) Update(ctx context.Context, c channels.Channel) (channels.Channel, error) { + ret := _m.Called(ctx, c) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, channels.Channel) (channels.Channel, error)); ok { + return rf(ctx, c) + } + if rf, ok := ret.Get(0).(func(context.Context, channels.Channel) channels.Channel); ok { + r0 = rf(ctx, c) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, channels.Channel) error); ok { + r1 = rf(ctx, c) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateRole provides a mock function with given fields: ctx, ro +func (_m *Repository) UpdateRole(ctx context.Context, ro roles.Role) (roles.Role, error) { + ret := _m.Called(ctx, ro) + + if len(ret) == 0 { + panic("no return value specified for UpdateRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) (roles.Role, error)); ok { + return rf(ctx, ro) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) roles.Role); ok { + r0 = rf(ctx, ro) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role) error); ok { + r1 = rf(ctx, ro) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateTags provides a mock function with given fields: ctx, ch +func (_m *Repository) UpdateTags(ctx context.Context, ch channels.Channel) (channels.Channel, error) { + ret := _m.Called(ctx, ch) + + if len(ret) == 0 { + panic("no return value specified for UpdateTags") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, channels.Channel) (channels.Channel, error)); ok { + return rf(ctx, ch) + } + if rf, ok := ret.Get(0).(func(context.Context, channels.Channel) channels.Channel); ok { + r0 = rf(ctx, ch) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, channels.Channel) error); ok { + r1 = rf(ctx, ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *Repository { + mock := &Repository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/channels/mocks/service.go b/channels/mocks/service.go new file mode 100644 index 0000000000..2bbd59abbd --- /dev/null +++ b/channels/mocks/service.go @@ -0,0 +1,784 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + channels "github.com/absmach/magistrala/channels" + authn "github.com/absmach/magistrala/pkg/authn" + + connections "github.com/absmach/magistrala/pkg/connections" + + context "context" + + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// AddRole provides a mock function with given fields: ctx, session, entityID, roleName, optionalActions, optionalMembers +func (_m *Service) AddRole(ctx context.Context, session authn.Session, entityID string, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName, optionalActions, optionalMembers) + + if len(ret) == 0 { + panic("no return value specified for AddRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Connect provides a mock function with given fields: ctx, session, chIDs, clIDs, connType +func (_m *Service) Connect(ctx context.Context, session authn.Session, chIDs []string, clIDs []string, connType []connections.ConnType) error { + ret := _m.Called(ctx, session, chIDs, clIDs, connType) + + if len(ret) == 0 { + panic("no return value specified for Connect") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, []string, []string, []connections.ConnType) error); ok { + r0 = rf(ctx, session, chIDs, clIDs, connType) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreateChannels provides a mock function with given fields: ctx, session, _a2 +func (_m *Service) CreateChannels(ctx context.Context, session authn.Session, _a2 ...channels.Channel) ([]channels.Channel, error) { + _va := make([]interface{}, len(_a2)) + for _i := range _a2 { + _va[_i] = _a2[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, session) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for CreateChannels") + } + + var r0 []channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, ...channels.Channel) ([]channels.Channel, error)); ok { + return rf(ctx, session, _a2...) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, ...channels.Channel) []channels.Channel); ok { + r0 = rf(ctx, session, _a2...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]channels.Channel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, ...channels.Channel) error); ok { + r1 = rf(ctx, session, _a2...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DisableChannel provides a mock function with given fields: ctx, session, id +func (_m *Service) DisableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for DisableChannel") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (channels.Channel, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) channels.Channel); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Disconnect provides a mock function with given fields: ctx, session, chIDs, clIDs, connType +func (_m *Service) Disconnect(ctx context.Context, session authn.Session, chIDs []string, clIDs []string, connType []connections.ConnType) error { + ret := _m.Called(ctx, session, chIDs, clIDs, connType) + + if len(ret) == 0 { + panic("no return value specified for Disconnect") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, []string, []string, []connections.ConnType) error); ok { + r0 = rf(ctx, session, chIDs, clIDs, connType) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EnableChannel provides a mock function with given fields: ctx, session, id +func (_m *Service) EnableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for EnableChannel") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (channels.Channel, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) channels.Channel); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAvailableActions provides a mock function with given fields: ctx, session +func (_m *Service) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + ret := _m.Called(ctx, session) + + if len(ret) == 0 { + panic("no return value specified for ListAvailableActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) ([]string, error)); ok { + return rf(ctx, session) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) []string); ok { + r0 = rf(ctx, session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session) error); ok { + r1 = rf(ctx, session) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListChannels provides a mock function with given fields: ctx, session, pm +func (_m *Service) ListChannels(ctx context.Context, session authn.Session, pm channels.PageMetadata) (channels.Page, error) { + ret := _m.Called(ctx, session, pm) + + if len(ret) == 0 { + panic("no return value specified for ListChannels") + } + + var r0 channels.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, channels.PageMetadata) (channels.Page, error)); ok { + return rf(ctx, session, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, channels.PageMetadata) channels.Page); ok { + r0 = rf(ctx, session, pm) + } else { + r0 = ret.Get(0).(channels.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, channels.PageMetadata) error); ok { + r1 = rf(ctx, session, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListChannelsByClient provides a mock function with given fields: ctx, session, id, pm +func (_m *Service) ListChannelsByClient(ctx context.Context, session authn.Session, id string, pm channels.PageMetadata) (channels.Page, error) { + ret := _m.Called(ctx, session, id, pm) + + if len(ret) == 0 { + panic("no return value specified for ListChannelsByClient") + } + + var r0 channels.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, channels.PageMetadata) (channels.Page, error)); ok { + return rf(ctx, session, id, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, channels.PageMetadata) channels.Page); ok { + r0 = rf(ctx, session, id, pm) + } else { + r0 = ret.Get(0).(channels.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, channels.PageMetadata) error); ok { + r1 = rf(ctx, session, id, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveChannel provides a mock function with given fields: ctx, session, id +func (_m *Service) RemoveChannel(ctx context.Context, session authn.Session, id string) error { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for RemoveChannel") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID +func (_m *Service) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { + ret := _m.Called(ctx, session, memberID) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, memberID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveParentGroup provides a mock function with given fields: ctx, session, id +func (_m *Service) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for RemoveParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RemoveRole(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RemoveRole") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, session, entityID, limit, offset +func (_m *Service) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, session, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, session, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, session, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RetrieveRole(ctx context.Context, session authn.Session, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleAddActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleAddMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleListActions(ctx context.Context, session authn.Session, entityID string, roleName string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) []string); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, session, entityID, roleName, limit, offset +func (_m *Service) RoleListMembers(ctx context.Context, session authn.Session, entityID string, roleName string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, session, entityID, roleName, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, session, entityID, roleName, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleRemoveActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) error { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) error { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetParentGroup provides a mock function with given fields: ctx, session, parentGroupID, id +func (_m *Service) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error { + ret := _m.Called(ctx, session, parentGroupID, id) + + if len(ret) == 0 { + panic("no return value specified for SetParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, parentGroupID, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateChannel provides a mock function with given fields: ctx, session, channel +func (_m *Service) UpdateChannel(ctx context.Context, session authn.Session, channel channels.Channel) (channels.Channel, error) { + ret := _m.Called(ctx, session, channel) + + if len(ret) == 0 { + panic("no return value specified for UpdateChannel") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, channels.Channel) (channels.Channel, error)); ok { + return rf(ctx, session, channel) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, channels.Channel) channels.Channel); ok { + r0 = rf(ctx, session, channel) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, channels.Channel) error); ok { + r1 = rf(ctx, session, channel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateChannelTags provides a mock function with given fields: ctx, session, channel +func (_m *Service) UpdateChannelTags(ctx context.Context, session authn.Session, channel channels.Channel) (channels.Channel, error) { + ret := _m.Called(ctx, session, channel) + + if len(ret) == 0 { + panic("no return value specified for UpdateChannelTags") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, channels.Channel) (channels.Channel, error)); ok { + return rf(ctx, session, channel) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, channels.Channel) channels.Channel); ok { + r0 = rf(ctx, session, channel) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, channels.Channel) error); ok { + r1 = rf(ctx, session, channel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateRoleName provides a mock function with given fields: ctx, session, entityID, oldRoleName, newRoleName +func (_m *Service) UpdateRoleName(ctx context.Context, session authn.Session, entityID string, oldRoleName string, newRoleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, oldRoleName, newRoleName) + + if len(ret) == 0 { + panic("no return value specified for UpdateRoleName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, oldRoleName, newRoleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, string) error); ok { + r1 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ViewChannel provides a mock function with given fields: ctx, session, id +func (_m *Service) ViewChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for ViewChannel") + } + + var r0 channels.Channel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (channels.Channel, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) channels.Channel); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(channels.Channel) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/channels/postgres/channels.go b/channels/postgres/channels.go new file mode 100644 index 0000000000..a18eac293e --- /dev/null +++ b/channels/postgres/channels.go @@ -0,0 +1,635 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/absmach/magistrala/channels" + clients "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + "github.com/absmach/magistrala/pkg/postgres" + rolesPostgres "github.com/absmach/magistrala/pkg/roles/repo/postgres" + "github.com/jackc/pgtype" +) + +const ( + rolesTableNamePrefix = "channels" + entityTableName = "channels" + entityIDColumnName = "id" +) + +var _ channels.Repository = (*channelRepository)(nil) + +type channelRepository struct { + db postgres.Database + rolesPostgres.Repository +} + +// NewChannelRepository instantiates a PostgreSQL implementation of channel +// repository. +func NewRepository(db postgres.Database) channels.Repository { + rolesRepo := rolesPostgres.NewRepository(db, rolesTableNamePrefix, entityTableName, entityIDColumnName) + return &channelRepository{ + db: db, + Repository: rolesRepo, + } +} + +func (cr *channelRepository) Save(ctx context.Context, chs ...channels.Channel) ([]channels.Channel, error) { + var dbchs []dbChannel + for _, ch := range chs { + dbch, err := toDBChannel(ch) + if err != nil { + return []channels.Channel{}, errors.Wrap(repoerr.ErrCreateEntity, err) + } + dbchs = append(dbchs, dbch) + } + + q := `INSERT INTO channels (id, name, tags, domain_id, parent_group_id, metadata, created_at, updated_at, updated_by, status) + VALUES (:id, :name, :tags, :domain_id, :parent_group_id, :metadata, :created_at, :updated_at, :updated_by, :status) + RETURNING id, name, tags, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + + row, err := cr.db.NamedQueryContext(ctx, q, dbchs) + if err != nil { + return []channels.Channel{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + defer row.Close() + + var reChs []channels.Channel + + for row.Next() { + dbch := dbChannel{} + if err := row.StructScan(&dbch); err != nil { + return []channels.Channel{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + + ch, err := toChannel(dbch) + if err != nil { + return []channels.Channel{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + reChs = append(reChs, ch) + } + return reChs, nil +} + +func (cr *channelRepository) Update(ctx context.Context, channel channels.Channel) (channels.Channel, error) { + var query []string + var upq string + if channel.Name != "" { + query = append(query, "name = :name,") + } + if channel.Metadata != nil { + query = append(query, "metadata = :metadata,") + } + if len(query) > 0 { + upq = strings.Join(query, " ") + } + q := fmt.Sprintf(`UPDATE channels SET %s updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id AND status = :status + RETURNING id, name, tags, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by`, + upq) + channel.Status = clients.EnabledStatus + return cr.update(ctx, channel, q) +} + +func (cr *channelRepository) UpdateTags(ctx context.Context, channel channels.Channel) (channels.Channel, error) { + q := `UPDATE channels SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id AND status = :status + RETURNING id, name, tags, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + channel.Status = clients.EnabledStatus + return cr.update(ctx, channel, q) +} + +func (cr *channelRepository) ChangeStatus(ctx context.Context, channel channels.Channel) (channels.Channel, error) { + q := `UPDATE channels SET status = :status, updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id + RETURNING id, name, tags, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + + return cr.update(ctx, channel, q) +} + +func (cr *channelRepository) RetrieveByID(ctx context.Context, id string) (channels.Channel, error) { + q := `SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, metadata, created_at, updated_at, updated_by, status FROM channels WHERE id = :id` + + dbch := dbChannel{ + ID: id, + } + + row, err := cr.db.NamedQueryContext(ctx, q, dbch) + if err != nil { + return channels.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + defer row.Close() + + dbch = dbChannel{} + if row.Next() { + if err := row.StructScan(&dbch); err != nil { + return channels.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + return toChannel(dbch) + } + + return channels.Channel{}, repoerr.ErrNotFound +} + +func (cr *channelRepository) RetrieveAll(ctx context.Context, pm channels.PageMetadata) (channels.Page, error) { + query, err := PageQuery(pm) + if err != nil { + return channels.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + query = applyOrdering(query, pm) + + q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, + c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM channels c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query) + + dbPage, err := toDBChannelsPage(pm) + if err != nil { + return channels.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + } + rows, err := cr.db.NamedQueryContext(ctx, q, dbPage) + if err != nil { + return channels.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + } + defer rows.Close() + + var items []channels.Channel + for rows.Next() { + dbch := dbChannel{} + if err := rows.StructScan(&dbch); err != nil { + return channels.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + ch, err := toChannel(dbch) + if err != nil { + return channels.Page{}, err + } + + items = append(items, ch) + } + cq := fmt.Sprintf(`SELECT COUNT(*) FROM channels c %s;`, query) + + total, err := postgres.Total(ctx, cr.db, cq, dbPage) + if err != nil { + return channels.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + page := channels.Page{ + Channels: items, + PageMetadata: channels.PageMetadata{ + Total: total, + Offset: pm.Offset, + Limit: pm.Limit, + }, + } + return page, nil +} + +func (cr *channelRepository) Remove(ctx context.Context, ids ...string) error { + q := "DELETE FROM channels AS c WHERE c.id = ANY(:channel_ids) ;" + params := map[string]interface{}{ + "channel_ids": ids, + } + result, err := cr.db.NamedExecContext(ctx, q, params) + if err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + if rows, _ := result.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } + return nil +} + +func (cr *channelRepository) SetParentGroup(ctx context.Context, ch channels.Channel) error { + q := "UPDATE channels SET parent_group_id = :parent_group_id, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id" + dbCh, err := toDBChannel(ch) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + result, err := cr.db.NamedExecContext(ctx, q, dbCh) + if err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + if rows, _ := result.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } + return nil +} + +func (cr *channelRepository) RemoveParentGroup(ctx context.Context, ch channels.Channel) error { + q := "UPDATE channels SET parent_group_id = NULL, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id" + dbCh, err := toDBChannel(ch) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + result, err := cr.db.NamedExecContext(ctx, q, dbCh) + if err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + if rows, _ := result.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } + return nil +} + +func (cr *channelRepository) AddConnections(ctx context.Context, conns []channels.Connection) error { + dbConns := toDBConnections(conns) + q := `INSERT INTO connections (channel_id, domain_id, client_id, type) + VALUES (:channel_id, :domain_id, :client_id, :type );` + + if _, err := cr.db.NamedExecContext(ctx, q, dbConns); err != nil { + return postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + return nil +} + +func (cr *channelRepository) RemoveConnections(ctx context.Context, conns []channels.Connection) (retErr error) { + tx, err := cr.db.BeginTxx(ctx, nil) + if err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + defer func() { + if retErr != nil { + if errRollBack := tx.Rollback(); errRollBack != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollBack)) + } + } + }() + + query := `DELETE FROM connections WHERE channel_id = :channel_id AND domain_id = :domain_id AND client_id = :client_id` + + for _, conn := range conns { + if uint8(conn.Type) > 0 { + query = query + " AND type = :type " + } + dbConn := toDBConnection(conn) + if _, err := tx.NamedExec(query, dbConn); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, errors.Wrap(fmt.Errorf("failed to delete connection for channel_id: %s, domain_id: %s client_id %s", conn.ChannelID, conn.DomainID, conn.ClientID), err)) + } + } + if err := tx.Commit(); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (cr *channelRepository) CheckConnection(ctx context.Context, conn channels.Connection) error { + query := `SELECT 1 FROM connections WHERE channel_id = :channel_id AND domain_id = :domain_id AND client_id = :client_id AND type = :type LIMIT 1` + dbConn := toDBConnection(conn) + rows, err := cr.db.NamedQueryContext(ctx, query, dbConn) + if err != nil { + return postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + if !rows.Next() { + return repoerr.ErrNotFound + } + return nil +} + +func (cr *channelRepository) ClientAuthorize(ctx context.Context, conn channels.Connection) error { + query := `SELECT 1 FROM connections WHERE channel_id = :channel_id AND client_id = :client_id AND type = :type LIMIT 1` + dbConn := toDBConnection(conn) + rows, err := cr.db.NamedQueryContext(ctx, query, dbConn) + if err != nil { + return postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + if !rows.Next() { + return repoerr.ErrNotFound + } + return nil +} + +func (cr *channelRepository) ChannelConnectionsCount(ctx context.Context, id string) (uint64, error) { + query := `SELECT COUNT(*) FROM connections WHERE channel_id = :channel_id` + dbConn := dbConnection{ChannelID: id} + + total, err := postgres.Total(ctx, cr.db, query, dbConn) + if err != nil { + return 0, postgres.HandleError(repoerr.ErrViewEntity, err) + } + return total, nil +} + +func (cr *channelRepository) DoesChannelHaveConnections(ctx context.Context, id string) (bool, error) { + query := `SELECT 1 FROM connections WHERE channel_id = :channel_id` + dbConn := dbConnection{ChannelID: id} + + rows, err := cr.db.NamedQueryContext(ctx, query, dbConn) + if err != nil { + return false, postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + return rows.Next(), nil +} + +func (cr *channelRepository) RemoveClientConnections(ctx context.Context, clientID string) error { + query := `DELETE FROM connections WHERE client_id = :client_id` + + dbConn := dbConnection{ClientID: clientID} + if _, err := cr.db.NamedExecContext(ctx, query, dbConn); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (cr *channelRepository) RemoveChannelConnections(ctx context.Context, channelID string) error { + query := `DELETE FROM connections WHERE channel_id = :channel_id` + + dbConn := dbConnection{ChannelID: channelID} + if _, err := cr.db.NamedExecContext(ctx, query, dbConn); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (cr *channelRepository) RetrieveParentGroupChannels(ctx context.Context, parentGroupID string) ([]channels.Channel, error) { + query := `SELECT c.id, c.name, c.tags, c.metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, + c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM channels c WHERE c.parent_group_id = :parent_group_id ;` + + rows, err := cr.db.NamedQueryContext(ctx, query, dbChannel{ParentGroup: toNullString(parentGroupID)}) + if err != nil { + return []channels.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + var chs []channels.Channel + for rows.Next() { + dbch := dbChannel{} + if err := rows.StructScan(&dbch); err != nil { + return []channels.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + ch, err := toChannel(dbch) + if err != nil { + return []channels.Channel{}, err + } + + chs = append(chs, ch) + } + return chs, nil +} + +func (cr *channelRepository) UnsetParentGroupFromChannels(ctx context.Context, parentGroupID string) error { + query := "UPDATE channels SET parent_group_id = NULL WHERE parent_group_id = :parent_group_id" + + if _, err := cr.db.NamedExecContext(ctx, query, dbChannel{ParentGroup: toNullString(parentGroupID)}); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (cr *channelRepository) update(ctx context.Context, ch channels.Channel, query string) (channels.Channel, error) { + dbch, err := toDBChannel(ch) + if err != nil { + return channels.Channel{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + } + + row, err := cr.db.NamedQueryContext(ctx, query, dbch) + if err != nil { + return channels.Channel{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) + } + defer row.Close() + + dbch = dbChannel{} + if row.Next() { + if err := row.StructScan(&dbch); err != nil { + return channels.Channel{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + } + + return toChannel(dbch) + } + + return channels.Channel{}, repoerr.ErrNotFound +} + +type dbChannel struct { + ID string `db:"id"` + Name string `db:"name,omitempty"` + ParentGroup sql.NullString `db:"parent_group_id,omitempty"` + Tags pgtype.TextArray `db:"tags,omitempty"` + Domain string `db:"domain_id"` + Metadata []byte `db:"metadata,omitempty"` + CreatedAt time.Time `db:"created_at,omitempty"` + UpdatedAt sql.NullTime `db:"updated_at,omitempty"` + UpdatedBy *string `db:"updated_by,omitempty"` + Status clients.Status `db:"status,omitempty"` + Role *clients.Role `db:"role,omitempty"` +} + +func toDBChannel(ch channels.Channel) (dbChannel, error) { + data := []byte("{}") + if len(ch.Metadata) > 0 { + b, err := json.Marshal(ch.Metadata) + if err != nil { + return dbChannel{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + } + data = b + } + var tags pgtype.TextArray + if err := tags.Set(ch.Tags); err != nil { + return dbChannel{}, err + } + var updatedBy *string + if ch.UpdatedBy != "" { + updatedBy = &ch.UpdatedBy + } + var updatedAt sql.NullTime + if ch.UpdatedAt != (time.Time{}) { + updatedAt = sql.NullTime{Time: ch.UpdatedAt, Valid: true} + } + return dbChannel{ + ID: ch.ID, + Name: ch.Name, + ParentGroup: toNullString(ch.ParentGroup), + Domain: ch.Domain, + Tags: tags, + Metadata: data, + CreatedAt: ch.CreatedAt, + UpdatedAt: updatedAt, + UpdatedBy: updatedBy, + Status: ch.Status, + }, nil +} + +func toNullString(s string) sql.NullString { + if s == "" { + return sql.NullString{} + } + + return sql.NullString{ + String: s, + Valid: true, + } +} + +func toString(s sql.NullString) string { + if s.Valid { + return s.String + } + return "" +} + +func toChannel(ch dbChannel) (channels.Channel, error) { + var metadata clients.Metadata + if ch.Metadata != nil { + if err := json.Unmarshal([]byte(ch.Metadata), &metadata); err != nil { + return channels.Channel{}, errors.Wrap(errors.ErrMalformedEntity, err) + } + } + var tags []string + for _, e := range ch.Tags.Elements { + tags = append(tags, e.String) + } + var updatedBy string + if ch.UpdatedBy != nil { + updatedBy = *ch.UpdatedBy + } + var updatedAt time.Time + if ch.UpdatedAt.Valid { + updatedAt = ch.UpdatedAt.Time + } + + newCh := channels.Channel{ + ID: ch.ID, + Name: ch.Name, + Tags: tags, + Domain: ch.Domain, + ParentGroup: toString(ch.ParentGroup), + Metadata: metadata, + CreatedAt: ch.CreatedAt, + UpdatedAt: updatedAt, + UpdatedBy: updatedBy, + Status: ch.Status, + } + + return newCh, nil +} + +func PageQuery(pm channels.PageMetadata) (string, error) { + mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) + if err != nil { + return "", errors.Wrap(errors.ErrMalformedEntity, err) + } + + var query []string + if pm.Name != "" { + query = append(query, "c.name ILIKE '%' || :name || '%'") + } + + if pm.ClientID != "" { + query = append(query, "conn.client_id = :client_id") + } + if pm.Id != "" { + query = append(query, "c.id ILIKE '%' || :id || '%'") + } + if pm.Tag != "" { + query = append(query, "EXISTS (SELECT 1 FROM unnest(tags) AS tag WHERE tag ILIKE '%' || :tag || '%')") + } + + // If there are search params presents, use search and ignore other options. + // Always combine role with search params, so len(query) > 1. + if len(query) > 1 { + return fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")), nil + } + + if mq != "" { + query = append(query, mq) + } + + if len(pm.IDs) != 0 { + query = append(query, fmt.Sprintf("id IN ('%s')", strings.Join(pm.IDs, "','"))) + } + if pm.Status != clients.AllStatus { + query = append(query, "c.status = :status") + } + if pm.Domain != "" { + query = append(query, "c.domain_id = :domain_id") + } + var emq string + if len(query) > 0 { + emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")) + } + return emq, nil +} + +func applyOrdering(emq string, pm channels.PageMetadata) string { + switch pm.Order { + case "name", "created_at", "updated_at": + emq = fmt.Sprintf("%s ORDER BY %s", emq, pm.Order) + if pm.Dir == api.AscDir || pm.Dir == api.DescDir { + emq = fmt.Sprintf("%s %s", emq, pm.Dir) + } + } + return emq +} + +func toDBChannelsPage(pm channels.PageMetadata) (dbChannelsPage, error) { + _, data, err := postgres.CreateMetadataQuery("", pm.Metadata) + if err != nil { + return dbChannelsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + return dbChannelsPage{ + Name: pm.Name, + Id: pm.Id, + Metadata: data, + Domain: pm.Domain, + Total: pm.Total, + Offset: pm.Offset, + Limit: pm.Limit, + Status: pm.Status, + Tag: pm.Tag, + }, nil +} + +type dbChannelsPage struct { + Total uint64 `db:"total"` + Limit uint64 `db:"limit"` + Offset uint64 `db:"offset"` + Name string `db:"name"` + Id string `db:"id"` + Domain string `db:"domain_id"` + Metadata []byte `db:"metadata"` + Tag string `db:"tag"` + Status clients.Status `db:"status"` +} + +type dbConnection struct { + ChannelID string `db:"channel_id"` + DomainID string `db:"domain_id"` + ClientID string `db:"client_id"` + Type connections.ConnType `db:"type"` +} + +func toDBConnections(conns []channels.Connection) []dbConnection { + var dbconns []dbConnection + for _, conn := range conns { + dbconns = append(dbconns, toDBConnection(conn)) + } + return dbconns +} + +func toDBConnection(conn channels.Connection) dbConnection { + return dbConnection{ + ClientID: conn.ClientID, + ChannelID: conn.ChannelID, + DomainID: conn.DomainID, + Type: conn.Type, + } +} diff --git a/channels/postgres/init.go b/channels/postgres/init.go new file mode 100644 index 0000000000..5d858d01b0 --- /dev/null +++ b/channels/postgres/init.go @@ -0,0 +1,60 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + rolesPostgres "github.com/absmach/magistrala/pkg/roles/repo/postgres" + _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access + migrate "github.com/rubenv/sql-migrate" +) + +func Migration() (*migrate.MemoryMigrationSource, error) { + rolesMigration, err := rolesPostgres.Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName) + if err != nil { + return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err) + } + channelsMigration := &migrate.MemoryMigrationSource{ + Migrations: []*migrate.Migration{ + { + Id: "channels_01", + // VARCHAR(36) for colums with IDs as UUIDS have a maximum of 36 characters + // STATUS 0 to imply enabled and 1 to imply disabled + Up: []string{ + `CREATE TABLE IF NOT EXISTS channels ( + id VARCHAR(36) PRIMARY KEY, + name VARCHAR(1024), + domain_id VARCHAR(36) NOT NULL, + parent_group_id VARCHAR(36) DEFAULT NULL, + tags TEXT[], + metadata JSONB, + created_by VARCHAR(254), + created_at TIMESTAMP, + updated_at TIMESTAMP, + updated_by VARCHAR(254), + status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), + UNIQUE (id, domain_id), + UNIQUE (domain_id, name) + )`, + `CREATE TABLE IF NOT EXISTS connections ( + channel_id VARCHAR(36), + domain_id VARCHAR(36), + client_id VARCHAR(36), + type SMALLINT NOT NULL CHECK (type IN (1, 2)), + FOREIGN KEY (channel_id, domain_id) REFERENCES channels (id, domain_id) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (channel_id, domain_id, client_id, type), + UNIQUE (channel_id, client_id) + )`, + }, + Down: []string{ + `DROP TABLE IF EXISTS channels`, + `DROP TABLE IF EXISTS connections`, + }, + }, + }, + } + channelsMigration.Migrations = append(channelsMigration.Migrations, rolesMigration.Migrations...) + return channelsMigration, nil +} diff --git a/channels/private/service.go b/channels/private/service.go new file mode 100644 index 0000000000..681767676b --- /dev/null +++ b/channels/private/service.go @@ -0,0 +1,96 @@ +package private + +import ( + "context" + + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" +) + +type Service interface { + Authorize(ctx context.Context, req channels.AuthzReq) error + UnsetParentGroupFromChannels(ctx context.Context, parentGroupID string) error + RemoveClientConnections(ctx context.Context, clientID string) error +} + +type service struct { + repo channels.Repository + evaluator policies.Evaluator + policy policies.Service +} + +var _ Service = (*service)(nil) + +func New(repo channels.Repository, evaluator policies.Evaluator, policy policies.Service) Service { + return service{repo, evaluator, policy} +} + +func (svc service) Authorize(ctx context.Context, req channels.AuthzReq) error { + switch req.ClientType { + case policies.UserType: + pr := policies.Policy{ + Subject: req.ClientID, + SubjectType: policies.UserType, + Object: req.ChannelID, + ObjectType: policies.ChannelType, + } + if err := svc.evaluator.CheckPolicy(ctx, pr); err != nil { + return errors.Wrap(svcerr.ErrAuthorization, err) + } + return nil + case policies.ClientType: + // Optimization: Add cache + if err := svc.repo.ClientAuthorize(ctx, channels.Connection{ + ChannelID: req.ChannelID, + ClientID: req.ClientID, + Type: req.Type, + }); err != nil { + return errors.Wrap(svcerr.ErrAuthorization, err) + } + return nil + default: + return svcerr.ErrAuthentication + } +} + +func (svc service) RemoveClientConnections(ctx context.Context, clientID string) error { + return svc.repo.RemoveClientConnections(ctx, clientID) +} + +func (svc service) UnsetParentGroupFromChannels(ctx context.Context, parentGroupID string) (retErr error) { + chs, err := svc.repo.RetrieveParentGroupChannels(ctx, parentGroupID) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + + if len(chs) > 0 { + prs := []policies.Policy{} + for _, ch := range chs { + prs = append(prs, policies.Policy{ + SubjectType: policies.GroupType, + Subject: ch.ParentGroup, + Relation: policies.ParentGroupRelation, + ObjectType: policies.ChannelType, + Object: ch.ID, + }) + } + + if err := svc.policy.DeletePolicies(ctx, prs); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.AddPolicies(ctx, prs); err != nil { + retErr = errors.Wrap(retErr, errors.Wrap(errors.ErrRollbackTx, errRollback)) + } + } + }() + + if err := svc.repo.UnsetParentGroupFromChannels(ctx, parentGroupID); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + } + return nil +} diff --git a/channels/roleactions.go b/channels/roleactions.go new file mode 100644 index 0000000000..471f9c8b5e --- /dev/null +++ b/channels/roleactions.go @@ -0,0 +1,43 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 +package channels + +import "github.com/absmach/magistrala/pkg/roles" + +// Below codes should moved out of service, may be can be kept in `cmd//main.go` + +const ( + ChannelUpdate roles.Action = "update" + ChannelRead roles.Action = "read" + ChannelDelete roles.Action = "delete" + ChannelSetParentGroup roles.Action = "set_parent_group" + ChannelConnectToChannel roles.Action = "connect_to_client" + ChannelManageRole roles.Action = "manage_role" + ChannelAddRoleUsers roles.Action = "add_role_users" + ChannelRemoveRoleUsers roles.Action = "remove_role_users" + ChannelViewRoleUsers roles.Action = "view_role_users" +) + +const ( + BuiltInRoleAdmin = "admin" +) + +func AvailableActions() []roles.Action { + return []roles.Action{ + ChannelUpdate, + ChannelRead, + ChannelDelete, + ChannelSetParentGroup, + ChannelConnectToChannel, + ChannelManageRole, + ChannelAddRoleUsers, + ChannelRemoveRoleUsers, + ChannelViewRoleUsers, + } +} + +func BuiltInRoles() map[roles.BuiltInRoleName][]roles.Action { + return map[roles.BuiltInRoleName][]roles.Action{ + BuiltInRoleAdmin: AvailableActions(), + } +} diff --git a/channels/roleoperations.go b/channels/roleoperations.go new file mode 100644 index 0000000000..475cfa2f88 --- /dev/null +++ b/channels/roleoperations.go @@ -0,0 +1,160 @@ +package channels + +import ( + "github.com/absmach/magistrala/pkg/roles" + "github.com/absmach/magistrala/pkg/svcutil" +) + +// Internal Operations + +const ( + OpViewChannel svcutil.Operation = iota + OpUpdateChannel + OpUpdateChannelTags + OpEnableChannel + OpDisableChannel + OpDeleteChannel + OpSetParentGroup + OpRemoveParentGroup + OpConnectClient + OpDisconnectClient +) + +var expectedOperations = []svcutil.Operation{ + OpViewChannel, + OpUpdateChannel, + OpUpdateChannelTags, + OpEnableChannel, + OpDisableChannel, + OpDeleteChannel, + OpSetParentGroup, + OpRemoveParentGroup, + OpConnectClient, + OpDisconnectClient, +} + +var operationNames = []string{ + "OpViewChannel", + "OpUpdateChannel", + "OpUpdateChannelTags", + "OpEnableChannel", + "OpDisableChannel", + "OpDeleteChannel", + "OpSetParentGroup", + "OpRemoveParentGroup", + "OpConnectClient", + "OpDisconnectClient", +} + +func NewOperationPerm() svcutil.OperationPerm { + return svcutil.NewOperationPerm(expectedOperations, operationNames) +} + +// External Operations +const ( + DomainOpCreateChannel svcutil.ExternalOperation = iota + DomainOpListChannel + GroupOpSetChildChannel + GroupsOpRemoveChildChannel + ClientsOpConnectChannel + ClientsOpDisconnectChannel +) + +var expectedExternalOperations = []svcutil.ExternalOperation{ + DomainOpCreateChannel, + DomainOpListChannel, + GroupOpSetChildChannel, + GroupsOpRemoveChildChannel, + ClientsOpConnectChannel, + ClientsOpDisconnectChannel, +} + +var externalOperationNames = []string{ + "DomainOpCreateChannel", + "DomainOpListChannel", + "GroupOpSetChildChannel", + "GroupsOpRemoveChildChannel", + "ClientsOpConnectChannel", + "ClientsOpDisconnectChannel", +} + +func NewExternalOperationPerm() svcutil.ExternalOperationPerm { + return svcutil.NewExternalOperationPerm(expectedExternalOperations, externalOperationNames) +} + +// Below codes should moved out of service, may be can be kept in `cmd//main.go` + +const ( + updatePermission = "update_permission" + readPermission = "read_permission" + deletePermission = "delete_permission" + setParentGroupPermission = "set_parent_group_permission" + connectToClientPermission = "connect_to_client_permission" + + manageRolePermission = "manage_role_permission" + addRoleUsersPermission = "add_role_users_permission" + removeRoleUsersPermission = "remove_role_users_permission" + viewRoleUsersPermission = "view_role_users_permission" +) + +func NewOperationPermissionMap() map[svcutil.Operation]svcutil.Permission { + opPerm := map[svcutil.Operation]svcutil.Permission{ + OpViewChannel: readPermission, + OpUpdateChannel: updatePermission, + OpUpdateChannelTags: updatePermission, + OpEnableChannel: updatePermission, + OpDisableChannel: updatePermission, + OpDeleteChannel: deletePermission, + OpSetParentGroup: setParentGroupPermission, + OpRemoveParentGroup: setParentGroupPermission, + OpConnectClient: connectToClientPermission, + OpDisconnectClient: connectToClientPermission, + } + return opPerm +} + +func NewRolesOperationPermissionMap() map[svcutil.Operation]svcutil.Permission { + opPerm := map[svcutil.Operation]svcutil.Permission{ + roles.OpAddRole: manageRolePermission, + roles.OpRemoveRole: manageRolePermission, + roles.OpUpdateRoleName: manageRolePermission, + roles.OpRetrieveRole: manageRolePermission, + roles.OpRetrieveAllRoles: manageRolePermission, + roles.OpRoleAddActions: manageRolePermission, + roles.OpRoleListActions: manageRolePermission, + roles.OpRoleCheckActionsExists: manageRolePermission, + roles.OpRoleRemoveActions: manageRolePermission, + roles.OpRoleRemoveAllActions: manageRolePermission, + roles.OpRoleAddMembers: addRoleUsersPermission, + roles.OpRoleListMembers: viewRoleUsersPermission, + roles.OpRoleCheckMembersExists: viewRoleUsersPermission, + roles.OpRoleRemoveMembers: removeRoleUsersPermission, + roles.OpRoleRemoveAllMembers: manageRolePermission, + } + return opPerm +} + +const ( + // External Permission + // Domains + domainCreateChannelPermission = "channel_create_permission" + domainListChanelPermission = "list_channels_permission" + // Groups + groupSetChildChannelPermission = "channel_create_permission" + groupRemoveChildChannelPermission = "channel_create_permission" + // Client + clientsConnectChannelPermission = "connect_to_channel_permission" + clientsDisconnectChannelPermission = "connect_to_channel_permission" +) + +func NewExternalOperationPermissionMap() map[svcutil.ExternalOperation]svcutil.Permission { + extOpPerm := map[svcutil.ExternalOperation]svcutil.Permission{ + DomainOpCreateChannel: domainCreateChannelPermission, + DomainOpListChannel: domainListChanelPermission, + GroupOpSetChildChannel: groupSetChildChannelPermission, + GroupsOpRemoveChildChannel: groupRemoveChildChannelPermission, + ClientsOpConnectChannel: clientsConnectChannelPermission, + ClientsOpDisconnectChannel: clientsDisconnectChannelPermission, + } + return extOpPerm +} diff --git a/channels/service.go b/channels/service.go new file mode 100644 index 0000000000..658632f516 --- /dev/null +++ b/channels/service.go @@ -0,0 +1,545 @@ +package channels + +import ( + "context" + "fmt" + "time" + + "github.com/absmach/magistrala" + mgclients "github.com/absmach/magistrala/clients" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + grpcCommonV1 "github.com/absmach/magistrala/internal/grpc/common/v1" + grpcGroupsV1 "github.com/absmach/magistrala/internal/grpc/groups/v1" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/roles" + "golang.org/x/sync/errgroup" +) + +var ( + errCreateChannelsPolicies = errors.New("failed to create channels policies") + errRollbackRepo = errors.New("failed to rollback repo") + errAddConnectionsClients = errors.New("failed to add connections in clients service") + errRemoveConnectionsClients = errors.New("failed to remove connections from clients service") + errSetParentGroup = errors.New("channel already have parent") +) + +type service struct { + repo Repository + policy policies.Service + idProvider magistrala.IDProvider + clients grpcClientsV1.ClientsServiceClient + groups grpcGroupsV1.GroupsServiceClient + roles.ProvisionManageService +} + +var _ Service = (*service)(nil) + +func New(repo Repository, policy policies.Service, idProvider magistrala.IDProvider, clients grpcClientsV1.ClientsServiceClient, groups grpcGroupsV1.GroupsServiceClient, sidProvider magistrala.IDProvider) (Service, error) { + rpms, err := roles.NewProvisionManageService(policies.ChannelType, repo, policy, sidProvider, AvailableActions(), BuiltInRoles()) + if err != nil { + return nil, err + } + + return service{ + repo: repo, + policy: policy, + idProvider: idProvider, + clients: clients, + groups: groups, + ProvisionManageService: rpms, + }, nil +} + +func (svc service) CreateChannels(ctx context.Context, session authn.Session, chs ...Channel) (retChs []Channel, retErr error) { + var reChs []Channel + for _, c := range chs { + if c.ID == "" { + clientID, err := svc.idProvider.ID() + if err != nil { + return []Channel{}, err + } + c.ID = clientID + } + + if c.Status != mgclients.DisabledStatus && c.Status != mgclients.EnabledStatus { + return []Channel{}, svcerr.ErrInvalidStatus + } + c.Domain = session.DomainID + c.CreatedAt = time.Now() + reChs = append(reChs, c) + } + + savedChs, err := svc.repo.Save(ctx, reChs...) + if err != nil { + return nil, errors.Wrap(svcerr.ErrCreateEntity, err) + } + chIDs := []string{} + for _, c := range savedChs { + chIDs = append(chIDs, c.ID) + } + + defer func() { + if retErr != nil { + if errRollBack := svc.repo.Remove(ctx, chIDs...); errRollBack != nil { + retErr = errors.Wrap(retErr, errors.Wrap(errRollbackRepo, errRollBack)) + } + } + }() + + newBuiltInRoleMembers := map[roles.BuiltInRoleName][]roles.Member{ + BuiltInRoleAdmin: {roles.Member(session.UserID)}, + } + + optionalPolicies := []policies.Policy{} + + for _, chID := range chIDs { + optionalPolicies = append(optionalPolicies, + policies.Policy{ + SubjectType: policies.DomainType, + Subject: session.DomainID, + Relation: policies.DomainRelation, + ObjectType: policies.ChannelType, + Object: chID, + }, + ) + } + if _, err := svc.AddNewEntitiesRoles(ctx, session.DomainID, session.UserID, chIDs, optionalPolicies, newBuiltInRoleMembers); err != nil { + return []Channel{}, errors.Wrap(errCreateChannelsPolicies, err) + } + return savedChs, nil +} + +func (svc service) UpdateChannel(ctx context.Context, session authn.Session, ch Channel) (Channel, error) { + channel := Channel{ + ID: ch.ID, + Name: ch.Name, + Metadata: ch.Metadata, + UpdatedAt: time.Now(), + UpdatedBy: session.UserID, + } + channel, err := svc.repo.Update(ctx, channel) + if err != nil { + return Channel{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return channel, nil +} + +func (svc service) UpdateChannelTags(ctx context.Context, session authn.Session, ch Channel) (Channel, error) { + channel := Channel{ + ID: ch.ID, + Tags: ch.Tags, + UpdatedAt: time.Now(), + UpdatedBy: session.UserID, + } + channel, err := svc.repo.UpdateTags(ctx, channel) + if err != nil { + return Channel{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return channel, nil +} + +func (svc service) EnableChannel(ctx context.Context, session authn.Session, id string) (Channel, error) { + channel := Channel{ + ID: id, + Status: mgclients.EnabledStatus, + UpdatedAt: time.Now(), + } + ch, err := svc.changeChannelStatus(ctx, session.UserID, channel) + if err != nil { + return Channel{}, errors.Wrap(mgclients.ErrEnableClient, err) + } + + return ch, nil +} + +func (svc service) DisableChannel(ctx context.Context, session authn.Session, id string) (Channel, error) { + channel := Channel{ + ID: id, + Status: mgclients.DisabledStatus, + UpdatedAt: time.Now(), + } + ch, err := svc.changeChannelStatus(ctx, session.UserID, channel) + if err != nil { + return Channel{}, errors.Wrap(mgclients.ErrDisableClient, err) + } + + return ch, nil +} + +func (svc service) ViewChannel(ctx context.Context, session authn.Session, id string) (Channel, error) { + channel, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return Channel{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + return channel, nil +} + +func (svc service) ListChannels(ctx context.Context, session authn.Session, pm PageMetadata) (Page, error) { + var ids []string + var err error + + switch session.SuperAdmin { + case true: + pm.Domain = session.DomainID + default: + ids, err = svc.listChannelIDs(ctx, session.DomainUserID, pm.Permission) + if err != nil { + return Page{}, errors.Wrap(svcerr.ErrNotFound, err) + } + } + if len(ids) == 0 && pm.Domain == "" { + return Page{}, nil + } + pm.IDs = ids + + cp, err := svc.repo.RetrieveAll(ctx, pm) + if err != nil { + return Page{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + + if pm.ListPerms && len(cp.Channels) > 0 { + g, ctx := errgroup.WithContext(ctx) + + for i := range cp.Channels { + // Copying loop variable "i" to avoid "loop variable captured by func literal" + iter := i + g.Go(func() error { + return svc.retrievePermissions(ctx, session.DomainUserID, &cp.Channels[iter]) + }) + } + + if err := g.Wait(); err != nil { + return Page{}, err + } + } + return cp, nil +} + +func (svc service) ListChannelsByClient(ctx context.Context, session authn.Session, clID string, pm PageMetadata) (Page, error) { + return Page{}, nil +} + +func (svc service) RemoveChannel(ctx context.Context, session authn.Session, id string) error { + ok, err := svc.repo.DoesChannelHaveConnections(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + if ok { + if _, err := svc.clients.RemoveChannelConnections(ctx, &grpcClientsV1.RemoveChannelConnectionsReq{ChannelId: id}); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + } + ch, err := svc.repo.ChangeStatus(ctx, Channel{ID: id, Status: mgclients.DeletedStatus}) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + deletePolicies := []policies.Policy{ + { + SubjectType: policies.DomainType, + Subject: session.DomainID, + Relation: policies.DomainRelation, + ObjectType: policies.ChannelType, + Object: id, + }, + } + + if ch.ParentGroup != "" { + deletePolicies = append(deletePolicies, policies.Policy{ + SubjectType: policies.GroupType, + Subject: ch.ParentGroup, + Relation: policies.ParentGroupRelation, + ObjectType: policies.ChannelType, + Object: id, + }) + } + + filterDeletePolicies := []policies.Policy{ + { + SubjectType: policies.ChannelType, + Subject: id, + }, + { + ObjectType: policies.ChannelType, + Object: id, + }, + } + + if err := svc.RemoveEntitiesRoles(ctx, session.DomainID, session.DomainUserID, []string{id}, filterDeletePolicies, deletePolicies); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + if err := svc.repo.Remove(ctx, id); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + return nil +} + +func (svc service) Connect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) (retErr error) { + for _, chID := range chIDs { + c, err := svc.repo.RetrieveByID(ctx, chID) + if err != nil { + return errors.Wrap(svcerr.ErrCreateEntity, err) + } + if c.Status != mgclients.EnabledStatus { + return errors.Wrap(svcerr.ErrCreateEntity, fmt.Errorf("channel id %s is not in enabled state", chID)) + } + if c.Domain != session.DomainID { + return errors.Wrap(svcerr.ErrCreateEntity, fmt.Errorf("channel id %s has invalid domain id", chID)) + } + } + + for _, thID := range thIDs { + resp, err := svc.clients.RetrieveEntity(ctx, &grpcCommonV1.RetrieveEntityReq{Id: thID}) + if err != nil { + return errors.Wrap(svcerr.ErrCreateEntity, err) + } + if resp.GetEntity().GetStatus() != uint32(mgclients.EnabledStatus) { + return errors.Wrap(svcerr.ErrCreateEntity, fmt.Errorf("client id %s is not in enabled state", thID)) + } + if resp.GetEntity().GetDomainId() != session.DomainID { + return errors.Wrap(svcerr.ErrCreateEntity, fmt.Errorf("client id %s has invalid domain id", thID)) + } + } + + conns := []Connection{} + cliConns := []*grpcCommonV1.Connection{} + for _, chID := range chIDs { + for _, thID := range thIDs { + for _, connType := range connTypes { + conns = append(conns, Connection{ + ClientID: thID, + ChannelID: chID, + DomainID: session.DomainID, + Type: connType, + }) + cliConns = append(cliConns, &grpcCommonV1.Connection{ + ClientId: thID, + ChannelId: chID, + DomainId: session.DomainID, + Type: uint32(connType), + }) + } + } + } + for _, conn := range conns { + err := svc.repo.CheckConnection(ctx, conn) + + switch { + case err == nil: + return errors.Wrap(svcerr.ErrConflict, fmt.Errorf("channel %s and client %s are already connected for type %s in domain %s ", conn.ChannelID, conn.ClientID, conn.Type.String(), conn.DomainID)) + case err != repoerr.ErrNotFound: + return errors.Wrap(svcerr.ErrCreateEntity, err) + } + } + if _, err := svc.clients.AddConnections(ctx, &grpcCommonV1.AddConnectionsReq{Connections: cliConns}); err != nil { + return errors.Wrap(svcerr.ErrCreateEntity, errors.Wrap(errAddConnectionsClients, err)) + } + + if err := svc.repo.AddConnections(ctx, conns); err != nil { + return errors.Wrap(svcerr.ErrCreateEntity, err) + } + + return nil +} + +func (svc service) Disconnect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) (retErr error) { + for _, chID := range chIDs { + c, err := svc.repo.RetrieveByID(ctx, chID) + if err != nil { + return errors.Wrap(svcerr.ErrCreateEntity, err) + } + if c.Domain != session.DomainID { + return errors.Wrap(svcerr.ErrCreateEntity, fmt.Errorf("channel id %s has invalid domain id", chID)) + } + } + + for _, thID := range thIDs { + resp, err := svc.clients.RetrieveEntity(ctx, &grpcCommonV1.RetrieveEntityReq{Id: thID}) + if err != nil { + return errors.Wrap(svcerr.ErrCreateEntity, err) + } + + if resp.GetEntity().GetDomainId() != session.DomainID { + return errors.Wrap(svcerr.ErrCreateEntity, fmt.Errorf("client id %s has invalid domain id", thID)) + } + } + + conns := []Connection{} + thConns := []*grpcCommonV1.Connection{} + for _, chID := range chIDs { + for _, thID := range thIDs { + for _, connType := range connTypes { + conns = append(conns, Connection{ + ClientID: thID, + ChannelID: chID, + DomainID: session.DomainID, + Type: connType, + }) + thConns = append(thConns, &grpcCommonV1.Connection{ + ClientId: thID, + ChannelId: chID, + DomainId: session.DomainID, + Type: uint32(connType), + }) + } + } + } + + if _, err := svc.clients.RemoveConnections(ctx, &grpcCommonV1.RemoveConnectionsReq{Connections: thConns}); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, errors.Wrap(errRemoveConnectionsClients, err)) + } + + if err := svc.repo.RemoveConnections(ctx, conns); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + return nil +} + +func (svc service) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (retErr error) { + ch, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrUpdateEntity, err) + } + + resp, err := svc.groups.RetrieveEntity(ctx, &grpcCommonV1.RetrieveEntityReq{Id: parentGroupID}) + if err != nil { + return errors.Wrap(svcerr.ErrUpdateEntity, err) + } + if resp.GetEntity().GetDomainId() != session.DomainID { + return errors.Wrap(svcerr.ErrUpdateEntity, fmt.Errorf("parent group id %s has invalid domain id", parentGroupID)) + } + if resp.GetEntity().GetStatus() != uint32(mgclients.EnabledStatus) { + return errors.Wrap(svcerr.ErrUpdateEntity, fmt.Errorf("parent group id %s is not in enabled state", parentGroupID)) + } + + var pols []policies.Policy + if ch.ParentGroup != "" { + return errors.Wrap(svcerr.ErrConflict, errSetParentGroup) + } + pols = append(pols, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: parentGroupID, + Relation: policies.ParentGroupRelation, + ObjectType: policies.ChannelType, + Object: id, + }) + + if err := svc.policy.AddPolicies(ctx, pols); err != nil { + return errors.Wrap(svcerr.ErrAddPolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.DeletePolicies(ctx, pols); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + ch = Channel{ID: id, ParentGroup: parentGroupID, UpdatedBy: session.UserID, UpdatedAt: time.Now()} + + if err := svc.repo.SetParentGroup(ctx, ch); err != nil { + return errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return nil +} + +func (svc service) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (retErr error) { + ch, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + + if ch.ParentGroup != "" { + var pols []policies.Policy + pols = append(pols, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: ch.ParentGroup, + Relation: policies.ParentGroupRelation, + ObjectType: policies.ChannelType, + Object: id, + }) + + if err := svc.policy.DeletePolicies(ctx, pols); err != nil { + return errors.Wrap(svcerr.ErrAddPolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.AddPolicies(ctx, pols); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + + ch := Channel{ID: id, UpdatedBy: session.UserID, UpdatedAt: time.Now()} + + if err := svc.repo.RemoveParentGroup(ctx, ch); err != nil { + return err + } + } + + return nil +} + +func (svc service) listChannelIDs(ctx context.Context, userID, permission string) ([]string, error) { + tids, err := svc.policy.ListAllObjects(ctx, policies.Policy{ + SubjectType: policies.UserType, + Subject: userID, + Permission: permission, + ObjectType: policies.ChannelType, + }) + if err != nil { + return nil, errors.Wrap(svcerr.ErrNotFound, err) + } + return tids.Policies, nil +} + +func (svc service) retrievePermissions(ctx context.Context, userID string, channel *Channel) error { + permissions, err := svc.listUserClientPermission(ctx, userID, channel.ID) + if err != nil { + return err + } + channel.Permissions = permissions + return nil +} + +func (svc service) listUserClientPermission(ctx context.Context, userID, clientID string) ([]string, error) { + lp, err := svc.policy.ListPermissions(ctx, policies.Policy{ + SubjectType: policies.UserType, + Subject: userID, + Object: clientID, + ObjectType: policies.ChannelType, + }, []string{}) + if err != nil { + return []string{}, errors.Wrap(svcerr.ErrAuthorization, err) + } + return lp, nil +} + +func (svc service) changeChannelStatus(ctx context.Context, userID string, channel Channel) (Channel, error) { + dbchannel, err := svc.repo.RetrieveByID(ctx, channel.ID) + if err != nil { + return Channel{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + if dbchannel.Status == channel.Status { + return Channel{}, errors.ErrStatusAlreadyAssigned + } + + channel.UpdatedBy = userID + + channel, err = svc.repo.ChangeStatus(ctx, channel) + if err != nil { + return Channel{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return channel, nil +} diff --git a/things/tracing/doc.go b/channels/tracing/doc.go similarity index 72% rename from things/tracing/doc.go rename to channels/tracing/doc.go index 1d803beca5..0820cc9289 100644 --- a/things/tracing/doc.go +++ b/channels/tracing/doc.go @@ -1,11 +1,11 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package tracing provides tracing instrumentation for Magistrala things clients service. +// Package tracing provides tracing instrumentation for Magistrala channels service. // -// This package provides tracing middleware for Magistrala things clients service. +// This package provides tracing middleware for Magistrala channels service. // It can be used to trace incoming requests and add tracing capabilities to -// Magistrala things clients service. +// Magistrala channels service. // // For more details about tracing instrumentation for Magistrala messaging refer // to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. diff --git a/channels/tracing/tracing.go b/channels/tracing/tracing.go new file mode 100644 index 0000000000..c2cbd8a9c3 --- /dev/null +++ b/channels/tracing/tracing.go @@ -0,0 +1,133 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" + rmTrace "github.com/absmach/magistrala/pkg/roles/rolemanager/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +var _ channels.Service = (*tracingMiddleware)(nil) + +type tracingMiddleware struct { + tracer trace.Tracer + svc channels.Service + rmTrace.RoleManagerTracing +} + +// New returns a new group service with tracing capabilities. +func New(svc channels.Service, tracer trace.Tracer) channels.Service { + return &tracingMiddleware{tracer, svc, rmTrace.NewRoleManagerTracing("channels", svc, tracer)} +} + +// CreateChannels traces the "CreateChannels" operation of the wrapped policies.Service. +func (tm *tracingMiddleware) CreateChannels(ctx context.Context, session authn.Session, chs ...channels.Channel) ([]channels.Channel, error) { + ctx, span := tm.tracer.Start(ctx, "svc_create_channel") + defer span.End() + + return tm.svc.CreateChannels(ctx, session, chs...) +} + +// ViewChannel traces the "ViewChannel" operation of the wrapped policies.Service. +func (tm *tracingMiddleware) ViewChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + ctx, span := tm.tracer.Start(ctx, "svc_view_channel", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + return tm.svc.ViewChannel(ctx, session, id) +} + +// ListChannels traces the "ListChannels" operation of the wrapped policies.Service. +func (tm *tracingMiddleware) ListChannels(ctx context.Context, session authn.Session, pm channels.PageMetadata) (channels.Page, error) { + ctx, span := tm.tracer.Start(ctx, "svc_list_channels") + defer span.End() + return tm.svc.ListChannels(ctx, session, pm) +} + +func (tm *tracingMiddleware) ListChannelsByClient(ctx context.Context, session authn.Session, clientID string, pm channels.PageMetadata) (channels.Page, error) { + ctx, span := tm.tracer.Start(ctx, "svc_list_channels") + defer span.End() + return tm.svc.ListChannelsByClient(ctx, session, clientID, pm) +} + +// UpdateChannel traces the "UpdateChannel" operation of the wrapped policies.Service. +func (tm *tracingMiddleware) UpdateChannel(ctx context.Context, session authn.Session, cli channels.Channel) (channels.Channel, error) { + ctx, span := tm.tracer.Start(ctx, "svc_update_channel", trace.WithAttributes(attribute.String("id", cli.ID))) + defer span.End() + + return tm.svc.UpdateChannel(ctx, session, cli) +} + +// UpdateChannelTags traces the "UpdateChannelTags" operation of the wrapped policies.Service. +func (tm *tracingMiddleware) UpdateChannelTags(ctx context.Context, session authn.Session, cli channels.Channel) (channels.Channel, error) { + ctx, span := tm.tracer.Start(ctx, "svc_update_channel_tags", trace.WithAttributes( + attribute.String("id", cli.ID), + attribute.StringSlice("tags", cli.Tags), + )) + defer span.End() + + return tm.svc.UpdateChannelTags(ctx, session, cli) +} + +// EnableChannel traces the "EnableChannel" operation of the wrapped policies.Service. +func (tm *tracingMiddleware) EnableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + ctx, span := tm.tracer.Start(ctx, "svc_enable_channel", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + + return tm.svc.EnableChannel(ctx, session, id) +} + +// DisableChannel traces the "DisableChannel" operation of the wrapped policies.Service. +func (tm *tracingMiddleware) DisableChannel(ctx context.Context, session authn.Session, id string) (channels.Channel, error) { + ctx, span := tm.tracer.Start(ctx, "svc_disable_channel", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + + return tm.svc.DisableChannel(ctx, session, id) +} + +// DeleteChannel traces the "DeleteChannel" operation of the wrapped channels.Service. +func (tm *tracingMiddleware) RemoveChannel(ctx context.Context, session authn.Session, id string) error { + ctx, span := tm.tracer.Start(ctx, "delete_channel", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + return tm.svc.RemoveChannel(ctx, session, id) +} + +func (tm *tracingMiddleware) Connect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error { + ctx, span := tm.tracer.Start(ctx, "connect", trace.WithAttributes( + attribute.StringSlice("channel_ids", chIDs), + attribute.StringSlice("client_ids", thIDs), + )) + defer span.End() + return tm.svc.Connect(ctx, session, chIDs, thIDs, connTypes) +} + +func (tm *tracingMiddleware) Disconnect(ctx context.Context, session authn.Session, chIDs, thIDs []string, connTypes []connections.ConnType) error { + ctx, span := tm.tracer.Start(ctx, "disconnect", trace.WithAttributes( + attribute.StringSlice("channel_ids", chIDs), + attribute.StringSlice("client_ids", thIDs), + )) + defer span.End() + return tm.svc.Disconnect(ctx, session, chIDs, thIDs, connTypes) +} + +func (tm *tracingMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error { + ctx, span := tm.tracer.Start(ctx, "set_parent_group", trace.WithAttributes( + attribute.String("parent_group_id", parentGroupID), + attribute.String("id", id), + )) + defer span.End() + return tm.svc.SetParentGroup(ctx, session, parentGroupID, id) +} + +func (tm *tracingMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + ctx, span := tm.tracer.Start(ctx, "remove_parent_group", trace.WithAttributes( + attribute.String("id", id), + )) + defer span.End() + return tm.svc.RemoveParentGroup(ctx, session, id) +} diff --git a/cli/README.md b/cli/README.md index 58800b7a09..997b6be1cd 100644 --- a/cli/README.md +++ b/cli/README.md @@ -72,55 +72,55 @@ magistrala-cli users disable ### System Provisioning -#### Create Thing +#### Create Client ```bash -magistrala-cli things create '{"name":"myThing"}' +magistrala-cli clients create '{"name":"myClient"}' ``` -#### Create Thing with metadata +#### Create Client with metadata ```bash -magistrala-cli things create '{"name":"myThing", "metadata": {"key1":"value1"}}' +magistrala-cli clients create '{"name":"myClient", "metadata": {"key1":"value1"}}' ``` -#### Bulk Provision Things +#### Bulk Provision Clients ```bash -magistrala-cli provision things +magistrala-cli provision clients ``` -- `file` - A CSV or JSON file containing thing names (must have extension `.csv` or `.json`) +- `file` - A CSV or JSON file containing client names (must have extension `.csv` or `.json`) - `user_token` - A valid user auth token for the current system An example CSV file might be: ```csv -thing1, -thing2, -thing3, +client1, +client2, +client3, ``` -in which the first column is the thing's name. +in which the first column is the client's name. A comparable JSON file would be ```json [ { - "name": "", + "name": "", "status": "enabled" }, { - "name": "", + "name": "", "status": "disabled" }, { - "name": "", + "name": "", "status": "enabled", "credentials": { - "identity": "", - "secret": "" + "identity": "", + "secret": "" } } ] @@ -128,46 +128,46 @@ A comparable JSON file would be With JSON you can be able to specify more fields of the channels you want to create -#### Update Thing +#### Update Client ```bash -magistrala-cli things update '{"name":"value1", "metadata":{"key1": "value2"}}' +magistrala-cli clients update '{"name":"value1", "metadata":{"key1": "value2"}}' ``` -#### Identify Thing +#### Identify Client ```bash -magistrala-cli things identify +magistrala-cli clients identify ``` -#### Enable Thing +#### Enable Client ```bash -magistrala-cli things enable +magistrala-cli clients enable ``` -#### Disable Thing +#### Disable Client ```bash -magistrala-cli things disable +magistrala-cli clients disable ``` -#### Get Thing +#### Get Client ```bash -magistrala-cli things get +magistrala-cli clients get ``` -#### Get Things +#### Get Clients ```bash -magistrala-cli things get all +magistrala-cli clients get all ``` -#### Get a subset list of provisioned Things +#### Get a subset list of provisioned Clients ```bash -magistrala-cli things get all --offset=1 --limit=5 +magistrala-cli clients get all --offset=1 --limit=5 ``` #### Create Channel @@ -257,52 +257,52 @@ magistrala-cli channels get all --offset=1 --limit=5 ### Access control -#### Connect Thing to Channel +#### Connect Client to Channel ```bash -magistrala-cli things connect +magistrala-cli clients connect ``` -#### Bulk Connect Things to Channels +#### Bulk Connect Clients to Channels ```bash magistrala-cli provision connect ``` -- `file` - A CSV or JSON file containing thing and channel ids (must have extension `.csv` or `.json`) +- `file` - A CSV or JSON file containing client and channel ids (must have extension `.csv` or `.json`) - `user_token` - A valid user auth token for the current system An example CSV file might be ```csv -, -, +, +, ``` -in which the first column is thing IDs and the second column is channel IDs. A connection will be created for each thing to each channel. This example would result in 4 connections being created. +in which the first column is client IDs and the second column is channel IDs. A connection will be created for each client to each channel. This example would result in 4 connections being created. A comparable JSON file would be ```json { - "client_ids": ["", ""], + "client_ids": ["", ""], "group_ids": ["", ""] } ``` -#### Disconnect Thing from Channel +#### Disconnect Client from Channel ```bash -magistrala-cli things disconnect +magistrala-cli clients disconnect ``` -#### Get a subset list of Channels connected to Thing +#### Get a subset list of Channels connected to Client ```bash -magistrala-cli things connections +magistrala-cli clients connections ``` -#### Get a subset list of Things connected to Channel +#### Get a subset list of Clients connected to Channel ```bash magistrala-cli channels connections @@ -313,7 +313,7 @@ magistrala-cli channels connections #### Send a message over HTTP ```bash -magistrala-cli messages send '[{"bn":"Dev1","n":"temp","v":20}, {"n":"hum","v":40}, {"bn":"Dev2", "n":"temp","v":20}, {"n":"hum","v":40}]' +magistrala-cli messages send '[{"bn":"Dev1","n":"temp","v":20}, {"n":"hum","v":40}, {"bn":"Dev2", "n":"temp","v":20}, {"n":"hum","v":40}]' ``` #### Read messages over HTTP @@ -333,19 +333,19 @@ magistrala-cli bootstrap create '{"external_id": "myExtID", "external_key": "myE #### View configuration ```bash -magistrala-cli bootstrap get -b +magistrala-cli bootstrap get -b ``` #### Update configuration ```bash -magistrala-cli bootstrap update '{"thing_id":"", "name": "newName", "content": "newContent"}' -b +magistrala-cli bootstrap update '{"client_id":"", "name": "newName", "content": "newContent"}' -b ``` #### Remove configuration ```bash -magistrala-cli bootstrap remove -b +magistrala-cli bootstrap remove -b ``` #### Bootstrap configuration diff --git a/cli/bootstrap.go b/cli/bootstrap.go index dde560fa02..4a9ee61925 100644 --- a/cli/bootstrap.go +++ b/cli/bootstrap.go @@ -14,7 +14,7 @@ var cmdBootstrap = []cobra.Command{ { Use: "create ", Short: "Create config", - Long: `Create new Thing Bootstrap Config to the user identified by the provided key`, + Long: `Create new Client Bootstrap Config to the user identified by the provided key`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) @@ -37,11 +37,11 @@ var cmdBootstrap = []cobra.Command{ }, }, { - Use: "get [all | ] ", + Use: "get [all | ] ", Short: "Get config", - Long: `Get Thing Config with given ID belonging to the user identified by the given key. + Long: `Get Client Config with given ID belonging to the user identified by the given key. all - lists all config - - view config of `, + - view config of `, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) @@ -77,7 +77,7 @@ var cmdBootstrap = []cobra.Command{ Short: "Update config", Long: `Updates editable fields of the provided Config. config - Updates editable fields of the provided Config. - connection - Updates connections performs update of the channel list corresponding Thing is connected to. + connection - Updates connections performs update of the channel list corresponding Client is connected to. channel_ids - '["channel_id1", ...]' certs - Update bootstrap config certificates.`, Run: func(cmd *cobra.Command, args []string) { @@ -128,7 +128,7 @@ var cmdBootstrap = []cobra.Command{ }, }, { - Use: "remove ", + Use: "remove ", Short: "Remove config", Long: `Removes Config with specified key that belongs to the user identified by the given key`, Run: func(cmd *cobra.Command, args []string) { @@ -148,7 +148,7 @@ var cmdBootstrap = []cobra.Command{ { Use: "bootstrap [ | secure ]", Short: "Bootstrap config", - Long: `Returns Config to the Thing with provided external ID using external key. + Long: `Returns Config to the Client with provided external ID using external key. secure - Retrieves a configuration with given external ID and encrypted external key.`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { @@ -177,7 +177,7 @@ var cmdBootstrap = []cobra.Command{ { Use: "whitelist ", Short: "Whitelist config", - Long: `Whitelist updates thing state config with given id from the authenticated user`, + Long: `Whitelist updates client state config with given id from the authenticated user`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) @@ -190,7 +190,7 @@ var cmdBootstrap = []cobra.Command{ return } - if err := sdk.Whitelist(cfg.ThingID, cfg.State, args[1], args[2]); err != nil { + if err := sdk.Whitelist(cfg.ClientID, cfg.State, args[1], args[2]); err != nil { logErrorCmd(*cmd, err) return } diff --git a/cli/bootstrap_test.go b/cli/bootstrap_test.go index 3fdacb6593..ed3e939fad 100644 --- a/cli/bootstrap_test.go +++ b/cli/bootstrap_test.go @@ -20,7 +20,7 @@ import ( ) var bootConfig = mgsdk.BootstrapConfig{ - ThingID: thing.ID, + ClientID: client.ID, Channels: []string{channel.ID}, Name: "Test Bootstrap", ExternalID: "09:6:0:sb:sa", @@ -33,8 +33,8 @@ func TestCreateBootstrapConfigCmd(t *testing.T) { bootCmd := cli.NewBootstrapCmd() rootCmd := setFlags(bootCmd) - jsonConfig := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"thing_id\": \"%s\", \"external_key\":\"key\", \"name\": \"%s\", \"channels\":[\"%s\"]}", thing.ID, "Test Bootstrap", channel.ID) - invalidJson := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"thing_id\": \"%s\", \"external_key\":\"key\", \"name\": \"%s\", \"channels\":[\"%s\"]", thing.ID, "Test Bootdtrap", channel.ID) + jsonConfig := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"client_id\": \"%s\", \"external_key\":\"key\", \"name\": \"%s\", \"channels\":[\"%s\"]}", client.ID, "Test Bootstrap", channel.ID) + invalidJson := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"client_id\": \"%s\", \"external_key\":\"key\", \"name\": \"%s\", \"channels\":[\"%s\"]", client.ID, "Test Bootdtrap", channel.ID) cases := []struct { desc string args []string @@ -52,8 +52,8 @@ func TestCreateBootstrapConfigCmd(t *testing.T) { validToken, }, logType: createLog, - id: thing.ID, - response: fmt.Sprintf("\ncreated: %s\n\n", thing.ID), + id: client.ID, + response: fmt.Sprintf("\ncreated: %s\n\n", client.ID), }, { desc: "create bootstrap config with invald args", @@ -231,7 +231,7 @@ func TestRemoveBootstrapConfigCmd(t *testing.T) { { desc: "remove bootstrap config successfully", args: []string{ - thing.ID, + client.ID, domainID, token, }, @@ -240,7 +240,7 @@ func TestRemoveBootstrapConfigCmd(t *testing.T) { { desc: "remove bootstrap config with invalid args", args: []string{ - thing.ID, + client.ID, domainID, token, extraArg, @@ -248,7 +248,7 @@ func TestRemoveBootstrapConfigCmd(t *testing.T) { logType: usageLog, }, { - desc: "remove bootstrap config with invalid thing id", + desc: "remove bootstrap config with invalid client id", args: []string{ invalidID, domainID, @@ -261,7 +261,7 @@ func TestRemoveBootstrapConfigCmd(t *testing.T) { { desc: "remove bootstrap config with invalid token", args: []string{ - thing.ID, + client.ID, domainID, invalidToken, }, @@ -334,7 +334,7 @@ func TestUpdateBootstrapConfigCmd(t *testing.T) { desc: "update bootstrap connections successfully", args: []string{ connection, - thing.ID, + client.ID, chanIDsJson, domainID, token, @@ -345,8 +345,8 @@ func TestUpdateBootstrapConfigCmd(t *testing.T) { desc: "update bootstrap connections with invalid json", args: []string{ connection, - thing.ID, - fmt.Sprintf("[\"%s\"", thing.ID), + client.ID, + fmt.Sprintf("[\"%s\"", client.ID), domainID, token, }, @@ -358,7 +358,7 @@ func TestUpdateBootstrapConfigCmd(t *testing.T) { desc: "update bootstrap connections with invalid token", args: []string{ connection, - thing.ID, + client.ID, chanIDsJson, domainID, invalidToken, @@ -371,7 +371,7 @@ func TestUpdateBootstrapConfigCmd(t *testing.T) { desc: "update bootstrap certs successfully", args: []string{ "certs", - thing.ID, + client.ID, "client cert", "client key", "ca", @@ -385,7 +385,7 @@ func TestUpdateBootstrapConfigCmd(t *testing.T) { desc: "update bootstrap certs with invalid token", args: []string{ "certs", - thing.ID, + client.ID, "client cert", "client key", "ca", @@ -462,7 +462,7 @@ func TestWhitelistConfigCmd(t *testing.T) { bootCmd := cli.NewBootstrapCmd() rootCmd := setFlags(bootCmd) - jsonConfig := fmt.Sprintf("{\"thing_id\": \"%s\", \"state\":%d}", thing.ID, 1) + jsonConfig := fmt.Sprintf("{\"client_id\": \"%s\", \"state\":%d}", client.ID, 1) cases := []struct { desc string @@ -493,7 +493,7 @@ func TestWhitelistConfigCmd(t *testing.T) { { desc: "whitelist config with invalid json", args: []string{ - fmt.Sprintf("{\"thing_id\": \"%s\", \"state\":%d", thing.ID, 1), + fmt.Sprintf("{\"client_id\": \"%s\", \"state\":%d", client.ID, 1), domainID, validToken, }, diff --git a/cli/certs.go b/cli/certs.go index 988e0c20e8..ee24c0e793 100644 --- a/cli/certs.go +++ b/cli/certs.go @@ -9,7 +9,7 @@ import ( var cmdCerts = []cobra.Command{ { - Use: "get [ | thing ] ", + Use: "get [ | client ] ", Short: "Get certificate", Long: `Gets a certificate for a given cert ID.`, Run: func(cmd *cobra.Command, args []string) { @@ -17,8 +17,8 @@ var cmdCerts = []cobra.Command{ logUsageCmd(*cmd, cmd.Use) return } - if args[0] == "thing" { - cert, err := sdk.ViewCertByThing(args[1], args[2], args[3]) + if args[0] == "client" { + cert, err := sdk.ViewCertByClient(args[1], args[2], args[3]) if err != nil { logErrorCmd(*cmd, err) return @@ -35,9 +35,9 @@ var cmdCerts = []cobra.Command{ }, }, { - Use: "revoke ", + Use: "revoke ", Short: "Revoke certificate", - Long: `Revokes a certificate for a given thing ID.`, + Long: `Revokes a certificate for a given client ID.`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) @@ -58,18 +58,18 @@ func NewCertsCmd() *cobra.Command { var ttl string issueCmd := cobra.Command{ - Use: "issue [--ttl=8760h]", + Use: "issue [--ttl=8760h]", Short: "Issue certificate", - Long: `Issues new certificate for a thing`, + Long: `Issues new certificate for a client`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) return } - thingID := args[0] + clientID := args[0] - c, err := sdk.IssueCert(thingID, ttl, args[1], args[2]) + c, err := sdk.IssueCert(clientID, ttl, args[1], args[2]) if err != nil { logErrorCmd(*cmd, err) return @@ -83,7 +83,7 @@ func NewCertsCmd() *cobra.Command { cmd := cobra.Command{ Use: "certs [issue | get | revoke ]", Short: "Certificates management", - Long: `Certificates management: issue, get or revoke certificates for things"`, + Long: `Certificates management: issue, get or revoke certificates for clients"`, } cmdCerts = append(cmdCerts, issueCmd) diff --git a/cli/certs_test.go b/cli/certs_test.go index efc057c107..69cb4907af 100644 --- a/cli/certs_test.go +++ b/cli/certs_test.go @@ -21,7 +21,7 @@ import ( ) var cert = mgsdk.Cert{ - ThingID: thing.ID, + ClientID: client.ID, } func TestGetCertCmd(t *testing.T) { @@ -45,8 +45,8 @@ func TestGetCertCmd(t *testing.T) { { desc: "get cert successfully", args: []string{ - "thing", - thing.ID, + "client", + client.ID, domainID, validToken, }, @@ -63,7 +63,7 @@ func TestGetCertCmd(t *testing.T) { { desc: "get cert successfully by id", args: []string{ - thing.ID, + client.ID, domainID, validToken, }, @@ -73,8 +73,8 @@ func TestGetCertCmd(t *testing.T) { { desc: "get cert with invalid token", args: []string{ - "thing", - thing.ID, + "client", + client.ID, domainID, invalidToken, }, @@ -85,7 +85,7 @@ func TestGetCertCmd(t *testing.T) { { desc: "get cert by id with invalid token", args: []string{ - thing.ID, + client.ID, domainID, invalidToken, }, @@ -96,7 +96,7 @@ func TestGetCertCmd(t *testing.T) { { desc: "get cert with invalid args", args: []string{ - thing.ID, + client.ID, }, logType: usageLog, }, @@ -104,12 +104,12 @@ func TestGetCertCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ViewCertByThing", mock.Anything, mock.Anything, mock.Anything).Return(tc.serials, tc.sdkErr) + sdkCall := sdkMock.On("ViewCertByClient", mock.Anything, mock.Anything, mock.Anything).Return(tc.serials, tc.sdkErr) sdkCall1 := sdkMock.On("ViewCert", mock.Anything, mock.Anything, mock.Anything).Return(tc.cert, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) switch tc.logType { case entityLog: - if tc.args[1] == "thing" { + if tc.args[1] == "client" { err := json.Unmarshal([]byte(out), &cts) assert.Nil(t, err) assert.Equal(t, tc.serials, cts, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.serials, cts)) @@ -150,7 +150,7 @@ func TestRevokeCertCmd(t *testing.T) { { desc: "revoke cert successfully", args: []string{ - thing.ID, + client.ID, domainID, token, }, @@ -161,7 +161,7 @@ func TestRevokeCertCmd(t *testing.T) { { desc: "revoke cert with invalid args", args: []string{ - thing.ID, + client.ID, domainID, token, extraArg, @@ -171,7 +171,7 @@ func TestRevokeCertCmd(t *testing.T) { { desc: "revoke cert with invalid token", args: []string{ - thing.ID, + client.ID, domainID, invalidToken, }, @@ -221,7 +221,7 @@ func TestIssueCertCmd(t *testing.T) { { desc: "issue cert successfully", args: []string{ - thing.ID, + client.ID, domainID, validToken, }, @@ -231,7 +231,7 @@ func TestIssueCertCmd(t *testing.T) { { desc: "issue cert with invalid args", args: []string{ - thing.ID, + client.ID, domainID, validToken, extraArg, @@ -241,7 +241,7 @@ func TestIssueCertCmd(t *testing.T) { { desc: "issue cert with invalid token", args: []string{ - thing.ID, + client.ID, domainID, invalidToken, }, diff --git a/cli/channels.go b/cli/channels.go index a033f1aa81..122024d457 100644 --- a/cli/channels.go +++ b/cli/channels.go @@ -43,7 +43,7 @@ var cmdChannels = []cobra.Command{ Short: "Get channel", Long: `Get all channels or get channel by id. Channels can be filtered by name or metadata. all - lists all channels - - shows thing with provided `, + - shows client with provided `, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { @@ -127,7 +127,7 @@ var cmdChannels = []cobra.Command{ { Use: "connections ", Short: "Connections list", - Long: `List of Things connected to a Channel`, + Long: `List of Clients connected to a Channel`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) @@ -137,7 +137,7 @@ var cmdChannels = []cobra.Command{ Offset: Offset, Limit: Limit, } - cl, err := sdk.ThingsByChannel(args[0], pm, args[1], args[2]) + cl, err := sdk.ClientsByChannel(args[0], pm, args[1], args[2]) if err != nil { logErrorCmd(*cmd, err) return @@ -363,7 +363,7 @@ func NewChannelsCmd() *cobra.Command { cmd := cobra.Command{ Use: "channels [create | get | update | delete | connections | not-connected | assign | unassign | users | groups]", Short: "Channels management", - Long: `Channels management: create, get, update or delete Channel and get list of Things connected or not connected to a Channel`, + Long: `Channels management: create, get, update or delete Channel and get list of Clients connected or not connected to a Channel`, } for i := range cmdChannels { diff --git a/cli/channels_test.go b/cli/channels_test.go index 428144fec2..b9de7cff1b 100644 --- a/cli/channels_test.go +++ b/cli/channels_test.go @@ -374,14 +374,14 @@ func TestListConnectionsCmd(t *testing.T) { channelCmd := cli.NewChannelsCmd() rootCmd := setFlags(channelCmd) - var tp mgsdk.ThingsPage + var tp mgsdk.ClientsPage cases := []struct { desc string args []string sdkErr errors.SDKError errLogMessage string logType outputLog - page mgsdk.ThingsPage + page mgsdk.ClientsPage }{ { desc: "list connections successfully", @@ -390,13 +390,13 @@ func TestListConnectionsCmd(t *testing.T) { domainID, token, }, - page: mgsdk.ThingsPage{ + page: mgsdk.ClientsPage{ PageRes: mgsdk.PageRes{ Total: 1, Offset: 0, Limit: 10, }, - Things: []mgsdk.Thing{thing}, + Clients: []mgsdk.Client{client}, }, logType: entityLog, }, @@ -425,7 +425,7 @@ func TestListConnectionsCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ThingsByChannel", tc.args[0], mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) + sdkCall := sdkMock.On("ClientsByChannel", tc.args[0], mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{connsCmd}, tc.args...)...) switch tc.logType { case entityLog: @@ -573,7 +573,7 @@ func TestDisableChannelCmd(t *testing.T) { logType: errLog, }, { - desc: "disable thing with invalid args", + desc: "disable client with invalid args", args: []string{ channel.ID, domainID, diff --git a/cli/clients.go b/cli/clients.go new file mode 100644 index 0000000000..8e6aa78b59 --- /dev/null +++ b/cli/clients.go @@ -0,0 +1,359 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package cli + +import ( + "encoding/json" + + "github.com/absmach/magistrala/clients" + mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" + "github.com/spf13/cobra" +) + +var cmdClients = []cobra.Command{ + { + Use: "create ", + Short: "Create client", + Long: "Creates new client with provided name and metadata\n" + + "Usage:\n" + + "\tmagistrala-cli clients create '{\"name\":\"new client\", \"metadata\":{\"key\": \"value\"}}' $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 3 { + logUsageCmd(*cmd, cmd.Use) + return + } + + var client mgxsdk.Client + if err := json.Unmarshal([]byte(args[0]), &client); err != nil { + logErrorCmd(*cmd, err) + return + } + client.Status = clients.EnabledStatus.String() + client, err := sdk.CreateClient(client, args[1], args[2]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, client) + }, + }, + { + Use: "get [all | ] ", + Short: "Get clients", + Long: "Get all clients or get client by id. Clients can be filtered by name or metadata\n" + + "Usage:\n" + + "\tmagistrala-cli clients get all $DOMAINID $USERTOKEN - lists all clients\n" + + "\tmagistrala-cli clients get all $DOMAINID $USERTOKEN --offset=10 --limit=10 - lists all clients with offset and limit\n" + + "\tmagistrala-cli clients get $DOMAINID $USERTOKEN - shows client with provided \n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 3 { + logUsageCmd(*cmd, cmd.Use) + return + } + metadata, err := convertMetadata(Metadata) + if err != nil { + logErrorCmd(*cmd, err) + return + } + pageMetadata := mgxsdk.PageMetadata{ + Name: Name, + Offset: Offset, + Limit: Limit, + Metadata: metadata, + } + if args[0] == all { + l, err := sdk.Clients(pageMetadata, args[1], args[2]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + logJSONCmd(*cmd, l) + return + } + t, err := sdk.Client(args[0], args[1], args[2]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, t) + }, + }, + { + Use: "delete ", + Short: "Delete client", + Long: "Delete client by id\n" + + "Usage:\n" + + "\tmagistrala-cli clients delete $DOMAINID $USERTOKEN - delete client with \n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 3 { + logUsageCmd(*cmd, cmd.Use) + return + } + if err := sdk.DeleteClient(args[0], args[1], args[2]); err != nil { + logErrorCmd(*cmd, err) + return + } + logOKCmd(*cmd) + }, + }, + { + Use: "update [ | tags | secret ] ", + Short: "Update client", + Long: "Updates client with provided id, name and metadata, or updates client's tags, secret\n" + + "Usage:\n" + + "\tmagistrala-cli client update '{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}' $DOMAINID $USERTOKEN\n" + + "\tmagistrala-cli client update tags '{\"tag1\":\"value1\", \"tag2\":\"value2\"}' $DOMAINID $USERTOKEN\n" + + "\tmagistrala-cli client update secret $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 && len(args) != 4 { + logUsageCmd(*cmd, cmd.Use) + return + } + + var client mgxsdk.Client + if args[0] == "tags" { + if err := json.Unmarshal([]byte(args[2]), &client.Tags); err != nil { + logErrorCmd(*cmd, err) + return + } + client.ID = args[1] + client, err := sdk.UpdateClientTags(client, args[3], args[4]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, client) + return + } + + if args[0] == "secret" { + client, err := sdk.UpdateClientSecret(args[1], args[2], args[3], args[4]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, client) + return + } + + if err := json.Unmarshal([]byte(args[1]), &client); err != nil { + logErrorCmd(*cmd, err) + return + } + client.ID = args[0] + client, err := sdk.UpdateClient(client, args[2], args[3]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, client) + }, + }, + { + Use: "enable ", + Short: "Change client status to enabled", + Long: "Change client status to enabled\n" + + "Usage:\n" + + "\tmagistrala-cli clients enable $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 3 { + logUsageCmd(*cmd, cmd.Use) + return + } + + client, err := sdk.EnableClient(args[0], args[1], args[2]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, client) + }, + }, + { + Use: "disable ", + Short: "Change client status to disabled", + Long: "Change client status to disabled\n" + + "Usage:\n" + + "\tmagistrala-cli clients disable $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 3 { + logUsageCmd(*cmd, cmd.Use) + return + } + + client, err := sdk.DisableClient(args[0], args[1], args[2]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, client) + }, + }, + { + Use: "share ", + Short: "Share client with a user", + Long: "Share client with a user\n" + + "Usage:\n" + + "\tmagistrala-cli clients share $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsageCmd(*cmd, cmd.Use) + return + } + req := mgxsdk.UsersRelationRequest{ + Relation: args[2], + UserIDs: []string{args[1]}, + } + err := sdk.ShareClient(args[0], req, args[3], args[4]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logOKCmd(*cmd) + }, + }, + { + Use: "unshare ", + Short: "Unshare client with a user", + Long: "Unshare client with a user\n" + + "Usage:\n" + + "\tmagistrala-cli clients share $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 5 { + logUsageCmd(*cmd, cmd.Use) + return + } + req := mgxsdk.UsersRelationRequest{ + Relation: args[2], + UserIDs: []string{args[1]}, + } + err := sdk.UnshareClient(args[0], req, args[3], args[4]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logOKCmd(*cmd) + }, + }, + { + Use: "connect ", + Short: "Connect client", + Long: "Connect client to the channel\n" + + "Usage:\n" + + "\tmagistrala-cli clients connect $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 4 { + logUsageCmd(*cmd, cmd.Use) + return + } + + connIDs := mgxsdk.Connection{ + ChannelID: args[1], + ClientID: args[0], + } + if err := sdk.Connect(connIDs, args[2], args[3]); err != nil { + logErrorCmd(*cmd, err) + return + } + + logOKCmd(*cmd) + }, + }, + { + Use: "disconnect ", + Short: "Disconnect client", + Long: "Disconnect client to the channel\n" + + "Usage:\n" + + "\tmagistrala-cli clients disconnect $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 4 { + logUsageCmd(*cmd, cmd.Use) + return + } + + connIDs := mgxsdk.Connection{ + ClientID: args[0], + ChannelID: args[1], + } + if err := sdk.Disconnect(connIDs, args[2], args[3]); err != nil { + logErrorCmd(*cmd, err) + return + } + + logOKCmd(*cmd) + }, + }, + { + Use: "connections ", + Short: "Connected list", + Long: "List of Channels connected to Client\n" + + "Usage:\n" + + "\tmagistrala-cli connections $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 3 { + logUsageCmd(*cmd, cmd.Use) + return + } + pm := mgxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + cl, err := sdk.ChannelsByClient(args[0], pm, args[1], args[2]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, cl) + }, + }, + { + Use: "users ", + Short: "List users", + Long: "List users of a client\n" + + "Usage:\n" + + "\tmagistrala-cli clients users $DOMAINID $USERTOKEN\n", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 3 { + logUsageCmd(*cmd, cmd.Use) + return + } + pm := mgxsdk.PageMetadata{ + Offset: Offset, + Limit: Limit, + } + ul, err := sdk.ListClientUsers(args[0], pm, args[1], args[2]) + if err != nil { + logErrorCmd(*cmd, err) + return + } + + logJSONCmd(*cmd, ul) + }, + }, +} + +// NewClientsCmd returns clients command. +func NewClientsCmd() *cobra.Command { + cmd := cobra.Command{ + Use: "clients [create | get | update | delete | share | connect | disconnect | connections | not-connected | users ]", + Short: "Clients management", + Long: `Clients management: create, get, update, delete or share Client, connect or disconnect Client from Channel and get the list of Channels connected or disconnected from a Client`, + } + + for i := range cmdClients { + cmd.AddCommand(&cmdClients[i]) + } + + return &cmd +} diff --git a/cli/things_test.go b/cli/clients_test.go similarity index 76% rename from cli/things_test.go rename to cli/clients_test.go index f9b403d95e..4776bedd93 100644 --- a/cli/things_test.go +++ b/cli/clients_test.go @@ -11,13 +11,13 @@ import ( "testing" "github.com/absmach/magistrala/cli" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" sdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/absmach/magistrala/things" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -30,55 +30,55 @@ var ( all = "all" ) -var thing = sdk.Thing{ +var client = sdk.Client{ ID: testsutil.GenerateUUID(&testing.T{}), - Name: "testthing", + Name: "testclient", Credentials: sdk.ClientCredentials{ Secret: "secret", }, DomainID: testsutil.GenerateUUID(&testing.T{}), - Status: things.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), } -func TestCreateThingsCmd(t *testing.T) { +func TestCreateClientsCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingJson := "{\"name\":\"testthing\", \"metadata\":{\"key1\":\"value1\"}}" - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientJson := "{\"name\":\"testclient\", \"metadata\":{\"key1\":\"value1\"}}" + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) - var tg sdk.Thing + var tg sdk.Client cases := []struct { desc string args []string sdkErr errors.SDKError errLogMessage string - thing sdk.Thing + client sdk.Client logType outputLog }{ { - desc: "create thing successfully with token", + desc: "create client successfully with token", args: []string{ - thingJson, + clientJson, domainID, token, }, - thing: thing, + client: client, logType: entityLog, }, { - desc: "create thing without token", + desc: "create client without token", args: []string{ - thingJson, + clientJson, domainID, }, logType: usageLog, }, { - desc: "create thing with invalid token", + desc: "create client with invalid token", args: []string{ - thingJson, + clientJson, domainID, invalidToken, }, @@ -87,9 +87,9 @@ func TestCreateThingsCmd(t *testing.T) { logType: errLog, }, { - desc: "failed to create thing", + desc: "failed to create client", args: []string{ - thingJson, + clientJson, domainID, token, }, @@ -98,9 +98,9 @@ func TestCreateThingsCmd(t *testing.T) { logType: errLog, }, { - desc: "create thing with invalid metadata", + desc: "create client with invalid metadata", args: []string{ - "{\"name\":\"testthing\", \"metadata\":{\"key1\":value1}}", + "{\"name\":\"testclient\", \"metadata\":{\"key1\":value1}}", domainID, token, }, @@ -112,14 +112,14 @@ func TestCreateThingsCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("CreateThing", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.thing, tc.sdkErr) + sdkCall := sdkMock.On("CreateClient", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.client, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...) switch tc.logType { case entityLog: err := json.Unmarshal([]byte(out), &tg) assert.Nil(t, err) - assert.Equal(t, tc.thing, tg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.thing, tg)) + assert.Equal(t, tc.client, tg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.client, tg)) case errLog: assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) case usageLog: @@ -131,48 +131,48 @@ func TestCreateThingsCmd(t *testing.T) { } } -func TestGetThingsCmd(t *testing.T) { +func TestGetClientssCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) - var tg sdk.Thing - var page sdk.ThingsPage + var tg sdk.Client + var page sdk.ClientsPage cases := []struct { desc string args []string sdkErr errors.SDKError errLogMessage string - thing sdk.Thing - page sdk.ThingsPage + client sdk.Client + page sdk.ClientsPage logType outputLog }{ { - desc: "get all things successfully", + desc: "get all clients successfully", args: []string{ all, domainID, token, }, logType: entityLog, - page: sdk.ThingsPage{ - Things: []sdk.Thing{thing}, + page: sdk.ClientsPage{ + Clients: []sdk.Client{client}, }, }, { - desc: "get thing successfully with id", + desc: "get client successfully with id", args: []string{ - thing.ID, + client.ID, domainID, token, }, logType: entityLog, - thing: thing, + client: client, }, { - desc: "get things with invalid token", + desc: "get clients with invalid token", args: []string{ all, domainID, @@ -180,11 +180,11 @@ func TestGetThingsCmd(t *testing.T) { }, sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - page: sdk.ThingsPage{}, + page: sdk.ClientsPage{}, logType: errLog, }, { - desc: "get things with invalid args", + desc: "get clients with invalid args", args: []string{ all, invalidToken, @@ -198,7 +198,7 @@ func TestGetThingsCmd(t *testing.T) { logType: usageLog, }, { - desc: "get thing without token", + desc: "get client without token", args: []string{ all, domainID, @@ -206,7 +206,7 @@ func TestGetThingsCmd(t *testing.T) { logType: usageLog, }, { - desc: "get thing with invalid thing id", + desc: "get client with invalid client id", args: []string{ invalidID, domainID, @@ -220,8 +220,8 @@ func TestGetThingsCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Things", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr) - sdkCall1 := sdkMock.On("Thing", mock.Anything, mock.Anything, mock.Anything).Return(tc.thing, tc.sdkErr) + sdkCall := sdkMock.On("Clients", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr) + sdkCall1 := sdkMock.On("Client", mock.Anything, mock.Anything, mock.Anything).Return(tc.client, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) @@ -249,7 +249,7 @@ func TestGetThingsCmd(t *testing.T) { if tc.logType == entityLog { if tc.args[1] != all { - assert.Equal(t, tc.thing, tg, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.thing, tg)) + assert.Equal(t, tc.client, tg, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.client, tg)) } else { assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) } @@ -261,17 +261,17 @@ func TestGetThingsCmd(t *testing.T) { } } -func TestUpdateThingCmd(t *testing.T) { +func TestUpdateClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) tagUpdateType := "tags" secretUpdateType := "secret" newTagsJson := "[\"tag1\", \"tag2\"]" newTagString := []string{"tag1", "tag2"} - newNameandMeta := "{\"name\": \"thingName\", \"metadata\": {\"role\": \"general\"}}" + newNameandMeta := "{\"name\": \"clientName\", \"metadata\": {\"role\": \"general\"}}" newSecret := "secret" cases := []struct { @@ -279,35 +279,35 @@ func TestUpdateThingCmd(t *testing.T) { args []string sdkErr errors.SDKError errLogMessage string - thing sdk.Thing + client sdk.Client logType outputLog }{ { - desc: "update thing name and metadata successfully", + desc: "update client name and metadata successfully", args: []string{ - thing.ID, + client.ID, newNameandMeta, domainID, token, }, - thing: sdk.Thing{ - Name: "thingName", + client: sdk.Client{ + Name: "clientName", Metadata: map[string]interface{}{ "metadata": map[string]interface{}{ "role": "general", }, }, - ID: thing.ID, - DomainID: thing.DomainID, - Status: thing.Status, + ID: client.ID, + DomainID: client.DomainID, + Status: client.Status, }, logType: entityLog, }, { - desc: "update thing name and metadata with invalid json", + desc: "update client name and metadata with invalid json", args: []string{ - thing.ID, - "{\"name\": \"thingName\", \"metadata\": {\"role\": \"general\"}", + client.ID, + "{\"name\": \"clientName\", \"metadata\": {\"role\": \"general\"}", domainID, token, }, @@ -316,7 +316,7 @@ func TestUpdateThingCmd(t *testing.T) { logType: errLog, }, { - desc: "update thing name and metadata with invalid thing id", + desc: "update client name and metadata with invalid client id", args: []string{ invalidID, newNameandMeta, @@ -328,28 +328,28 @@ func TestUpdateThingCmd(t *testing.T) { logType: errLog, }, { - desc: "update thing tags successfully", + desc: "update client tags successfully", args: []string{ tagUpdateType, - thing.ID, + client.ID, newTagsJson, domainID, token, }, - thing: sdk.Thing{ - Name: thing.Name, - ID: thing.ID, - DomainID: thing.DomainID, - Status: thing.Status, + client: sdk.Client{ + Name: client.Name, + ID: client.ID, + DomainID: client.DomainID, + Status: client.Status, Tags: newTagString, }, logType: entityLog, }, { - desc: "update thing with invalid tags", + desc: "update client with invalid tags", args: []string{ tagUpdateType, - thing.ID, + client.ID, "[\"tag1\", \"tag2\"", domainID, token, @@ -359,7 +359,7 @@ func TestUpdateThingCmd(t *testing.T) { errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), }, { - desc: "update thing tags with invalid thing id", + desc: "update client tags with invalid client id", args: []string{ tagUpdateType, invalidID, @@ -372,19 +372,19 @@ func TestUpdateThingCmd(t *testing.T) { logType: errLog, }, { - desc: "update thing secret successfully", + desc: "update client secret successfully", args: []string{ secretUpdateType, - thing.ID, + client.ID, newSecret, domainID, token, }, - thing: sdk.Thing{ - Name: thing.Name, - ID: thing.ID, - DomainID: thing.DomainID, - Status: thing.Status, + client: sdk.Client{ + Name: client.Name, + ID: client.ID, + DomainID: client.DomainID, + Status: client.Status, Credentials: sdk.ClientCredentials{ Secret: newSecret, }, @@ -392,10 +392,10 @@ func TestUpdateThingCmd(t *testing.T) { logType: entityLog, }, { - desc: "update thing with invalid secret", + desc: "update client with invalid secret", args: []string{ secretUpdateType, - thing.ID, + client.ID, "", domainID, token, @@ -405,10 +405,10 @@ func TestUpdateThingCmd(t *testing.T) { logType: errLog, }, { - desc: "update thing with invalid token", + desc: "update client with invalid token", args: []string{ secretUpdateType, - thing.ID, + client.ID, newSecret, domainID, invalidToken, @@ -418,10 +418,10 @@ func TestUpdateThingCmd(t *testing.T) { logType: errLog, }, { - desc: "update thing with invalid args", + desc: "update client with invalid args", args: []string{ secretUpdateType, - thing.ID, + client.ID, newSecret, domainID, token, @@ -433,24 +433,24 @@ func TestUpdateThingCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - var tg sdk.Thing - sdkCall := sdkMock.On("UpdateThing", mock.Anything, mock.Anything, mock.Anything).Return(tc.thing, tc.sdkErr) - sdkCall1 := sdkMock.On("UpdateThingTags", mock.Anything, mock.Anything, mock.Anything).Return(tc.thing, tc.sdkErr) - sdkCall2 := sdkMock.On("UpdateThingSecret", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.thing, tc.sdkErr) + var tg sdk.Client + sdkCall := sdkMock.On("UpdateClient", mock.Anything, mock.Anything, mock.Anything).Return(tc.client, tc.sdkErr) + sdkCall1 := sdkMock.On("UpdateClientTags", mock.Anything, mock.Anything, mock.Anything).Return(tc.client, tc.sdkErr) + sdkCall2 := sdkMock.On("UpdateClientSecret", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.client, tc.sdkErr) switch { case tc.args[0] == tagUpdateType: - var th sdk.Thing + var th sdk.Client th.Tags = []string{"tag1", "tag2"} th.ID = tc.args[1] - sdkCall1 = sdkMock.On("UpdateThingTags", th, tc.args[3]).Return(tc.thing, tc.sdkErr) + sdkCall1 = sdkMock.On("UpdateClientTags", th, tc.args[3]).Return(tc.client, tc.sdkErr) case tc.args[0] == secretUpdateType: - var th sdk.Thing + var th sdk.Client th.Credentials.Secret = tc.args[2] th.ID = tc.args[1] - sdkCall2 = sdkMock.On("UpdateThingSecret", th, tc.args[2], tc.args[3]).Return(tc.thing, tc.sdkErr) + sdkCall2 = sdkMock.On("UpdateClientSecret", th, tc.args[2], tc.args[3]).Return(tc.client, tc.sdkErr) } out := executeCommand(t, rootCmd, append([]string{updCmd}, tc.args...)...) @@ -458,7 +458,7 @@ func TestUpdateThingCmd(t *testing.T) { case entityLog: err := json.Unmarshal([]byte(out), &tg) assert.Nil(t, err) - assert.Equal(t, tc.thing, tg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.thing, tg)) + assert.Equal(t, tc.client, tg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.client, tg)) case errLog: assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) case usageLog: @@ -472,11 +472,11 @@ func TestUpdateThingCmd(t *testing.T) { } } -func TestDeleteThingCmd(t *testing.T) { +func TestDeleteClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientdCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientdCmd) cases := []struct { desc string @@ -486,18 +486,18 @@ func TestDeleteThingCmd(t *testing.T) { logType outputLog }{ { - desc: "delete thing successfully", + desc: "delete client successfully", args: []string{ - thing.ID, + client.ID, domainID, token, }, logType: okLog, }, { - desc: "delete thing with invalid token", + desc: "delete client with invalid token", args: []string{ - thing.ID, + client.ID, domainID, invalidToken, }, @@ -506,7 +506,7 @@ func TestDeleteThingCmd(t *testing.T) { logType: errLog, }, { - desc: "delete thing with invalid thing id", + desc: "delete client with invalid client id", args: []string{ invalidID, domainID, @@ -517,9 +517,9 @@ func TestDeleteThingCmd(t *testing.T) { logType: errLog, }, { - desc: "delete thing with invalid args", + desc: "delete client with invalid args", args: []string{ - thing.ID, + client.ID, domainID, token, extraArg, @@ -530,7 +530,7 @@ func TestDeleteThingCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DeleteThing", tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkErr) + sdkCall := sdkMock.On("DeleteClient", tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{delCmd}, tc.args...)...) switch tc.logType { @@ -546,36 +546,36 @@ func TestDeleteThingCmd(t *testing.T) { } } -func TestEnableThingCmd(t *testing.T) { +func TestEnableClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - var tg sdk.Thing + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) + var tg sdk.Client cases := []struct { desc string args []string sdkErr errors.SDKError errLogMessage string - thing sdk.Thing + client sdk.Client logType outputLog }{ { - desc: "enable thing successfully", + desc: "enable client successfully", args: []string{ - thing.ID, + client.ID, domainID, validToken, }, sdkErr: nil, - thing: thing, + client: client, logType: entityLog, }, { - desc: "delete thing with invalid token", + desc: "delete client with invalid token", args: []string{ - thing.ID, + client.ID, domainID, invalidToken, }, @@ -584,7 +584,7 @@ func TestEnableThingCmd(t *testing.T) { logType: errLog, }, { - desc: "delete thing with invalid thing ID", + desc: "delete client with invalid client ID", args: []string{ invalidID, domainID, @@ -595,9 +595,9 @@ func TestEnableThingCmd(t *testing.T) { logType: errLog, }, { - desc: "enable thing with invalid args", + desc: "enable client with invalid args", args: []string{ - thing.ID, + client.ID, domainID, validToken, extraArg, @@ -608,7 +608,7 @@ func TestEnableThingCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("EnableThing", tc.args[0], tc.args[1], tc.args[2]).Return(tc.thing, tc.sdkErr) + sdkCall := sdkMock.On("EnableClient", tc.args[0], tc.args[1], tc.args[2]).Return(tc.client, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{enableCmd}, tc.args...)...) switch tc.logType { @@ -619,7 +619,7 @@ func TestEnableThingCmd(t *testing.T) { case entityLog: err := json.Unmarshal([]byte(out), &tg) assert.Nil(t, err) - assert.Equal(t, tc.thing, tg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.thing, tg)) + assert.Equal(t, tc.client, tg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.client, tg)) } sdkCall.Unset() @@ -627,36 +627,36 @@ func TestEnableThingCmd(t *testing.T) { } } -func TestDisablethingCmd(t *testing.T) { +func TestDisableclientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) - var tg sdk.Thing + var tg sdk.Client cases := []struct { desc string args []string sdkErr errors.SDKError errLogMessage string - thing sdk.Thing + client sdk.Client logType outputLog }{ { - desc: "disable thing successfully", + desc: "disable client successfully", args: []string{ - thing.ID, + client.ID, domainID, validToken, }, logType: entityLog, - thing: thing, + client: client, }, { - desc: "delete thing with invalid token", + desc: "delete client with invalid token", args: []string{ - thing.ID, + client.ID, domainID, invalidToken, }, @@ -665,7 +665,7 @@ func TestDisablethingCmd(t *testing.T) { logType: errLog, }, { - desc: "delete thing with invalid thing ID", + desc: "delete client with invalid client ID", args: []string{ invalidID, domainID, @@ -676,9 +676,9 @@ func TestDisablethingCmd(t *testing.T) { logType: errLog, }, { - desc: "disable thing with invalid args", + desc: "disable client with invalid args", args: []string{ - thing.ID, + client.ID, domainID, validToken, extraArg, @@ -689,7 +689,7 @@ func TestDisablethingCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DisableThing", tc.args[0], tc.args[1], tc.args[2]).Return(tc.thing, tc.sdkErr) + sdkCall := sdkMock.On("DisableClient", tc.args[0], tc.args[1], tc.args[2]).Return(tc.client, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{disableCmd}, tc.args...)...) switch tc.logType { @@ -702,7 +702,7 @@ func TestDisablethingCmd(t *testing.T) { if err != nil { t.Fatalf("json.Unmarshal failed: %v", err) } - assert.Equal(t, tc.thing, tg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.thing, tg)) + assert.Equal(t, tc.client, tg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.client, tg)) } sdkCall.Unset() @@ -710,11 +710,11 @@ func TestDisablethingCmd(t *testing.T) { } } -func TestUsersThingCmd(t *testing.T) { +func TestUsersClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) page := sdk.UsersPage{} @@ -727,9 +727,9 @@ func TestUsersThingCmd(t *testing.T) { sdkErr errors.SDKError }{ { - desc: "get thing's users successfully", + desc: "get client's users successfully", args: []string{ - thing.ID, + client.ID, domainID, token, }, @@ -744,9 +744,9 @@ func TestUsersThingCmd(t *testing.T) { logType: entityLog, }, { - desc: "list thing users' with invalid args", + desc: "list client users' with invalid args", args: []string{ - thing.ID, + client.ID, domainID, token, extraArg, @@ -754,9 +754,9 @@ func TestUsersThingCmd(t *testing.T) { logType: usageLog, }, { - desc: "list thing users' with invalid domain", + desc: "list client users' with invalid domain", args: []string{ - thing.ID, + client.ID, invalidID, token, }, @@ -765,7 +765,7 @@ func TestUsersThingCmd(t *testing.T) { logType: errLog, }, { - desc: "list thing users with invalid id", + desc: "list client users with invalid id", args: []string{ invalidID, domainID, @@ -779,7 +779,7 @@ func TestUsersThingCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListThingUsers", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr) + sdkCall := sdkMock.On("ListClientUsers", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{usrCmd}, tc.args...)...) switch tc.logType { @@ -799,11 +799,11 @@ func TestUsersThingCmd(t *testing.T) { } } -func TestConnectThingCmd(t *testing.T) { +func TestConnectClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) cases := []struct { desc string @@ -813,9 +813,9 @@ func TestConnectThingCmd(t *testing.T) { errLogMessage string }{ { - desc: "Connect thing to channel successfully", + desc: "Connect client to channel successfully", args: []string{ - thing.ID, + client.ID, channel.ID, domainID, token, @@ -825,7 +825,7 @@ func TestConnectThingCmd(t *testing.T) { { desc: "connect with invalid args", args: []string{ - thing.ID, + client.ID, channel.ID, domainID, token, @@ -834,7 +834,7 @@ func TestConnectThingCmd(t *testing.T) { logType: usageLog, }, { - desc: "connect with invalid thing id", + desc: "connect with invalid client id", args: []string{ invalidID, channel.ID, @@ -848,7 +848,7 @@ func TestConnectThingCmd(t *testing.T) { { desc: "connect with invalid channel id", args: []string{ - thing.ID, + client.ID, invalidID, domainID, token, @@ -858,9 +858,9 @@ func TestConnectThingCmd(t *testing.T) { logType: errLog, }, { - desc: "list thing users' with invalid domain", + desc: "list client users' with invalid domain", args: []string{ - thing.ID, + client.ID, channel.ID, invalidID, token, @@ -889,11 +889,11 @@ func TestConnectThingCmd(t *testing.T) { } } -func TestDisconnectThingCmd(t *testing.T) { +func TestDisconnectClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) cases := []struct { desc string @@ -903,9 +903,9 @@ func TestDisconnectThingCmd(t *testing.T) { errLogMessage string }{ { - desc: "Disconnect thing to channel successfully", + desc: "Disconnect client to channel successfully", args: []string{ - thing.ID, + client.ID, channel.ID, domainID, token, @@ -915,7 +915,7 @@ func TestDisconnectThingCmd(t *testing.T) { { desc: "Disconnect with invalid args", args: []string{ - thing.ID, + client.ID, channel.ID, domainID, token, @@ -924,7 +924,7 @@ func TestDisconnectThingCmd(t *testing.T) { logType: usageLog, }, { - desc: "disconnect with invalid thing id", + desc: "disconnect with invalid client id", args: []string{ invalidID, channel.ID, @@ -938,7 +938,7 @@ func TestDisconnectThingCmd(t *testing.T) { { desc: "disconnect with invalid channel id", args: []string{ - thing.ID, + client.ID, invalidID, domainID, token, @@ -948,9 +948,9 @@ func TestDisconnectThingCmd(t *testing.T) { logType: errLog, }, { - desc: "disconnect thing with invalid domain", + desc: "disconnect client with invalid domain", args: []string{ - thing.ID, + client.ID, channel.ID, invalidID, token, @@ -982,8 +982,8 @@ func TestDisconnectThingCmd(t *testing.T) { func TestListConnectionCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) cp := sdk.ChannelsPage{} cases := []struct { @@ -997,7 +997,7 @@ func TestListConnectionCmd(t *testing.T) { { desc: "list connections successfully", args: []string{ - thing.ID, + client.ID, domainID, token, }, @@ -1014,7 +1014,7 @@ func TestListConnectionCmd(t *testing.T) { { desc: "list connections with invalid args", args: []string{ - thing.ID, + client.ID, domainID, token, extraArg, @@ -1022,7 +1022,7 @@ func TestListConnectionCmd(t *testing.T) { logType: usageLog, }, { - desc: "list connections with invalid thing ID", + desc: "list connections with invalid client ID", args: []string{ invalidID, domainID, @@ -1035,7 +1035,7 @@ func TestListConnectionCmd(t *testing.T) { { desc: "list connections with invalid token", args: []string{ - thing.ID, + client.ID, domainID, invalidToken, }, @@ -1046,7 +1046,7 @@ func TestListConnectionCmd(t *testing.T) { } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ChannelsByThing", tc.args[0], mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) + sdkCall := sdkMock.On("ChannelsByClient", tc.args[0], mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{connsCmd}, tc.args...)...) switch tc.logType { @@ -1067,11 +1067,11 @@ func TestListConnectionCmd(t *testing.T) { } } -func TestShareThingCmd(t *testing.T) { +func TestShareClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientCmd) cases := []struct { desc string @@ -1081,9 +1081,9 @@ func TestShareThingCmd(t *testing.T) { errLogMessage string }{ { - desc: "share thing successfully", + desc: "share client successfully", args: []string{ - thing.ID, + client.ID, user.ID, relation, domainID, @@ -1092,9 +1092,9 @@ func TestShareThingCmd(t *testing.T) { logType: okLog, }, { - desc: "share thing with invalid user id", + desc: "share client with invalid user id", args: []string{ - thing.ID, + client.ID, invalidID, relation, domainID, @@ -1105,7 +1105,7 @@ func TestShareThingCmd(t *testing.T) { logType: errLog, }, { - desc: "share thing with invalid thing ID", + desc: "share client with invalid client ID", args: []string{ invalidID, user.ID, @@ -1118,9 +1118,9 @@ func TestShareThingCmd(t *testing.T) { logType: errLog, }, { - desc: "share thing with invalid args", + desc: "share client with invalid args", args: []string{ - thing.ID, + client.ID, user.ID, relation, domainID, @@ -1130,9 +1130,9 @@ func TestShareThingCmd(t *testing.T) { logType: usageLog, }, { - desc: "share thing with invalid relation", + desc: "share client with invalid relation", args: []string{ - thing.ID, + client.ID, user.ID, "invalid", domainID, @@ -1145,7 +1145,7 @@ func TestShareThingCmd(t *testing.T) { } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ShareThing", tc.args[0], mock.Anything, tc.args[3], tc.args[4]).Return(tc.sdkErr) + sdkCall := sdkMock.On("ShareClient", tc.args[0], mock.Anything, tc.args[3], tc.args[4]).Return(tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{shrCmd}, tc.args...)...) switch tc.logType { @@ -1161,11 +1161,11 @@ func TestShareThingCmd(t *testing.T) { } } -func TestUnshareThingCmd(t *testing.T) { +func TestUnshareClientCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) + clientsCmd := cli.NewClientsCmd() + rootCmd := setFlags(clientsCmd) cases := []struct { desc string @@ -1175,9 +1175,9 @@ func TestUnshareThingCmd(t *testing.T) { errLogMessage string }{ { - desc: "unshare thing successfully", + desc: "unshare client successfully", args: []string{ - thing.ID, + client.ID, user.ID, relation, domainID, @@ -1186,7 +1186,7 @@ func TestUnshareThingCmd(t *testing.T) { logType: okLog, }, { - desc: "unshare thing with invalid thing ID", + desc: "unshare client with invalid client ID", args: []string{ invalidID, user.ID, @@ -1199,9 +1199,9 @@ func TestUnshareThingCmd(t *testing.T) { logType: errLog, }, { - desc: "unshare thing with invalid args", + desc: "unshare client with invalid args", args: []string{ - thing.ID, + client.ID, user.ID, relation, domainID, @@ -1211,9 +1211,9 @@ func TestUnshareThingCmd(t *testing.T) { logType: usageLog, }, { - desc: "unshare thing with invalid relation", + desc: "unshare client with invalid relation", args: []string{ - thing.ID, + client.ID, user.ID, "invalid", domainID, @@ -1226,7 +1226,7 @@ func TestUnshareThingCmd(t *testing.T) { } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("UnshareThing", tc.args[0], mock.Anything, tc.args[3], tc.args[4]).Return(tc.sdkErr) + sdkCall := sdkMock.On("UnshareClient", tc.args[0], mock.Anything, tc.args[3], tc.args[4]).Return(tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{unshrCmd}, tc.args...)...) switch tc.logType { diff --git a/cli/commands_test.go b/cli/commands_test.go index 3e432f2fe1..04cf2d75de 100644 --- a/cli/commands_test.go +++ b/cli/commands_test.go @@ -26,9 +26,9 @@ const ( domsCmd = "domains" ) -// Things commands +// Clients commands const ( - thsCmd = "things" + cliCmd = "clients" connsCmd = "connections" connCmd = "connect" disconnCmd = "disconnect" diff --git a/cli/config.go b/cli/config.go index e3910aaa6d..07cee7d38d 100644 --- a/cli/config.go +++ b/cli/config.go @@ -20,7 +20,7 @@ import ( const ( defURL string = "http://localhost" defUsersURL string = defURL + ":9002" - defThingsURL string = defURL + ":9000" + defCLientsURL string = defURL + ":9000" defReaderURL string = defURL + ":9011" defBootstrapURL string = defURL + ":9013" defDomainsURL string = defURL + ":8189" @@ -36,7 +36,7 @@ const ( ) type remotes struct { - ThingsURL string `toml:"things_url"` + ClientsURL string `toml:"clients_url"` UsersURL string `toml:"users_url"` ReaderURL string `toml:"reader_url"` DomainsURL string `toml:"domains_url"` @@ -107,7 +107,7 @@ func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { case os.IsNotExist(err): defaultConfig := config{ Remotes: remotes{ - ThingsURL: defThingsURL, + ClientsURL: defCLientsURL, UsersURL: defUsersURL, ReaderURL: defReaderURL, DomainsURL: defDomainsURL, @@ -171,8 +171,8 @@ func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { RawOutput = rawOutput || RawOutput } - if sdkConf.ThingsURL == "" && config.Remotes.ThingsURL != "" { - sdkConf.ThingsURL = config.Remotes.ThingsURL + if sdkConf.ClientsURL == "" && config.Remotes.ClientsURL != "" { + sdkConf.ClientsURL = config.Remotes.ClientsURL } if sdkConf.UsersURL == "" && config.Remotes.UsersURL != "" { @@ -258,7 +258,7 @@ func setConfigValue(key, value string) error { } configKeyToField := map[string]interface{}{ - "things_url": &config.Remotes.ThingsURL, + "clients_url": &config.Remotes.ClientsURL, "users_url": &config.Remotes.UsersURL, "reader_url": &config.Remotes.ReaderURL, "http_adapter_url": &config.Remotes.HTTPAdapterURL, diff --git a/cli/groups.go b/cli/groups.go index 867d1ec664..2a26bec9e0 100644 --- a/cli/groups.go +++ b/cli/groups.go @@ -6,7 +6,7 @@ package cli import ( "encoding/json" - "github.com/absmach/magistrala/internal/groups" + "github.com/absmach/magistrala/groups" mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/spf13/cobra" ) diff --git a/cli/groups_test.go b/cli/groups_test.go index 5f3daed862..1161a443e5 100644 --- a/cli/groups_test.go +++ b/cli/groups_test.go @@ -758,7 +758,7 @@ func TestDisablegroupCmd(t *testing.T) { logType: errLog, }, { - desc: "disable thing with invalid args", + desc: "disable client with invalid args", args: []string{ group.ID, domainID, diff --git a/cli/message.go b/cli/message.go index e4cfc0b27f..cf495f6bf8 100644 --- a/cli/message.go +++ b/cli/message.go @@ -10,7 +10,7 @@ import ( var cmdMessages = []cobra.Command{ { - Use: "send ", + Use: "send ", Short: "Send messages", Long: `Sends message on the channel`, Run: func(cmd *cobra.Command, args []string) { diff --git a/cli/message_test.go b/cli/message_test.go index a145fe6021..891717bbeb 100644 --- a/cli/message_test.go +++ b/cli/message_test.go @@ -40,7 +40,7 @@ func TestSendMesageCmd(t *testing.T) { args: []string{ channel.ID, message, - thing.Credentials.Secret, + client.Credentials.Secret, }, logType: okLog, }, @@ -49,13 +49,13 @@ func TestSendMesageCmd(t *testing.T) { args: []string{ channel.ID, message, - thing.Credentials.Secret, + client.Credentials.Secret, extraArg, }, logType: usageLog, }, { - desc: "send message with invalid thing secret", + desc: "send message with invalid client secret", args: []string{ channel.ID, message, diff --git a/cli/provision.go b/cli/provision.go index 6811a290d6..813b11bf8b 100644 --- a/cli/provision.go +++ b/cli/provision.go @@ -31,9 +31,9 @@ var ( var cmdProvision = []cobra.Command{ { - Use: "things ", - Short: "Provision things", - Long: `Bulk create things`, + Use: "clients ", + Short: "Provision clients", + Long: `Bulk create clients`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) @@ -45,19 +45,19 @@ var cmdProvision = []cobra.Command{ return } - things, err := thingsFromFile(args[0]) + clients, err := clientsFromFile(args[0]) if err != nil { logErrorCmd(*cmd, err) return } - things, err = sdk.CreateThings(things, args[1], args[2]) + clients, err = sdk.CreateClients(clients, args[1], args[2]) if err != nil { logErrorCmd(*cmd, err) return } - logJSONCmd(*cmd, things) + logJSONCmd(*cmd, clients) }, }, { @@ -93,7 +93,7 @@ var cmdProvision = []cobra.Command{ { Use: "connect ", Short: "Provision connections", - Long: `Bulk connect things to channels`, + Long: `Bulk connect clients to channels`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) @@ -118,13 +118,13 @@ var cmdProvision = []cobra.Command{ { Use: "test", Short: "test", - Long: `Provisions test setup: one test user, two things and two channels. \ - Connect both things to one of the channels, \ - and only on thing to other channel.`, + Long: `Provisions test setup: one test user, two clients and two channels. \ + Connect both clients to one of the channels, \ + and only on client to other channel.`, Run: func(cmd *cobra.Command, args []string) { - numThings := 2 + numClients := 2 numChan := 2 - things := []mgxsdk.Thing{} + clients := []mgxsdk.Client{} channels := []mgxsdk.Channel{} if len(args) != 0 { @@ -172,16 +172,16 @@ var cmdProvision = []cobra.Command{ return } - // Create things - for i := 0; i < numThings; i++ { - t := mgxsdk.Thing{ - Name: fmt.Sprintf("%s-thing-%d", name, i), + // Create clients + for i := 0; i < numClients; i++ { + t := mgxsdk.Client{ + Name: fmt.Sprintf("%s-client-%d", name, i), Status: mgxsdk.EnabledStatus, } - things = append(things, t) + clients = append(clients, t) } - things, err = sdk.CreateThings(things, domain.ID, ut.AccessToken) + clients, err = sdk.CreateClients(clients, domain.ID, ut.AccessToken) if err != nil { logErrorCmd(*cmd, err) return @@ -202,10 +202,10 @@ var cmdProvision = []cobra.Command{ channels = append(channels, c) } - // Connect things to channels - first thing to both channels, second only to first + // Connect clients to channels - first client to both channels, second only to first conIDs := mgxsdk.Connection{ ChannelID: channels[0].ID, - ThingID: things[0].ID, + ClientID: clients[0].ID, } if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil { logErrorCmd(*cmd, err) @@ -214,7 +214,7 @@ var cmdProvision = []cobra.Command{ conIDs = mgxsdk.Connection{ ChannelID: channels[1].ID, - ThingID: things[0].ID, + ClientID: clients[0].ID, } if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil { logErrorCmd(*cmd, err) @@ -223,7 +223,7 @@ var cmdProvision = []cobra.Command{ conIDs = mgxsdk.Connection{ ChannelID: channels[0].ID, - ThingID: things[1].ID, + ClientID: clients[1].ID, } if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil { logErrorCmd(*cmd, err) @@ -231,20 +231,20 @@ var cmdProvision = []cobra.Command{ } // send message to test connectivity - if err := sdk.SendMessage(channels[0].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), things[0].Credentials.Secret); err != nil { + if err := sdk.SendMessage(channels[0].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), clients[0].Credentials.Secret); err != nil { logErrorCmd(*cmd, err) return } - if err := sdk.SendMessage(channels[0].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), things[1].Credentials.Secret); err != nil { + if err := sdk.SendMessage(channels[0].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), clients[1].Credentials.Secret); err != nil { logErrorCmd(*cmd, err) return } - if err := sdk.SendMessage(channels[1].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), things[0].Credentials.Secret); err != nil { + if err := sdk.SendMessage(channels[1].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), clients[0].Credentials.Secret); err != nil { logErrorCmd(*cmd, err) return } - logJSONCmd(*cmd, user, ut, things, channels) + logJSONCmd(*cmd, user, ut, clients, channels) }, }, } @@ -252,9 +252,9 @@ var cmdProvision = []cobra.Command{ // NewProvisionCmd returns provision command. func NewProvisionCmd() *cobra.Command { cmd := cobra.Command{ - Use: "provision [things | channels | connect | test]", - Short: "Provision things and channels from a config file", - Long: `Provision things and channels: use json or csv file to bulk provision things and channels`, + Use: "provision [clients | channels | connect | test]", + Short: "Provision clients and channels from a config file", + Long: `Provision clients and channels: use json or csv file to bulk provision clients and channels`, } for i := range cmdProvision { @@ -264,18 +264,18 @@ func NewProvisionCmd() *cobra.Command { return &cmd } -func thingsFromFile(path string) ([]mgxsdk.Thing, error) { +func clientsFromFile(path string) ([]mgxsdk.Client, error) { if _, err := os.Stat(path); os.IsNotExist(err) { - return []mgxsdk.Thing{}, err + return []mgxsdk.Client{}, err } file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm) if err != nil { - return []mgxsdk.Thing{}, err + return []mgxsdk.Client{}, err } defer file.Close() - things := []mgxsdk.Thing{} + clients := []mgxsdk.Client{} switch filepath.Ext(path) { case csvExt: reader := csv.NewReader(file) @@ -286,29 +286,29 @@ func thingsFromFile(path string) ([]mgxsdk.Thing, error) { break } if err != nil { - return []mgxsdk.Thing{}, err + return []mgxsdk.Client{}, err } if len(l) < 1 { - return []mgxsdk.Thing{}, errors.New("empty line found in file") + return []mgxsdk.Client{}, errors.New("empty line found in file") } - thing := mgxsdk.Thing{ + client := mgxsdk.Client{ Name: l[0], } - things = append(things, thing) + clients = append(clients, client) } case jsonExt: - err := json.NewDecoder(file).Decode(&things) + err := json.NewDecoder(file).Decode(&clients) if err != nil { - return []mgxsdk.Thing{}, err + return []mgxsdk.Client{}, err } default: - return []mgxsdk.Thing{}, err + return []mgxsdk.Client{}, err } - return things, nil + return clients, nil } func channelsFromFile(path string) ([]mgxsdk.Channel, error) { @@ -387,7 +387,7 @@ func connectionsFromFile(path string) ([]mgxsdk.Connection, error) { return []mgxsdk.Connection{}, errors.New("empty line found in file") } connections = append(connections, mgxsdk.Connection{ - ThingID: l[0], + ClientID: l[0], ChannelID: l[1], }) } diff --git a/cli/things.go b/cli/things.go deleted file mode 100644 index b5ec1ad462..0000000000 --- a/cli/things.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "encoding/json" - - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/absmach/magistrala/things" - "github.com/spf13/cobra" -) - -var cmdThings = []cobra.Command{ - { - Use: "create ", - Short: "Create thing", - Long: "Creates new thing with provided name and metadata\n" + - "Usage:\n" + - "\tmagistrala-cli things create '{\"name\":\"new thing\", \"metadata\":{\"key\": \"value\"}}' $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - var thing mgxsdk.Thing - if err := json.Unmarshal([]byte(args[0]), &thing); err != nil { - logErrorCmd(*cmd, err) - return - } - thing.Status = things.EnabledStatus.String() - thing, err := sdk.CreateThing(thing, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, thing) - }, - }, - { - Use: "get [all | ] ", - Short: "Get things", - Long: "Get all things or get thing by id. Things can be filtered by name or metadata\n" + - "Usage:\n" + - "\tmagistrala-cli things get all $DOMAINID $USERTOKEN - lists all things\n" + - "\tmagistrala-cli things get all $DOMAINID $USERTOKEN --offset=10 --limit=10 - lists all things with offset and limit\n" + - "\tmagistrala-cli things get $DOMAINID $USERTOKEN - shows thing with provided \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - metadata, err := convertMetadata(Metadata) - if err != nil { - logErrorCmd(*cmd, err) - return - } - pageMetadata := mgxsdk.PageMetadata{ - Name: Name, - Offset: Offset, - Limit: Limit, - Metadata: metadata, - } - if args[0] == all { - l, err := sdk.Things(pageMetadata, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - return - } - t, err := sdk.Thing(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, t) - }, - }, - { - Use: "delete ", - Short: "Delete thing", - Long: "Delete thing by id\n" + - "Usage:\n" + - "\tmagistrala-cli things delete $DOMAINID $USERTOKEN - delete thing with \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - if err := sdk.DeleteThing(args[0], args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, - { - Use: "update [ | tags | secret ] ", - Short: "Update thing", - Long: "Updates thing with provided id, name and metadata, or updates thing tags, secret\n" + - "Usage:\n" + - "\tmagistrala-cli things update '{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}' $DOMAINID $USERTOKEN\n" + - "\tmagistrala-cli things update tags '{\"tag1\":\"value1\", \"tag2\":\"value2\"}' $DOMAINID $USERTOKEN\n" + - "\tmagistrala-cli things update secret $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 && len(args) != 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - - var thing mgxsdk.Thing - if args[0] == "tags" { - if err := json.Unmarshal([]byte(args[2]), &thing.Tags); err != nil { - logErrorCmd(*cmd, err) - return - } - thing.ID = args[1] - thing, err := sdk.UpdateThingTags(thing, args[3], args[4]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, thing) - return - } - - if args[0] == "secret" { - thing, err := sdk.UpdateThingSecret(args[1], args[2], args[3], args[4]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, thing) - return - } - - if err := json.Unmarshal([]byte(args[1]), &thing); err != nil { - logErrorCmd(*cmd, err) - return - } - thing.ID = args[0] - thing, err := sdk.UpdateThing(thing, args[2], args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, thing) - }, - }, - { - Use: "enable ", - Short: "Change thing status to enabled", - Long: "Change thing status to enabled\n" + - "Usage:\n" + - "\tmagistrala-cli things enable $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - thing, err := sdk.EnableThing(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, thing) - }, - }, - { - Use: "disable ", - Short: "Change thing status to disabled", - Long: "Change thing status to disabled\n" + - "Usage:\n" + - "\tmagistrala-cli things disable $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - thing, err := sdk.DisableThing(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, thing) - }, - }, - { - Use: "share ", - Short: "Share thing with a user", - Long: "Share thing with a user\n" + - "Usage:\n" + - "\tmagistrala-cli things share $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsageCmd(*cmd, cmd.Use) - return - } - req := mgxsdk.UsersRelationRequest{ - Relation: args[2], - UserIDs: []string{args[1]}, - } - err := sdk.ShareThing(args[0], req, args[3], args[4]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "unshare ", - Short: "Unshare thing with a user", - Long: "Unshare thing with a user\n" + - "Usage:\n" + - "\tmagistrala-cli things share $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsageCmd(*cmd, cmd.Use) - return - } - req := mgxsdk.UsersRelationRequest{ - Relation: args[2], - UserIDs: []string{args[1]}, - } - err := sdk.UnshareThing(args[0], req, args[3], args[4]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "connect ", - Short: "Connect thing", - Long: "Connect thing to the channel\n" + - "Usage:\n" + - "\tmagistrala-cli things connect $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - - connIDs := mgxsdk.Connection{ - ChannelID: args[1], - ThingID: args[0], - } - if err := sdk.Connect(connIDs, args[2], args[3]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "disconnect ", - Short: "Disconnect thing", - Long: "Disconnect thing to the channel\n" + - "Usage:\n" + - "\tmagistrala-cli things disconnect $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - - connIDs := mgxsdk.Connection{ - ThingID: args[0], - ChannelID: args[1], - } - if err := sdk.Disconnect(connIDs, args[2], args[3]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "connections ", - Short: "Connected list", - Long: "List of Channels connected to Thing\n" + - "Usage:\n" + - "\tmagistrala-cli connections $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - cl, err := sdk.ChannelsByThing(args[0], pm, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, cl) - }, - }, - { - Use: "users ", - Short: "List users", - Long: "List users of a thing\n" + - "Usage:\n" + - "\tmagistrala-cli things users $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - ul, err := sdk.ListThingUsers(args[0], pm, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, ul) - }, - }, -} - -// NewThingsCmd returns things command. -func NewThingsCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "things [create | get | update | delete | share | connect | disconnect | connections | not-connected | users ]", - Short: "Things management", - Long: `Things management: create, get, update, delete or share Thing, connect or disconnect Thing from Channel and get the list of Channels connected or disconnected from a Thing`, - } - - for i := range cmdThings { - cmd.AddCommand(&cmdThings[i]) - } - - return &cmd -} diff --git a/cli/users.go b/cli/users.go index 54b4158572..7c39f49f15 100644 --- a/cli/users.go +++ b/cli/users.go @@ -398,11 +398,11 @@ var cmdUsers = []cobra.Command{ }, { - Use: "things ", - Short: "List things", - Long: "List things of user\n" + + Use: "clients ", + Short: "List clients", + Long: "List clients of user\n" + "Usage:\n" + - "\tmagistrala-cli users things \n", + "\tmagistrala-cli users clients \n", Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { logUsageCmd(*cmd, cmd.Use) @@ -414,7 +414,7 @@ var cmdUsers = []cobra.Command{ Limit: Limit, } - tp, err := sdk.ListUserThings(args[0], pm, args[1]) + tp, err := sdk.ListUserClients(args[0], pm, args[1]) if err != nil { logErrorCmd(*cmd, err) return @@ -524,7 +524,7 @@ var cmdUsers = []cobra.Command{ // NewUsersCmd returns users command. func NewUsersCmd() *cobra.Command { cmd := cobra.Command{ - Use: "users [create | get | update | token | password | enable | disable | delete | channels | things | groups | search]", + Use: "users [create | get | update | token | password | enable | disable | delete | channels | clients | groups | search]", Short: "Users management", Long: `Users management: create accounts and tokens"`, } diff --git a/cli/users_test.go b/cli/users_test.go index b78a89fdbd..52ae71767a 100644 --- a/cli/users_test.go +++ b/cli/users_test.go @@ -1218,41 +1218,41 @@ func TestListUserChannelsCmd(t *testing.T) { } } -func TestListUserThingsCmd(t *testing.T) { +func TestListUserClientsCmd(t *testing.T) { sdkMock := new(sdkmocks.SDK) cli.SetSDK(sdkMock) usersCmd := cli.NewUsersCmd() rootCmd := setFlags(usersCmd) - th := mgsdk.Thing{ + th := mgsdk.Client{ ID: testsutil.GenerateUUID(t), - Name: "testthing", + Name: "testclient", } - var pg mgsdk.ThingsPage + var pg mgsdk.ClientsPage cases := []struct { desc string args []string sdkerr errors.SDKError errLogMessage string - thing mgsdk.Thing - page mgsdk.ThingsPage + client mgsdk.Client + page mgsdk.ClientsPage logType outputLog }{ { - desc: "list user things successfully", + desc: "list user clients successfully", args: []string{ user.ID, validToken, }, sdkerr: nil, logType: entityLog, - page: mgsdk.ThingsPage{ - Things: []mgsdk.Thing{th}, + page: mgsdk.ClientsPage{ + Clients: []mgsdk.Client{th}, }, }, { - desc: "list user things with invalid args", + desc: "list user clients with invalid args", args: []string{ user.ID, validToken, @@ -1261,7 +1261,7 @@ func TestListUserThingsCmd(t *testing.T) { logType: usageLog, }, { - desc: "list user things with invalid token", + desc: "list user clients with invalid token", args: []string{ user.ID, invalidToken, @@ -1274,8 +1274,8 @@ func TestListUserThingsCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListUserThings", tc.args[0], mock.Anything, tc.args[1]).Return(tc.page, tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{thsCmd}, tc.args...)...) + sdkCall := sdkMock.On("ListUserClients", tc.args[0], mock.Anything, tc.args[1]).Return(tc.page, tc.sdkerr) + out := executeCommand(t, rootCmd, append([]string{cliCmd}, tc.args...)...) switch tc.logType { case errLog: diff --git a/clients/README.md b/clients/README.md new file mode 100644 index 0000000000..bf4c205930 --- /dev/null +++ b/clients/README.md @@ -0,0 +1,122 @@ +# Clients + +Clients service provides an HTTP API for managing platform resources: clients and channels. +Through this API clients are able to do the following actions: + +- provision new clients +- create new channels +- "connect" clients into the channels + +For an in-depth explanation of the aforementioned scenarios, as well as thorough +understanding of Magistrala, please check out the [official documentation][doc]. + +## Configuration + +The service is configured using the environment variables presented in the +following table. Note that any unset variables will be replaced with their +default values. + +| Variable | Description | Default | +| -------------------------------- | ----------------------------------------------------------------------- | ------------------------------ | +| MG_CLIENTS_LOG_LEVEL | Log level for Clients (debug, info, warn, error) | info | +| MG_CLIENTS_HTTP_HOST | Clients service HTTP host | localhost | +| MG_CLIENTS_HTTP_PORT | Clients service HTTP port | 9000 | +| MG_CLIENTS_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| MG_CLIENTS_SERVER_KEY | Path to the PEM encoded server key file | "" | +| MG_CLIENTS_AUTH_GRPC_HOST | Clients service gRPC host | localhost | +| MG_CLIENTS_AUTH_GRPC_PORT | Clients service gRPC port | 7000 | +| MG_CLIENTS_AUTH_GRPC_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| MG_CLIENTS_AUTH_GRPC_SERVER_KEY | Path to the PEM encoded server key file | "" | +| MG_CLIENTS_DB_HOST | Database host address | localhost | +| MG_CLIENTS_DB_PORT | Database host port | 5432 | +| MG_CLIENTS_DB_USER | Database user | magistrala | +| MG_CLIENTS_DB_PASS | Database password | magistrala | +| MG_CLIENTS_DB_NAME | Name of the database used by the service | clients | +| MG_CLIENTS_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | +| MG_CLIENTS_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | +| MG_CLIENTS_DB_SSL_KEY | Path to the PEM encoded key file | "" | +| MG_CLIENTS_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | +| MG_CLIENTS_CACHE_URL | Cache database URL | | +| MG_CLIENTS_CACHE_KEY_DURATION | Cache key duration in seconds | 3600 | +| MG_CLIENTS_ES_URL | Event store URL | | +| MG_CLIENTS_ES_PASS | Event store password | "" | +| MG_CLIENTS_ES_DB | Event store instance name | 0 | +| MG_CLIENTS_STANDALONE_ID | User ID for standalone mode (no gRPC communication with Auth) | "" | +| MG_CLIENTS_STANDALONE_TOKEN | User token for standalone mode that should be passed in auth header | "" | +| MG_JAEGER_URL | Jaeger server URL | | +| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 | +| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | +| MG_AUTH_GRPC_CLIENT_TLS | Enable TLS for gRPC client | false | +| MG_AUTH_GRPC_CA_CERT | Path to the CA certificate file | "" | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server. | true | +| Clients_INSTANCE_ID | Clients instance ID | "" | + +**Note** that if you want `clients` service to have only one user locally, you should use `CLIENTS_STANDALONE` env vars. By specifying these, you don't need `auth` service in your deployment for users' authorization. + +## Deployment + +The service itself is distributed as Docker container. Check the [`clients `](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yml#L167-L194) service section in +docker-compose file to see how service is deployed. + +To start the service outside of the container, execute the following shell script: + +```bash +# download the latest version of the service +git clone https://github.com/absmach/magistrala + +cd magistrala + +# compile the clients +make clients + +# copy binary to bin +make install + +# set the environment variables and run the service +Clients_LOG_LEVEL=[Clients log level] \ +Clients_STANDALONE_ID=[User ID for standalone mode (no gRPC communication with auth)] \ +Clients_STANDALONE_TOKEN=[User token for standalone mode that should be passed in auth header] \ +Clients_CACHE_KEY_DURATION=[Cache key duration in seconds] \ +Clients_HTTP_HOST=[Clients service HTTP host] \ +Clients_HTTP_PORT=[Clients service HTTP port] \ +Clients_HTTP_SERVER_CERT=[Path to server certificate in pem format] \ +Clients_HTTP_SERVER_KEY=[Path to server key in pem format] \ +Clients_AUTH_GRPC_HOST=[Clients service gRPC host] \ +Clients_AUTH_GRPC_PORT=[Clients service gRPC port] \ +Clients_AUTH_GRPC_SERVER_CERT=[Path to server certificate in pem format] \ +Clients_AUTH_GRPC_SERVER_KEY=[Path to server key in pem format] \ +Clients_DB_HOST=[Database host address] \ +Clients_DB_PORT=[Database host port] \ +Clients_DB_USER=[Database user] \ +Clients_DB_PASS=[Database password] \ +Clients_DB_NAME=[Name of the database used by the service] \ +Clients_DB_SSL_MODE=[SSL mode to connect to the database with] \ +Clients_DB_SSL_CERT=[Path to the PEM encoded certificate file] \ +Clients_DB_SSL_KEY=[Path to the PEM encoded key file] \ +Clients_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] \ +Clients_CACHE_URL=[Cache database URL] \ +Clients_ES_URL=[Event store URL] \ +Clients_ES_PASS=[Event store password] \ +Clients_ES_DB=[Event store instance name] \ +MG_AUTH_GRPC_URL=[Auth service gRPC URL] \ +MG_AUTH_GRPC_TIMEOUT=[Auth service gRPC request timeout in seconds] \ +MG_AUTH_GRPC_CLIENT_TLS=[Enable TLS for gRPC client] \ +MG_AUTH_GRPC_CA_CERT=[Path to trusted CA certificate file] \ +MG_JAEGER_URL=[Jaeger server URL] \ +MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \ +Clients_INSTANCE_ID=[Clients instance ID] \ +$GOBIN/magistrala-clients +``` + +Setting `Clients_CA_CERTS` expects a file in PEM format of trusted CAs. This will enable TLS against the Auth gRPC endpoint trusting only those CAs that are provided. + +In constrained environments, sometimes it makes sense to run Clients service as a standalone to reduce network traffic and simplify deployment. This means that Clients service +operates only using a single user and is able to authorize it without gRPC communication with Auth service. +To run service in a standalone mode, set `Clients_STANDALONE_EMAIL` and `Clients_STANDALONE_TOKEN`. + +## Usage + +For more information about service capabilities and its usage, please check out +the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=clients-openapi.yml). + +[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/internal/groups/api/doc.go b/clients/api/doc.go similarity index 100% rename from internal/groups/api/doc.go rename to clients/api/doc.go diff --git a/clients/api/grpc/client.go b/clients/api/grpc/client.go new file mode 100644 index 0000000000..cadf02523e --- /dev/null +++ b/clients/api/grpc/client.go @@ -0,0 +1,372 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + "fmt" + "time" + + "github.com/absmach/magistrala/clients" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + grpcCommonV1 "github.com/absmach/magistrala/internal/grpc/common/v1" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/go-kit/kit/endpoint" + kitgrpc "github.com/go-kit/kit/transport/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const svcName = "clients.v1.ClientsService" + +var _ grpcClientsV1.ClientsServiceClient = (*grpcClient)(nil) + +type grpcClient struct { + timeout time.Duration + authenticate endpoint.Endpoint + retrieveEntity endpoint.Endpoint + retrieveEntities endpoint.Endpoint + addConnections endpoint.Endpoint + removeConnections endpoint.Endpoint + removeChannelConnections endpoint.Endpoint + unsetParentGroupFromClient endpoint.Endpoint +} + +// NewClient returns new gRPC client instance. +func NewClient(conn *grpc.ClientConn, timeout time.Duration) grpcClientsV1.ClientsServiceClient { + return &grpcClient{ + authenticate: kitgrpc.NewClient( + conn, + svcName, + "Authenticate", + encodeAuthenticateRequest, + decodeAuthenticateResponse, + grpcClientsV1.AuthnRes{}, + ).Endpoint(), + + retrieveEntity: kitgrpc.NewClient( + conn, + svcName, + "RetrieveEntity", + encodeRetrieveEntityRequest, + decodeRetrieveEntityResponse, + grpcCommonV1.RetrieveEntityRes{}, + ).Endpoint(), + + retrieveEntities: kitgrpc.NewClient( + conn, + svcName, + "RetrieveEntities", + encodeRetrieveEntitiesRequest, + decodeRetrieveEntitiesResponse, + grpcCommonV1.RetrieveEntitiesRes{}, + ).Endpoint(), + + addConnections: kitgrpc.NewClient( + conn, + svcName, + "AddConnections", + encodeAddConnectionsRequest, + decodeAddConnectionsResponse, + grpcCommonV1.AddConnectionsRes{}, + ).Endpoint(), + + removeConnections: kitgrpc.NewClient( + conn, + svcName, + "RemoveConnections", + encodeRemoveConnectionsRequest, + decodeRemoveConnectionsResponse, + grpcCommonV1.RemoveConnectionsRes{}, + ).Endpoint(), + + removeChannelConnections: kitgrpc.NewClient( + conn, + svcName, + "RemoveChannelConnections", + encodeRemoveChannelConnectionsRequest, + decodeRemoveChannelConnectionsResponse, + grpcClientsV1.RemoveChannelConnectionsRes{}, + ).Endpoint(), + + unsetParentGroupFromClient: kitgrpc.NewClient( + conn, + svcName, + "UnsetParentGroupFromClient", + encodeUnsetParentGroupFromClientRequest, + decodeUnsetParentGroupFromClientResponse, + grpcClientsV1.UnsetParentGroupFromClientRes{}, + ).Endpoint(), + + timeout: timeout, + } +} + +func (client grpcClient) Authenticate(ctx context.Context, req *grpcClientsV1.AuthnReq, _ ...grpc.CallOption) (r *grpcClientsV1.AuthnRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + res, err := client.authenticate(ctx, authenticateReq{ + ClientID: req.GetClientId(), + ClientSecret: req.GetClientSecret(), + }) + if err != nil { + return &grpcClientsV1.AuthnRes{}, decodeError(err) + } + + ar := res.(authenticateRes) + return &grpcClientsV1.AuthnRes{Authenticated: ar.authenticated, Id: ar.id}, nil +} + +func encodeAuthenticateRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(authenticateReq) + return &grpcClientsV1.AuthnReq{ + ClientId: req.ClientID, + ClientSecret: req.ClientSecret, + }, nil +} + +func decodeAuthenticateResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(*grpcClientsV1.AuthnRes) + return authenticateRes{authenticated: res.GetAuthenticated(), id: res.GetId()}, nil +} + +func (client grpcClient) RetrieveEntity(ctx context.Context, req *grpcCommonV1.RetrieveEntityReq, _ ...grpc.CallOption) (r *grpcCommonV1.RetrieveEntityRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + res, err := client.retrieveEntity(ctx, req.GetId()) + if err != nil { + return &grpcCommonV1.RetrieveEntityRes{}, decodeError(err) + } + + ebr := res.(retrieveEntityRes) + + return &grpcCommonV1.RetrieveEntityRes{Entity: &grpcCommonV1.EntityBasic{Id: ebr.id, DomainId: ebr.domain, Status: uint32(ebr.status)}}, nil +} + +func encodeRetrieveEntityRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(string) + return &grpcCommonV1.RetrieveEntityReq{ + Id: req, + }, nil +} + +func decodeRetrieveEntityResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(*grpcCommonV1.RetrieveEntityRes) + + return retrieveEntityRes{ + id: res.Entity.GetId(), + domain: res.Entity.GetDomainId(), + status: uint8(res.Entity.GetStatus()), + }, nil +} + +func (client grpcClient) RetrieveEntities(ctx context.Context, req *grpcCommonV1.RetrieveEntitiesReq, _ ...grpc.CallOption) (r *grpcCommonV1.RetrieveEntitiesRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + res, err := client.retrieveEntities(ctx, req.GetIds()) + if err != nil { + return &grpcCommonV1.RetrieveEntitiesRes{}, decodeError(err) + } + + ep := res.(retrieveEntitiesRes) + + entities := []*grpcCommonV1.EntityBasic{} + for _, c := range ep.clients { + entities = append(entities, &grpcCommonV1.EntityBasic{ + Id: c.id, + DomainId: c.domain, + Status: uint32(c.status), + }) + } + return &grpcCommonV1.RetrieveEntitiesRes{Total: ep.total, Limit: ep.limit, Offset: ep.offset, Entities: entities}, nil +} + +func encodeRetrieveEntitiesRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.([]string) + return &grpcCommonV1.RetrieveEntitiesReq{ + Ids: req, + }, nil +} + +func decodeRetrieveEntitiesResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(*grpcCommonV1.RetrieveEntitiesRes) + + clis := []enitity{} + + for _, e := range res.Entities { + clis = append(clis, enitity{ + id: e.GetId(), + domain: e.GetDomainId(), + status: uint8(e.GetStatus()), + }) + } + return retrieveEntitiesRes{total: res.GetTotal(), limit: res.GetLimit(), offset: res.GetOffset(), clients: clis}, nil +} + +func (client grpcClient) AddConnections(ctx context.Context, req *grpcCommonV1.AddConnectionsReq, _ ...grpc.CallOption) (r *grpcCommonV1.AddConnectionsRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + conns := []clients.Connection{} + for _, c := range req.Connections { + conns = append(conns, clients.Connection{ + ClientID: c.GetClientId(), + ChannelID: c.GetChannelId(), + DomainID: c.GetDomainId(), + Type: connections.ConnType(c.GetType()), + }) + } + + res, err := client.addConnections(ctx, conns) + if err != nil { + return &grpcCommonV1.AddConnectionsRes{}, decodeError(err) + } + + cr := res.(connectionsRes) + + return &grpcCommonV1.AddConnectionsRes{Ok: cr.ok}, nil +} + +func encodeAddConnectionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.([]clients.Connection) + + conns := []*grpcCommonV1.Connection{} + + for _, r := range req { + conns = append(conns, &grpcCommonV1.Connection{ + ClientId: r.ClientID, + ChannelId: r.ChannelID, + DomainId: r.DomainID, + Type: uint32(r.Type), + }) + } + return &grpcCommonV1.AddConnectionsReq{ + Connections: conns, + }, nil +} + +func decodeAddConnectionsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(*grpcCommonV1.AddConnectionsRes) + + return connectionsRes{ok: res.GetOk()}, nil +} + +func (client grpcClient) RemoveConnections(ctx context.Context, req *grpcCommonV1.RemoveConnectionsReq, _ ...grpc.CallOption) (r *grpcCommonV1.RemoveConnectionsRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + conns := []clients.Connection{} + for _, c := range req.Connections { + conns = append(conns, clients.Connection{ + ClientID: c.GetClientId(), + ChannelID: c.GetChannelId(), + DomainID: c.GetDomainId(), + Type: connections.ConnType(c.GetType()), + }) + } + + res, err := client.removeConnections(ctx, conns) + if err != nil { + return &grpcCommonV1.RemoveConnectionsRes{}, decodeError(err) + } + + cr := res.(connectionsRes) + + return &grpcCommonV1.RemoveConnectionsRes{Ok: cr.ok}, nil +} + +func encodeRemoveConnectionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.([]clients.Connection) + + conns := []*grpcCommonV1.Connection{} + + for _, r := range req { + conns = append(conns, &grpcCommonV1.Connection{ + ClientId: r.ClientID, + ChannelId: r.ChannelID, + DomainId: r.DomainID, + Type: uint32(r.Type), + }) + } + return &grpcCommonV1.RemoveConnectionsReq{ + Connections: conns, + }, nil +} + +func decodeRemoveConnectionsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(*grpcCommonV1.RemoveConnectionsRes) + + return connectionsRes{ok: res.GetOk()}, nil +} + +func (client grpcClient) RemoveChannelConnections(ctx context.Context, req *grpcClientsV1.RemoveChannelConnectionsReq, _ ...grpc.CallOption) (r *grpcClientsV1.RemoveChannelConnectionsRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + if _, err := client.removeChannelConnections(ctx, req); err != nil { + return &grpcClientsV1.RemoveChannelConnectionsRes{}, decodeError(err) + } + + return &grpcClientsV1.RemoveChannelConnectionsRes{}, nil +} + +func encodeRemoveChannelConnectionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + return grpcReq.(*grpcClientsV1.RemoveChannelConnectionsReq), nil +} + +func decodeRemoveChannelConnectionsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + return grpcRes.(*grpcClientsV1.RemoveChannelConnectionsRes), nil +} + +func (client grpcClient) UnsetParentGroupFromClient(ctx context.Context, req *grpcClientsV1.UnsetParentGroupFromClientReq, _ ...grpc.CallOption) (r *grpcClientsV1.UnsetParentGroupFromClientRes, err error) { + ctx, cancel := context.WithTimeout(ctx, client.timeout) + defer cancel() + + if _, err := client.unsetParentGroupFromClient(ctx, req); err != nil { + return &grpcClientsV1.UnsetParentGroupFromClientRes{}, decodeError(err) + } + + return &grpcClientsV1.UnsetParentGroupFromClientRes{}, nil +} + +func encodeUnsetParentGroupFromClientRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + return grpcReq.(*grpcClientsV1.UnsetParentGroupFromClientReq), nil +} + +func decodeUnsetParentGroupFromClientResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + return grpcRes.(*grpcClientsV1.UnsetParentGroupFromClientRes), nil +} + +func decodeError(err error) error { + if st, ok := status.FromError(err); ok { + switch st.Code() { + case codes.Unauthenticated: + return errors.Wrap(svcerr.ErrAuthentication, errors.New(st.Message())) + case codes.PermissionDenied: + return errors.Wrap(svcerr.ErrAuthorization, errors.New(st.Message())) + case codes.InvalidArgument: + return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) + case codes.FailedPrecondition: + return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) + case codes.NotFound: + return errors.Wrap(svcerr.ErrNotFound, errors.New(st.Message())) + case codes.AlreadyExists: + return errors.Wrap(svcerr.ErrConflict, errors.New(st.Message())) + case codes.OK: + if msg := st.Message(); msg != "" { + return errors.Wrap(errors.ErrUnidentified, errors.New(msg)) + } + return nil + default: + return errors.Wrap(fmt.Errorf("unexpected gRPC status: %s (status code:%v)", st.Code().String(), st.Code()), errors.New(st.Message())) + } + } + return err +} diff --git a/clients/api/grpc/doc.go b/clients/api/grpc/doc.go new file mode 100644 index 0000000000..20956ee50b --- /dev/null +++ b/clients/api/grpc/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package grpc contains implementation of Auth service gRPC API. +package grpc diff --git a/clients/api/grpc/endpoint.go b/clients/api/grpc/endpoint.go new file mode 100644 index 0000000000..a561e099ab --- /dev/null +++ b/clients/api/grpc/endpoint.go @@ -0,0 +1,127 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + + "github.com/absmach/magistrala/clients" + pClients "github.com/absmach/magistrala/clients/private" + "github.com/go-kit/kit/endpoint" +) + +func authenticateEndpoint(svc pClients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(authenticateReq) + id, err := svc.Authenticate(ctx, req.ClientSecret) + if err != nil { + return authenticateRes{}, err + } + return authenticateRes{ + authenticated: true, + id: id, + }, err + } +} + +func retrieveEntityEndpoint(svc pClients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(retrieveEntityReq) + Client, err := svc.RetrieveById(ctx, req.Id) + if err != nil { + return retrieveEntityRes{}, err + } + + return retrieveEntityRes{id: Client.ID, domain: Client.Domain, status: uint8(Client.Status)}, nil + } +} + +func retrieveEntitiesEndpoint(svc pClients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(retrieveEntitiesReq) + tp, err := svc.RetrieveByIds(ctx, req.Ids) + if err != nil { + return retrieveEntitiesRes{}, err + } + clientsBasic := []enitity{} + for _, client := range tp.Clients { + clientsBasic = append(clientsBasic, enitity{id: client.ID, domain: client.Domain, status: uint8(client.Status)}) + } + return retrieveEntitiesRes{ + total: tp.Total, + limit: tp.Limit, + offset: tp.Offset, + clients: clientsBasic, + }, nil + } +} + +func addConnectionsEndpoint(svc pClients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connectionsReq) + + var conns []clients.Connection + + for _, c := range req.connections { + conns = append(conns, clients.Connection{ + ClientID: c.clientID, + ChannelID: c.channelID, + DomainID: c.domainID, + Type: c.connType, + }) + } + + if err := svc.AddConnections(ctx, conns); err != nil { + return connectionsRes{ok: false}, err + } + + return connectionsRes{ok: true}, nil + } +} + +func removeConnectionsEndpoint(svc pClients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(connectionsReq) + + var conns []clients.Connection + + for _, c := range req.connections { + conns = append(conns, clients.Connection{ + ClientID: c.clientID, + ChannelID: c.channelID, + DomainID: c.domainID, + Type: c.connType, + }) + } + if err := svc.RemoveConnections(ctx, conns); err != nil { + return connectionsRes{ok: false}, err + } + + return connectionsRes{ok: true}, nil + } +} + +func removeChannelConnectionsEndpoint(svc pClients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(removeChannelConnectionsReq) + + if err := svc.RemoveChannelConnections(ctx, req.channelID); err != nil { + return removeChannelConnectionsRes{}, err + } + + return removeChannelConnectionsRes{}, nil + } +} + +func UnsetParentGroupFromClientEndpoint(svc pClients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(UnsetParentGroupFromClientReq) + + if err := svc.UnsetParentGroupFromClient(ctx, req.parentGroupID); err != nil { + return UnsetParentGroupFromClientRes{}, err + } + + return UnsetParentGroupFromClientRes{}, nil + } +} diff --git a/clients/api/grpc/request.go b/clients/api/grpc/request.go new file mode 100644 index 0000000000..807a5093a3 --- /dev/null +++ b/clients/api/grpc/request.go @@ -0,0 +1,25 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +type authenticateReq struct { + ClientID string + ClientSecret string +} + +type retrieveEntitiesReq struct { + Ids []string +} + +type retrieveEntityReq struct { + Id string +} + +type removeChannelConnectionsReq struct { + channelID string +} + +type UnsetParentGroupFromClientReq struct { + parentGroupID string +} diff --git a/clients/api/grpc/responses.go b/clients/api/grpc/responses.go new file mode 100644 index 0000000000..cd7630340e --- /dev/null +++ b/clients/api/grpc/responses.go @@ -0,0 +1,44 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import "github.com/absmach/magistrala/pkg/connections" + +type enitity struct { + id string + domain string + status uint8 +} + +type authenticateRes struct { + id string + authenticated bool +} + +type retrieveEntitiesRes struct { + total uint64 + limit uint64 + offset uint64 + clients []enitity +} + +type retrieveEntityRes enitity + +type connectionsReq struct { + connections []connection +} + +type connection struct { + clientID string + channelID string + domainID string + connType connections.ConnType +} +type connectionsRes struct { + ok bool +} + +type removeChannelConnectionsRes struct{} + +type UnsetParentGroupFromClientRes struct{} diff --git a/clients/api/grpc/server.go b/clients/api/grpc/server.go new file mode 100644 index 0000000000..12d30926b1 --- /dev/null +++ b/clients/api/grpc/server.go @@ -0,0 +1,287 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + + mgauth "github.com/absmach/magistrala/auth" + clients "github.com/absmach/magistrala/clients/private" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + grpcCommonV1 "github.com/absmach/magistrala/internal/grpc/common/v1" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + kitgrpc "github.com/go-kit/kit/transport/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var _ grpcClientsV1.ClientsServiceServer = (*grpcServer)(nil) + +type grpcServer struct { + grpcClientsV1.UnimplementedClientsServiceServer + authenticate kitgrpc.Handler + retrieveEntity kitgrpc.Handler + retrieveEntities kitgrpc.Handler + addConnections kitgrpc.Handler + removeConnections kitgrpc.Handler + removeChannelConnections kitgrpc.Handler + unsetParentGroupFromClient kitgrpc.Handler +} + +// NewServer returns new AuthServiceServer instance. +func NewServer(svc clients.Service) grpcClientsV1.ClientsServiceServer { + return &grpcServer{ + authenticate: kitgrpc.NewServer( + authenticateEndpoint(svc), + decodeAuthorizeRequest, + encodeAuthorizeResponse, + ), + retrieveEntity: kitgrpc.NewServer( + retrieveEntityEndpoint(svc), + decodeRetrieveEntityRequest, + encodeRetrieveEntityResponse, + ), + retrieveEntities: kitgrpc.NewServer( + retrieveEntitiesEndpoint(svc), + decodeRetrieveEntitiesRequest, + encodeRetrieveEntitiesResponse, + ), + addConnections: kitgrpc.NewServer( + addConnectionsEndpoint(svc), + decodeAddConnectionsRequest, + encodeAddConnectionsResponse, + ), + removeConnections: kitgrpc.NewServer( + removeConnectionsEndpoint(svc), + decodeRemoveConnectionsRequest, + encodeRemoveConnectionsResponse, + ), + removeChannelConnections: kitgrpc.NewServer( + removeChannelConnectionsEndpoint(svc), + decodeRemoveChannelConnectionsRequest, + encodeRemoveChannelConnectionsResponse, + ), + unsetParentGroupFromClient: kitgrpc.NewServer( + UnsetParentGroupFromClientEndpoint(svc), + decodeUnsetParentGroupFromClientRequest, + encodeUnsetParentGroupFromClientResponse, + ), + } +} + +func (s *grpcServer) Authenticate(ctx context.Context, req *grpcClientsV1.AuthnReq) (*grpcClientsV1.AuthnRes, error) { + _, res, err := s.authenticate.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcClientsV1.AuthnRes), nil +} + +func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcClientsV1.AuthnReq) + return authenticateReq{ + ClientID: req.GetClientId(), + ClientSecret: req.GetClientSecret(), + }, nil +} + +func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(authenticateRes) + return &grpcClientsV1.AuthnRes{Authenticated: res.authenticated, Id: res.id}, nil +} + +func (s *grpcServer) RetrieveEntity(ctx context.Context, req *grpcCommonV1.RetrieveEntityReq) (*grpcCommonV1.RetrieveEntityRes, error) { + _, res, err := s.retrieveEntity.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcCommonV1.RetrieveEntityRes), nil +} + +func decodeRetrieveEntityRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcCommonV1.RetrieveEntityReq) + return retrieveEntityReq{ + Id: req.GetId(), + }, nil +} + +func encodeRetrieveEntityResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(retrieveEntityRes) + + return &grpcCommonV1.RetrieveEntityRes{ + Entity: &grpcCommonV1.EntityBasic{ + Id: res.id, + DomainId: res.domain, + Status: uint32(res.status), + }, + }, nil +} + +func (s *grpcServer) RetrieveEntities(ctx context.Context, req *grpcCommonV1.RetrieveEntitiesReq) (*grpcCommonV1.RetrieveEntitiesRes, error) { + _, res, err := s.retrieveEntities.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcCommonV1.RetrieveEntitiesRes), nil +} + +func decodeRetrieveEntitiesRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcCommonV1.RetrieveEntitiesReq) + return retrieveEntitiesReq{ + Ids: req.GetIds(), + }, nil +} + +func encodeRetrieveEntitiesResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(retrieveEntitiesRes) + + entities := []*grpcCommonV1.EntityBasic{} + for _, c := range res.clients { + entities = append(entities, &grpcCommonV1.EntityBasic{ + Id: c.id, + DomainId: c.domain, + Status: uint32(c.status), + }) + } + return &grpcCommonV1.RetrieveEntitiesRes{Total: res.total, Limit: res.limit, Offset: res.offset, Entities: entities}, nil +} + +func (s *grpcServer) AddConnections(ctx context.Context, req *grpcCommonV1.AddConnectionsReq) (*grpcCommonV1.AddConnectionsRes, error) { + _, res, err := s.addConnections.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcCommonV1.AddConnectionsRes), nil +} + +func decodeAddConnectionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcCommonV1.AddConnectionsReq) + + conns := []connection{} + for _, c := range req.Connections { + connType := connections.ConnType(c.GetType()) + if err := connections.CheckConnType(connType); err != nil { + return nil, err + } + conns = append(conns, connection{ + clientID: c.GetClientId(), + channelID: c.GetChannelId(), + domainID: c.GetDomainId(), + connType: connType, + }) + } + return connectionsReq{ + connections: conns, + }, nil +} + +func encodeAddConnectionsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(connectionsRes) + + return &grpcCommonV1.AddConnectionsRes{Ok: res.ok}, nil +} + +func (s *grpcServer) RemoveConnections(ctx context.Context, req *grpcCommonV1.RemoveConnectionsReq) (*grpcCommonV1.RemoveConnectionsRes, error) { + _, res, err := s.removeConnections.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcCommonV1.RemoveConnectionsRes), nil +} + +func decodeRemoveConnectionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcCommonV1.RemoveConnectionsReq) + + conns := []connection{} + for _, c := range req.Connections { + connType := connections.ConnType(c.GetType()) + if err := connections.CheckConnType(connType); err != nil { + return nil, err + } + conns = append(conns, connection{ + clientID: c.GetClientId(), + channelID: c.GetChannelId(), + domainID: c.GetDomainId(), + connType: connType, + }) + } + return connectionsReq{ + connections: conns, + }, nil +} + +func encodeRemoveConnectionsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(connectionsRes) + + return &grpcCommonV1.RemoveConnectionsRes{Ok: res.ok}, nil +} + +func (s *grpcServer) RemoveChannelConnections(ctx context.Context, req *grpcClientsV1.RemoveChannelConnectionsReq) (*grpcClientsV1.RemoveChannelConnectionsRes, error) { + _, res, err := s.removeChannelConnections.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcClientsV1.RemoveChannelConnectionsRes), nil +} + +func decodeRemoveChannelConnectionsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcClientsV1.RemoveChannelConnectionsReq) + + return removeChannelConnectionsReq{ + channelID: req.GetChannelId(), + }, nil +} + +func encodeRemoveChannelConnectionsResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + _ = grpcRes.(removeChannelConnectionsRes) + return &grpcClientsV1.RemoveChannelConnectionsRes{}, nil +} + +func (s *grpcServer) UnsetParentGroupFromClient(ctx context.Context, req *grpcClientsV1.UnsetParentGroupFromClientReq) (*grpcClientsV1.UnsetParentGroupFromClientRes, error) { + _, res, err := s.unsetParentGroupFromClient.ServeGRPC(ctx, req) + if err != nil { + return nil, encodeError(err) + } + return res.(*grpcClientsV1.UnsetParentGroupFromClientRes), nil +} + +func decodeUnsetParentGroupFromClientRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcClientsV1.UnsetParentGroupFromClientReq) + + return UnsetParentGroupFromClientReq{ + parentGroupID: req.GetParentGroupId(), + }, nil +} + +func encodeUnsetParentGroupFromClientResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + _ = grpcRes.(UnsetParentGroupFromClientRes) + return &grpcClientsV1.UnsetParentGroupFromClientRes{}, 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.ErrMissingMemberType, + err == apiutil.ErrMissingPolicySub, + err == apiutil.ErrMissingPolicyObj, + err == apiutil.ErrMalformedPolicyAct: + return status.Error(codes.InvalidArgument, err.Error()) + case errors.Contains(err, svcerr.ErrAuthentication), + errors.Contains(err, mgauth.ErrKeyExpired), + err == apiutil.ErrMissingEmail, + err == apiutil.ErrBearerToken: + return status.Error(codes.Unauthenticated, err.Error()) + case errors.Contains(err, svcerr.ErrAuthorization): + return status.Error(codes.PermissionDenied, err.Error()) + default: + return status.Error(codes.Internal, err.Error()) + } +} diff --git a/clients/api/http/clients.go b/clients/api/http/clients.go new file mode 100644 index 0000000000..d07b02e860 --- /dev/null +++ b/clients/api/http/clients.go @@ -0,0 +1,126 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "log/slog" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" + roleManagerHttp "github.com/absmach/magistrala/pkg/roles/rolemanager/api" + "github.com/go-chi/chi/v5" + kithttp "github.com/go-kit/kit/transport/http" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +func clientsHandler(svc clients.Service, authn mgauthn.Authentication, r *chi.Mux, logger *slog.Logger) *chi.Mux { + opts := []kithttp.ServerOption{ + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), + } + d := roleManagerHttp.NewDecoder("clientID") + + r.Group(func(r chi.Router) { + r.Use(api.AuthenticateMiddleware(authn, true)) + + r.Route("/{domainID}/clients", func(r chi.Router) { + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + createClientEndpoint(svc), + decodeCreateClientReq, + api.EncodeResponse, + opts..., + ), "create_client").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + listClientsEndpoint(svc), + decodeListClients, + api.EncodeResponse, + opts..., + ), "list_clients").ServeHTTP) + + r.Post("/bulk", otelhttp.NewHandler(kithttp.NewServer( + createClientsEndpoint(svc), + decodeCreateClientsReq, + api.EncodeResponse, + opts..., + ), "create_clients").ServeHTTP) + r = roleManagerHttp.EntityAvailableActionsRouter(svc, d, r, opts) + + r.Route("/{clientID}", func(r chi.Router) { + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + viewClientEndpoint(svc), + decodeViewClient, + api.EncodeResponse, + opts..., + ), "view_client").ServeHTTP) + + r.Patch("/", otelhttp.NewHandler(kithttp.NewServer( + updateClientEndpoint(svc), + decodeUpdateClient, + api.EncodeResponse, + opts..., + ), "update_client").ServeHTTP) + + r.Patch("/tags", otelhttp.NewHandler(kithttp.NewServer( + updateClientTagsEndpoint(svc), + decodeUpdateClientTags, + api.EncodeResponse, + opts..., + ), "update_client_tags").ServeHTTP) + + r.Patch("/secret", otelhttp.NewHandler(kithttp.NewServer( + updateClientSecretEndpoint(svc), + decodeUpdateClientCredentials, + api.EncodeResponse, + opts..., + ), "update_client_credentials").ServeHTTP) + + r.Post("/enable", otelhttp.NewHandler(kithttp.NewServer( + enableClientEndpoint(svc), + decodeChangeClientStatus, + api.EncodeResponse, + opts..., + ), "enable_client").ServeHTTP) + + r.Post("/disable", otelhttp.NewHandler(kithttp.NewServer( + disableClientEndpoint(svc), + decodeChangeClientStatus, + api.EncodeResponse, + opts..., + ), "disable_client").ServeHTTP) + + r.Post("/parent", otelhttp.NewHandler(kithttp.NewServer( + setClientParentGroupEndpoint(svc), + decodeSetClientParentGroupStatus, + api.EncodeResponse, + opts..., + ), "set_client_parent_group").ServeHTTP) + + r.Delete("/parent", otelhttp.NewHandler(kithttp.NewServer( + removeClientParentGroupEndpoint(svc), + decodeRemoveClientParentGroupStatus, + api.EncodeResponse, + opts..., + ), "remove_client_parent_group").ServeHTTP) + + r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( + deleteClientEndpoint(svc), + decodeDeleteClientReq, + api.EncodeResponse, + opts..., + ), "delete_client").ServeHTTP) + roleManagerHttp.EntityRoleMangerRouter(svc, d, r, opts) + }) + }) + + r.Get("/{domainID}/users/{userID}/clients", otelhttp.NewHandler(kithttp.NewServer( + listClientsEndpoint(svc), + decodeListClients, + api.EncodeResponse, + opts..., + ), "list_user_clients").ServeHTTP) + }) + return r +} diff --git a/things/api/http/clients.go b/clients/api/http/decode.go similarity index 55% rename from things/api/http/clients.go rename to clients/api/http/decode.go index 285f5c4397..2b22f8bb94 100644 --- a/things/api/http/clients.go +++ b/clients/api/http/decode.go @@ -1,150 +1,23 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - package http import ( "context" "encoding/json" - "log/slog" "net/http" "strings" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/things" "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -func clientsHandler(svc things.Service, r *chi.Mux, authn mgauthn.Authentication, logger *slog.Logger) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, true)) - - r.Route("/{domainID}/things", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - createClientEndpoint(svc), - decodeCreateClientReq, - api.EncodeResponse, - opts..., - ), "create_thing").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - listClientsEndpoint(svc), - decodeListClients, - api.EncodeResponse, - opts..., - ), "list_things").ServeHTTP) - - r.Post("/bulk", otelhttp.NewHandler(kithttp.NewServer( - createClientsEndpoint(svc), - decodeCreateClientsReq, - api.EncodeResponse, - opts..., - ), "create_things").ServeHTTP) - - r.Get("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - viewClientEndpoint(svc), - decodeViewClient, - api.EncodeResponse, - opts..., - ), "view_thing").ServeHTTP) - - r.Get("/{thingID}/permissions", otelhttp.NewHandler(kithttp.NewServer( - viewClientPermsEndpoint(svc), - decodeViewClientPerms, - api.EncodeResponse, - opts..., - ), "view_thing_permissions").ServeHTTP) - - r.Patch("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - updateClientEndpoint(svc), - decodeUpdateClient, - api.EncodeResponse, - opts..., - ), "update_thing").ServeHTTP) - - r.Patch("/{thingID}/tags", otelhttp.NewHandler(kithttp.NewServer( - updateClientTagsEndpoint(svc), - decodeUpdateClientTags, - api.EncodeResponse, - opts..., - ), "update_thing_tags").ServeHTTP) - - r.Patch("/{thingID}/secret", otelhttp.NewHandler(kithttp.NewServer( - updateClientSecretEndpoint(svc), - decodeUpdateClientCredentials, - api.EncodeResponse, - opts..., - ), "update_thing_credentials").ServeHTTP) - - r.Post("/{thingID}/enable", otelhttp.NewHandler(kithttp.NewServer( - enableClientEndpoint(svc), - decodeChangeClientStatus, - api.EncodeResponse, - opts..., - ), "enable_thing").ServeHTTP) - - r.Post("/{thingID}/disable", otelhttp.NewHandler(kithttp.NewServer( - disableClientEndpoint(svc), - decodeChangeClientStatus, - api.EncodeResponse, - opts..., - ), "disable_thing").ServeHTTP) - - r.Post("/{thingID}/share", otelhttp.NewHandler(kithttp.NewServer( - thingShareEndpoint(svc), - decodeThingShareRequest, - api.EncodeResponse, - opts..., - ), "share_thing").ServeHTTP) - - r.Post("/{thingID}/unshare", otelhttp.NewHandler(kithttp.NewServer( - thingUnshareEndpoint(svc), - decodeThingUnshareRequest, - api.EncodeResponse, - opts..., - ), "unshare_thing").ServeHTTP) - - r.Delete("/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - deleteClientEndpoint(svc), - decodeDeleteClientReq, - api.EncodeResponse, - opts..., - ), "delete_thing").ServeHTTP) - }) - - // Ideal location: things service, channels endpoint - // Reason for placing here : - // SpiceDB provides list of thing ids present in given channel id - // and things service can access spiceDB and get the list of thing ids present in given channel id. - // Request to get list of things present in channelID ({groupID}) . - r.Get("/{domainID}/channels/{groupID}/things", otelhttp.NewHandler(kithttp.NewServer( - listMembersEndpoint(svc), - decodeListMembersRequest, - api.EncodeResponse, - opts..., - ), "list_things_by_channel_id").ServeHTTP) - - r.Get("/{domainID}/users/{userID}/things", otelhttp.NewHandler(kithttp.NewServer( - listClientsEndpoint(svc), - decodeListClients, - api.EncodeResponse, - opts..., - ), "list_user_things").ServeHTTP) - }) - return r -} +const clientID = "clientID" func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) { req := viewClientReq{ - id: chi.URLParam(r, "thingID"), + id: chi.URLParam(r, clientID), } return req, nil @@ -152,7 +25,7 @@ func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) { func decodeViewClientPerms(_ context.Context, r *http.Request) (interface{}, error) { req := viewClientPermsReq{ - id: chi.URLParam(r, "thingID"), + id: chi.URLParam(r, clientID), } return req, nil @@ -196,7 +69,7 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - st, err := things.ToStatus(s) + st, err := clients.ToStatus(s) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } @@ -221,7 +94,7 @@ func decodeUpdateClient(_ context.Context, r *http.Request) (interface{}, error) } req := updateClientReq{ - id: chi.URLParam(r, "thingID"), + id: chi.URLParam(r, clientID), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) @@ -236,7 +109,7 @@ func decodeUpdateClientTags(_ context.Context, r *http.Request) (interface{}, er } req := updateClientTagsReq{ - id: chi.URLParam(r, "thingID"), + id: chi.URLParam(r, clientID), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) @@ -251,7 +124,7 @@ func decodeUpdateClientCredentials(_ context.Context, r *http.Request) (interfac } req := updateClientCredentialsReq{ - id: chi.URLParam(r, "thingID"), + id: chi.URLParam(r, clientID), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) @@ -265,12 +138,12 @@ func decodeCreateClientReq(_ context.Context, r *http.Request) (interface{}, err return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) } - var c things.Client + var c clients.Client if err := json.NewDecoder(r.Body).Decode(&c); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) } req := createClientReq{ - thing: c, + client: c, } return req, nil @@ -282,7 +155,7 @@ func decodeCreateClientsReq(_ context.Context, r *http.Request) (interface{}, er } c := createClientsReq{} - if err := json.NewDecoder(r.Body).Decode(&c.Things); err != nil { + if err := json.NewDecoder(r.Body).Decode(&c.Clients); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) } @@ -291,7 +164,29 @@ func decodeCreateClientsReq(_ context.Context, r *http.Request) (interface{}, er func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, error) { req := changeClientStatusReq{ - id: chi.URLParam(r, "thingID"), + id: chi.URLParam(r, clientID), + } + + return req, nil +} + +func decodeSetClientParentGroupStatus(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + + req := setClientParentGroupReq{ + id: chi.URLParam(r, clientID), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func decodeRemoveClientParentGroupStatus(_ context.Context, r *http.Request) (interface{}, error) { + req := removeClientParentGroupReq{ + id: chi.URLParam(r, clientID), } return req, nil @@ -314,7 +209,7 @@ func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - st, err := things.ToStatus(s) + st, err := clients.ToStatus(s) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } @@ -328,7 +223,7 @@ func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, return nil, errors.Wrap(apiutil.ErrValidation, err) } req := listMembersReq{ - Page: things.Page{ + Page: clients.Page{ Status: st, Offset: o, Limit: l, @@ -341,39 +236,9 @@ func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, return req, nil } -func decodeThingShareRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := thingShareRequest{ - thingID: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeThingUnshareRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := thingShareRequest{ - thingID: chi.URLParam(r, "thingID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - func decodeDeleteClientReq(_ context.Context, r *http.Request) (interface{}, error) { req := deleteClientReq{ - id: chi.URLParam(r, "thingID"), + id: chi.URLParam(r, clientID), } return req, nil diff --git a/clients/api/http/endpoints.go b/clients/api/http/endpoints.go new file mode 100644 index 0000000000..f6b0fb92c4 --- /dev/null +++ b/clients/api/http/endpoints.go @@ -0,0 +1,308 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "context" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/go-kit/kit/endpoint" +) + +func createClientEndpoint(svc clients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createClientReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + client, err := svc.CreateClients(ctx, session, req.client) + if err != nil { + return nil, err + } + + return createClientRes{ + Client: client[0], + created: true, + }, nil + } +} + +func createClientsEndpoint(svc clients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createClientsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + page, err := svc.CreateClients(ctx, session, req.Clients...) + if err != nil { + return nil, err + } + + res := clientsPageRes{ + clientsPageMetaRes: clientsPageMetaRes{ + Total: uint64(len(page)), + }, + Clients: []viewClientRes{}, + } + for _, c := range page { + res.Clients = append(res.Clients, viewClientRes{Client: c}) + } + + return res, 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, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + c, err := svc.View(ctx, session, 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 nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + pm := clients.Page{ + Status: req.status, + Offset: req.offset, + Limit: req.limit, + Name: req.name, + Tag: req.tag, + Permission: req.permission, + Metadata: req.metadata, + ListPerms: req.listPerms, + Id: req.id, + } + page, err := svc.ListClients(ctx, session, req.userID, pm) + if err != nil { + return nil, err + } + + res := clientsPageRes{ + clientsPageMetaRes: clientsPageMetaRes{ + 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 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, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + cli := clients.Client{ + ID: req.id, + Name: req.Name, + Metadata: req.Metadata, + } + client, err := svc.Update(ctx, session, 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, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + cli := clients.Client{ + ID: req.id, + Tags: req.Tags, + } + client, err := svc.UpdateTags(ctx, session, cli) + 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, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + client, err := svc.UpdateSecret(ctx, session, req.id, req.Secret) + if err != nil { + return nil, err + } + + return updateClientRes{Client: client}, 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, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + client, err := svc.Enable(ctx, session, req.id) + if err != nil { + return nil, err + } + + return changeClientStatusRes{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, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + client, err := svc.Disable(ctx, session, req.id) + if err != nil { + return nil, err + } + + return changeClientStatusRes{Client: client}, nil + } +} + +func setClientParentGroupEndpoint(svc clients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(setClientParentGroupReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + if err := svc.SetParentGroup(ctx, session, req.ParentGroupID, req.id); err != nil { + return nil, err + } + + return setParentGroupRes{}, nil + } +} + +func removeClientParentGroupEndpoint(svc clients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(removeClientParentGroupReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + if err := svc.RemoveParentGroup(ctx, session, req.id); err != nil { + return nil, err + } + + return removeParentGroupRes{}, nil + } +} + +func deleteClientEndpoint(svc clients.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(deleteClientReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.Delete(ctx, session, req.id); err != nil { + return nil, err + } + + return deleteClientRes{}, nil + } +} diff --git a/things/api/http/endpoints_test.go b/clients/api/http/endpoints_test.go similarity index 58% rename from things/api/http/endpoints_test.go rename to clients/api/http/endpoints_test.go index 3c16c92e4e..4290d20eb3 100644 --- a/things/api/http/endpoints_test.go +++ b/clients/api/http/endpoints_test.go @@ -13,6 +13,9 @@ import ( "testing" "github.com/0x6flab/namegenerator" + "github.com/absmach/magistrala/clients" + httpapi "github.com/absmach/magistrala/clients/api/http" + "github.com/absmach/magistrala/clients/mocks" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" @@ -21,10 +24,6 @@ import ( authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - gmocks "github.com/absmach/magistrala/pkg/groups/mocks" - "github.com/absmach/magistrala/things" - httpapi "github.com/absmach/magistrala/things/api/http" - "github.com/absmach/magistrala/things/mocks" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -32,15 +31,15 @@ import ( var ( secret = "strongsecret" - validCMetadata = things.Metadata{"role": "client"} + validCMetadata = clients.Metadata{"role": "client"} ID = testsutil.GenerateUUID(&testing.T{}) - client = things.Client{ + client = clients.Client{ ID: ID, Name: "clientname", Tags: []string{"tag1", "tag2"}, - Credentials: things.Credentials{Identity: "clientidentity", Secret: secret}, + Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, Metadata: validCMetadata, - Status: things.EnabledStatus, + Status: clients.EnabledStatus, } validToken = "token" inValidToken = "invalid" @@ -88,25 +87,24 @@ func toJSON(data interface{}) string { return string(jsonData) } -func newThingsServer() (*httptest.Server, *mocks.Service, *gmocks.Service, *authnmocks.Authentication) { +func newClientsServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { svc := new(mocks.Service) - gsvc := new(gmocks.Service) authn := new(authnmocks.Authentication) logger := mglog.NewMock() mux := chi.NewRouter() - httpapi.MakeHandler(svc, gsvc, authn, mux, logger, "") + httpapi.MakeHandler(svc, authn, mux, logger, "") - return httptest.NewServer(mux), svc, gsvc, authn + return httptest.NewServer(mux), svc, authn } -func TestCreateThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestCreateClient(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { desc string - client things.Client + client clients.Client domainID string token string contentType string @@ -116,7 +114,7 @@ func TestCreateThing(t *testing.T) { err error }{ { - desc: "register a new thing with a valid token", + desc: "register a new client with a valid token", client: client, domainID: domainID, token: validToken, @@ -126,7 +124,7 @@ func TestCreateThing(t *testing.T) { err: nil, }, { - desc: "register an existing thing", + desc: "register an existing client", client: client, domainID: domainID, token: validToken, @@ -136,7 +134,7 @@ func TestCreateThing(t *testing.T) { err: svcerr.ErrConflict, }, { - desc: "register a new thing with an empty token", + desc: "register a new client with an empty token", client: client, domainID: domainID, token: "", @@ -146,10 +144,10 @@ func TestCreateThing(t *testing.T) { err: apiutil.ErrBearerToken, }, { - desc: "register a thing with an invalid ID", - client: things.Client{ + desc: "register a client with an invalid ID", + client: clients.Client{ ID: inValid, - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "user@example.com", Secret: "12345678", }, @@ -162,9 +160,9 @@ func TestCreateThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "register a thing that can't be marshalled", - client: things.Client{ - Credentials: things.Credentials{ + desc: "register a client that can't be marshalled", + client: clients.Client{ + Credentials: clients.Credentials{ Identity: "user@example.com", Secret: "12345678", }, @@ -180,14 +178,14 @@ func TestCreateThing(t *testing.T) { err: errors.ErrMalformedEntity, }, { - desc: "register thing with invalid status", - client: things.Client{ + desc: "register client with invalid status", + client: clients.Client{ ID: testsutil.GenerateUUID(t), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "newclientwithinvalidstatus@example.com", Secret: secret, }, - Status: things.AllStatus, + Status: clients.AllStatus, }, domainID: domainID, token: validToken, @@ -197,10 +195,10 @@ func TestCreateThing(t *testing.T) { err: svcerr.ErrInvalidStatus, }, { - desc: "create thing with invalid contentype", - client: things.Client{ + desc: "create client with invalid contentype", + client: clients.Client{ ID: testsutil.GenerateUUID(t), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "example@example.com", Secret: secret, }, @@ -220,14 +218,14 @@ func TestCreateThing(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/", ts.URL, tc.domainID), + url: fmt.Sprintf("%s/%s/clients/", ts.URL, tc.domainID), contentType: tc.contentType, token: tc.token, body: strings.NewReader(data), } authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("CreateClients", mock.Anything, tc.authnRes, tc.client).Return([]things.Client{tc.client}, tc.err) + svcCall := svc.On("CreateClients", mock.Anything, tc.authnRes, tc.client).Return([]clients.Client{tc.client}, tc.err) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var errRes respBody @@ -244,29 +242,29 @@ func TestCreateThing(t *testing.T) { } } -func TestCreateThings(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestCreateClients(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() num := 3 - var items []things.Client + var items []clients.Client for i := 0; i < num; i++ { - client := things.Client{ + client := clients.Client{ ID: testsutil.GenerateUUID(t), Name: namesgen.Generate(), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), Secret: secret, }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, } items = append(items, client) } cases := []struct { desc string - client []things.Client + client []clients.Client domainID string token string contentType string @@ -277,7 +275,7 @@ func TestCreateThings(t *testing.T) { len int }{ { - desc: "create things with valid token", + desc: "create clients with valid token", client: items, domainID: domainID, token: validToken, @@ -288,7 +286,7 @@ func TestCreateThings(t *testing.T) { len: 3, }, { - desc: "create things with invalid token", + desc: "create clients with invalid token", client: items, token: inValidToken, contentType: contentType, @@ -298,7 +296,7 @@ func TestCreateThings(t *testing.T) { len: 0, }, { - desc: "create things with empty token", + desc: "create clients with empty token", client: items, token: "", contentType: contentType, @@ -307,8 +305,8 @@ func TestCreateThings(t *testing.T) { len: 0, }, { - desc: "create things with empty request", - client: []things.Client{}, + desc: "create clients with empty request", + client: []clients.Client{}, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -318,8 +316,8 @@ func TestCreateThings(t *testing.T) { len: 0, }, { - desc: "create things with invalid IDs", - client: []things.Client{ + desc: "create clients with invalid IDs", + client: []clients.Client{ { ID: inValid, }, @@ -338,8 +336,8 @@ func TestCreateThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "create things with invalid contentype", - client: []things.Client{ + desc: "create clients with invalid contentype", + client: []clients.Client{ { ID: testsutil.GenerateUUID(t), }, @@ -352,11 +350,11 @@ func TestCreateThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "create a thing that can't be marshalled", - client: []things.Client{ + desc: "create a client that can't be marshalled", + client: []clients.Client{ { ID: testsutil.GenerateUUID(t), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "user@example.com", Secret: "12345678", }, @@ -373,7 +371,7 @@ func TestCreateThings(t *testing.T) { err: errors.ErrMalformedEntity, }, { - desc: "create things with service error", + desc: "create clients with service error", client: items, contentType: contentType, domainID: domainID, @@ -390,7 +388,7 @@ func TestCreateThings(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/bulk", ts.URL, domainID), + url: fmt.Sprintf("%s/%s/clients/bulk", ts.URL, domainID), contentType: tc.contentType, token: tc.token, body: strings.NewReader(data), @@ -416,58 +414,58 @@ func TestCreateThings(t *testing.T) { } } -func TestListThings(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestListClients(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { - desc string - query string - domainID string - token string - listThingsResponse things.ClientsPage - status int - authnRes mgauthn.Session - authnErr error - err error + desc string + query string + domainID string + token string + listClientsResponse clients.ClientsPage + status int + authnRes mgauthn.Session + authnErr error + err error }{ { - desc: "list things as admin with valid token", + desc: "list clients as admin with valid token", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, status: http.StatusOK, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, err: nil, }, { - desc: "list things as non admin with valid token", + desc: "list clients as non admin with valid token", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, status: http.StatusOK, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, err: nil, }, { - desc: "list things with empty token", + desc: "list clients with empty token", domainID: domainID, token: "", status: http.StatusUnauthorized, err: apiutil.ErrBearerToken, }, { - desc: "list things with invalid token", + desc: "list clients with invalid token", domainID: domainID, token: inValidToken, status: http.StatusUnauthorized, @@ -475,23 +473,23 @@ func TestListThings(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "list things with offset", + desc: "list clients with offset", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Offset: 1, Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, query: "offset=1", status: http.StatusOK, err: nil, }, { - desc: "list things with invalid offset", + desc: "list clients with invalid offset", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -500,23 +498,23 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with limit", + desc: "list clients with limit", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Limit: 1, Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, query: "limit=1", status: http.StatusOK, err: nil, }, { - desc: "list things with invalid limit", + desc: "list clients with invalid limit", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -525,7 +523,7 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with limit greater than max", + desc: "list clients with limit greater than max", token: validToken, domainID: domainID, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -534,22 +532,22 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with name", + desc: "list clients with name", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, query: "name=clientname", status: http.StatusOK, err: nil, }, { - desc: "list things with invalid name", + desc: "list clients with invalid name", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -558,7 +556,7 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with duplicate name", + desc: "list clients with duplicate name", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -567,22 +565,22 @@ func TestListThings(t *testing.T) { err: apiutil.ErrInvalidQueryParams, }, { - desc: "list things with status", + desc: "list clients with status", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, query: "status=enabled", status: http.StatusOK, err: nil, }, { - desc: "list things with invalid status", + desc: "list clients with invalid status", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -591,7 +589,7 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with duplicate status", + desc: "list clients with duplicate status", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -600,22 +598,22 @@ func TestListThings(t *testing.T) { err: apiutil.ErrInvalidQueryParams, }, { - desc: "list things with tags", + desc: "list clients with tags", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, query: "tag=tag1,tag2", status: http.StatusOK, err: nil, }, { - desc: "list things with invalid tags", + desc: "list clients with invalid tags", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -624,7 +622,7 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with duplicate tags", + desc: "list clients with duplicate tags", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -633,22 +631,22 @@ func TestListThings(t *testing.T) { err: apiutil.ErrInvalidQueryParams, }, { - desc: "list things with metadata", + desc: "list clients with metadata", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", status: http.StatusOK, err: nil, }, { - desc: "list things with invalid metadata", + desc: "list clients with invalid metadata", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -657,7 +655,7 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with duplicate metadata", + desc: "list clients with duplicate metadata", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -666,22 +664,22 @@ func TestListThings(t *testing.T) { err: apiutil.ErrInvalidQueryParams, }, { - desc: "list things with permissions", + desc: "list clients with permissions", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, query: "permission=view", status: http.StatusOK, err: nil, }, { - desc: "list things with invalid permissions", + desc: "list clients with invalid permissions", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -690,7 +688,7 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with duplicate permissions", + desc: "list clients with duplicate permissions", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -699,22 +697,22 @@ func TestListThings(t *testing.T) { err: apiutil.ErrInvalidQueryParams, }, { - desc: "list things with list perms", + desc: "list clients with list perms", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ + listClientsResponse: clients.ClientsPage{ + Page: clients.Page{ Total: 1, }, - Clients: []things.Client{client}, + Clients: []clients.Client{client}, }, query: "list_perms=true", status: http.StatusOK, err: nil, }, { - desc: "list things with invalid list perms", + desc: "list clients with invalid list perms", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -723,7 +721,7 @@ func TestListThings(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "list things with duplicate list perms", + desc: "list clients with duplicate list perms", domainID: domainID, token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, @@ -738,13 +736,13 @@ func TestListThings(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodGet, - url: ts.URL + "/" + tc.domainID + "/things?" + tc.query, + url: ts.URL + "/" + tc.domainID + "/clients?" + tc.query, contentType: contentType, token: tc.token, } authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListClients", mock.Anything, tc.authnRes, "", mock.Anything).Return(tc.listThingsResponse, tc.err) + svcCall := svc.On("ListClients", mock.Anything, tc.authnRes, "", mock.Anything).Return(tc.listClientsResponse, tc.err) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) @@ -762,8 +760,8 @@ func TestListThings(t *testing.T) { } } -func TestViewThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestViewClient(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { @@ -820,12 +818,12 @@ func TestViewThing(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodGet, - url: fmt.Sprintf("%s/%s/things/%s", ts.URL, tc.domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/%s", ts.URL, tc.domainID, tc.id), token: tc.token, } authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("View", mock.Anything, tc.authnRes, tc.id).Return(things.Client{}, tc.err) + svcCall := svc.On("View", mock.Anything, tc.authnRes, tc.id).Return(clients.Client{}, tc.err) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var errRes respBody @@ -842,15 +840,15 @@ func TestViewThing(t *testing.T) { } } -func TestViewThingPerms(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestViewClientPerms(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { desc string domainID string token string - thingID string + clientID string response []string status int authnRes mgauthn.Session @@ -858,52 +856,52 @@ func TestViewThingPerms(t *testing.T) { err error }{ { - desc: "view thing permissions with valid token", + desc: "view client permissions with valid token", domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - thingID: client.ID, + clientID: client.ID, response: []string{"view", "delete", "membership"}, status: http.StatusOK, err: nil, }, { - desc: "view thing permissions with invalid token", + desc: "view client permissions with invalid token", domainID: domainID, token: inValidToken, - thingID: client.ID, + clientID: client.ID, response: []string{}, status: http.StatusUnauthorized, authnErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { - desc: "view thing permissions with empty token", + desc: "view client permissions with empty token", domainID: domainID, token: "", - thingID: client.ID, + clientID: client.ID, response: []string{}, status: http.StatusUnauthorized, err: apiutil.ErrBearerToken, }, { - desc: "view thing permissions with invalid id", + desc: "view client permissions with invalid id", domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - thingID: inValid, + clientID: inValid, response: []string{}, status: http.StatusForbidden, err: svcerr.ErrAuthorization, }, { - desc: "view thing permissions with empty id", + desc: "view client permissions with empty id", domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - thingID: "", + clientID: "", response: []string{}, status: http.StatusBadRequest, @@ -916,12 +914,12 @@ func TestViewThingPerms(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodGet, - url: fmt.Sprintf("%s/%s/things/%s/permissions", ts.URL, tc.domainID, tc.thingID), + url: fmt.Sprintf("%s/%s/clients/%s/permissions", ts.URL, tc.domainID, tc.clientID), token: tc.token, } authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ViewPerms", mock.Anything, tc.authnRes, tc.thingID).Return(tc.response, tc.err) + svcCall := svc.On("ViewPerms", mock.Anything, tc.authnRes, tc.clientID).Return(tc.response, tc.err) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var resBody respBody @@ -939,19 +937,19 @@ func TestViewThingPerms(t *testing.T) { } } -func TestUpdateThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestUpdateClient(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() newName := "newname" newTag := "newtag" - newMetadata := things.Metadata{"newkey": "newvalue"} + newMetadata := clients.Metadata{"newkey": "newvalue"} cases := []struct { desc string id string data string - clientResponse things.Client + clientResponse clients.Client domainID string token string contentType string @@ -961,14 +959,14 @@ func TestUpdateThing(t *testing.T) { err error }{ { - desc: "update thing with valid token", + desc: "update client with valid token", domainID: domainID, id: client.ID, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), token: validToken, contentType: contentType, - clientResponse: things.Client{ + clientResponse: clients.Client{ ID: client.ID, Name: newName, Tags: []string{newTag}, @@ -979,7 +977,7 @@ func TestUpdateThing(t *testing.T) { err: nil, }, { - desc: "update thing with invalid token", + desc: "update client with invalid token", id: client.ID, data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), domainID: domainID, @@ -990,7 +988,7 @@ func TestUpdateThing(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "update thing with empty token", + desc: "update client with empty token", id: client.ID, data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), domainID: domainID, @@ -1000,7 +998,7 @@ func TestUpdateThing(t *testing.T) { err: apiutil.ErrBearerToken, }, { - desc: "update thing with invalid contentype", + desc: "update client with invalid contentype", id: client.ID, data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), domainID: domainID, @@ -1012,7 +1010,7 @@ func TestUpdateThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "update thing with malformed data", + desc: "update client with malformed data", id: client.ID, data: fmt.Sprintf(`{"name":%s}`, "invalid"), domainID: domainID, @@ -1024,7 +1022,7 @@ func TestUpdateThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "update thing with empty id", + desc: "update client with empty id", id: " ", data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), domainID: domainID, @@ -1036,14 +1034,14 @@ func TestUpdateThing(t *testing.T) { err: apiutil.ErrMissingID, }, { - desc: "update thing with name that is too long", + desc: "update client with name that is too long", id: client.ID, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, strings.Repeat("a", api.MaxNameSize+1), newTag, toJSON(newMetadata)), domainID: domainID, token: validToken, contentType: contentType, - clientResponse: things.Client{}, + clientResponse: clients.Client{}, status: http.StatusBadRequest, err: apiutil.ErrNameSize, }, @@ -1054,7 +1052,7 @@ func TestUpdateThing(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPatch, - url: fmt.Sprintf("%s/%s/things/%s", ts.URL, tc.domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/%s", ts.URL, tc.domainID, tc.id), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.data), @@ -1083,8 +1081,8 @@ func TestUpdateThing(t *testing.T) { } } -func TestUpdateThingsTags(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestUpdateClientsTags(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() newTag := "newtag" @@ -1094,7 +1092,7 @@ func TestUpdateThingsTags(t *testing.T) { id string data string contentType string - clientResponse things.Client + clientResponse clients.Client domainID string token string status int @@ -1103,11 +1101,11 @@ func TestUpdateThingsTags(t *testing.T) { err error }{ { - desc: "update thing tags with valid token", + desc: "update client tags with valid token", id: client.ID, data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), contentType: contentType, - clientResponse: things.Client{ + clientResponse: clients.Client{ ID: client.ID, Tags: []string{newTag}, }, @@ -1119,7 +1117,7 @@ func TestUpdateThingsTags(t *testing.T) { err: nil, }, { - desc: "update thing tags with empty token", + desc: "update client tags with empty token", id: client.ID, data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), contentType: contentType, @@ -1129,7 +1127,7 @@ func TestUpdateThingsTags(t *testing.T) { err: apiutil.ErrBearerToken, }, { - desc: "update thing tags with invalid token", + desc: "update client tags with invalid token", id: client.ID, data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), contentType: contentType, @@ -1140,7 +1138,7 @@ func TestUpdateThingsTags(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "update thing tags with invalid id", + desc: "update client tags with invalid id", id: client.ID, data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), contentType: contentType, @@ -1152,7 +1150,7 @@ func TestUpdateThingsTags(t *testing.T) { err: svcerr.ErrAuthorization, }, { - desc: "update thing tags with invalid contentype", + desc: "update client tags with invalid contentype", id: client.ID, data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), contentType: "application/xml", @@ -1163,7 +1161,7 @@ func TestUpdateThingsTags(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "update things tags with empty id", + desc: "update clients tags with empty id", id: "", data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), contentType: contentType, @@ -1175,7 +1173,7 @@ func TestUpdateThingsTags(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "update things with malfomed data", + desc: "update clients with malfomed data", id: client.ID, data: fmt.Sprintf(`{"tags":[%s]}`, newTag), contentType: contentType, @@ -1193,7 +1191,7 @@ func TestUpdateThingsTags(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPatch, - url: fmt.Sprintf("%s/%s/things/%s/tags", ts.URL, tc.domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/%s/tags", ts.URL, tc.domainID, tc.id), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.data), @@ -1218,13 +1216,13 @@ func TestUpdateThingsTags(t *testing.T) { } func TestUpdateClientSecret(t *testing.T) { - ts, svc, _, authn := newThingsServer() + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { desc string data string - client things.Client + client clients.Client contentType string domainID string token string @@ -1234,11 +1232,11 @@ func TestUpdateClientSecret(t *testing.T) { err error }{ { - desc: "update thing secret with valid token", + desc: "update client secret with valid token", data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), - client: things.Client{ + client: clients.Client{ ID: client.ID, - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "clientname", Secret: "strongersecret", }, @@ -1251,11 +1249,11 @@ func TestUpdateClientSecret(t *testing.T) { err: nil, }, { - desc: "update thing secret with empty token", + desc: "update client secret with empty token", data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), - client: things.Client{ + client: clients.Client{ ID: client.ID, - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "clientname", Secret: "strongersecret", }, @@ -1267,11 +1265,11 @@ func TestUpdateClientSecret(t *testing.T) { err: apiutil.ErrBearerToken, }, { - desc: "update thing secret with invalid token", + desc: "update client secret with invalid token", data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), - client: things.Client{ + client: clients.Client{ ID: client.ID, - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "clientname", Secret: "strongersecret", }, @@ -1284,11 +1282,11 @@ func TestUpdateClientSecret(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "update thing secret with empty id", + desc: "update client secret with empty id", data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), - client: things.Client{ + client: clients.Client{ ID: "", - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "clientname", Secret: "strongersecret", }, @@ -1301,11 +1299,11 @@ func TestUpdateClientSecret(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "update thing secret with empty secret", + desc: "update client secret with empty secret", data: fmt.Sprintf(`{"secret": "%s"}`, ""), - client: things.Client{ + client: clients.Client{ ID: client.ID, - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "clientname", Secret: "", }, @@ -1319,11 +1317,11 @@ func TestUpdateClientSecret(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "update thing secret with invalid contentype", + desc: "update client secret with invalid contentype", data: fmt.Sprintf(`{"secret": "%s"}`, ""), - client: things.Client{ + client: clients.Client{ ID: client.ID, - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "clientname", Secret: "", }, @@ -1337,11 +1335,11 @@ func TestUpdateClientSecret(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "update thing secret with malformed data", + desc: "update client secret with malformed data", data: fmt.Sprintf(`{"secret": %s}`, "invalid"), - client: things.Client{ + client: clients.Client{ ID: client.ID, - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: "clientname", Secret: "", }, @@ -1361,7 +1359,7 @@ func TestUpdateClientSecret(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPatch, - url: fmt.Sprintf("%s/%s/things/%s/secret", ts.URL, tc.domainID, tc.client.ID), + url: fmt.Sprintf("%s/%s/clients/%s/secret", ts.URL, tc.domainID, tc.client.ID), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.data), @@ -1385,14 +1383,14 @@ func TestUpdateClientSecret(t *testing.T) { } } -func TestEnableThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestEnableClient(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { desc string - client things.Client - response things.Client + client clients.Client + response clients.Client domainID string token string status int @@ -1401,11 +1399,11 @@ func TestEnableThing(t *testing.T) { err error }{ { - desc: "enable thing with valid token", + desc: "enable client with valid token", client: client, - response: things.Client{ + response: clients.Client{ ID: client.ID, - Status: things.EnabledStatus, + Status: clients.EnabledStatus, }, domainID: domainID, token: validToken, @@ -1415,7 +1413,7 @@ func TestEnableThing(t *testing.T) { err: nil, }, { - desc: "enable thing with invalid token", + desc: "enable client with invalid token", client: client, domainID: domainID, token: inValidToken, @@ -1424,8 +1422,8 @@ func TestEnableThing(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "enable thing with empty id", - client: things.Client{ + desc: "enable client with empty id", + client: clients.Client{ ID: "", }, domainID: domainID, @@ -1443,7 +1441,7 @@ func TestEnableThing(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/%s/enable", ts.URL, tc.domainID, tc.client.ID), + url: fmt.Sprintf("%s/%s/clients/%s/enable", ts.URL, tc.domainID, tc.client.ID), contentType: contentType, token: tc.token, body: strings.NewReader(data), @@ -1470,14 +1468,14 @@ func TestEnableThing(t *testing.T) { } } -func TestDisableThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestDisableClient(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { desc string - client things.Client - response things.Client + client clients.Client + response clients.Client domainID string token string status int @@ -1486,11 +1484,11 @@ func TestDisableThing(t *testing.T) { err error }{ { - desc: "disable thing with valid token", + desc: "disable client with valid token", client: client, - response: things.Client{ + response: clients.Client{ ID: client.ID, - Status: things.DisabledStatus, + Status: clients.DisabledStatus, }, domainID: domainID, token: validToken, @@ -1500,7 +1498,7 @@ func TestDisableThing(t *testing.T) { err: nil, }, { - desc: "disable thing with invalid token", + desc: "disable client with invalid token", client: client, domainID: domainID, token: inValidToken, @@ -1509,8 +1507,8 @@ func TestDisableThing(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "disable thing with empty id", - client: things.Client{ + desc: "disable client with empty id", + client: clients.Client{ ID: "", }, domainID: domainID, @@ -1528,7 +1526,7 @@ func TestDisableThing(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/%s/disable", ts.URL, tc.domainID, tc.client.ID), + url: fmt.Sprintf("%s/%s/clients/%s/disable", ts.URL, tc.domainID, tc.client.ID), contentType: contentType, token: tc.token, body: strings.NewReader(data), @@ -1555,14 +1553,14 @@ func TestDisableThing(t *testing.T) { } } -func TestShareThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestShareClient(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { desc string data string - thingID string + clientID string domainID string token string contentType string @@ -1572,9 +1570,9 @@ func TestShareThing(t *testing.T) { err error }{ { - desc: "share thing with valid token", + desc: "share client with valid token", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1584,9 +1582,9 @@ func TestShareThing(t *testing.T) { err: nil, }, { - desc: "share thing with invalid token", + desc: "share client with invalid token", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: inValidToken, contentType: contentType, @@ -1595,9 +1593,9 @@ func TestShareThing(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "share thing with empty token", + desc: "share client with empty token", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: "", contentType: contentType, @@ -1605,9 +1603,9 @@ func TestShareThing(t *testing.T) { err: apiutil.ErrBearerToken, }, { - desc: "share thing with empty id", + desc: "share client with empty id", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: " ", + clientID: " ", domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1617,9 +1615,9 @@ func TestShareThing(t *testing.T) { err: apiutil.ErrMissingID, }, { - desc: "share thing with missing relation", + desc: "share client with missing relation", data: fmt.Sprintf(`{"relation": "%s", user_ids" : ["%s", "%s"]}`, " ", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1629,9 +1627,9 @@ func TestShareThing(t *testing.T) { err: apiutil.ErrMissingRelation, }, { - desc: "share thing with malformed data", + desc: "share client with malformed data", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [%s, "%s"]}`, "editor", "invalid", validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1641,9 +1639,9 @@ func TestShareThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "share thing with empty thing id", + desc: "share client with empty client id", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: "", + clientID: "", domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1653,9 +1651,9 @@ func TestShareThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "share thing with empty relation", + desc: "share client with empty relation", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, " ", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1665,9 +1663,9 @@ func TestShareThing(t *testing.T) { err: apiutil.ErrMissingRelation, }, { - desc: "share thing with empty user ids", + desc: "share client with empty user ids", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [" ", " "]}`, "editor"), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1677,9 +1675,9 @@ func TestShareThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "share thing with invalid content type", + desc: "share client with invalid content type", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1695,14 +1693,14 @@ func TestShareThing(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/%s/share", ts.URL, tc.domainID, tc.thingID), + url: fmt.Sprintf("%s/%s/clients/%s/share", ts.URL, tc.domainID, tc.clientID), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.data), } authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("Share", mock.Anything, tc.authnRes, tc.thingID, mock.Anything, mock.Anything, mock.Anything).Return(tc.err) + svcCall := svc.On("Share", mock.Anything, tc.authnRes, tc.clientID, mock.Anything, mock.Anything, mock.Anything).Return(tc.err) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) @@ -1712,14 +1710,14 @@ func TestShareThing(t *testing.T) { } } -func TestUnShareThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestUnShareClient(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { desc string data string - thingID string + clientID string domainID string token string contentType string @@ -1729,9 +1727,9 @@ func TestUnShareThing(t *testing.T) { err error }{ { - desc: "unshare thing with valid token", + desc: "unshare client with valid token", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1741,9 +1739,9 @@ func TestUnShareThing(t *testing.T) { err: nil, }, { - desc: "unshare thing with invalid token", + desc: "unshare client with invalid token", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: inValidToken, contentType: contentType, @@ -1752,9 +1750,9 @@ func TestUnShareThing(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "unshare thing with empty token", + desc: "unshare client with empty token", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: "", contentType: contentType, @@ -1762,9 +1760,9 @@ func TestUnShareThing(t *testing.T) { err: apiutil.ErrBearerToken, }, { - desc: "unshare thing with empty id", + desc: "unshare client with empty id", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: " ", + clientID: " ", domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1774,9 +1772,9 @@ func TestUnShareThing(t *testing.T) { err: apiutil.ErrMissingID, }, { - desc: "unshare thing with missing relation", + desc: "unshare client with missing relation", data: fmt.Sprintf(`{"relation": "%s", user_ids" : ["%s", "%s"]}`, " ", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1786,9 +1784,9 @@ func TestUnShareThing(t *testing.T) { err: apiutil.ErrMissingRelation, }, { - desc: "unshare thing with malformed data", + desc: "unshare client with malformed data", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [%s, "%s"]}`, "editor", "invalid", validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1798,9 +1796,9 @@ func TestUnShareThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "unshare thing with empty thing id", + desc: "unshare client with empty client id", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: "", + clientID: "", domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1810,9 +1808,9 @@ func TestUnShareThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "unshare thing with empty relation", + desc: "unshare client with empty relation", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, " ", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1822,9 +1820,9 @@ func TestUnShareThing(t *testing.T) { err: apiutil.ErrMissingRelation, }, { - desc: "unshare thing with empty user ids", + desc: "unshare client with empty user ids", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [" ", " "]}`, "editor"), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1834,9 +1832,9 @@ func TestUnShareThing(t *testing.T) { err: apiutil.ErrValidation, }, { - desc: "unshare thing with invalid content type", + desc: "unshare client with invalid content type", data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, + clientID: client.ID, domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -1852,14 +1850,14 @@ func TestUnShareThing(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/%s/unshare", ts.URL, tc.domainID, tc.thingID), + url: fmt.Sprintf("%s/%s/clients/%s/unshare", ts.URL, tc.domainID, tc.clientID), contentType: tc.contentType, token: tc.token, body: strings.NewReader(tc.data), } authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("Unshare", mock.Anything, tc.authnRes, tc.thingID, mock.Anything, mock.Anything, mock.Anything).Return(tc.err) + svcCall := svc.On("Unshare", mock.Anything, tc.authnRes, tc.clientID, mock.Anything, mock.Anything, mock.Anything).Return(tc.err) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) @@ -1869,8 +1867,8 @@ func TestUnShareThing(t *testing.T) { } } -func TestDeleteThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() +func TestDeleteClient(t *testing.T) { + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { @@ -1884,7 +1882,7 @@ func TestDeleteThing(t *testing.T) { err error }{ { - desc: "delete thing with valid token", + desc: "delete client with valid token", id: client.ID, domainID: domainID, token: validToken, @@ -1894,7 +1892,7 @@ func TestDeleteThing(t *testing.T) { err: nil, }, { - desc: "delete thing with invalid token", + desc: "delete client with invalid token", id: client.ID, domainID: domainID, token: inValidToken, @@ -1904,7 +1902,7 @@ func TestDeleteThing(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "delete thing with empty token", + desc: "delete client with empty token", id: client.ID, domainID: domainID, token: "", @@ -1912,7 +1910,7 @@ func TestDeleteThing(t *testing.T) { err: apiutil.ErrBearerToken, }, { - desc: "delete thing with empty id", + desc: "delete client with empty id", id: " ", domainID: domainID, token: validToken, @@ -1928,7 +1926,7 @@ func TestDeleteThing(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodDelete, - url: fmt.Sprintf("%s/%s/things/%s", ts.URL, tc.domainID, tc.id), + url: fmt.Sprintf("%s/%s/clients/%s", ts.URL, tc.domainID, tc.id), token: tc.token, } @@ -1944,7 +1942,7 @@ func TestDeleteThing(t *testing.T) { } func TestListMembers(t *testing.T) { - ts, svc, _, authn := newThingsServer() + ts, svc, authn := newClientsServer() defer ts.Close() cases := []struct { @@ -1953,7 +1951,7 @@ func TestListMembers(t *testing.T) { groupID string domainID string token string - listMembersResponse things.MembersPage + listMembersResponse clients.MembersPage status int authnRes mgauthn.Session authnErr error @@ -1965,11 +1963,11 @@ func TestListMembers(t *testing.T) { token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, status: http.StatusOK, @@ -1999,12 +1997,12 @@ func TestListMembers(t *testing.T) { authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, query: "offset=1", groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Offset: 1, Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, status: http.StatusOK, @@ -2028,12 +2026,12 @@ func TestListMembers(t *testing.T) { authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, query: "limit=1", groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Limit: 1, Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, status: http.StatusOK, @@ -2068,11 +2066,11 @@ func TestListMembers(t *testing.T) { authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, query: fmt.Sprintf("channel_id=%s", validID), groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, status: http.StatusOK, @@ -2107,11 +2105,11 @@ func TestListMembers(t *testing.T) { authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, query: "connected=true", groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, status: http.StatusOK, @@ -2151,12 +2149,12 @@ func TestListMembers(t *testing.T) { }, { desc: "list members with status", - query: fmt.Sprintf("status=%s", things.EnabledStatus), - listMembersResponse: things.MembersPage{ - Page: things.Page{ + query: fmt.Sprintf("status=%s", clients.EnabledStatus), + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, domainID: domainID, token: validToken, @@ -2179,7 +2177,7 @@ func TestListMembers(t *testing.T) { }, { desc: "list members with duplicate status", - query: fmt.Sprintf("status=%s&status=%s", things.EnabledStatus, things.DisabledStatus), + query: fmt.Sprintf("status=%s&status=%s", clients.EnabledStatus, clients.DisabledStatus), domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, @@ -2193,11 +2191,11 @@ func TestListMembers(t *testing.T) { domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, groupID: client.ID, query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", @@ -2230,11 +2228,11 @@ func TestListMembers(t *testing.T) { { desc: "list members with permission", query: fmt.Sprintf("permission=%s", "view"), - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, domainID: domainID, token: validToken, @@ -2261,11 +2259,11 @@ func TestListMembers(t *testing.T) { domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, groupID: client.ID, status: http.StatusOK, @@ -2296,18 +2294,18 @@ func TestListMembers(t *testing.T) { }, { desc: "list members with all query params", - query: fmt.Sprintf("offset=1&limit=1&channel_id=%s&connected=true&status=%s&metadata=%s&permission=%s&list_perms=true", validID, things.EnabledStatus, "%7B%22domain%22%3A%20%22example.com%22%7D", "view"), + query: fmt.Sprintf("offset=1&limit=1&channel_id=%s&connected=true&status=%s&metadata=%s&permission=%s&list_perms=true", validID, clients.EnabledStatus, "%7B%22domain%22%3A%20%22example.com%22%7D", "view"), domainID: domainID, token: validToken, authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ + listMembersResponse: clients.MembersPage{ + Page: clients.Page{ Offset: 1, Limit: 1, Total: 1, }, - Members: []things.Client{client}, + Members: []clients.Client{client}, }, status: http.StatusOK, @@ -2320,7 +2318,7 @@ func TestListMembers(t *testing.T) { req := testRequest{ client: ts.Client(), method: http.MethodGet, - url: ts.URL + fmt.Sprintf("/%s/channels/%s/things?", tc.domainID, tc.groupID) + tc.query, + url: ts.URL + fmt.Sprintf("/%s/channels/%s/clients?", tc.domainID, tc.groupID) + tc.query, contentType: contentType, token: tc.token, } @@ -2344,1007 +2342,14 @@ func TestListMembers(t *testing.T) { } } -func TestAssignUsers(t *testing.T) { - ts, _, gsvc, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - groupID string - reqBody interface{} - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "assign users to a group successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusCreated, - - err: nil, - }, - { - desc: "assign users to a group with invalid token", - domainID: domainID, - token: inValidToken, - authnRes: mgauthn.Session{}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "assign users to a group with empty token", - domainID: domainID, - token: "", - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "assign users to a group with empty group id", - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: "", - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "assign users to a group with empty relation", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "assign users to a group with empty user ids", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "assign users to a group with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: map[string]interface{}{ - "relation": make(chan int), - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: nil, - }, - { - desc: "assign users to a group with invalid content type", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/channels/%s/users/assign", ts.URL, tc.domainID, tc.groupID), - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(data), - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Assign", mock.Anything, tc.authnRes, tc.groupID, mock.Anything, "users", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUnassignUsers(t *testing.T) { - ts, _, gsvc, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - groupID string - reqBody interface{} - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "unassign users from a group successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusNoContent, - - err: nil, - }, - { - desc: "unassign users from a group with invalid token", - domainID: domainID, - token: inValidToken, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "unassign users from a group with empty token", - domainID: domainID, - token: "", - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "unassign users from a group with empty group id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: "", - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unassign users from a group with empty relation", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unassign users from a group with empty user ids", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unassign users from a group with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: map[string]interface{}{ - "relation": make(chan int), - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: nil, - }, - { - desc: "unassign users from a group with invalid content type", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/channels/%s/users/unassign", ts.URL, tc.domainID, tc.groupID), - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(data), - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Unassign", mock.Anything, tc.authnRes, tc.groupID, mock.Anything, "users", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestAssignGroupsToChannel(t *testing.T) { - ts, _, gsvc, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - groupID string - reqBody interface{} - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "assign groups to a channel successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusCreated, - - err: nil, - }, - { - desc: "assign groups to a channel with invalid token", - domainID: domainID, - token: inValidToken, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "assign groups to a channel with empty token", - domainID: domainID, - token: "", - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "assign groups to a channel with empty group id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: "", - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "assign groups to a channel with empty group ids", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "assign groups to a channel with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: map[string]interface{}{ - "group_ids": make(chan int), - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "assign groups to a channel with invalid content type", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/channels/%s/groups/assign", ts.URL, tc.domainID, tc.groupID), - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(data), - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Assign", mock.Anything, tc.authnRes, tc.groupID, mock.Anything, "channels", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUnassignGroupsFromChannel(t *testing.T) { - ts, _, gsvc, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - groupID string - reqBody interface{} - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "unassign groups from a channel successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusNoContent, - - err: nil, - }, - { - desc: "unassign groups from a channel with invalid token", - domainID: domainID, - token: inValidToken, - authnRes: mgauthn.Session{}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "unassign groups from a channel with empty token", - domainID: domainID, - token: "", - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "unassign groups from a channel with empty group id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: "", - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unassign groups from a channel with empty group ids", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{}, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unassign groups from a channel with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: map[string]interface{}{ - "group_ids": make(chan int), - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unassign groups from a channel with invalid content type", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - - err: apiutil.ErrValidation, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/channels/%s/groups/unassign", ts.URL, tc.domainID, tc.groupID), - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(data), - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Unassign", mock.Anything, tc.authnRes, tc.groupID, mock.Anything, "channels", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestConnectThingToChannel(t *testing.T) { - ts, _, gsvc, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - channelID string - thingID string - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "connect thing to a channel successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - channelID: validID, - thingID: validID, - contentType: contentType, - status: http.StatusCreated, - err: nil, - }, - { - desc: "connect thing to a channel with invalid token", - domainID: domainID, - token: inValidToken, - channelID: validID, - thingID: validID, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "connect thing to a channel with empty channel id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID}, - channelID: "", - thingID: validID, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "connect thing to a channel with empty thing id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - channelID: validID, - thingID: "", - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/channels/%s/things/%s/connect", ts.URL, tc.domainID, tc.channelID, tc.thingID), - token: tc.token, - contentType: tc.contentType, - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Assign", mock.Anything, tc.authnRes, tc.channelID, "group", "things", []string{tc.thingID}).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDisconnectThingFromChannel(t *testing.T) { - ts, _, gsvc, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - channelID string - thingID string - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "disconnect thing from a channel successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - channelID: validID, - thingID: validID, - contentType: contentType, - status: http.StatusNoContent, - - err: nil, - }, - { - desc: "disconnect thing from a channel with invalid token", - domainID: domainID, - token: inValidToken, - channelID: validID, - thingID: validID, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "disconnect thing from a channel with empty channel id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - channelID: "", - thingID: validID, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "disconnect thing from a channel with empty thing id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - channelID: validID, - thingID: "", - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/channels/%s/things/%s/disconnect", ts.URL, tc.domainID, tc.channelID, tc.thingID), - token: tc.token, - contentType: tc.contentType, - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Unassign", mock.Anything, tc.authnRes, tc.channelID, "group", "things", []string{tc.thingID}).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestConnect(t *testing.T) { - ts, _, gsvc, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - reqBody interface{} - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "connect thing to a channel successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: groupReqBody{ - ChannelID: validID, - ThingID: validID, - }, - contentType: contentType, - status: http.StatusCreated, - - err: nil, - }, - { - desc: "connect thing to a channel with invalid token", - domainID: domainID, - token: inValidToken, - reqBody: groupReqBody{ - ChannelID: validID, - ThingID: validID, - }, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "connect thing to a channel with empty channel id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: groupReqBody{ - ChannelID: "", - ThingID: validID, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "connect thing to a channel with empty thing id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: groupReqBody{ - ChannelID: validID, - ThingID: "", - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "connect thing to a channel with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: map[string]interface{}{ - "channel_id": make(chan int), - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "connect thing to a channel with invalid content type", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: groupReqBody{ - ChannelID: validID, - ThingID: validID, - }, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/connect", ts.URL, tc.domainID), - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(data), - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Assign", mock.Anything, tc.authnRes, mock.Anything, "group", "things", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDisconnect(t *testing.T) { - ts, _, gsvc, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - reqBody interface{} - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "Disconnect thing from a channel successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: groupReqBody{ - ChannelID: validID, - ThingID: validID, - }, - contentType: contentType, - status: http.StatusNoContent, - - err: nil, - }, - { - desc: "Disconnect thing from a channel with invalid token", - domainID: domainID, - token: inValidToken, - authnRes: mgauthn.Session{}, - reqBody: groupReqBody{ - ChannelID: validID, - ThingID: validID, - }, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "Disconnect thing from a channel with empty channel id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: groupReqBody{ - ChannelID: "", - ThingID: validID, - }, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "Disconnect thing from a channel with empty thing id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: groupReqBody{ - ChannelID: validID, - ThingID: "", - }, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "Disconnect thing from a channel with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: map[string]interface{}{ - "channel_id": make(chan int), - }, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "Disconnect thing from a channel with invalid content type", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - reqBody: groupReqBody{ - ChannelID: validID, - ThingID: validID, - }, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/disconnect", ts.URL, tc.domainID), - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(data), - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Unassign", mock.Anything, tc.authnRes, mock.Anything, "group", "things", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authCall.Unset() - }) - } -} - type respBody struct { - Err string `json:"error"` - Message string `json:"message"` - Total int `json:"total"` - Permissions []string `json:"permissions"` - ID string `json:"id"` - Tags []string `json:"tags"` - Status things.Status `json:"status"` + Err string `json:"error"` + Message string `json:"message"` + Total int `json:"total"` + Permissions []string `json:"permissions"` + ID string `json:"id"` + Tags []string `json:"tags"` + Status clients.Status `json:"status"` } type groupReqBody struct { @@ -3352,5 +2357,5 @@ type groupReqBody struct { UserIDs []string `json:"user_ids"` GroupIDs []string `json:"group_ids"` ChannelID string `json:"channel_id"` - ThingID string `json:"thing_id"` + ClientID string `json:"client_id"` } diff --git a/things/api/http/requests.go b/clients/api/http/requests.go similarity index 64% rename from things/api/http/requests.go rename to clients/api/http/requests.go index 8c644cd953..d5bab68881 100644 --- a/things/api/http/requests.go +++ b/clients/api/http/requests.go @@ -4,41 +4,41 @@ package http import ( + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/things" ) type createClientReq struct { - thing things.Client + client clients.Client } func (req createClientReq) validate() error { - if len(req.thing.Name) > api.MaxNameSize { + if len(req.client.Name) > api.MaxNameSize { return apiutil.ErrNameSize } - if req.thing.ID != "" { - return api.ValidateUUID(req.thing.ID) + if req.client.ID != "" { + return api.ValidateUUID(req.client.ID) } return nil } type createClientsReq struct { - Things []things.Client + Clients []clients.Client } func (req createClientsReq) validate() error { - if len(req.Things) == 0 { + if len(req.Clients) == 0 { return apiutil.ErrEmptyList } - for _, thing := range req.Things { - if thing.ID != "" { - if err := api.ValidateUUID(thing.ID); err != nil { + for _, c := range req.Clients { + if c.ID != "" { + if err := api.ValidateUUID(c.ID); err != nil { return err } } - if len(thing.Name) > api.MaxNameSize { + if len(c.Name) > api.MaxNameSize { return apiutil.ErrNameSize } } @@ -71,7 +71,7 @@ func (req viewClientPermsReq) validate() error { } type listClientsReq struct { - status things.Status + status clients.Status offset uint64 limit uint64 name string @@ -80,7 +80,7 @@ type listClientsReq struct { visibility string userID string listPerms bool - metadata things.Metadata + metadata clients.Metadata id string } @@ -102,7 +102,7 @@ func (req listClientsReq) validate() error { } type listMembersReq struct { - things.Page + clients.Page groupID string } @@ -175,70 +175,29 @@ func (req changeClientStatusReq) validate() error { return nil } -type assignUsersRequest struct { - groupID string - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` +type setClientParentGroupReq struct { + id string + ParentGroupID string `json:"parent_group_id"` } -func (req assignUsersRequest) validate() error { - if req.Relation == "" { - return apiutil.ErrMissingRelation - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.UserIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type assignUserGroupsRequest struct { - groupID string - UserGroupIDs []string `json:"group_ids"` -} - -func (req assignUserGroupsRequest) validate() error { - if req.groupID == "" { +func (req setClientParentGroupReq) validate() error { + if req.id == "" { return apiutil.ErrMissingID } - - if len(req.UserGroupIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type connectChannelThingRequest struct { - ThingID string `json:"thing_id,omitempty"` - ChannelID string `json:"channel_id,omitempty"` -} - -func (req *connectChannelThingRequest) validate() error { - if req.ThingID == "" || req.ChannelID == "" { - return apiutil.ErrMissingID + if req.ParentGroupID == "" { + return apiutil.ErrMissingParentGroupID } return nil } -type thingShareRequest struct { - thingID string - Relation string `json:"relation,omitempty"` - UserIDs []string `json:"user_ids,omitempty"` +type removeClientParentGroupReq struct { + id string } -func (req *thingShareRequest) validate() error { - if req.thingID == "" { +func (req removeClientParentGroupReq) validate() error { + if req.id == "" { return apiutil.ErrMissingID } - if req.Relation == "" || len(req.UserIDs) == 0 { - return apiutil.ErrMalformedPolicy - } return nil } diff --git a/things/api/http/requests_test.go b/clients/api/http/requests_test.go similarity index 65% rename from things/api/http/requests_test.go rename to clients/api/http/requests_test.go index a4529a9b5c..ab7f15a4ff 100644 --- a/things/api/http/requests_test.go +++ b/clients/api/http/requests_test.go @@ -7,10 +7,10 @@ import ( "strings" "testing" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/things" "github.com/stretchr/testify/assert" ) @@ -22,7 +22,7 @@ const ( var validID = testsutil.GenerateUUID(&testing.T{}) -func TestCreateThingReqValidate(t *testing.T) { +func TestCreateClientReqValidate(t *testing.T) { cases := []struct { desc string req createClientReq @@ -31,7 +31,7 @@ func TestCreateThingReqValidate(t *testing.T) { { desc: "valid request", req: createClientReq{ - thing: things.Client{ + client: clients.Client{ ID: validID, Name: valid, }, @@ -41,7 +41,7 @@ func TestCreateThingReqValidate(t *testing.T) { { desc: "name too long", req: createClientReq{ - thing: things.Client{ + client: clients.Client{ ID: validID, Name: strings.Repeat("a", api.MaxNameSize+1), }, @@ -51,7 +51,7 @@ func TestCreateThingReqValidate(t *testing.T) { { desc: "invalid id", req: createClientReq{ - thing: things.Client{ + client: clients.Client{ ID: invalid, Name: valid, }, @@ -67,7 +67,7 @@ func TestCreateThingReqValidate(t *testing.T) { } } -func TestCreateThingsReqValidate(t *testing.T) { +func TestCreateClientsReqValidate(t *testing.T) { cases := []struct { desc string req createClientsReq @@ -76,7 +76,7 @@ func TestCreateThingsReqValidate(t *testing.T) { { desc: "valid request", req: createClientsReq{ - Things: []things.Client{ + Clients: []clients.Client{ { ID: validID, Name: valid, @@ -88,14 +88,14 @@ func TestCreateThingsReqValidate(t *testing.T) { { desc: "empty list", req: createClientsReq{ - Things: []things.Client{}, + Clients: []clients.Client{}, }, err: apiutil.ErrEmptyList, }, { desc: "name too long", req: createClientsReq{ - Things: []things.Client{ + Clients: []clients.Client{ { ID: validID, Name: strings.Repeat("a", api.MaxNameSize+1), @@ -107,7 +107,7 @@ func TestCreateThingsReqValidate(t *testing.T) { { desc: "invalid id", req: createClientsReq{ - Things: []things.Client{ + Clients: []clients.Client{ { ID: invalid, Name: valid, @@ -402,186 +402,6 @@ func TestChangeClientStatusReqValidate(t *testing.T) { } } -func TestAssignUsersRequestValidate(t *testing.T) { - cases := []struct { - desc string - req assignUsersRequest - err error - }{ - { - desc: "valid request", - req: assignUsersRequest{ - groupID: validID, - UserIDs: []string{validID}, - Relation: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: assignUsersRequest{ - groupID: "", - UserIDs: []string{validID}, - Relation: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty users", - req: assignUsersRequest{ - groupID: validID, - UserIDs: []string{}, - Relation: valid, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty relation", - req: assignUsersRequest{ - groupID: validID, - UserIDs: []string{validID}, - Relation: "", - }, - err: apiutil.ErrMissingRelation, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - err := tc.req.validate() - assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err) - }) - } -} - -func TestAssignUserGroupsRequestValidate(t *testing.T) { - cases := []struct { - desc string - req assignUserGroupsRequest - err error - }{ - { - desc: "valid request", - req: assignUserGroupsRequest{ - groupID: validID, - UserGroupIDs: []string{validID}, - }, - err: nil, - }, - { - desc: "empty group id", - req: assignUserGroupsRequest{ - groupID: "", - UserGroupIDs: []string{validID}, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty user group ids", - req: assignUserGroupsRequest{ - groupID: validID, - UserGroupIDs: []string{}, - }, - err: apiutil.ErrEmptyList, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - err := tc.req.validate() - assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err) - }) - } -} - -func TestConnectChannelThingRequestValidate(t *testing.T) { - cases := []struct { - desc string - req connectChannelThingRequest - err error - }{ - { - desc: "valid request", - req: connectChannelThingRequest{ - ChannelID: validID, - ThingID: validID, - }, - err: nil, - }, - { - desc: "empty channel id", - req: connectChannelThingRequest{ - ChannelID: "", - ThingID: validID, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty thing id", - req: connectChannelThingRequest{ - ChannelID: validID, - ThingID: "", - }, - err: apiutil.ErrMissingID, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - err := tc.req.validate() - assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err) - }) - } -} - -func TestThingShareRequestValidate(t *testing.T) { - cases := []struct { - desc string - req thingShareRequest - err error - }{ - { - desc: "valid request", - req: thingShareRequest{ - thingID: validID, - UserIDs: []string{validID}, - Relation: valid, - }, - err: nil, - }, - { - desc: "empty thing id", - req: thingShareRequest{ - thingID: "", - UserIDs: []string{validID}, - Relation: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty user ids", - req: thingShareRequest{ - thingID: validID, - UserIDs: []string{}, - Relation: valid, - }, - err: apiutil.ErrMalformedPolicy, - }, - { - desc: "empty relation", - req: thingShareRequest{ - thingID: validID, - UserIDs: []string{validID}, - Relation: "", - }, - err: apiutil.ErrMalformedPolicy, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - err := tc.req.validate() - assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err) - }) - } -} - func TestDeleteClientReqValidate(t *testing.T) { cases := []struct { desc string diff --git a/clients/api/http/responses.go b/clients/api/http/responses.go new file mode 100644 index 0000000000..d3a51d44fd --- /dev/null +++ b/clients/api/http/responses.go @@ -0,0 +1,177 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "fmt" + "net/http" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/clients" +) + +var ( + _ magistrala.Response = (*createClientRes)(nil) + _ magistrala.Response = (*viewClientRes)(nil) + _ magistrala.Response = (*viewClientPermsRes)(nil) + _ magistrala.Response = (*clientsPageRes)(nil) + _ magistrala.Response = (*changeClientStatusRes)(nil) + _ magistrala.Response = (*deleteClientRes)(nil) +) + +type clientsPageMetaRes struct { + Limit uint64 `json:"limit,omitempty"` + Offset uint64 `json:"offset"` + Total uint64 `json:"total"` +} + +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 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 viewClientPermsRes struct { + Permissions []string `json:"permissions"` +} + +func (res viewClientPermsRes) Code() int { + return http.StatusOK +} + +func (res viewClientPermsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res viewClientPermsRes) Empty() bool { + return false +} + +type clientsPageRes struct { + clientsPageMetaRes + 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 changeClientStatusRes struct { + clients.Client +} + +func (res changeClientStatusRes) Code() int { + return http.StatusOK +} + +func (res changeClientStatusRes) Headers() map[string]string { + return map[string]string{} +} + +func (res changeClientStatusRes) Empty() bool { + return false +} + +type setParentGroupRes struct{} + +func (res setParentGroupRes) Code() int { + return http.StatusAccepted +} + +func (res setParentGroupRes) Headers() map[string]string { + return map[string]string{} +} + +func (res setParentGroupRes) Empty() bool { + return true +} + +type removeParentGroupRes struct{} + +func (res removeParentGroupRes) Code() int { + return http.StatusNoContent +} + +func (res removeParentGroupRes) Headers() map[string]string { + return map[string]string{} +} + +func (res removeParentGroupRes) Empty() bool { + return true +} + +type deleteClientRes struct{} + +func (res deleteClientRes) Code() int { + return http.StatusNoContent +} + +func (res deleteClientRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteClientRes) Empty() bool { + return true +} diff --git a/clients/api/http/transport.go b/clients/api/http/transport.go new file mode 100644 index 0000000000..a7cd8b551d --- /dev/null +++ b/clients/api/http/transport.go @@ -0,0 +1,25 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "log/slog" + "net/http" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/clients" + mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/go-chi/chi/v5" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// MakeHandler returns a HTTP handler for clients and Groups API endpoints. +func MakeHandler(tsvc clients.Service, authn mgauthn.Authentication, mux *chi.Mux, logger *slog.Logger, instanceID string) http.Handler { + mux = clientsHandler(tsvc, authn, mux, logger) + + mux.Get("/health", magistrala.Health("clients", instanceID)) + mux.Handle("/metrics", promhttp.Handler()) + + return mux +} diff --git a/things/cache/doc.go b/clients/cache/doc.go similarity index 72% rename from things/cache/doc.go rename to clients/cache/doc.go index c73f0c0413..62e81d59ac 100644 --- a/things/cache/doc.go +++ b/clients/cache/doc.go @@ -2,5 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 // Package cache contains the domain concept definitions needed to -// support Magistrala things cache service functionality. +// support Magistrala clients cache service functionality. package cache diff --git a/things/cache/setup_test.go b/clients/cache/setup_test.go similarity index 100% rename from things/cache/setup_test.go rename to clients/cache/setup_test.go diff --git a/clients/cache/things.go b/clients/cache/things.go new file mode 100644 index 0000000000..7b8536cd5b --- /dev/null +++ b/clients/cache/things.go @@ -0,0 +1,85 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package cache + +import ( + "context" + "fmt" + "time" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + "github.com/redis/go-redis/v9" +) + +const ( + keyPrefix = "client_key" + idPrefix = "client_id" +) + +var _ clients.Cache = (*clientCache)(nil) + +type clientCache struct { + client *redis.Client + keyDuration time.Duration +} + +// NewCache returns redis client cache implementation. +func NewCache(client *redis.Client, duration time.Duration) clients.Cache { + return &clientCache{ + client: client, + keyDuration: duration, + } +} + +func (tc *clientCache) Save(ctx context.Context, clientKey, clientID string) error { + if clientKey == "" || clientID == "" { + return errors.Wrap(repoerr.ErrCreateEntity, errors.New("client key or client id is empty")) + } + tkey := fmt.Sprintf("%s:%s", keyPrefix, clientKey) + if err := tc.client.Set(ctx, tkey, clientID, tc.keyDuration).Err(); err != nil { + return errors.Wrap(repoerr.ErrCreateEntity, err) + } + + tid := fmt.Sprintf("%s:%s", idPrefix, clientID) + if err := tc.client.Set(ctx, tid, clientKey, tc.keyDuration).Err(); err != nil { + return errors.Wrap(repoerr.ErrCreateEntity, err) + } + + return nil +} + +func (tc *clientCache) ID(ctx context.Context, clientKey string) (string, error) { + if clientKey == "" { + return "", repoerr.ErrNotFound + } + + tkey := fmt.Sprintf("%s:%s", keyPrefix, clientKey) + clientID, err := tc.client.Get(ctx, tkey).Result() + if err != nil { + return "", errors.Wrap(repoerr.ErrNotFound, err) + } + + return clientID, nil +} + +func (tc *clientCache) Remove(ctx context.Context, clientID string) error { + tid := fmt.Sprintf("%s:%s", idPrefix, clientID) + key, err := tc.client.Get(ctx, tid).Result() + // Redis returns Nil Reply when key does not exist. + if err == redis.Nil { + return nil + } + if err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + + tkey := fmt.Sprintf("%s:%s", keyPrefix, key) + if err := tc.client.Del(ctx, tkey, tid).Err(); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + + return nil +} diff --git a/things/cache/things_test.go b/clients/cache/things_test.go similarity index 81% rename from things/cache/things_test.go rename to clients/cache/things_test.go index 8fa34e2227..679c4130bb 100644 --- a/things/cache/things_test.go +++ b/clients/cache/things_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" + "github.com/absmach/magistrala/clients/cache" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/things/cache" "github.com/stretchr/testify/assert" ) @@ -35,49 +35,49 @@ func TestSave(t *testing.T) { err error }{ { - desc: "Save thing to cache", + desc: "Save client to cache", key: testKey, id: testID, err: nil, }, { - desc: "Save already cached thing to cache", + desc: "Save already cached client to cache", key: testKey, id: testID, err: nil, }, { - desc: "Save another thing to cache", + desc: "Save another client to cache", key: testKey2, id: testID2, err: nil, }, { - desc: "Save thing with long key ", + desc: "Save client with long key ", key: strings.Repeat("a", 513*1024*1024), id: testID, err: repoerr.ErrCreateEntity, }, { - desc: "Save thing with long id ", + desc: "Save client with long id ", key: testKey, id: strings.Repeat("a", 513*1024*1024), err: repoerr.ErrCreateEntity, }, { - desc: "Save thing with empty key", + desc: "Save client with empty key", key: "", id: testID, err: repoerr.ErrCreateEntity, }, { - desc: "Save thing with empty id", + desc: "Save client with empty id", key: testKey, id: "", err: repoerr.ErrCreateEntity, }, { - desc: "Save thing with empty key and id", + desc: "Save client with empty key and id", key: "", id: "", err: repoerr.ErrCreateEntity, @@ -109,19 +109,19 @@ func TestID(t *testing.T) { err error }{ { - desc: "Get thing ID from cache", + desc: "Get client ID from cache", key: testKey, id: testID, err: nil, }, { - desc: "Get thing ID from cache for non existing thing", + desc: "Get client ID from cache for non existing client", key: "nonExistingKey", id: "", err: repoerr.ErrNotFound, }, { - desc: "Get thing ID from cache for empty key", + desc: "Get client ID from cache for empty key", key: "", id: "", err: repoerr.ErrNotFound, @@ -151,22 +151,22 @@ func TestRemove(t *testing.T) { err error }{ { - desc: "Remove existing thing from cache", + desc: "Remove existing client from cache", key: testID, err: nil, }, { - desc: "Remove non existing thing from cache", + desc: "Remove non existing client from cache", key: testID2, err: nil, }, { - desc: "Remove thing with empty ID from cache", + desc: "Remove client with empty ID from cache", key: "", err: nil, }, { - desc: "Remove thing with long id from cache", + desc: "Remove client with long id from cache", key: strings.Repeat("a", 513*1024*1024), err: repoerr.ErrRemoveEntity, }, diff --git a/things/clients.go b/clients/clients.go similarity index 77% rename from things/clients.go rename to clients/clients.go index 8894c171e6..9eee3c5851 100644 --- a/things/clients.go +++ b/clients/clients.go @@ -1,21 +1,23 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package things +package clients import ( "context" "time" "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" "github.com/absmach/magistrala/pkg/postgres" + "github.com/absmach/magistrala/pkg/roles" ) -type AuthzReq struct { - ChannelID string - ClientID string - ClientKey string - Permission string +type Connection struct { + ClientID string + ChannelID string + DomainID string + Type connections.ConnType } type ClientRepository struct { @@ -55,7 +57,7 @@ type Repository interface { ChangeStatus(ctx context.Context, client Client) (Client, error) // Delete deletes client with given id - Delete(ctx context.Context, id string) error + Delete(ctx context.Context, clientIDs ...string) error // Save persists the client account. A non-nil error is returned to indicate // operation failure. @@ -63,12 +65,38 @@ type Repository interface { // RetrieveBySecret retrieves a client based on the secret (key). RetrieveBySecret(ctx context.Context, key string) (Client, error) + + RetrieveByIds(ctx context.Context, ids []string) (ClientsPage, error) + + AddConnections(ctx context.Context, conns []Connection) error + + RemoveConnections(ctx context.Context, conns []Connection) error + + ClientConnectionsCount(ctx context.Context, id string) (uint64, error) + + DoesClientHaveConnections(ctx context.Context, id string) (bool, error) + + RemoveChannelConnections(ctx context.Context, channelID string) error + + RemoveClientConnections(ctx context.Context, clientID string) error + + // SetParentGroup set parent group id to a given channel id + SetParentGroup(ctx context.Context, cli Client) error + + // RemoveParentGroup remove parent group id fr given chanel id + RemoveParentGroup(ctx context.Context, cli Client) error + + RetrieveParentGroupClients(ctx context.Context, parentGroupID string) ([]Client, error) + + UnsetParentGroupFromClient(ctx context.Context, parentGroupID string) error + + roles.Repository } // Service specifies an API that must be fullfiled by the domain service // implementation, and all of its decorators (e.g. logging & metrics). // -//go:generate mockery --name Service --filename service.go --quiet --note "Copyright (c) Abstract Machines" +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" type Service interface { // CreateClients creates new client. In case of the failed registration, a // non-nil error value is returned. @@ -77,17 +105,9 @@ type Service interface { // View retrieves client info for a given client ID and an authorized token. View(ctx context.Context, session authn.Session, id string) (Client, error) - // ViewPerms retrieves permissions on the client id for the given authorized token. - ViewPerms(ctx context.Context, session authn.Session, id string) ([]string, error) - // ListClients retrieves clients list for a valid auth token. ListClients(ctx context.Context, session authn.Session, reqUserID string, pm Page) (ClientsPage, error) - // ListClientsByGroup retrieves data about subset of clients that are - // connected or not connected to specified channel and belong to the user identified by - // the provided key. - ListClientsByGroup(ctx context.Context, session authn.Session, groupID string, pm Page) (MembersPage, error) - // Update updates the client's name and metadata. Update(ctx context.Context, session authn.Session, client Client) (Client, error) @@ -103,25 +123,19 @@ type Service interface { // Disable logically disables the client identified with the provided ID Disable(ctx context.Context, session authn.Session, id string) (Client, error) - // Share add share policy to client id with given relation for given user ids - Share(ctx context.Context, session authn.Session, id string, relation string, userids ...string) error - - // Unshare remove share policy to client id with given relation for given user ids - Unshare(ctx context.Context, session authn.Session, id string, relation string, userids ...string) error + // Delete deletes client with given ID. + Delete(ctx context.Context, session authn.Session, id string) error - // Identify returns client ID for given client key. - Identify(ctx context.Context, key string) (string, error) + SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error - // Authorize used for Clients authorization. - Authorize(ctx context.Context, req AuthzReq) (string, error) + RemoveParentGroup(ctx context.Context, session authn.Session, id string) error - // Delete deletes client with given ID. - Delete(ctx context.Context, session authn.Session, id string) error + roles.RoleManager } // Cache contains client caching interface. // -//go:generate mockery --name Cache --filename cache.go --quiet --note "Copyright (c) Abstract Machines" +//go:generate mockery --name Cache --output=./mocks --filename cache.go --quiet --note "Copyright (c) Abstract Machines" type Cache interface { // Save stores pair client secret, client id. Save(ctx context.Context, clientSecret, clientID string) error @@ -140,6 +154,7 @@ type Client struct { Name string `json:"name,omitempty"` Tags []string `json:"tags,omitempty"` Domain string `json:"domain_id,omitempty"` + ParentGroup string `josn:"parent_group_id,omitempty"` Credentials Credentials `json:"credentials,omitempty"` Metadata Metadata `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` diff --git a/clients/doc.go b/clients/doc.go new file mode 100644 index 0000000000..6295edfb78 --- /dev/null +++ b/clients/doc.go @@ -0,0 +1,11 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package clients contains the domain concept definitions needed to +// support Magistrala clients service functionality. +// +// This package defines the core domain concepts and types necessary to +// handle clients in the context of a Magistrala clients service. It abstracts +// the underlying complexities of user management and provides a structured +// approach to working with clients. +package clients diff --git a/users/errors.go b/clients/errors.go similarity index 95% rename from users/errors.go rename to clients/errors.go index 7dc6b0a9a3..e5ee295160 100644 --- a/users/errors.go +++ b/clients/errors.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package users +package clients import "errors" diff --git a/things/events/doc.go b/clients/events/doc.go similarity index 80% rename from things/events/doc.go rename to clients/events/doc.go index cb8cccbf3a..1dc6d5265b 100644 --- a/things/events/doc.go +++ b/clients/events/doc.go @@ -2,5 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 // Package events provides the domain concept definitions needed to support -// things clients events functionality. +// clients clients events functionality. package events diff --git a/things/events/events.go b/clients/events/events.go similarity index 89% rename from things/events/events.go rename to clients/events/events.go index 486ebba09a..2142d3b568 100644 --- a/things/events/events.go +++ b/clients/events/events.go @@ -6,8 +6,8 @@ package events import ( "time" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/things" ) const ( @@ -22,6 +22,8 @@ const ( clientListByGroup = clientPrefix + "list_by_channel" clientIdentify = clientPrefix + "identify" clientAuthorize = clientPrefix + "authorize" + clientSetParent = clientPrefix + "set_parent" + clientRemoveParent = clientPrefix + "remove_parent" ) var ( @@ -39,7 +41,7 @@ var ( ) type createClientEvent struct { - things.Client + clients.Client } func (cce createClientEvent) Encode() (map[string]interface{}, error) { @@ -70,7 +72,7 @@ func (cce createClientEvent) Encode() (map[string]interface{}, error) { } type updateClientEvent struct { - things.Client + clients.Client operation string } @@ -130,7 +132,7 @@ func (rce changeStatusClientEvent) Encode() (map[string]interface{}, error) { } type viewClientEvent struct { - things.Client + clients.Client } func (vce viewClientEvent) Encode() (map[string]interface{}, error) { @@ -184,7 +186,7 @@ func (vcpe viewClientPermsEvent) Encode() (map[string]interface{}, error) { type listClientEvent struct { reqUserID string - things.Page + clients.Page } func (lce listClientEvent) Encode() (map[string]interface{}, error) { @@ -231,7 +233,7 @@ func (lce listClientEvent) Encode() (map[string]interface{}, error) { } type listClientByGroupEvent struct { - things.Page + clients.Page channelID string } @@ -276,18 +278,18 @@ func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) { } type identifyClientEvent struct { - thingID string + clientID string } func (ice identifyClientEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ "operation": clientIdentify, - "id": ice.thingID, + "id": ice.clientID, }, nil } type authorizeClientEvent struct { - thingID string + clientID string channelID string permission string } @@ -295,7 +297,7 @@ type authorizeClientEvent struct { func (ice authorizeClientEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ "operation": clientAuthorize, - "id": ice.thingID, + "id": ice.clientID, } if ice.permission != "" { @@ -334,3 +336,27 @@ func (dce removeClientEvent) Encode() (map[string]interface{}, error) { "id": dce.id, }, nil } + +type setParentGroupEvent struct { + id string + parentGroupID string +} + +func (spge setParentGroupEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": clientSetParent, + "id": spge.id, + "parent_group_id": spge.parentGroupID, + }, nil +} + +type removeParentGroupEvent struct { + id string +} + +func (rpge removeParentGroupEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": clientRemoveParent, + "id": rpge.id, + }, nil +} diff --git a/clients/events/streams.go b/clients/events/streams.go new file mode 100644 index 0000000000..194ea3c786 --- /dev/null +++ b/clients/events/streams.go @@ -0,0 +1,203 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "context" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/events" + "github.com/absmach/magistrala/pkg/events/store" + rmEvents "github.com/absmach/magistrala/pkg/roles/rolemanager/events" +) + +const streamID = "magistrala.clients" + +var _ clients.Service = (*eventStore)(nil) + +type eventStore struct { + events.Publisher + svc clients.Service + rmEvents.RoleManagerEventStore +} + +// NewEventStoreMiddleware returns wrapper around clients service that sends +// events to event store. +func NewEventStoreMiddleware(ctx context.Context, svc clients.Service, url string) (clients.Service, error) { + publisher, err := store.NewPublisher(ctx, url, streamID) + if err != nil { + return nil, err + } + res := rmEvents.NewRoleManagerEventStore("clients", svc, publisher) + + return &eventStore{ + svc: svc, + Publisher: publisher, + RoleManagerEventStore: res, + }, nil +} + +func (es *eventStore) CreateClients(ctx context.Context, session authn.Session, clients ...clients.Client) ([]clients.Client, error) { + clis, err := es.svc.CreateClients(ctx, session, clients...) + if err != nil { + return clis, err + } + + for _, cli := range clis { + event := createClientEvent{ + cli, + } + if err := es.Publish(ctx, event); err != nil { + return clis, err + } + } + + return clis, nil +} + +func (es *eventStore) Update(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) { + cli, err := es.svc.Update(ctx, session, client) + if err != nil { + return cli, err + } + + return es.update(ctx, "", cli) +} + +func (es *eventStore) UpdateTags(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) { + cli, err := es.svc.UpdateTags(ctx, session, client) + if err != nil { + return cli, err + } + + return es.update(ctx, "tags", cli) +} + +func (es *eventStore) UpdateSecret(ctx context.Context, session authn.Session, id, key string) (clients.Client, error) { + cli, err := es.svc.UpdateSecret(ctx, session, id, key) + if err != nil { + return cli, err + } + + return es.update(ctx, "secret", cli) +} + +func (es *eventStore) update(ctx context.Context, operation string, client clients.Client) (clients.Client, error) { + event := updateClientEvent{ + client, operation, + } + + if err := es.Publish(ctx, event); err != nil { + return client, err + } + + return client, nil +} + +func (es *eventStore) View(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + cli, err := es.svc.View(ctx, session, id) + if err != nil { + return cli, err + } + + event := viewClientEvent{ + cli, + } + if err := es.Publish(ctx, event); err != nil { + return cli, err + } + + return cli, nil +} + +func (es *eventStore) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm clients.Page) (clients.ClientsPage, error) { + cp, err := es.svc.ListClients(ctx, session, reqUserID, pm) + if err != nil { + return cp, err + } + event := listClientEvent{ + reqUserID, + pm, + } + if err := es.Publish(ctx, event); err != nil { + return cp, err + } + + return cp, nil +} + +func (es *eventStore) Enable(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + cli, err := es.svc.Enable(ctx, session, id) + if err != nil { + return cli, err + } + + return es.changeStatus(ctx, cli) +} + +func (es *eventStore) Disable(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + cli, err := es.svc.Disable(ctx, session, id) + if err != nil { + return cli, err + } + + return es.changeStatus(ctx, cli) +} + +func (es *eventStore) changeStatus(ctx context.Context, cli clients.Client) (clients.Client, error) { + event := changeStatusClientEvent{ + id: cli.ID, + updatedAt: cli.UpdatedAt, + updatedBy: cli.UpdatedBy, + status: cli.Status.String(), + } + if err := es.Publish(ctx, event); err != nil { + return cli, err + } + + return cli, nil +} + +func (es *eventStore) Delete(ctx context.Context, session authn.Session, id string) error { + if err := es.svc.Delete(ctx, session, id); err != nil { + return err + } + + event := removeClientEvent{id} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} + +func (es *eventStore) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (err error) { + if err := es.svc.SetParentGroup(ctx, session, parentGroupID, id); err != nil { + return err + } + + event := setParentGroupEvent{parentGroupID: parentGroupID, id: id} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} + +func (es *eventStore) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (err error) { + if err := es.svc.RemoveParentGroup(ctx, session, id); err != nil { + return err + } + + event := removeParentGroupEvent{id: id} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} diff --git a/clients/middleware/authorization.go b/clients/middleware/authorization.go new file mode 100644 index 0000000000..3b8a4cc801 --- /dev/null +++ b/clients/middleware/authorization.go @@ -0,0 +1,286 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "context" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/authz" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" + "github.com/absmach/magistrala/pkg/svcutil" +) + +var ( + errView = errors.New("not authorized to view client") + errUpdate = errors.New("not authorized to update client") + errUpdateTags = errors.New("not authorized to update client tags") + errUpdateSecret = errors.New("not authorized to update client secret") + errEnable = errors.New("not authorized to enable client") + errDisable = errors.New("not authorized to disable client") + errDelete = errors.New("not authorized to delete client") + errSetParentGroup = errors.New("not authorized to set parent group to client") + errRemoveParentGroup = errors.New("not authorized to remove parent group from client") + errDomainCreateClients = errors.New("not authorized to create client in domain") + errGroupSetChildClients = errors.New("not authorized to set child client for group") + errGroupRemoveChildClients = errors.New("not authorized to remove child client for group") +) + +var _ clients.Service = (*authorizationMiddleware)(nil) + +type authorizationMiddleware struct { + svc clients.Service + repo clients.Repository + authz authz.Authorization + opp svcutil.OperationPerm + extOpp svcutil.ExternalOperationPerm + rmMW.RoleManagerAuthorizationMiddleware +} + +// AuthorizationMiddleware adds authorization to the clients service. +func AuthorizationMiddleware(entityType string, svc clients.Service, authz authz.Authorization, repo clients.Repository, clientsOpPerm, rolesOpPerm map[svcutil.Operation]svcutil.Permission, extOpPerm map[svcutil.ExternalOperation]svcutil.Permission) (clients.Service, error) { + opp := clients.NewOperationPerm() + if err := opp.AddOperationPermissionMap(clientsOpPerm); err != nil { + return nil, err + } + if err := opp.Validate(); err != nil { + return nil, err + } + ram, err := rmMW.NewRoleManagerAuthorizationMiddleware(policies.ClientType, svc, authz, rolesOpPerm) + if err != nil { + return nil, err + } + extOpp := clients.NewExternalOperationPerm() + if err := extOpp.AddOperationPermissionMap(extOpPerm); err != nil { + return nil, err + } + if err := extOpp.Validate(); err != nil { + return nil, err + } + return &authorizationMiddleware{ + svc: svc, + authz: authz, + repo: repo, + opp: opp, + extOpp: extOpp, + RoleManagerAuthorizationMiddleware: ram, + }, nil +} + +func (am *authorizationMiddleware) CreateClients(ctx context.Context, session authn.Session, client ...clients.Client) ([]clients.Client, error) { + if err := am.extAuthorize(ctx, clients.DomainOpCreateClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.DomainType, + Object: session.DomainID, + }); err != nil { + return []clients.Client{}, errors.Wrap(err, errDomainCreateClients) + } + + return am.svc.CreateClients(ctx, session, client...) +} + +func (am *authorizationMiddleware) View(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + if err := am.authorize(ctx, clients.OpViewClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: id, + }); err != nil { + return clients.Client{}, errors.Wrap(err, errView) + } + return am.svc.View(ctx, session, id) +} + +func (am *authorizationMiddleware) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm clients.Page) (clients.ClientsPage, error) { + if err := am.checkSuperAdmin(ctx, session.UserID); err != nil { + session.SuperAdmin = true + } + + return am.svc.ListClients(ctx, session, reqUserID, pm) +} + +func (am *authorizationMiddleware) Update(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) { + if err := am.authorize(ctx, clients.OpUpdateClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: client.ID, + }); err != nil { + return clients.Client{}, errors.Wrap(err, errUpdate) + } + + return am.svc.Update(ctx, session, client) +} + +func (am *authorizationMiddleware) UpdateTags(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) { + if err := am.authorize(ctx, clients.OpUpdateClientTags, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: client.ID, + }); err != nil { + return clients.Client{}, errors.Wrap(err, errUpdateTags) + } + + return am.svc.UpdateTags(ctx, session, client) +} + +func (am *authorizationMiddleware) UpdateSecret(ctx context.Context, session authn.Session, id, key string) (clients.Client, error) { + if err := am.authorize(ctx, clients.OpUpdateClientSecret, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: id, + }); err != nil { + return clients.Client{}, errors.Wrap(err, errUpdateSecret) + } + return am.svc.UpdateSecret(ctx, session, id, key) +} + +func (am *authorizationMiddleware) Enable(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + if err := am.authorize(ctx, clients.OpEnableClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: id, + }); err != nil { + return clients.Client{}, errors.Wrap(err, errEnable) + } + + return am.svc.Enable(ctx, session, id) +} + +func (am *authorizationMiddleware) Disable(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + if err := am.authorize(ctx, clients.OpDisableClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: id, + }); err != nil { + return clients.Client{}, errors.Wrap(err, errDisable) + } + return am.svc.Disable(ctx, session, id) +} + +func (am *authorizationMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { + if err := am.authorize(ctx, clients.OpDeleteClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: id, + }); err != nil { + return errors.Wrap(err, errDelete) + } + + return am.svc.Delete(ctx, session, id) +} + +func (am *authorizationMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error { + if err := am.authorize(ctx, clients.OpSetParentGroup, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: id, + }); err != nil { + return errors.Wrap(err, errSetParentGroup) + } + + if err := am.extAuthorize(ctx, clients.GroupOpSetChildClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.GroupType, + Object: parentGroupID, + }); err != nil { + return errors.Wrap(err, errGroupSetChildClients) + } + return am.svc.SetParentGroup(ctx, session, parentGroupID, id) +} + +func (am *authorizationMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + if err := am.authorize(ctx, clients.OpRemoveParentGroup, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.ClientType, + Object: id, + }); err != nil { + return errors.Wrap(err, errRemoveParentGroup) + } + + cli, err := am.repo.RetrieveByID(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + if cli.ParentGroup != "" { + if err := am.extAuthorize(ctx, clients.GroupOpSetChildClient, authz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + ObjectType: policies.GroupType, + Object: cli.ParentGroup, + }); err != nil { + return errors.Wrap(err, errGroupRemoveChildClients) + } + return am.svc.RemoveParentGroup(ctx, session, id) + } + return nil +} + +func (am *authorizationMiddleware) authorize(ctx context.Context, op svcutil.Operation, req authz.PolicyReq) error { + perm, err := am.opp.GetPermission(op) + if err != nil { + return err + } + + req.Permission = perm.String() + + if err := am.authz.Authorize(ctx, req); err != nil { + return err + } + + return nil +} + +func (am *authorizationMiddleware) extAuthorize(ctx context.Context, extOp svcutil.ExternalOperation, req authz.PolicyReq) error { + perm, err := am.extOpp.GetPermission(extOp) + if err != nil { + return err + } + req.Permission = perm.String() + + if err := am.authz.Authorize(ctx, req); err != nil { + return err + } + + return nil +} + +func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, userID string) error { + if err := am.authz.Authorize(ctx, authz.PolicyReq{ + SubjectType: policies.UserType, + Subject: userID, + Permission: policies.AdminPermission, + ObjectType: policies.PlatformType, + Object: policies.MagistralaObject, + }); err != nil { + return err + } + return nil +} diff --git a/things/middleware/doc.go b/clients/middleware/doc.go similarity index 55% rename from things/middleware/doc.go rename to clients/middleware/doc.go index 253c83585f..1f8ff716cb 100644 --- a/things/middleware/doc.go +++ b/clients/middleware/doc.go @@ -1,5 +1,5 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package middleware provides middleware for Magistrala Things service. +// Package middleware provides middleware for Magistrala Clients service. package middleware diff --git a/clients/middleware/logging.go b/clients/middleware/logging.go new file mode 100644 index 0000000000..3dc375db76 --- /dev/null +++ b/clients/middleware/logging.go @@ -0,0 +1,232 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/pkg/authn" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" +) + +var _ clients.Service = (*loggingMiddleware)(nil) + +type loggingMiddleware struct { + logger *slog.Logger + svc clients.Service + rmMW.RoleManagerLoggingMiddleware +} + +func LoggingMiddleware(svc clients.Service, logger *slog.Logger) clients.Service { + return &loggingMiddleware{ + logger: logger, + svc: svc, + RoleManagerLoggingMiddleware: rmMW.NewRoleManagerLoggingMiddleware("clients", svc, logger), + } +} + +func (lm *loggingMiddleware) CreateClients(ctx context.Context, session authn.Session, clients ...clients.Client) (cs []clients.Client, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(fmt.Sprintf("Create %d clients failed", len(clients)), args...) + return + } + lm.logger.Info(fmt.Sprintf("Create %d clients completed successfully", len(clients)), args...) + }(time.Now()) + return lm.svc.CreateClients(ctx, session, clients...) +} + +func (lm *loggingMiddleware) View(ctx context.Context, session authn.Session, id string) (c clients.Client, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("client", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("View client failed", args...) + return + } + lm.logger.Info("View client completed successfully", args...) + }(time.Now()) + return lm.svc.View(ctx, session, id) +} + +func (lm *loggingMiddleware) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm clients.Page) (cp clients.ClientsPage, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("user_id", reqUserID), + slog.Group("page", + slog.Uint64("limit", pm.Limit), + slog.Uint64("offset", pm.Offset), + slog.Uint64("total", cp.Total), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("List clients failed", args...) + return + } + lm.logger.Info("List clients completed successfully", args...) + }(time.Now()) + return lm.svc.ListClients(ctx, session, reqUserID, pm) +} + +func (lm *loggingMiddleware) Update(ctx context.Context, session authn.Session, client clients.Client) (c clients.Client, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("client", + slog.String("id", client.ID), + slog.String("name", client.Name), + slog.Any("metadata", client.Metadata), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Update client failed", args...) + return + } + lm.logger.Info("Update client completed successfully", args...) + }(time.Now()) + return lm.svc.Update(ctx, session, client) +} + +func (lm *loggingMiddleware) UpdateTags(ctx context.Context, session authn.Session, client clients.Client) (c clients.Client, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("client", + slog.String("id", c.ID), + slog.String("name", c.Name), + slog.Any("tags", c.Tags), + ), + } + if err != nil { + args := append(args, slog.String("error", err.Error())) + lm.logger.Warn("Update client tags failed", args...) + return + } + lm.logger.Info("Update client tags completed successfully", args...) + }(time.Now()) + return lm.svc.UpdateTags(ctx, session, client) +} + +func (lm *loggingMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (c clients.Client, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("client", + slog.String("id", c.ID), + slog.String("name", c.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Update client secret failed", args...) + return + } + lm.logger.Info("Update client secret completed successfully", args...) + }(time.Now()) + return lm.svc.UpdateSecret(ctx, session, oldSecret, newSecret) +} + +func (lm *loggingMiddleware) Enable(ctx context.Context, session authn.Session, id string) (c clients.Client, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("client", + slog.String("id", id), + slog.String("name", c.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Enable client failed", args...) + return + } + lm.logger.Info("Enable client completed successfully", args...) + }(time.Now()) + return lm.svc.Enable(ctx, session, id) +} + +func (lm *loggingMiddleware) Disable(ctx context.Context, session authn.Session, id string) (c clients.Client, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("client", + slog.String("id", id), + slog.String("name", c.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Disable client failed", args...) + return + } + lm.logger.Info("Disable client completed successfully", args...) + }(time.Now()) + return lm.svc.Disable(ctx, session, id) +} + +func (lm *loggingMiddleware) Delete(ctx context.Context, session authn.Session, id string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("client_id", id), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Delete client failed", args...) + return + } + lm.logger.Info("Delete client completed successfully", args...) + }(time.Now()) + return lm.svc.Delete(ctx, session, id) +} + +func (lm *loggingMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("parent_group_id", parentGroupID), + slog.String("client_id", id), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Set parent group to client failed", args...) + return + } + lm.logger.Info("Set parent group to client completed successfully", args...) + }(time.Now()) + return lm.svc.SetParentGroup(ctx, session, parentGroupID, id) +} + +func (lm *loggingMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("client_id", id), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Remove parent group from client failed", args...) + return + } + lm.logger.Info("Remove parent group from client completed successfully", args...) + }(time.Now()) + return lm.svc.RemoveParentGroup(ctx, session, id) +} diff --git a/things/middleware/metrics.go b/clients/middleware/metrics.go similarity index 50% rename from things/middleware/metrics.go rename to clients/middleware/metrics.go index 6b6ecd2d62..4c93f3e66d 100644 --- a/things/middleware/metrics.go +++ b/clients/middleware/metrics.go @@ -7,37 +7,40 @@ import ( "context" "time" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/things" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" "github.com/go-kit/kit/metrics" ) -var _ things.Service = (*metricsMiddleware)(nil) +var _ clients.Service = (*metricsMiddleware)(nil) type metricsMiddleware struct { counter metrics.Counter latency metrics.Histogram - svc things.Service + svc clients.Service + rmMW.RoleManagerMetricsMiddleware } // MetricsMiddleware returns a new metrics middleware wrapper. -func MetricsMiddleware(svc things.Service, counter metrics.Counter, latency metrics.Histogram) things.Service { +func MetricsMiddleware(svc clients.Service, counter metrics.Counter, latency metrics.Histogram) clients.Service { return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, + counter: counter, + latency: latency, + svc: svc, + RoleManagerMetricsMiddleware: rmMW.NewRoleManagerMetricsMiddleware("clients", svc, counter, latency), } } -func (ms *metricsMiddleware) CreateClients(ctx context.Context, session authn.Session, things ...things.Client) ([]things.Client, error) { +func (ms *metricsMiddleware) CreateClients(ctx context.Context, session authn.Session, clients ...clients.Client) ([]clients.Client, error) { defer func(begin time.Time) { ms.counter.With("method", "register_clients").Add(1) ms.latency.With("method", "register_clients").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.CreateClients(ctx, session, things...) + return ms.svc.CreateClients(ctx, session, clients...) } -func (ms *metricsMiddleware) View(ctx context.Context, session authn.Session, id string) (things.Client, error) { +func (ms *metricsMiddleware) View(ctx context.Context, session authn.Session, 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()) @@ -45,15 +48,7 @@ func (ms *metricsMiddleware) View(ctx context.Context, session authn.Session, id return ms.svc.View(ctx, session, id) } -func (ms *metricsMiddleware) ViewPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_client_permissions").Add(1) - ms.latency.With("method", "view_client_permissions").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ViewPerms(ctx, session, id) -} - -func (ms *metricsMiddleware) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm things.Page) (things.ClientsPage, error) { +func (ms *metricsMiddleware) ListClients(ctx context.Context, session authn.Session, reqUserID 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()) @@ -61,23 +56,23 @@ func (ms *metricsMiddleware) ListClients(ctx context.Context, session authn.Sess return ms.svc.ListClients(ctx, session, reqUserID, pm) } -func (ms *metricsMiddleware) Update(ctx context.Context, session authn.Session, thing things.Client) (things.Client, error) { +func (ms *metricsMiddleware) Update(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) { defer func(begin time.Time) { ms.counter.With("method", "update_client").Add(1) ms.latency.With("method", "update_client").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.Update(ctx, session, thing) + return ms.svc.Update(ctx, session, client) } -func (ms *metricsMiddleware) UpdateTags(ctx context.Context, session authn.Session, thing things.Client) (things.Client, error) { +func (ms *metricsMiddleware) UpdateTags(ctx context.Context, session authn.Session, 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.UpdateTags(ctx, session, thing) + return ms.svc.UpdateTags(ctx, session, client) } -func (ms *metricsMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (things.Client, error) { +func (ms *metricsMiddleware) UpdateSecret(ctx context.Context, session authn.Session, 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()) @@ -85,7 +80,7 @@ func (ms *metricsMiddleware) UpdateSecret(ctx context.Context, session authn.Ses return ms.svc.UpdateSecret(ctx, session, oldSecret, newSecret) } -func (ms *metricsMiddleware) Enable(ctx context.Context, session authn.Session, id string) (things.Client, error) { +func (ms *metricsMiddleware) Enable(ctx context.Context, session authn.Session, 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()) @@ -93,7 +88,7 @@ func (ms *metricsMiddleware) Enable(ctx context.Context, session authn.Session, return ms.svc.Enable(ctx, session, id) } -func (ms *metricsMiddleware) Disable(ctx context.Context, session authn.Session, id string) (things.Client, error) { +func (ms *metricsMiddleware) Disable(ctx context.Context, session authn.Session, 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()) @@ -101,50 +96,26 @@ func (ms *metricsMiddleware) Disable(ctx context.Context, session authn.Session, return ms.svc.Disable(ctx, session, id) } -func (ms *metricsMiddleware) ListClientsByGroup(ctx context.Context, session authn.Session, groupID string, pm things.Page) (mp things.MembersPage, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_clients_by_channel").Add(1) - ms.latency.With("method", "list_clients_by_channel").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListClientsByGroup(ctx, session, groupID, pm) -} - -func (ms *metricsMiddleware) Identify(ctx context.Context, key string) (string, error) { - defer func(begin time.Time) { - ms.counter.With("method", "identify_client").Add(1) - ms.latency.With("method", "identify_client").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Identify(ctx, key) -} - -func (ms *metricsMiddleware) Authorize(ctx context.Context, req things.AuthzReq) (id string, 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, req) -} - -func (ms *metricsMiddleware) Share(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { +func (ms *metricsMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { defer func(begin time.Time) { - ms.counter.With("method", "share").Add(1) - ms.latency.With("method", "share").Observe(time.Since(begin).Seconds()) + ms.counter.With("method", "delete_client").Add(1) + ms.latency.With("method", "delete_client").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.Share(ctx, session, id, relation, userids...) + return ms.svc.Delete(ctx, session, id) } -func (ms *metricsMiddleware) Unshare(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { +func (ms *metricsMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (err error) { defer func(begin time.Time) { - ms.counter.With("method", "unshare").Add(1) - ms.latency.With("method", "unshare").Observe(time.Since(begin).Seconds()) + ms.counter.With("method", "set_parent_group").Add(1) + ms.latency.With("method", "set_parent_group").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.Unshare(ctx, session, id, relation, userids...) + return ms.svc.SetParentGroup(ctx, session, parentGroupID, id) } -func (ms *metricsMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { +func (ms *metricsMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (err error) { defer func(begin time.Time) { - ms.counter.With("method", "delete_client").Add(1) - ms.latency.With("method", "delete_client").Observe(time.Since(begin).Seconds()) + ms.counter.With("method", "remove_parent_group").Add(1) + ms.latency.With("method", "remove_parent_group").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.Delete(ctx, session, id) + return ms.svc.RemoveParentGroup(ctx, session, id) } diff --git a/things/mocks/cache.go b/clients/mocks/cache.go similarity index 100% rename from things/mocks/cache.go rename to clients/mocks/cache.go diff --git a/clients/mocks/clients_client.go b/clients/mocks/clients_client.go new file mode 100644 index 0000000000..faeadfed46 --- /dev/null +++ b/clients/mocks/clients_client.go @@ -0,0 +1,564 @@ +// Copyright (c) Abstract Machines + +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + clientsv1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" + + v1 "github.com/absmach/magistrala/internal/grpc/common/v1" +) + +// ClientsServiceClient is an autogenerated mock type for the ClientsServiceClient type +type ClientsServiceClient struct { + mock.Mock +} + +type ClientsServiceClient_Expecter struct { + mock *mock.Mock +} + +func (_m *ClientsServiceClient) EXPECT() *ClientsServiceClient_Expecter { + return &ClientsServiceClient_Expecter{mock: &_m.Mock} +} + +// AddConnections provides a mock function with given fields: ctx, in, opts +func (_m *ClientsServiceClient) AddConnections(ctx context.Context, in *v1.AddConnectionsReq, opts ...grpc.CallOption) (*v1.AddConnectionsRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for AddConnections") + } + + var r0 *v1.AddConnectionsRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v1.AddConnectionsReq, ...grpc.CallOption) (*v1.AddConnectionsRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v1.AddConnectionsReq, ...grpc.CallOption) *v1.AddConnectionsRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1.AddConnectionsRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v1.AddConnectionsReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientsServiceClient_AddConnections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddConnections' +type ClientsServiceClient_AddConnections_Call struct { + *mock.Call +} + +// AddConnections is a helper method to define mock.On call +// - ctx context.Context +// - in *v1.AddConnectionsReq +// - opts ...grpc.CallOption +func (_e *ClientsServiceClient_Expecter) AddConnections(ctx interface{}, in interface{}, opts ...interface{}) *ClientsServiceClient_AddConnections_Call { + return &ClientsServiceClient_AddConnections_Call{Call: _e.mock.On("AddConnections", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ClientsServiceClient_AddConnections_Call) Run(run func(ctx context.Context, in *v1.AddConnectionsReq, opts ...grpc.CallOption)) *ClientsServiceClient_AddConnections_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*v1.AddConnectionsReq), variadicArgs...) + }) + return _c +} + +func (_c *ClientsServiceClient_AddConnections_Call) Return(_a0 *v1.AddConnectionsRes, _a1 error) *ClientsServiceClient_AddConnections_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ClientsServiceClient_AddConnections_Call) RunAndReturn(run func(context.Context, *v1.AddConnectionsReq, ...grpc.CallOption) (*v1.AddConnectionsRes, error)) *ClientsServiceClient_AddConnections_Call { + _c.Call.Return(run) + return _c +} + +// Authenticate provides a mock function with given fields: ctx, in, opts +func (_m *ClientsServiceClient) Authenticate(ctx context.Context, in *clientsv1.AuthnReq, opts ...grpc.CallOption) (*clientsv1.AuthnRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Authenticate") + } + + var r0 *clientsv1.AuthnRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *clientsv1.AuthnReq, ...grpc.CallOption) (*clientsv1.AuthnRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *clientsv1.AuthnReq, ...grpc.CallOption) *clientsv1.AuthnRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*clientsv1.AuthnRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *clientsv1.AuthnReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientsServiceClient_Authenticate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Authenticate' +type ClientsServiceClient_Authenticate_Call struct { + *mock.Call +} + +// Authenticate is a helper method to define mock.On call +// - ctx context.Context +// - in *clientsv1.AuthnReq +// - opts ...grpc.CallOption +func (_e *ClientsServiceClient_Expecter) Authenticate(ctx interface{}, in interface{}, opts ...interface{}) *ClientsServiceClient_Authenticate_Call { + return &ClientsServiceClient_Authenticate_Call{Call: _e.mock.On("Authenticate", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ClientsServiceClient_Authenticate_Call) Run(run func(ctx context.Context, in *clientsv1.AuthnReq, opts ...grpc.CallOption)) *ClientsServiceClient_Authenticate_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*clientsv1.AuthnReq), variadicArgs...) + }) + return _c +} + +func (_c *ClientsServiceClient_Authenticate_Call) Return(_a0 *clientsv1.AuthnRes, _a1 error) *ClientsServiceClient_Authenticate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ClientsServiceClient_Authenticate_Call) RunAndReturn(run func(context.Context, *clientsv1.AuthnReq, ...grpc.CallOption) (*clientsv1.AuthnRes, error)) *ClientsServiceClient_Authenticate_Call { + _c.Call.Return(run) + return _c +} + +// RemoveChannelConnections provides a mock function with given fields: ctx, in, opts +func (_m *ClientsServiceClient) RemoveChannelConnections(ctx context.Context, in *clientsv1.RemoveChannelConnectionsReq, opts ...grpc.CallOption) (*clientsv1.RemoveChannelConnectionsRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for RemoveChannelConnections") + } + + var r0 *clientsv1.RemoveChannelConnectionsRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *clientsv1.RemoveChannelConnectionsReq, ...grpc.CallOption) (*clientsv1.RemoveChannelConnectionsRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *clientsv1.RemoveChannelConnectionsReq, ...grpc.CallOption) *clientsv1.RemoveChannelConnectionsRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*clientsv1.RemoveChannelConnectionsRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *clientsv1.RemoveChannelConnectionsReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientsServiceClient_RemoveChannelConnections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveChannelConnections' +type ClientsServiceClient_RemoveChannelConnections_Call struct { + *mock.Call +} + +// RemoveChannelConnections is a helper method to define mock.On call +// - ctx context.Context +// - in *clientsv1.RemoveChannelConnectionsReq +// - opts ...grpc.CallOption +func (_e *ClientsServiceClient_Expecter) RemoveChannelConnections(ctx interface{}, in interface{}, opts ...interface{}) *ClientsServiceClient_RemoveChannelConnections_Call { + return &ClientsServiceClient_RemoveChannelConnections_Call{Call: _e.mock.On("RemoveChannelConnections", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ClientsServiceClient_RemoveChannelConnections_Call) Run(run func(ctx context.Context, in *clientsv1.RemoveChannelConnectionsReq, opts ...grpc.CallOption)) *ClientsServiceClient_RemoveChannelConnections_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*clientsv1.RemoveChannelConnectionsReq), variadicArgs...) + }) + return _c +} + +func (_c *ClientsServiceClient_RemoveChannelConnections_Call) Return(_a0 *clientsv1.RemoveChannelConnectionsRes, _a1 error) *ClientsServiceClient_RemoveChannelConnections_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ClientsServiceClient_RemoveChannelConnections_Call) RunAndReturn(run func(context.Context, *clientsv1.RemoveChannelConnectionsReq, ...grpc.CallOption) (*clientsv1.RemoveChannelConnectionsRes, error)) *ClientsServiceClient_RemoveChannelConnections_Call { + _c.Call.Return(run) + return _c +} + +// RemoveConnections provides a mock function with given fields: ctx, in, opts +func (_m *ClientsServiceClient) RemoveConnections(ctx context.Context, in *v1.RemoveConnectionsReq, opts ...grpc.CallOption) (*v1.RemoveConnectionsRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for RemoveConnections") + } + + var r0 *v1.RemoveConnectionsRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v1.RemoveConnectionsReq, ...grpc.CallOption) (*v1.RemoveConnectionsRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v1.RemoveConnectionsReq, ...grpc.CallOption) *v1.RemoveConnectionsRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1.RemoveConnectionsRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v1.RemoveConnectionsReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientsServiceClient_RemoveConnections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveConnections' +type ClientsServiceClient_RemoveConnections_Call struct { + *mock.Call +} + +// RemoveConnections is a helper method to define mock.On call +// - ctx context.Context +// - in *v1.RemoveConnectionsReq +// - opts ...grpc.CallOption +func (_e *ClientsServiceClient_Expecter) RemoveConnections(ctx interface{}, in interface{}, opts ...interface{}) *ClientsServiceClient_RemoveConnections_Call { + return &ClientsServiceClient_RemoveConnections_Call{Call: _e.mock.On("RemoveConnections", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ClientsServiceClient_RemoveConnections_Call) Run(run func(ctx context.Context, in *v1.RemoveConnectionsReq, opts ...grpc.CallOption)) *ClientsServiceClient_RemoveConnections_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*v1.RemoveConnectionsReq), variadicArgs...) + }) + return _c +} + +func (_c *ClientsServiceClient_RemoveConnections_Call) Return(_a0 *v1.RemoveConnectionsRes, _a1 error) *ClientsServiceClient_RemoveConnections_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ClientsServiceClient_RemoveConnections_Call) RunAndReturn(run func(context.Context, *v1.RemoveConnectionsReq, ...grpc.CallOption) (*v1.RemoveConnectionsRes, error)) *ClientsServiceClient_RemoveConnections_Call { + _c.Call.Return(run) + return _c +} + +// RetrieveEntities provides a mock function with given fields: ctx, in, opts +func (_m *ClientsServiceClient) RetrieveEntities(ctx context.Context, in *v1.RetrieveEntitiesReq, opts ...grpc.CallOption) (*v1.RetrieveEntitiesRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for RetrieveEntities") + } + + var r0 *v1.RetrieveEntitiesRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v1.RetrieveEntitiesReq, ...grpc.CallOption) (*v1.RetrieveEntitiesRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v1.RetrieveEntitiesReq, ...grpc.CallOption) *v1.RetrieveEntitiesRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1.RetrieveEntitiesRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v1.RetrieveEntitiesReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientsServiceClient_RetrieveEntities_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveEntities' +type ClientsServiceClient_RetrieveEntities_Call struct { + *mock.Call +} + +// RetrieveEntities is a helper method to define mock.On call +// - ctx context.Context +// - in *v1.RetrieveEntitiesReq +// - opts ...grpc.CallOption +func (_e *ClientsServiceClient_Expecter) RetrieveEntities(ctx interface{}, in interface{}, opts ...interface{}) *ClientsServiceClient_RetrieveEntities_Call { + return &ClientsServiceClient_RetrieveEntities_Call{Call: _e.mock.On("RetrieveEntities", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ClientsServiceClient_RetrieveEntities_Call) Run(run func(ctx context.Context, in *v1.RetrieveEntitiesReq, opts ...grpc.CallOption)) *ClientsServiceClient_RetrieveEntities_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*v1.RetrieveEntitiesReq), variadicArgs...) + }) + return _c +} + +func (_c *ClientsServiceClient_RetrieveEntities_Call) Return(_a0 *v1.RetrieveEntitiesRes, _a1 error) *ClientsServiceClient_RetrieveEntities_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ClientsServiceClient_RetrieveEntities_Call) RunAndReturn(run func(context.Context, *v1.RetrieveEntitiesReq, ...grpc.CallOption) (*v1.RetrieveEntitiesRes, error)) *ClientsServiceClient_RetrieveEntities_Call { + _c.Call.Return(run) + return _c +} + +// RetrieveEntity provides a mock function with given fields: ctx, in, opts +func (_m *ClientsServiceClient) RetrieveEntity(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption) (*v1.RetrieveEntityRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for RetrieveEntity") + } + + var r0 *v1.RetrieveEntityRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v1.RetrieveEntityReq, ...grpc.CallOption) (*v1.RetrieveEntityRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v1.RetrieveEntityReq, ...grpc.CallOption) *v1.RetrieveEntityRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1.RetrieveEntityRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v1.RetrieveEntityReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientsServiceClient_RetrieveEntity_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveEntity' +type ClientsServiceClient_RetrieveEntity_Call struct { + *mock.Call +} + +// RetrieveEntity is a helper method to define mock.On call +// - ctx context.Context +// - in *v1.RetrieveEntityReq +// - opts ...grpc.CallOption +func (_e *ClientsServiceClient_Expecter) RetrieveEntity(ctx interface{}, in interface{}, opts ...interface{}) *ClientsServiceClient_RetrieveEntity_Call { + return &ClientsServiceClient_RetrieveEntity_Call{Call: _e.mock.On("RetrieveEntity", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ClientsServiceClient_RetrieveEntity_Call) Run(run func(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption)) *ClientsServiceClient_RetrieveEntity_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*v1.RetrieveEntityReq), variadicArgs...) + }) + return _c +} + +func (_c *ClientsServiceClient_RetrieveEntity_Call) Return(_a0 *v1.RetrieveEntityRes, _a1 error) *ClientsServiceClient_RetrieveEntity_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ClientsServiceClient_RetrieveEntity_Call) RunAndReturn(run func(context.Context, *v1.RetrieveEntityReq, ...grpc.CallOption) (*v1.RetrieveEntityRes, error)) *ClientsServiceClient_RetrieveEntity_Call { + _c.Call.Return(run) + return _c +} + +// UnsetParentGroupFromClient provides a mock function with given fields: ctx, in, opts +func (_m *ClientsServiceClient) UnsetParentGroupFromClient(ctx context.Context, in *clientsv1.UnsetParentGroupFromClientReq, opts ...grpc.CallOption) (*clientsv1.UnsetParentGroupFromClientRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for UnsetParentGroupFromClient") + } + + var r0 *clientsv1.UnsetParentGroupFromClientRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *clientsv1.UnsetParentGroupFromClientReq, ...grpc.CallOption) (*clientsv1.UnsetParentGroupFromClientRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *clientsv1.UnsetParentGroupFromClientReq, ...grpc.CallOption) *clientsv1.UnsetParentGroupFromClientRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*clientsv1.UnsetParentGroupFromClientRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *clientsv1.UnsetParentGroupFromClientReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientsServiceClient_UnsetParentGroupFromClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnsetParentGroupFromClient' +type ClientsServiceClient_UnsetParentGroupFromClient_Call struct { + *mock.Call +} + +// UnsetParentGroupFromClient is a helper method to define mock.On call +// - ctx context.Context +// - in *clientsv1.UnsetParentGroupFromClientReq +// - opts ...grpc.CallOption +func (_e *ClientsServiceClient_Expecter) UnsetParentGroupFromClient(ctx interface{}, in interface{}, opts ...interface{}) *ClientsServiceClient_UnsetParentGroupFromClient_Call { + return &ClientsServiceClient_UnsetParentGroupFromClient_Call{Call: _e.mock.On("UnsetParentGroupFromClient", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ClientsServiceClient_UnsetParentGroupFromClient_Call) Run(run func(ctx context.Context, in *clientsv1.UnsetParentGroupFromClientReq, opts ...grpc.CallOption)) *ClientsServiceClient_UnsetParentGroupFromClient_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*clientsv1.UnsetParentGroupFromClientReq), variadicArgs...) + }) + return _c +} + +func (_c *ClientsServiceClient_UnsetParentGroupFromClient_Call) Return(_a0 *clientsv1.UnsetParentGroupFromClientRes, _a1 error) *ClientsServiceClient_UnsetParentGroupFromClient_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ClientsServiceClient_UnsetParentGroupFromClient_Call) RunAndReturn(run func(context.Context, *clientsv1.UnsetParentGroupFromClientReq, ...grpc.CallOption) (*clientsv1.UnsetParentGroupFromClientRes, error)) *ClientsServiceClient_UnsetParentGroupFromClient_Call { + _c.Call.Return(run) + return _c +} + +// NewClientsServiceClient creates a new instance of ClientsServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewClientsServiceClient(t interface { + mock.TestingT + Cleanup(func()) +}) *ClientsServiceClient { + mock := &ClientsServiceClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/groups/mocks/doc.go b/clients/mocks/doc.go similarity index 100% rename from pkg/groups/mocks/doc.go rename to clients/mocks/doc.go diff --git a/clients/mocks/repository.go b/clients/mocks/repository.go new file mode 100644 index 0000000000..fc8b581994 --- /dev/null +++ b/clients/mocks/repository.go @@ -0,0 +1,1079 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + clients "github.com/absmach/magistrala/clients" + + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Repository is an autogenerated mock type for the Repository type +type Repository struct { + mock.Mock +} + +// AddConnections provides a mock function with given fields: ctx, conns +func (_m *Repository) AddConnections(ctx context.Context, conns []clients.Connection) error { + ret := _m.Called(ctx, conns) + + if len(ret) == 0 { + panic("no return value specified for AddConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []clients.Connection) error); ok { + r0 = rf(ctx, conns) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AddRoles provides a mock function with given fields: ctx, rps +func (_m *Repository) AddRoles(ctx context.Context, rps []roles.RoleProvision) ([]roles.Role, error) { + ret := _m.Called(ctx, rps) + + if len(ret) == 0 { + panic("no return value specified for AddRoles") + } + + var r0 []roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) ([]roles.Role, error)); ok { + return rf(ctx, rps) + } + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) []roles.Role); ok { + r0 = rf(ctx, rps) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.Role) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []roles.RoleProvision) error); ok { + r1 = rf(ctx, rps) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChangeStatus provides a mock function with given fields: ctx, client +func (_m *Repository) ChangeStatus(ctx context.Context, client clients.Client) (clients.Client, error) { + ret := _m.Called(ctx, client) + + if len(ret) == 0 { + panic("no return value specified for ChangeStatus") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok { + return rf(ctx, client) + } + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok { + r0 = rf(ctx, client) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok { + r1 = rf(ctx, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ClientConnectionsCount provides a mock function with given fields: ctx, id +func (_m *Repository) ClientConnectionsCount(ctx context.Context, id string) (uint64, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for ClientConnectionsCount") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (uint64, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) uint64); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, clientIDs +func (_m *Repository) Delete(ctx context.Context, clientIDs ...string) error { + _va := make([]interface{}, len(clientIDs)) + for _i := range clientIDs { + _va[_i] = clientIDs[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok { + r0 = rf(ctx, clientIDs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DoesClientHaveConnections provides a mock function with given fields: ctx, id +func (_m *Repository) DoesClientHaveConnections(ctx context.Context, id string) (bool, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for DoesClientHaveConnections") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveChannelConnections provides a mock function with given fields: ctx, channelID +func (_m *Repository) RemoveChannelConnections(ctx context.Context, channelID string) error { + ret := _m.Called(ctx, channelID) + + if len(ret) == 0 { + panic("no return value specified for RemoveChannelConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, channelID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveClientConnections provides a mock function with given fields: ctx, clientID +func (_m *Repository) RemoveClientConnections(ctx context.Context, clientID string) error { + ret := _m.Called(ctx, clientID) + + if len(ret) == 0 { + panic("no return value specified for RemoveClientConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, clientID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveConnections provides a mock function with given fields: ctx, conns +func (_m *Repository) RemoveConnections(ctx context.Context, conns []clients.Connection) error { + ret := _m.Called(ctx, conns) + + if len(ret) == 0 { + panic("no return value specified for RemoveConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []clients.Connection) error); ok { + r0 = rf(ctx, conns) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, members +func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, members string) error { + ret := _m.Called(ctx, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveParentGroup provides a mock function with given fields: ctx, th +func (_m *Repository) RemoveParentGroup(ctx context.Context, th clients.Client) error { + ret := _m.Called(ctx, th) + + if len(ret) == 0 { + panic("no return value specified for RemoveParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) error); ok { + r0 = rf(ctx, th) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRoles provides a mock function with given fields: ctx, roleIDs +func (_m *Repository) RemoveRoles(ctx context.Context, roleIDs []string) error { + ret := _m.Called(ctx, roleIDs) + + if len(ret) == 0 { + panic("no return value specified for RemoveRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { + r0 = rf(ctx, roleIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAll provides a mock function with given fields: ctx, pm +func (_m *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { + ret := _m.Called(ctx, pm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAll") + } + + var r0 clients.ClientsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok { + return rf(ctx, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok { + r0 = rf(ctx, pm) + } else { + r0 = ret.Get(0).(clients.ClientsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok { + r1 = rf(ctx, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveAllByIDs provides a mock function with given fields: ctx, pm +func (_m *Repository) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { + ret := _m.Called(ctx, pm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllByIDs") + } + + var r0 clients.ClientsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok { + return rf(ctx, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok { + r0 = rf(ctx, pm) + } else { + r0 = ret.Get(0).(clients.ClientsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok { + r1 = rf(ctx, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, entityID, limit, offset +func (_m *Repository) RetrieveAllRoles(ctx context.Context, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByID provides a mock function with given fields: ctx, id +func (_m *Repository) RetrieveByID(ctx context.Context, id string) (clients.Client, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByID") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (clients.Client, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) clients.Client); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByIds provides a mock function with given fields: ctx, ids +func (_m *Repository) RetrieveByIds(ctx context.Context, ids []string) (clients.ClientsPage, error) { + ret := _m.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByIds") + } + + var r0 clients.ClientsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) (clients.ClientsPage, error)); ok { + return rf(ctx, ids) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) clients.ClientsPage); ok { + r0 = rf(ctx, ids) + } else { + r0 = ret.Get(0).(clients.ClientsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveBySecret provides a mock function with given fields: ctx, key +func (_m *Repository) RetrieveBySecret(ctx context.Context, key string) (clients.Client, error) { + ret := _m.Called(ctx, key) + + if len(ret) == 0 { + panic("no return value specified for RetrieveBySecret") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (clients.Client, error)); ok { + return rf(ctx, key) + } + if rf, ok := ret.Get(0).(func(context.Context, string) clients.Client); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, key) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveEntitiesRolesActionsMembers provides a mock function with given fields: ctx, entityIDs +func (_m *Repository) RetrieveEntitiesRolesActionsMembers(ctx context.Context, entityIDs []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error) { + ret := _m.Called(ctx, entityIDs) + + if len(ret) == 0 { + panic("no return value specified for RetrieveEntitiesRolesActionsMembers") + } + + var r0 []roles.EntityActionRole + var r1 []roles.EntityMemberRole + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error)); ok { + return rf(ctx, entityIDs) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []roles.EntityActionRole); ok { + r0 = rf(ctx, entityIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.EntityActionRole) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) []roles.EntityMemberRole); ok { + r1 = rf(ctx, entityIDs) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]roles.EntityMemberRole) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, []string) error); ok { + r2 = rf(ctx, entityIDs) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// RetrieveParentGroupClients provides a mock function with given fields: ctx, parentGroupID +func (_m *Repository) RetrieveParentGroupClients(ctx context.Context, parentGroupID string) ([]clients.Client, error) { + ret := _m.Called(ctx, parentGroupID) + + if len(ret) == 0 { + panic("no return value specified for RetrieveParentGroupClients") + } + + var r0 []clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]clients.Client, error)); ok { + return rf(ctx, parentGroupID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []clients.Client); ok { + r0 = rf(ctx, parentGroupID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]clients.Client) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, parentGroupID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRole provides a mock function with given fields: ctx, roleID +func (_m *Repository) RetrieveRole(ctx context.Context, roleID string) (roles.Role, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (roles.Role, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) roles.Role); ok { + r0 = rf(ctx, roleID) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRoleByEntityIDAndName provides a mock function with given fields: ctx, entityID, roleName +func (_m *Repository) RetrieveRoleByEntityIDAndName(ctx context.Context, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRoleByEntityIDAndName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (roles.Role, error)); ok { + return rf(ctx, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) roles.Role); ok { + r0 = rf(ctx, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleAddActions(ctx context.Context, role roles.Role, actions []string) ([]string, error) { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleAddMembers(ctx context.Context, role roles.Role, members []string) ([]string, error) { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, members) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, roleID, actions +func (_m *Repository) RoleCheckActionsExists(ctx context.Context, roleID string, actions []string) (bool, error) { + ret := _m.Called(ctx, roleID, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, roleID, members +func (_m *Repository) RoleCheckMembersExists(ctx context.Context, roleID string, members []string) (bool, error) { + ret := _m.Called(ctx, roleID, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, members) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, roleID +func (_m *Repository) RoleListActions(ctx context.Context, roleID string) ([]string, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []string); ok { + r0 = rf(ctx, roleID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, roleID, limit, offset +func (_m *Repository) RoleListMembers(ctx context.Context, roleID string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, roleID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, roleID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, roleID, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, roleID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleRemoveActions(ctx context.Context, role roles.Role, actions []string) error { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllActions(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllMembers(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleRemoveMembers(ctx context.Context, role roles.Role, members []string) error { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Save provides a mock function with given fields: ctx, client +func (_m *Repository) Save(ctx context.Context, client ...clients.Client) ([]clients.Client, error) { + _va := make([]interface{}, len(client)) + for _i := range client { + _va[_i] = client[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Save") + } + + var r0 []clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ...clients.Client) ([]clients.Client, error)); ok { + return rf(ctx, client...) + } + if rf, ok := ret.Get(0).(func(context.Context, ...clients.Client) []clients.Client); ok { + r0 = rf(ctx, client...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]clients.Client) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ...clients.Client) error); ok { + r1 = rf(ctx, client...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SearchClients provides a mock function with given fields: ctx, pm +func (_m *Repository) SearchClients(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { + ret := _m.Called(ctx, pm) + + if len(ret) == 0 { + panic("no return value specified for SearchClients") + } + + var r0 clients.ClientsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Page) (clients.ClientsPage, error)); ok { + return rf(ctx, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, clients.Page) clients.ClientsPage); ok { + r0 = rf(ctx, pm) + } else { + r0 = ret.Get(0).(clients.ClientsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, clients.Page) error); ok { + r1 = rf(ctx, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetParentGroup provides a mock function with given fields: ctx, th +func (_m *Repository) SetParentGroup(ctx context.Context, th clients.Client) error { + ret := _m.Called(ctx, th) + + if len(ret) == 0 { + panic("no return value specified for SetParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) error); ok { + r0 = rf(ctx, th) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnsetParentGroupFromClient provides a mock function with given fields: ctx, parentGroupID +func (_m *Repository) UnsetParentGroupFromClient(ctx context.Context, parentGroupID string) error { + ret := _m.Called(ctx, parentGroupID) + + if len(ret) == 0 { + panic("no return value specified for UnsetParentGroupFromClient") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, parentGroupID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: ctx, client +func (_m *Repository) Update(ctx context.Context, client clients.Client) (clients.Client, error) { + ret := _m.Called(ctx, client) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok { + return rf(ctx, client) + } + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok { + r0 = rf(ctx, client) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok { + r1 = rf(ctx, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateIdentity provides a mock function with given fields: ctx, client +func (_m *Repository) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) { + ret := _m.Called(ctx, client) + + if len(ret) == 0 { + panic("no return value specified for UpdateIdentity") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok { + return rf(ctx, client) + } + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok { + r0 = rf(ctx, client) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok { + r1 = rf(ctx, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateRole provides a mock function with given fields: ctx, ro +func (_m *Repository) UpdateRole(ctx context.Context, ro roles.Role) (roles.Role, error) { + ret := _m.Called(ctx, ro) + + if len(ret) == 0 { + panic("no return value specified for UpdateRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) (roles.Role, error)); ok { + return rf(ctx, ro) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) roles.Role); ok { + r0 = rf(ctx, ro) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role) error); ok { + r1 = rf(ctx, ro) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateSecret provides a mock function with given fields: ctx, client +func (_m *Repository) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) { + ret := _m.Called(ctx, client) + + if len(ret) == 0 { + panic("no return value specified for UpdateSecret") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok { + return rf(ctx, client) + } + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok { + r0 = rf(ctx, client) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok { + r1 = rf(ctx, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateTags provides a mock function with given fields: ctx, client +func (_m *Repository) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) { + ret := _m.Called(ctx, client) + + if len(ret) == 0 { + panic("no return value specified for UpdateTags") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) (clients.Client, error)); ok { + return rf(ctx, client) + } + if rf, ok := ret.Get(0).(func(context.Context, clients.Client) clients.Client); ok { + r0 = rf(ctx, client) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, clients.Client) error); ok { + r1 = rf(ctx, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *Repository { + mock := &Repository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/clients/mocks/service.go b/clients/mocks/service.go new file mode 100644 index 0000000000..50811eb9fb --- /dev/null +++ b/clients/mocks/service.go @@ -0,0 +1,746 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + clients "github.com/absmach/magistrala/clients" + authn "github.com/absmach/magistrala/pkg/authn" + + context "context" + + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// AddRole provides a mock function with given fields: ctx, session, entityID, roleName, optionalActions, optionalMembers +func (_m *Service) AddRole(ctx context.Context, session authn.Session, entityID string, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName, optionalActions, optionalMembers) + + if len(ret) == 0 { + panic("no return value specified for AddRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateClients provides a mock function with given fields: ctx, session, client +func (_m *Service) CreateClients(ctx context.Context, session authn.Session, client ...clients.Client) ([]clients.Client, error) { + _va := make([]interface{}, len(client)) + for _i := range client { + _va[_i] = client[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, session) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for CreateClients") + } + + var r0 []clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, ...clients.Client) ([]clients.Client, error)); ok { + return rf(ctx, session, client...) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, ...clients.Client) []clients.Client); ok { + r0 = rf(ctx, session, client...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]clients.Client) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, ...clients.Client) error); ok { + r1 = rf(ctx, session, client...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, session, id +func (_m *Service) Delete(ctx context.Context, session authn.Session, id string) error { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Disable provides a mock function with given fields: ctx, session, id +func (_m *Service) Disable(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for Disable") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (clients.Client, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) clients.Client); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Enable provides a mock function with given fields: ctx, session, id +func (_m *Service) Enable(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for Enable") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (clients.Client, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) clients.Client); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAvailableActions provides a mock function with given fields: ctx, session +func (_m *Service) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + ret := _m.Called(ctx, session) + + if len(ret) == 0 { + panic("no return value specified for ListAvailableActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) ([]string, error)); ok { + return rf(ctx, session) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) []string); ok { + r0 = rf(ctx, session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session) error); ok { + r1 = rf(ctx, session) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListClients provides a mock function with given fields: ctx, session, reqUserID, pm +func (_m *Service) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm clients.Page) (clients.ClientsPage, error) { + ret := _m.Called(ctx, session, reqUserID, pm) + + if len(ret) == 0 { + panic("no return value specified for ListClients") + } + + var r0 clients.ClientsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, clients.Page) (clients.ClientsPage, error)); ok { + return rf(ctx, session, reqUserID, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, clients.Page) clients.ClientsPage); ok { + r0 = rf(ctx, session, reqUserID, pm) + } else { + r0 = ret.Get(0).(clients.ClientsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, clients.Page) error); ok { + r1 = rf(ctx, session, reqUserID, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID +func (_m *Service) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { + ret := _m.Called(ctx, session, memberID) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, memberID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveParentGroup provides a mock function with given fields: ctx, session, id +func (_m *Service) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for RemoveParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RemoveRole(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RemoveRole") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, session, entityID, limit, offset +func (_m *Service) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, session, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, session, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, session, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RetrieveRole(ctx context.Context, session authn.Session, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleAddActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleAddMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleListActions(ctx context.Context, session authn.Session, entityID string, roleName string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) []string); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, session, entityID, roleName, limit, offset +func (_m *Service) RoleListMembers(ctx context.Context, session authn.Session, entityID string, roleName string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, session, entityID, roleName, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, session, entityID, roleName, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleRemoveActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) error { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) error { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetParentGroup provides a mock function with given fields: ctx, session, parentGroupID, id +func (_m *Service) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error { + ret := _m.Called(ctx, session, parentGroupID, id) + + if len(ret) == 0 { + panic("no return value specified for SetParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, parentGroupID, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: ctx, session, client +func (_m *Service) Update(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) { + ret := _m.Called(ctx, session, client) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) (clients.Client, error)); ok { + return rf(ctx, session, client) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) clients.Client); ok { + r0 = rf(ctx, session, client) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, clients.Client) error); ok { + r1 = rf(ctx, session, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateRoleName provides a mock function with given fields: ctx, session, entityID, oldRoleName, newRoleName +func (_m *Service) UpdateRoleName(ctx context.Context, session authn.Session, entityID string, oldRoleName string, newRoleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, oldRoleName, newRoleName) + + if len(ret) == 0 { + panic("no return value specified for UpdateRoleName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, oldRoleName, newRoleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, string) error); ok { + r1 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateSecret provides a mock function with given fields: ctx, session, id, key +func (_m *Service) UpdateSecret(ctx context.Context, session authn.Session, id string, key string) (clients.Client, error) { + ret := _m.Called(ctx, session, id, key) + + if len(ret) == 0 { + panic("no return value specified for UpdateSecret") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (clients.Client, error)); ok { + return rf(ctx, session, id, key) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) clients.Client); ok { + r0 = rf(ctx, session, id, key) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, id, key) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateTags provides a mock function with given fields: ctx, session, client +func (_m *Service) UpdateTags(ctx context.Context, session authn.Session, client clients.Client) (clients.Client, error) { + ret := _m.Called(ctx, session, client) + + if len(ret) == 0 { + panic("no return value specified for UpdateTags") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) (clients.Client, error)); ok { + return rf(ctx, session, client) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, clients.Client) clients.Client); ok { + r0 = rf(ctx, session, client) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, clients.Client) error); ok { + r1 = rf(ctx, session, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// View provides a mock function with given fields: ctx, session, id +func (_m *Service) View(ctx context.Context, session authn.Session, id string) (clients.Client, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for View") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (clients.Client, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) clients.Client); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/clients/postgres/clients.go b/clients/postgres/clients.go new file mode 100644 index 0000000000..1c6cdefb8f --- /dev/null +++ b/clients/postgres/clients.go @@ -0,0 +1,836 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/connections" + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + "github.com/absmach/magistrala/pkg/postgres" + rolesPostgres "github.com/absmach/magistrala/pkg/roles/repo/postgres" + "github.com/jackc/pgtype" +) + +const ( + entityTableName = "clients" + entityIDColumnName = "id" + rolesTableNamePrefix = "clients" +) + +var _ clients.Repository = (*clientRepo)(nil) + +type clientRepo struct { + DB postgres.Database + rolesPostgres.Repository +} + +// NewRepository instantiates a PostgreSQL +// implementation of Clients repository. +func NewRepository(db postgres.Database) clients.Repository { + repo := rolesPostgres.NewRepository(db, rolesTableNamePrefix, entityTableName, entityIDColumnName) + + return &clientRepo{ + DB: db, + Repository: repo, + } +} + +func (repo *clientRepo) Save(ctx context.Context, cls ...clients.Client) ([]clients.Client, error) { + var dbClients []DBClient + + for _, client := range cls { + dbcli, err := ToDBClient(client) + if err != nil { + return []clients.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) + } + dbClients = append(dbClients, dbcli) + } + q := `INSERT INTO clients (id, name, tags, domain_id, parent_group_id, identity, secret, metadata, created_at, updated_at, updated_by, status) + VALUES (:id, :name, :tags, :domain_id, :parent_group_id, :identity, :secret, :metadata, :created_at, :updated_at, :updated_by, :status) + RETURNING id, name, tags, identity, secret, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + + row, err := repo.DB.NamedQueryContext(ctx, q, dbClients) + if err != nil { + return []clients.Client{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + defer row.Close() + + var reClients []clients.Client + for row.Next() { + dbcli := DBClient{} + if err := row.StructScan(&dbcli); err != nil { + return []clients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + + client, err := ToClient(dbcli) + if err != nil { + return []clients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + reClients = append(reClients, client) + } + return reClients, nil +} + +func (repo *clientRepo) RetrieveBySecret(ctx context.Context, key string) (clients.Client, error) { + q := fmt.Sprintf(`SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, identity, secret, metadata, created_at, updated_at, updated_by, status + FROM clients + WHERE secret = :secret AND status = %d`, clients.EnabledStatus) + + dbc := DBClient{ + Secret: key, + } + + rows, err := repo.DB.NamedQueryContext(ctx, q, dbc) + if err != nil { + return clients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + dbc = DBClient{} + if rows.Next() { + if err = rows.StructScan(&dbc); err != nil { + return clients.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + client, err := ToClient(dbc) + if err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + } + + return client, nil + } + + return clients.Client{}, repoerr.ErrNotFound +} + +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, updated_by = :updated_by + WHERE id = :id AND status = :status + RETURNING id, name, tags, identity, secret, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by`, + upq) + client.Status = clients.EnabledStatus + return repo.update(ctx, client, q) +} + +func (repo *clientRepo) UpdateTags(ctx context.Context, client clients.Client) (clients.Client, error) { + q := `UPDATE clients SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id AND status = :status + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + client.Status = clients.EnabledStatus + return repo.update(ctx, client, q) +} + +func (repo *clientRepo) UpdateIdentity(ctx context.Context, client clients.Client) (clients.Client, error) { + q := `UPDATE clients SET identity = :identity, updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id AND status = :status + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, COALESCE(parent_group_id, '') AS parent_group_id, created_at, updated_at, updated_by` + client.Status = clients.EnabledStatus + return repo.update(ctx, client, q) +} + +func (repo *clientRepo) UpdateSecret(ctx context.Context, client clients.Client) (clients.Client, error) { + q := `UPDATE clients SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id AND status = :status + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + client.Status = clients.EnabledStatus + return repo.update(ctx, client, q) +} + +func (repo *clientRepo) ChangeStatus(ctx context.Context, client clients.Client) (clients.Client, error) { + q := `UPDATE clients SET status = :status, updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id + RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, status, created_at, updated_at, updated_by` + + return repo.update(ctx, client, q) +} + +func (repo *clientRepo) RetrieveByID(ctx context.Context, id string) (clients.Client, error) { + q := `SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, identity, secret, metadata, created_at, updated_at, updated_by, status + FROM clients WHERE id = :id` + + dbc := DBClient{ + ID: id, + } + + row, err := repo.DB.NamedQueryContext(ctx, q, dbc) + if err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + defer row.Close() + + dbc = DBClient{} + if row.Next() { + if err := row.StructScan(&dbc); err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + return ToClient(dbc) + } + + return clients.Client{}, repoerr.ErrNotFound +} + +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(repoerr.ErrViewEntity, err) + } + query = applyOrdering(query, pm) + + q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, + c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by 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(repoerr.ErrFailedToRetrieveAllGroups, err) + } + rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, 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(repoerr.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(repoerr.ErrViewEntity, err) + } + + page := clients.ClientsPage{ + Clients: items, + Page: clients.Page{ + Total: total, + Offset: pm.Offset, + Limit: pm.Limit, + }, + } + + return page, nil +} + +func (repo *clientRepo) SearchClients(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { + query, err := PageQuery(pm) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + tq := query + query = applyOrdering(query, pm) + + q := fmt.Sprintf(`SELECT c.id, c.name, c.created_at, c.updated_at FROM clients c %s LIMIT :limit OFFSET :offset;`, query) + + dbPage, err := ToDBClientsPage(pm) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + } + + rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, 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(repoerr.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;`, tq) + total, err := postgres.Total(ctx, repo.DB, cq, dbPage) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + page := clients.ClientsPage{ + Clients: items, + Page: clients.Page{ + Total: total, + Offset: pm.Offset, + Limit: pm.Limit, + }, + } + + return page, nil +} + +func (repo *clientRepo) RetrieveAllByIDs(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) { + if (len(pm.IDs) == 0) && (pm.Domain == "") { + return clients.ClientsPage{ + Page: clients.Page{Total: pm.Total, Offset: pm.Offset, Limit: pm.Limit}, + }, nil + } + query, err := PageQuery(pm) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + query = applyOrdering(query, pm) + + q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, + c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by 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(repoerr.ErrFailedToRetrieveAllGroups, err) + } + rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, 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(repoerr.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(repoerr.ErrViewEntity, err) + } + + page := clients.ClientsPage{ + Clients: 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, query string) (clients.Client, error) { + dbc, err := ToDBClient(client) + if err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + } + + row, err := repo.DB.NamedQueryContext(ctx, query, dbc) + if err != nil { + return clients.Client{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) + } + defer row.Close() + + dbc = DBClient{} + if row.Next() { + if err := row.StructScan(&dbc); err != nil { + return clients.Client{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + } + + return ToClient(dbc) + } + + return clients.Client{}, repoerr.ErrNotFound +} + +func (repo *clientRepo) Delete(ctx context.Context, clientIDs ...string) error { + q := "DELETE FROM clients AS c WHERE c.id = ANY(:client_ids) ;" + + params := map[string]interface{}{ + "client_ids": clientIDs, + } + result, err := repo.DB.NamedExecContext(ctx, q, params) + if err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + if rows, _ := result.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } + + return nil +} + +type DBClient struct { + ID string `db:"id"` + Name string `db:"name,omitempty"` + Tags pgtype.TextArray `db:"tags,omitempty"` + Identity string `db:"identity"` + Domain string `db:"domain_id"` + ParentGroup sql.NullString `db:"parent_group_id,omitempty"` + Secret string `db:"secret"` + Metadata []byte `db:"metadata,omitempty"` + CreatedAt time.Time `db:"created_at,omitempty"` + UpdatedAt sql.NullTime `db:"updated_at,omitempty"` + UpdatedBy *string `db:"updated_by,omitempty"` + Status clients.Status `db:"status,omitempty"` +} + +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(repoerr.ErrMalformedEntity, err) + } + data = b + } + var tags pgtype.TextArray + if err := tags.Set(c.Tags); err != nil { + return DBClient{}, err + } + var updatedBy *string + if c.UpdatedBy != "" { + updatedBy = &c.UpdatedBy + } + var updatedAt sql.NullTime + if c.UpdatedAt != (time.Time{}) { + updatedAt = sql.NullTime{Time: c.UpdatedAt, Valid: true} + } + + return DBClient{ + ID: c.ID, + Name: c.Name, + Tags: tags, + Domain: c.Domain, + ParentGroup: toNullString(c.ParentGroup), + Identity: c.Credentials.Identity, + Secret: c.Credentials.Secret, + Metadata: data, + CreatedAt: c.CreatedAt, + UpdatedAt: updatedAt, + UpdatedBy: updatedBy, + Status: c.Status, + }, nil +} + +func ToClient(t DBClient) (clients.Client, error) { + var metadata clients.Metadata + if t.Metadata != nil { + if err := json.Unmarshal([]byte(t.Metadata), &metadata); err != nil { + return clients.Client{}, errors.Wrap(errors.ErrMalformedEntity, err) + } + } + var tags []string + for _, e := range t.Tags.Elements { + tags = append(tags, e.String) + } + var updatedBy string + if t.UpdatedBy != nil { + updatedBy = *t.UpdatedBy + } + var updatedAt time.Time + if t.UpdatedAt.Valid { + updatedAt = t.UpdatedAt.Time + } + + cli := clients.Client{ + ID: t.ID, + Name: t.Name, + Tags: tags, + Domain: t.Domain, + ParentGroup: toString(t.ParentGroup), + Credentials: clients.Credentials{ + Identity: t.Identity, + Secret: t.Secret, + }, + Metadata: metadata, + CreatedAt: t.CreatedAt, + UpdatedAt: updatedAt, + UpdatedBy: updatedBy, + Status: t.Status, + } + return cli, nil +} + +func ToDBClientsPage(pm clients.Page) (dbClientsPage, error) { + _, data, err := postgres.CreateMetadataQuery("", pm.Metadata) + if err != nil { + return dbClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + return dbClientsPage{ + Name: pm.Name, + Identity: pm.Identity, + Id: pm.Id, + Metadata: data, + Domain: pm.Domain, + Total: pm.Total, + Offset: pm.Offset, + Limit: pm.Limit, + Status: pm.Status, + Tag: pm.Tag, + }, nil +} + +type dbClientsPage struct { + Total uint64 `db:"total"` + Limit uint64 `db:"limit"` + Offset uint64 `db:"offset"` + Name string `db:"name"` + Id string `db:"id"` + Domain string `db:"domain_id"` + Identity string `db:"identity"` + Metadata []byte `db:"metadata"` + Tag string `db:"tag"` + Status clients.Status `db:"status"` + GroupID string `db:"group_id"` +} + +func PageQuery(pm clients.Page) (string, error) { + mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) + if err != nil { + return "", errors.Wrap(errors.ErrMalformedEntity, err) + } + + var query []string + if pm.Name != "" { + query = append(query, "name ILIKE '%' || :name || '%'") + } + if pm.Identity != "" { + query = append(query, "identity ILIKE '%' || :identity || '%'") + } + if pm.Id != "" { + query = append(query, "id ILIKE '%' || :id || '%'") + } + if pm.Tag != "" { + query = append(query, "EXISTS (SELECT 1 FROM unnest(tags) AS tag WHERE tag ILIKE '%' || :tag || '%')") + } + // If there are search params presents, use search and ignore other options. + // Always combine role with search params, so len(query) > 1. + if len(query) > 1 { + return fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")), nil + } + + if mq != "" { + query = append(query, mq) + } + + if len(pm.IDs) != 0 { + query = append(query, fmt.Sprintf("id IN ('%s')", strings.Join(pm.IDs, "','"))) + } + if pm.Status != clients.AllStatus { + query = append(query, "c.status = :status") + } + if pm.Domain != "" { + query = append(query, "c.domain_id = :domain_id") + } + var emq string + if len(query) > 0 { + emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")) + } + return emq, nil +} + +func applyOrdering(emq string, pm clients.Page) string { + switch pm.Order { + case "name", "identity", "created_at", "updated_at": + emq = fmt.Sprintf("%s ORDER BY %s", emq, pm.Order) + if pm.Dir == api.AscDir || pm.Dir == api.DescDir { + emq = fmt.Sprintf("%s %s", emq, pm.Dir) + } + } + return emq +} + +func toNullString(s string) sql.NullString { + if s == "" { + return sql.NullString{} + } + + return sql.NullString{ + String: s, + Valid: true, + } +} + +func toString(s sql.NullString) string { + if s.Valid { + return s.String + } + return "" +} + +func (repo *clientRepo) RetrieveByIds(ctx context.Context, ids []string) (clients.ClientsPage, error) { + if len(ids) == 0 { + return clients.ClientsPage{}, nil + } + + pm := clients.Page{IDs: ids} + query, err := PageQuery(pm) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, + c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at`, query) + + dbPage, err := ToDBClientsPage(pm) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + } + rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) + if err != nil { + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, 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(repoerr.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(repoerr.ErrViewEntity, err) + } + + page := clients.ClientsPage{ + Clients: items, + Page: clients.Page{ + Total: total, + Offset: pm.Offset, + Limit: total, + }, + } + + return page, nil +} + +func (repo *clientRepo) AddConnections(ctx context.Context, conns []clients.Connection) error { + dbConns := toDBConnections(conns) + q := `INSERT INTO connections (channel_id, domain_id, client_id, type) + VALUES (:channel_id, :domain_id, :client_id, :type);` + if _, err := repo.DB.NamedExecContext(ctx, q, dbConns); err != nil { + return postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + return nil +} + +func (repo *clientRepo) RemoveConnections(ctx context.Context, conns []clients.Connection) (retErr error) { + tx, err := repo.DB.BeginTxx(ctx, nil) + if err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + defer func() { + if retErr != nil { + if errRollBack := tx.Rollback(); errRollBack != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollBack)) + } + } + }() + + query := `DELETE FROM connections WHERE channel_id = :channel_id AND domain_id = :domain_id AND client_id = :client_id` + + for _, conn := range conns { + if uint8(conn.Type) > 0 { + query = query + " AND type = :type " + } + dbConn := toDBConnection(conn) + if _, err := tx.NamedExec(query, dbConn); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, errors.Wrap(fmt.Errorf("failed to delete connection for channel_id: %s, domain_id: %s client_id %s", conn.ChannelID, conn.DomainID, conn.ClientID), err)) + } + } + if err := tx.Commit(); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (repo *clientRepo) SetParentGroup(ctx context.Context, cli clients.Client) error { + q := "UPDATE clients SET parent_group_id = :parent_group_id, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id" + + dbcli, err := ToDBClient(cli) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + result, err := repo.DB.NamedExecContext(ctx, q, dbcli) + if err != nil { + return postgres.HandleError(repoerr.ErrUpdateEntity, err) + } + if rows, _ := result.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } + return nil +} + +func (repo *clientRepo) RemoveParentGroup(ctx context.Context, cli clients.Client) error { + q := "UPDATE clients SET parent_group_id = NULL, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id" + dbcli, err := ToDBClient(cli) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + result, err := repo.DB.NamedExecContext(ctx, q, dbcli) + if err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + if rows, _ := result.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } + return nil +} + +func (repo *clientRepo) ClientConnectionsCount(ctx context.Context, id string) (uint64, error) { + query := `SELECT COUNT(*) FROM connections WHERE client_id = :client_id` + dbConn := dbConnection{ClientID: id} + + total, err := postgres.Total(ctx, repo.DB, query, dbConn) + if err != nil { + return 0, postgres.HandleError(repoerr.ErrViewEntity, err) + } + return total, nil +} + +func (repo *clientRepo) DoesClientHaveConnections(ctx context.Context, id string) (bool, error) { + query := `SELECT 1 FROM connections WHERE client_id = :client_id` + dbConn := dbConnection{ClientID: id} + + rows, err := repo.DB.NamedQueryContext(ctx, query, dbConn) + if err != nil { + return false, postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + return rows.Next(), nil +} + +func (repo *clientRepo) RemoveChannelConnections(ctx context.Context, channelID string) error { + query := `DELETE FROM connections WHERE channel_id = :channel_id` + + dbConn := dbConnection{ChannelID: channelID} + if _, err := repo.DB.NamedExecContext(ctx, query, dbConn); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (repo *clientRepo) RemoveClientConnections(ctx context.Context, clientID string) error { + query := `DELETE FROM connections WHERE client_id = :client_id` + + dbConn := dbConnection{ClientID: clientID} + if _, err := repo.DB.NamedExecContext(ctx, query, dbConn); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (repo *clientRepo) RetrieveParentGroupClients(ctx context.Context, parentGroupID string) ([]clients.Client, error) { + query := `SELECT c.id, c.name, c.tags, c.metadata, COALESCE(c.domain_id, '') AS domain_id, COALESCE(parent_group_id, '') AS parent_group_id, c.status, + c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c WHERE c.parent_group_id = :parent_group_id ;` + + rows, err := repo.DB.NamedQueryContext(ctx, query, DBClient{ParentGroup: toNullString(parentGroupID)}) + if err != nil { + return []clients.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + var clis []clients.Client + for rows.Next() { + dbCli := DBClient{} + if err := rows.StructScan(&dbCli); err != nil { + return []clients.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + cli, err := ToClient(dbCli) + if err != nil { + return []clients.Client{}, err + } + + clis = append(clis, cli) + } + return clis, nil +} + +func (repo *clientRepo) UnsetParentGroupFromClient(ctx context.Context, parentGroupID string) error { + query := "UPDATE clients SET parent_group_id = NULL WHERE parent_group_id = :parent_group_id" + + if _, err := repo.DB.NamedExecContext(ctx, query, DBClient{ParentGroup: toNullString(parentGroupID)}); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +type dbConnection struct { + ClientID string `db:"client_id"` + ChannelID string `db:"channel_id"` + DomainID string `db:"domain_id"` + Type connections.ConnType `db:"type"` +} + +func toDBConnections(conns []clients.Connection) []dbConnection { + var dbconns []dbConnection + for _, conn := range conns { + dbconns = append(dbconns, toDBConnection(conn)) + } + return dbconns +} + +func toDBConnection(conn clients.Connection) dbConnection { + return dbConnection{ + ClientID: conn.ClientID, + ChannelID: conn.ChannelID, + DomainID: conn.DomainID, + Type: conn.Type, + } +} diff --git a/things/postgres/clients_test.go b/clients/postgres/clients_test.go similarity index 54% rename from things/postgres/clients_test.go rename to clients/postgres/clients_test.go index b03b7d4f6c..ee4453f6d6 100644 --- a/things/postgres/clients_test.go +++ b/clients/postgres/clients_test.go @@ -10,11 +10,11 @@ import ( "testing" "github.com/0x6flab/namegenerator" + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/clients/postgres" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/things" - "github.com/absmach/magistrala/things/postgres" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,8 +23,8 @@ const maxNameSize = 1024 var ( invalidName = strings.Repeat("m", maxNameSize+10) - thingIdentity = "thing-identity@example.com" - thingName = "thing name" + clientIdentity = "client-identity@example.com" + clientName = "client name" invalidDomainID = strings.Repeat("m", maxNameSize+10) namegen = namegenerator.NewGenerator() ) @@ -41,253 +41,253 @@ func TestClientsSave(t *testing.T) { secret := testsutil.GenerateUUID(t) cases := []struct { - desc string - things []things.Client - err error + desc string + clients []clients.Client + err error }{ { - desc: "add new thing successfully", - things: []things.Client{ + desc: "add new client successfully", + clients: []clients.Client{ { ID: uid, Domain: domainID, - Name: thingName, - Credentials: things.Credentials{ - Identity: thingIdentity, + Name: clientName, + Credentials: clients.Credentials{ + Identity: clientIdentity, Secret: secret, }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: nil, }, { - desc: "add multiple things successfully", - things: []things.Client{ + desc: "add multiple clients successfully", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, { ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, { ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: nil, }, { - desc: "add new thing with duplicate secret", - things: []things.Client{ + desc: "add new client with duplicate secret", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Domain: domainID, Name: namegen.Generate(), - Credentials: things.Credentials{ - Identity: thingIdentity, + Credentials: clients.Credentials{ + Identity: clientIdentity, Secret: secret, }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, }, { - desc: "add multiple things with one thing having duplicate secret", - things: []things.Client{ + desc: "add multiple clients with one client having duplicate secret", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, { ID: testsutil.GenerateUUID(t), Domain: domainID, Name: namegen.Generate(), - Credentials: things.Credentials{ - Identity: thingIdentity, + Credentials: clients.Credentials{ + Identity: clientIdentity, Secret: secret, }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, }, { - desc: "add new thing without domain id", - things: []things.Client{ + desc: "add new client without domain id", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), - Name: thingName, - Credentials: things.Credentials{ - Identity: "withoutdomain-thing@example.com", + Name: clientName, + Credentials: clients.Credentials{ + Identity: "withoutdomain-client@example.com", Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: nil, }, { - desc: "add thing with invalid thing id", - things: []things.Client{ + desc: "add client with invalid client id", + clients: []clients.Client{ { ID: invalidName, Domain: domainID, - Name: thingName, - Credentials: things.Credentials{ - Identity: "invalidid-thing@example.com", + Name: clientName, + Credentials: clients.Credentials{ + Identity: "invalidid-client@example.com", Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, }, { - desc: "add multiple things with one thing having invalid thing id", - things: []things.Client{ + desc: "add multiple clients with one client having invalid client id", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, { ID: invalidName, Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, }, { - desc: "add thing with invalid thing name", - things: []things.Client{ + desc: "add client with invalid client name", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Name: invalidName, Domain: domainID, - Credentials: things.Credentials{ - Identity: "invalidname-thing@example.com", + Credentials: clients.Credentials{ + Identity: "invalidname-client@example.com", Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, }, { - desc: "add thing with invalid thing domain id", - things: []things.Client{ + desc: "add client with invalid client domain id", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Domain: invalidDomainID, - Credentials: things.Credentials{ - Identity: "invaliddomainid-thing@example.com", + Credentials: clients.Credentials{ + Identity: "invaliddomainid-client@example.com", Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, }, { - desc: "add thing with invalid thing identity", - things: []things.Client{ + desc: "add client with invalid client identity", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), - Name: thingName, - Credentials: things.Credentials{ + Name: clientName, + Credentials: clients.Credentials{ Identity: invalidName, Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, }, }, err: repoerr.ErrCreateEntity, }, { - desc: "add thing with a missing thing identity", - things: []things.Client{ + desc: "add client with a missing client identity", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), - Name: "missing-thing-identity", - Credentials: things.Credentials{ + Name: "missing-client-identity", + Credentials: clients.Credentials{ Identity: "", Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, + Metadata: clients.Metadata{}, }, }, err: nil, }, { - desc: "add thing with a missing thing secret", - things: []things.Client{ + desc: "add client with a missing client secret", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), - Credentials: things.Credentials{ - Identity: "missing-thing-secret@example.com", + Credentials: clients.Credentials{ + Identity: "missing-client-secret@example.com", Secret: "", }, - Metadata: things.Metadata{}, + Metadata: clients.Metadata{}, }, }, err: nil, }, { - desc: "add a thing with invalid metadata", - things: []things.Client{ + desc: "add a client with invalid metadata", + clients: []clients.Client{ { ID: testsutil.GenerateUUID(t), Name: namegen.Generate(), - Credentials: things.Credentials{ + Credentials: clients.Credentials{ Identity: fmt.Sprintf("%s@example.com", namegen.Generate()), Secret: testsutil.GenerateUUID(t), }, @@ -300,60 +300,60 @@ func TestClientsSave(t *testing.T) { }, } for _, tc := range cases { - rThings, err := repo.Save(context.Background(), tc.things...) + rClients, err := repo.Save(context.Background(), tc.clients...) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) if err == nil { - for i := range rThings { - tc.things[i].Credentials.Secret = rThings[i].Credentials.Secret + for i := range rClients { + tc.clients[i].Credentials.Secret = rClients[i].Credentials.Secret } - assert.Equal(t, tc.things, rThings, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.things, rThings)) + assert.Equal(t, tc.clients, rClients, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.clients, rClients)) } } } -func TestThingsRetrieveBySecret(t *testing.T) { +func TestClientsRetrieveBySecret(t *testing.T) { t.Cleanup(func() { _, err := db.Exec("DELETE FROM clients") require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err)) }) repo := postgres.NewRepository(database) - thing := things.Client{ + client := clients.Client{ ID: testsutil.GenerateUUID(t), - Name: thingName, - Credentials: things.Credentials{ - Identity: thingIdentity, + Name: clientName, + Credentials: clients.Credentials{ + Identity: clientIdentity, Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, } - _, err := repo.Save(context.Background(), thing) + _, err := repo.Save(context.Background(), client) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { desc string secret string - response things.Client + response clients.Client err error }{ { - desc: "retrieve thing by secret successfully", - secret: thing.Credentials.Secret, - response: thing, + desc: "retrieve client by secret successfully", + secret: client.Credentials.Secret, + response: client, err: nil, }, { - desc: "retrieve thing by invalid secret", + desc: "retrieve client by invalid secret", secret: "non-existent-secret", - response: things.Client{}, + response: clients.Client{}, err: repoerr.ErrNotFound, }, { - desc: "retrieve thing by empty secret", + desc: "retrieve client by empty secret", secret: "", - response: things.Client{}, + response: clients.Client{}, err: repoerr.ErrNotFound, }, } @@ -372,42 +372,42 @@ func TestRetrieveByID(t *testing.T) { }) repo := postgres.NewRepository(database) - thing := things.Client{ + client := clients.Client{ ID: testsutil.GenerateUUID(t), - Name: thingName, - Credentials: things.Credentials{ - Identity: thingIdentity, + Name: clientName, + Credentials: clients.Credentials{ + Identity: clientIdentity, Secret: testsutil.GenerateUUID(t), }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, + Metadata: clients.Metadata{}, + Status: clients.EnabledStatus, } - _, err := repo.Save(context.Background(), thing) + _, err := repo.Save(context.Background(), client) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { desc string id string - response things.Client + response clients.Client err error }{ { desc: "successfully", - id: thing.ID, - response: thing, + id: client.ID, + response: client, err: nil, }, { desc: "with invalid id", id: testsutil.GenerateUUID(t), - response: things.Client{}, + response: clients.Client{}, err: repoerr.ErrNotFound, }, { desc: "with empty id", id: "", - response: things.Client{}, + response: clients.Client{}, err: repoerr.ErrNotFound, }, } @@ -416,12 +416,12 @@ func TestRetrieveByID(t *testing.T) { cli, err := repo.RetrieveByID(context.Background(), c.id) assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s got %s\n", c.err, err)) if err == nil { - assert.Equal(t, thing.ID, cli.ID) - assert.Equal(t, thing.Name, cli.Name) - assert.Equal(t, thing.Metadata, cli.Metadata) - assert.Equal(t, thing.Credentials.Identity, cli.Credentials.Identity) - assert.Equal(t, thing.Credentials.Secret, cli.Credentials.Secret) - assert.Equal(t, thing.Status, cli.Status) + assert.Equal(t, client.ID, cli.ID) + assert.Equal(t, client.Name, cli.Name) + assert.Equal(t, client.Metadata, cli.Metadata) + assert.Equal(t, client.Credentials.Identity, cli.Credentials.Identity) + assert.Equal(t, client.Credentials.Secret, cli.Credentials.Secret) + assert.Equal(t, client.Status, cli.Status) } }) } diff --git a/things/postgres/doc.go b/clients/postgres/doc.go similarity index 100% rename from things/postgres/doc.go rename to clients/postgres/doc.go diff --git a/clients/postgres/init.go b/clients/postgres/init.go new file mode 100644 index 0000000000..ed68d8dfd5 --- /dev/null +++ b/clients/postgres/init.go @@ -0,0 +1,64 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + rolesPostgres "github.com/absmach/magistrala/pkg/roles/repo/postgres" + _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access + migrate "github.com/rubenv/sql-migrate" +) + +func Migration() (*migrate.MemoryMigrationSource, error) { + clientsRolesMigration, err := rolesPostgres.Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName) + if err != nil { + return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err) + } + + clientsMigration := &migrate.MemoryMigrationSource{ + Migrations: []*migrate.Migration{ + { + Id: "clients_01", + // VARCHAR(36) for columns with IDs as UUIDS have a maximum of 36 characters + // STATUS 0 to imply enabled and 1 to imply disabled + Up: []string{ + `CREATE TABLE IF NOT EXISTS clients ( + id VARCHAR(36) PRIMARY KEY, + name VARCHAR(1024), + domain_id VARCHAR(36) NOT NULL, + parent_group_id VARCHAR(36) DEFAULT NULL, + identity VARCHAR(254), + secret VARCHAR(4096) NOT NULL, + tags TEXT[], + metadata JSONB, + created_at TIMESTAMP, + updated_at TIMESTAMP, + updated_by VARCHAR(254), + status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), + UNIQUE (domain_id, secret), + UNIQUE (domain_id, name), + UNIQUE (domain_id, id) + )`, + `CREATE TABLE IF NOT EXISTS connections ( + channel_id VARCHAR(36), + domain_id VARCHAR(36), + client_id VARCHAR(36), + type SMALLINT NOT NULL CHECK (type IN (1, 2)), + FOREIGN KEY (client_id, domain_id) REFERENCES clients (id, domain_id) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (channel_id, domain_id, client_id, type) + )`, + }, + Down: []string{ + `DROP TABLE IF EXISTS clients`, + `DROP TABLE IF EXISTS connections`, + }, + }, + }, + } + + clientsMigration.Migrations = append(clientsMigration.Migrations, clientsRolesMigration.Migrations...) + + return clientsMigration, nil +} diff --git a/things/postgres/setup_test.go b/clients/postgres/setup_test.go similarity index 90% rename from things/postgres/setup_test.go rename to clients/postgres/setup_test.go index a167f6434c..165895fec0 100644 --- a/things/postgres/setup_test.go +++ b/clients/postgres/setup_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" + cpostgres "github.com/absmach/magistrala/clients/postgres" pgclient "github.com/absmach/magistrala/pkg/postgres" - cpostgres "github.com/absmach/magistrala/things/postgres" "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" @@ -75,7 +75,11 @@ func TestMain(m *testing.M) { SSLRootCert: "", } - if db, err = pgclient.Setup(dbConfig, *cpostgres.Migration()); err != nil { + mig, err := cpostgres.Migration() + if err != nil { + log.Fatalf("Could not get DB migrations: %s", err) + } + if db, err = pgclient.Setup(dbConfig, *mig); err != nil { log.Fatalf("Could not setup test DB connection: %s", err) } diff --git a/clients/private/doc.go b/clients/private/doc.go new file mode 100644 index 0000000000..85534043e8 --- /dev/null +++ b/clients/private/doc.go @@ -0,0 +1,3 @@ +// Private package is a service wrapper around the underlying Repository. +// This is used for internal service communication purpose only. +package private diff --git a/clients/private/mocks/service.go b/clients/private/mocks/service.go new file mode 100644 index 0000000000..268f1bb415 --- /dev/null +++ b/clients/private/mocks/service.go @@ -0,0 +1,188 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + clients "github.com/absmach/magistrala/clients" + + mock "github.com/stretchr/testify/mock" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// AddConnections provides a mock function with given fields: ctx, conns +func (_m *Service) AddConnections(ctx context.Context, conns []clients.Connection) error { + ret := _m.Called(ctx, conns) + + if len(ret) == 0 { + panic("no return value specified for AddConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []clients.Connection) error); ok { + r0 = rf(ctx, conns) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Authenticate provides a mock function with given fields: ctx, key +func (_m *Service) Authenticate(ctx context.Context, key string) (string, error) { + ret := _m.Called(ctx, key) + + if len(ret) == 0 { + panic("no return value specified for Authenticate") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { + return rf(ctx, key) + } + if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, key) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveChannelConnections provides a mock function with given fields: ctx, channelID +func (_m *Service) RemoveChannelConnections(ctx context.Context, channelID string) error { + ret := _m.Called(ctx, channelID) + + if len(ret) == 0 { + panic("no return value specified for RemoveChannelConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, channelID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveConnections provides a mock function with given fields: ctx, conns +func (_m *Service) RemoveConnections(ctx context.Context, conns []clients.Connection) error { + ret := _m.Called(ctx, conns) + + if len(ret) == 0 { + panic("no return value specified for RemoveConnections") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []clients.Connection) error); ok { + r0 = rf(ctx, conns) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveById provides a mock function with given fields: ctx, id +func (_m *Service) RetrieveById(ctx context.Context, id string) (clients.Client, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveById") + } + + var r0 clients.Client + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (clients.Client, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) clients.Client); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByIds provides a mock function with given fields: ctx, ids +func (_m *Service) RetrieveByIds(ctx context.Context, ids []string) (clients.ClientsPage, error) { + ret := _m.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByIds") + } + + var r0 clients.ClientsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) (clients.ClientsPage, error)); ok { + return rf(ctx, ids) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) clients.ClientsPage); ok { + r0 = rf(ctx, ids) + } else { + r0 = ret.Get(0).(clients.ClientsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnsetParentGroupFromClient provides a mock function with given fields: ctx, parentGroupID +func (_m *Service) UnsetParentGroupFromClient(ctx context.Context, parentGroupID string) error { + ret := _m.Called(ctx, parentGroupID) + + if len(ret) == 0 { + panic("no return value specified for UnsetParentGroupFromClient") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, parentGroupID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/clients/private/service.go b/clients/private/service.go new file mode 100644 index 0000000000..5a11e65e09 --- /dev/null +++ b/clients/private/service.go @@ -0,0 +1,119 @@ +package private + +import ( + "context" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" +) + +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" +type Service interface { + // Authenticate returns client ID for given client key. + Authenticate(ctx context.Context, key string) (string, error) + + RetrieveById(ctx context.Context, id string) (clients.Client, error) + + RetrieveByIds(ctx context.Context, ids []string) (clients.ClientsPage, error) + + AddConnections(ctx context.Context, conns []clients.Connection) error + + RemoveConnections(ctx context.Context, conns []clients.Connection) error + + RemoveChannelConnections(ctx context.Context, channelID string) error + + UnsetParentGroupFromClient(ctx context.Context, parentGroupID string) error +} + +var _ Service = (*service)(nil) + +func New(repo clients.Repository, cache clients.Cache, evaluator policies.Evaluator, policy policies.Service) Service { + return service{ + repo: repo, + cache: cache, + evaluator: evaluator, + policy: policy, + } +} + +type service struct { + repo clients.Repository + cache clients.Cache + evaluator policies.Evaluator + policy policies.Service +} + +func (svc service) Authenticate(ctx context.Context, key string) (string, error) { + id, err := svc.cache.ID(ctx, key) + if err == nil { + return id, nil + } + + client, err := svc.repo.RetrieveBySecret(ctx, key) + if err != nil { + return "", errors.Wrap(svcerr.ErrAuthorization, err) + } + if err := svc.cache.Save(ctx, key, client.ID); err != nil { + return "", errors.Wrap(svcerr.ErrAuthorization, err) + } + + return client.ID, nil +} + +func (svc service) RetrieveById(ctx context.Context, ids string) (clients.Client, error) { + return svc.repo.RetrieveByID(ctx, ids) +} + +func (svc service) RetrieveByIds(ctx context.Context, ids []string) (clients.ClientsPage, error) { + return svc.repo.RetrieveByIds(ctx, ids) +} + +func (svc service) AddConnections(ctx context.Context, conns []clients.Connection) (err error) { + return svc.repo.AddConnections(ctx, conns) +} + +func (svc service) RemoveConnections(ctx context.Context, conns []clients.Connection) (err error) { + return svc.repo.RemoveConnections(ctx, conns) +} + +func (svc service) RemoveChannelConnections(ctx context.Context, channelID string) error { + return svc.repo.RemoveChannelConnections(ctx, channelID) +} + +func (svc service) UnsetParentGroupFromClient(ctx context.Context, parentGroupID string) (retErr error) { + clients, err := svc.repo.RetrieveParentGroupClients(ctx, parentGroupID) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + + if len(clients) > 0 { + prs := []policies.Policy{} + for _, client := range clients { + prs = append(prs, policies.Policy{ + SubjectType: policies.GroupType, + Subject: client.ParentGroup, + Relation: policies.ParentGroupRelation, + ObjectType: policies.ClientType, + Object: client.ID, + }) + } + + if err := svc.policy.DeletePolicies(ctx, prs); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.AddPolicies(ctx, prs); err != nil { + retErr = errors.Wrap(retErr, errors.Wrap(errors.ErrRollbackTx, errRollback)) + } + } + }() + + if err := svc.repo.UnsetParentGroupFromClient(ctx, parentGroupID); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + } + return nil +} diff --git a/clients/roleactions.go b/clients/roleactions.go new file mode 100644 index 0000000000..79f7b35e4d --- /dev/null +++ b/clients/roleactions.go @@ -0,0 +1,44 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package clients + +import "github.com/absmach/magistrala/pkg/roles" + +// Below codes should moved out of service, may be can be kept in `cmd//main.go` + +const ( + ClientUpdate roles.Action = "update" + ClientRead roles.Action = "read" + ClientDelete roles.Action = "delete" + ClientSetParentGroup roles.Action = "set_parent_group" + ClientConnectToChannel roles.Action = "connect_to_channel" + ClientManageRole roles.Action = "manage_role" + ClientAddRoleUsers roles.Action = "add_role_users" + ClientRemoveRoleUsers roles.Action = "remove_role_users" + ClientViewRoleUsers roles.Action = "view_role_users" +) + +const ( + ClientBuiltInRoleAdmin = "admin" +) + +func AvailableActions() []roles.Action { + return []roles.Action{ + ClientUpdate, + ClientRead, + ClientDelete, + ClientSetParentGroup, + ClientConnectToChannel, + ClientManageRole, + ClientAddRoleUsers, + ClientRemoveRoleUsers, + ClientViewRoleUsers, + } +} + +func BuiltInRoles() map[roles.BuiltInRoleName][]roles.Action { + return map[roles.BuiltInRoleName][]roles.Action{ + ClientBuiltInRoleAdmin: AvailableActions(), + } +} diff --git a/clients/roleoperations.go b/clients/roleoperations.go new file mode 100644 index 0000000000..2da41050ef --- /dev/null +++ b/clients/roleoperations.go @@ -0,0 +1,167 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package clients + +import ( + "github.com/absmach/magistrala/pkg/roles" + "github.com/absmach/magistrala/pkg/svcutil" +) + +// Internal Operations + +const ( + OpViewClient svcutil.Operation = iota + OpUpdateClient + OpUpdateClientTags + OpUpdateClientSecret + OpEnableClient + OpDisableClient + OpDeleteClient + OpSetParentGroup + OpRemoveParentGroup + OpConnectToChannel + OpDisconnectFromChannel +) + +var expectedOperations = []svcutil.Operation{ + OpViewClient, + OpUpdateClient, + OpUpdateClientTags, + OpUpdateClientSecret, + OpEnableClient, + OpDisableClient, + OpDeleteClient, + OpSetParentGroup, + OpRemoveParentGroup, + OpConnectToChannel, + OpDisconnectFromChannel, +} + +var operationNames = []string{ + "OpViewClient", + "OpUpdateClient", + "OpUpdateClientTags", + "OpUpdateClientSecret", + "OpEnableClient", + "OpDisableClient", + "OpDeleteClient", + "OpSetParentGroup", + "OpRemoveParentGroup", + "OpConnectToChannel", + "OpDisconnectFromChannel", +} + +func NewOperationPerm() svcutil.OperationPerm { + return svcutil.NewOperationPerm(expectedOperations, operationNames) +} + +// External Operations +const ( + DomainOpCreateClient svcutil.ExternalOperation = iota + DomainOpListClients + GroupOpSetChildClient + GroupsOpRemoveChildClient + ChannelsOpConnectChannel + ChannelsOpDisconnectChannel +) + +var expectedExternalOperations = []svcutil.ExternalOperation{ + DomainOpCreateClient, + DomainOpListClients, + GroupOpSetChildClient, + GroupsOpRemoveChildClient, + ChannelsOpConnectChannel, + ChannelsOpDisconnectChannel, +} + +var externalOperationNames = []string{ + "DomainOpCreateClient", + "DomainOpListClients", + "GroupOpSetChildClient", + "GroupsOpRemoveChildClient", + "ChannelsOpConnectChannel", + "ChannelsOpDisconnectChannel", +} + +func NewExternalOperationPerm() svcutil.ExternalOperationPerm { + return svcutil.NewExternalOperationPerm(expectedExternalOperations, externalOperationNames) +} + +// Below codes should moved out of service, may be can be kept in `cmd//main.go` + +const ( + updatePermission = "update_permission" + readPermission = "read_permission" + deletePermission = "delete_permission" + setParentGroupPermission = "set_parent_group_permission" + connectToChannelPermission = "connect_to_channel_permission" + + manageRolePermission = "manage_role_permission" + addRoleUsersPermission = "add_role_users_permission" + removeRoleUsersPermission = "remove_role_users_permission" + viewRoleUsersPermission = "view_role_users_permission" +) + +func NewOperationPermissionMap() map[svcutil.Operation]svcutil.Permission { + opPerm := map[svcutil.Operation]svcutil.Permission{ + OpViewClient: readPermission, + OpUpdateClient: updatePermission, + OpUpdateClientTags: updatePermission, + OpUpdateClientSecret: updatePermission, + OpEnableClient: updatePermission, + OpDisableClient: updatePermission, + OpDeleteClient: deletePermission, + OpSetParentGroup: setParentGroupPermission, + OpRemoveParentGroup: setParentGroupPermission, + OpConnectToChannel: connectToChannelPermission, + OpDisconnectFromChannel: connectToChannelPermission, + } + return opPerm +} + +func NewRolesOperationPermissionMap() map[svcutil.Operation]svcutil.Permission { + opPerm := map[svcutil.Operation]svcutil.Permission{ + roles.OpAddRole: manageRolePermission, + roles.OpRemoveRole: manageRolePermission, + roles.OpUpdateRoleName: manageRolePermission, + roles.OpRetrieveRole: manageRolePermission, + roles.OpRetrieveAllRoles: manageRolePermission, + roles.OpRoleAddActions: manageRolePermission, + roles.OpRoleListActions: manageRolePermission, + roles.OpRoleCheckActionsExists: manageRolePermission, + roles.OpRoleRemoveActions: manageRolePermission, + roles.OpRoleRemoveAllActions: manageRolePermission, + roles.OpRoleAddMembers: addRoleUsersPermission, + roles.OpRoleListMembers: viewRoleUsersPermission, + roles.OpRoleCheckMembersExists: viewRoleUsersPermission, + roles.OpRoleRemoveMembers: removeRoleUsersPermission, + roles.OpRoleRemoveAllMembers: manageRolePermission, + } + return opPerm +} + +const ( + // External Permission + // Domains + domainCreateClientPermission = "client_create_permission" + domainListClientsPermission = "list_clients_permission" + // Groups + groupSetChildClientPermission = "client_create_permission" + groupRemoveChildClientPermission = "client_create_permission" + // Channels + channelsConnectClientPermission = "connect_to_client_permission" + channelsDisconnectClientPermission = "connect_to_client_permission" +) + +func NewExternalOperationPermissionMap() map[svcutil.ExternalOperation]svcutil.Permission { + extOpPerm := map[svcutil.ExternalOperation]svcutil.Permission{ + DomainOpCreateClient: domainCreateClientPermission, + DomainOpListClients: domainListClientsPermission, + GroupOpSetChildClient: groupSetChildClientPermission, + GroupsOpRemoveChildClient: groupRemoveChildClientPermission, + ChannelsOpConnectChannel: channelsConnectClientPermission, + ChannelsOpDisconnectChannel: channelsDisconnectClientPermission, + } + return extOpPerm +} diff --git a/things/roles.go b/clients/roles.go similarity index 98% rename from things/roles.go rename to clients/roles.go index 390ebbc9bc..4d606f1be2 100644 --- a/things/roles.go +++ b/clients/roles.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package things +package clients import ( "encoding/json" diff --git a/things/roles_test.go b/clients/roles_test.go similarity index 79% rename from things/roles_test.go rename to clients/roles_test.go index 2d50aeaa88..e5940d5bd9 100644 --- a/things/roles_test.go +++ b/clients/roles_test.go @@ -1,40 +1,40 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package things_test +package clients_test import ( "testing" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/things" "github.com/stretchr/testify/assert" ) func TestRoleString(t *testing.T) { cases := []struct { desc string - role things.Role + role clients.Role expected string }{ { desc: "User", - role: things.UserRole, + role: clients.UserRole, expected: "user", }, { desc: "Admin", - role: things.AdminRole, + role: clients.AdminRole, expected: "admin", }, { desc: "All", - role: things.AllRole, + role: clients.AllRole, expected: "all", }, { desc: "Unknown", - role: things.Role(100), + role: clients.Role(100), expected: "unknown", }, } @@ -51,38 +51,38 @@ func TestToRole(t *testing.T) { cases := []struct { desc string role string - expected things.Role + expected clients.Role err error }{ { desc: "User", role: "user", - expected: things.UserRole, + expected: clients.UserRole, err: nil, }, { desc: "Admin", role: "admin", - expected: things.AdminRole, + expected: clients.AdminRole, err: nil, }, { desc: "All", role: "all", - expected: things.AllRole, + expected: clients.AllRole, err: nil, }, { desc: "Unknown", role: "unknown", - expected: things.Role(0), + expected: clients.Role(0), err: apiutil.ErrInvalidRole, }, } for _, c := range cases { t.Run(c.desc, func(t *testing.T) { - got, err := things.ToRole(c.role) + got, err := clients.ToRole(c.role) assert.Equal(t, c.err, err, "ToRole() error = %v, expected %v", err, c.err) assert.Equal(t, c.expected, got, "ToRole() = %v, expected %v", got, c.expected) }) @@ -93,31 +93,31 @@ func TestRoleMarshalJSON(t *testing.T) { cases := []struct { desc string expected []byte - role things.Role + role clients.Role err error }{ { desc: "User", expected: []byte(`"user"`), - role: things.UserRole, + role: clients.UserRole, err: nil, }, { desc: "Admin", expected: []byte(`"admin"`), - role: things.AdminRole, + role: clients.AdminRole, err: nil, }, { desc: "All", expected: []byte(`"all"`), - role: things.AllRole, + role: clients.AllRole, err: nil, }, { desc: "Unknown", expected: []byte(`"unknown"`), - role: things.Role(100), + role: clients.Role(100), err: nil, }, } @@ -134,31 +134,31 @@ func TestRoleMarshalJSON(t *testing.T) { func TestRoleUnmarshalJSON(t *testing.T) { cases := []struct { desc string - expected things.Role + expected clients.Role role []byte err error }{ { desc: "User", - expected: things.UserRole, + expected: clients.UserRole, role: []byte(`"user"`), err: nil, }, { desc: "Admin", - expected: things.AdminRole, + expected: clients.AdminRole, role: []byte(`"admin"`), err: nil, }, { desc: "All", - expected: things.AllRole, + expected: clients.AllRole, role: []byte(`"all"`), err: nil, }, { desc: "Unknown", - expected: things.Role(0), + expected: clients.Role(0), role: []byte(`"unknown"`), err: apiutil.ErrInvalidRole, }, @@ -166,7 +166,7 @@ func TestRoleUnmarshalJSON(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - var r things.Role + var r clients.Role err := r.UnmarshalJSON(tc.role) assert.Equal(t, tc.err, err, "UnmarshalJSON() error = %v, expected %v", err, tc.err) assert.Equal(t, tc.expected, r, "UnmarshalJSON() = %v, expected %v", r, tc.expected) diff --git a/things/service.go b/clients/service.go similarity index 50% rename from things/service.go rename to clients/service.go index 475902084d..f7012354a2 100644 --- a/things/service.go +++ b/clients/service.go @@ -1,63 +1,62 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package things +package clients import ( "context" + "fmt" "time" - "github.com/absmach/magistrala" + mg "github.com/absmach/magistrala" mgauth "github.com/absmach/magistrala/auth" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcCommonV1 "github.com/absmach/magistrala/internal/grpc/common/v1" + grpcGroupsV1 "github.com/absmach/magistrala/internal/grpc/groups/v1" + "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/roles" "golang.org/x/sync/errgroup" ) -type service struct { - evaluator policies.Evaluator - policysvc policies.Service - clients Repository - clientCache Cache - idProvider magistrala.IDProvider -} +var ( + errRollbackRepo = errors.New("failed to rollback repo") + errSetParentGroup = errors.New("client already have parent") +) +var _ Service = (*service)(nil) -// NewService returns a new Things service implementation. -func NewService(policyEvaluator policies.Evaluator, policyService policies.Service, c Repository, tcache Cache, idp magistrala.IDProvider) Service { - return service{ - evaluator: policyEvaluator, - policysvc: policyService, - clients: c, - clientCache: tcache, - idProvider: idp, - } +type service struct { + repo Repository + policy policies.Service + channels grpcChannelsV1.ChannelsServiceClient + groups grpcGroupsV1.GroupsServiceClient + cache Cache + idProvider mg.IDProvider + roles.ProvisionManageService } -func (svc service) Authorize(ctx context.Context, req AuthzReq) (string, error) { - clientID, err := svc.Identify(ctx, req.ClientKey) +// NewService returns a new Clients service implementation. +func NewService(repo Repository, policy policies.Service, cache Cache, channels grpcChannelsV1.ChannelsServiceClient, groups grpcGroupsV1.GroupsServiceClient, idProvider mg.IDProvider, sIDProvider mg.IDProvider) (Service, error) { + rpms, err := roles.NewProvisionManageService(policies.ClientType, repo, policy, sIDProvider, AvailableActions(), BuiltInRoles()) if err != nil { - return "", err + return service{}, err } - - r := policies.Policy{ - SubjectType: policies.GroupType, - Subject: req.ChannelID, - ObjectType: policies.ThingType, - Object: clientID, - Permission: req.Permission, - } - err = svc.evaluator.CheckPolicy(ctx, r) - if err != nil { - return "", errors.Wrap(svcerr.ErrAuthorization, err) - } - - return clientID, nil + return service{ + repo: repo, + policy: policy, + channels: channels, + groups: groups, + cache: cache, + idProvider: idProvider, + ProvisionManageService: rpms, + }, nil } -func (svc service) CreateClients(ctx context.Context, session authn.Session, cli ...Client) ([]Client, error) { +func (svc service) CreateClients(ctx context.Context, session authn.Session, cls ...Client) (retClients []Client, retErr error) { var clients []Client - for _, c := range cli { + for _, c := range cls { if c.ID == "" { clientID, err := svc.idProvider.ID() if err != nil { @@ -80,45 +79,57 @@ func (svc service) CreateClients(ctx context.Context, session authn.Session, cli clients = append(clients, c) } - err := svc.addClientPolicies(ctx, session.DomainUserID, session.DomainID, clients) + saved, err := svc.repo.Save(ctx, clients...) if err != nil { - return []Client{}, err + return nil, errors.Wrap(svcerr.ErrCreateEntity, err) + } + clientIDs := []string{} + for _, c := range saved { + clientIDs = append(clientIDs, c.ID) } + defer func() { - if err != nil { - if errRollback := svc.addClientPoliciesRollback(ctx, session.DomainUserID, session.DomainID, clients); errRollback != nil { - err = errors.Wrap(errors.Wrap(errors.ErrRollbackTx, errRollback), err) + if retErr != nil { + if errRollBack := svc.repo.Delete(ctx, clientIDs...); errRollBack != nil { + retErr = errors.Wrap(retErr, errors.Wrap(errRollbackRepo, errRollBack)) } } }() - saved, err := svc.clients.Save(ctx, clients...) - if err != nil { - return nil, errors.Wrap(svcerr.ErrCreateEntity, err) + newBuiltInRoleMembers := map[roles.BuiltInRoleName][]roles.Member{ + ClientBuiltInRoleAdmin: {roles.Member(session.UserID)}, + } + + optionalPolicies := []policies.Policy{} + + for _, clientID := range clientIDs { + optionalPolicies = append(optionalPolicies, + policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.DomainType, + Subject: session.DomainID, + Relation: policies.DomainRelation, + ObjectType: policies.ClientType, + Object: clientID, + }, + ) + } + + if _, err := svc.AddNewEntitiesRoles(ctx, session.DomainID, session.UserID, clientIDs, optionalPolicies, newBuiltInRoleMembers); err != nil { + return []Client{}, errors.Wrap(svcerr.ErrAddPolicies, err) } return saved, nil } func (svc service) View(ctx context.Context, session authn.Session, id string) (Client, error) { - client, err := svc.clients.RetrieveByID(ctx, id) + client, err := svc.repo.RetrieveByID(ctx, id) if err != nil { return Client{}, errors.Wrap(svcerr.ErrViewEntity, err) } return client, nil } -func (svc service) ViewPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - permissions, err := svc.listUserClientPermission(ctx, session.DomainUserID, id) - if err != nil { - return nil, err - } - if len(permissions) == 0 { - return nil, svcerr.ErrAuthorization - } - return permissions, nil -} - func (svc service) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm Page) (ClientsPage, error) { var ids []string var err error @@ -148,7 +159,7 @@ func (svc service) ListClients(ctx context.Context, session authn.Session, reqUs return ClientsPage{}, nil } pm.IDs = ids - tp, err := svc.clients.SearchClients(ctx, pm) + tp, err := svc.repo.SearchClients(ctx, pm) if err != nil { return ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) } @@ -182,11 +193,11 @@ func (svc service) retrievePermissions(ctx context.Context, userID string, clien } func (svc service) listUserClientPermission(ctx context.Context, userID, clientID string) ([]string, error) { - permissions, err := svc.policysvc.ListPermissions(ctx, policies.Policy{ + permissions, err := svc.policy.ListPermissions(ctx, policies.Policy{ SubjectType: policies.UserType, Subject: userID, Object: clientID, - ObjectType: policies.ThingType, + ObjectType: policies.ClientType, }, []string{}) if err != nil { return []string{}, errors.Wrap(svcerr.ErrAuthorization, err) @@ -195,11 +206,11 @@ func (svc service) listUserClientPermission(ctx context.Context, userID, clientI } func (svc service) listClientIDs(ctx context.Context, userID, permission string) ([]string, error) { - tids, err := svc.policysvc.ListAllObjects(ctx, policies.Policy{ + tids, err := svc.policy.ListAllObjects(ctx, policies.Policy{ SubjectType: policies.UserType, Subject: userID, Permission: permission, - ObjectType: policies.ThingType, + ObjectType: policies.ClientType, }) if err != nil { return nil, errors.Wrap(svcerr.ErrNotFound, err) @@ -209,11 +220,11 @@ func (svc service) listClientIDs(ctx context.Context, userID, permission string) func (svc service) filterAllowedClientIDs(ctx context.Context, userID, permission string, clientIDs []string) ([]string, error) { var ids []string - tids, err := svc.policysvc.ListAllObjects(ctx, policies.Policy{ + tids, err := svc.policy.ListAllObjects(ctx, policies.Policy{ SubjectType: policies.UserType, Subject: userID, Permission: permission, - ObjectType: policies.ThingType, + ObjectType: policies.ClientType, }) if err != nil { return nil, errors.Wrap(svcerr.ErrNotFound, err) @@ -228,29 +239,29 @@ func (svc service) filterAllowedClientIDs(ctx context.Context, userID, permissio return ids, nil } -func (svc service) Update(ctx context.Context, session authn.Session, thi Client) (Client, error) { +func (svc service) Update(ctx context.Context, session authn.Session, cli Client) (Client, error) { client := Client{ - ID: thi.ID, - Name: thi.Name, - Metadata: thi.Metadata, + ID: cli.ID, + Name: cli.Name, + Metadata: cli.Metadata, UpdatedAt: time.Now(), UpdatedBy: session.UserID, } - client, err := svc.clients.Update(ctx, client) + client, err := svc.repo.Update(ctx, client) if err != nil { return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err) } return client, nil } -func (svc service) UpdateTags(ctx context.Context, session authn.Session, thi Client) (Client, error) { +func (svc service) UpdateTags(ctx context.Context, session authn.Session, cli Client) (Client, error) { client := Client{ - ID: thi.ID, - Tags: thi.Tags, + ID: cli.ID, + Tags: cli.Tags, UpdatedAt: time.Now(), UpdatedBy: session.UserID, } - client, err := svc.clients.UpdateTags(ctx, client) + client, err := svc.repo.UpdateTags(ctx, client) if err != nil { return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err) } @@ -267,7 +278,7 @@ func (svc service) UpdateSecret(ctx context.Context, session authn.Session, id, UpdatedBy: session.UserID, Status: EnabledStatus, } - client, err := svc.clients.UpdateSecret(ctx, client) + client, err := svc.repo.UpdateSecret(ctx, client) if err != nil { return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err) } @@ -299,64 +310,148 @@ func (svc service) Disable(ctx context.Context, session authn.Session, id string return Client{}, errors.Wrap(ErrDisableClient, err) } - if err := svc.clientCache.Remove(ctx, client.ID); err != nil { + if err := svc.cache.Remove(ctx, client.ID); err != nil { return client, errors.Wrap(svcerr.ErrRemoveEntity, err) } return client, nil } -func (svc service) Share(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { - policyList := []policies.Policy{} - for _, userid := range userids { - policyList = append(policyList, policies.Policy{ - SubjectType: policies.UserType, - Subject: mgauth.EncodeDomainUserID(session.DomainID, userid), - Relation: relation, - ObjectType: policies.ThingType, - Object: id, - }) +func (svc service) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) (retErr error) { + cli, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrUpdateEntity, err) + } + switch cli.ParentGroup { + case parentGroupID: + return nil + case "": + // No action needed, proceed to next code after switch + default: + return errors.Wrap(svcerr.ErrConflict, errSetParentGroup) } - if err := svc.policysvc.AddPolicies(ctx, policyList); err != nil { + + resp, err := svc.groups.RetrieveEntity(ctx, &grpcCommonV1.RetrieveEntityReq{Id: parentGroupID}) + if err != nil { return errors.Wrap(svcerr.ErrUpdateEntity, err) } + if resp.GetEntity().GetDomainId() != session.DomainID { + return errors.Wrap(svcerr.ErrUpdateEntity, fmt.Errorf("parent group id %s has invalid domain id", parentGroupID)) + } + if resp.GetEntity().GetStatus() != uint32(EnabledStatus) { + return errors.Wrap(svcerr.ErrUpdateEntity, fmt.Errorf("parent group id %s is not in enabled state", parentGroupID)) + } + + var pols []policies.Policy + + pols = append(pols, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: parentGroupID, + Relation: policies.ParentGroupRelation, + ObjectType: policies.ClientType, + Object: id, + }) + + if err := svc.policy.AddPolicies(ctx, pols); err != nil { + return errors.Wrap(svcerr.ErrAddPolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.DeletePolicies(ctx, pols); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + cli = Client{ID: id, ParentGroup: parentGroupID, UpdatedBy: session.UserID, UpdatedAt: time.Now()} + if err := svc.repo.SetParentGroup(ctx, cli); err != nil { + return errors.Wrap(svcerr.ErrUpdateEntity, err) + } return nil } -func (svc service) Unshare(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { - policyList := []policies.Policy{} - for _, userid := range userids { - policyList = append(policyList, policies.Policy{ - SubjectType: policies.UserType, - Subject: mgauth.EncodeDomainUserID(session.DomainID, userid), - Relation: relation, - ObjectType: policies.ThingType, +func (svc service) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (retErr error) { + cli, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + + if cli.ParentGroup != "" { + var pols []policies.Policy + pols = append(pols, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: cli.ParentGroup, + Relation: policies.ParentGroupRelation, + ObjectType: policies.ClientType, Object: id, }) - } - if err := svc.policysvc.DeletePolicies(ctx, policyList); err != nil { - return errors.Wrap(svcerr.ErrUpdateEntity, err) - } + if err := svc.policy.DeletePolicies(ctx, pols); err != nil { + return errors.Wrap(svcerr.ErrAddPolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.AddPolicies(ctx, pols); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + + cli := Client{ID: id, UpdatedBy: session.UserID, UpdatedAt: time.Now()} + + if err := svc.repo.RemoveParentGroup(ctx, cli); err != nil { + return err + } + } return nil } func (svc service) Delete(ctx context.Context, session authn.Session, id string) error { - if err := svc.clientCache.Remove(ctx, id); err != nil { + ok, err := svc.repo.DoesClientHaveConnections(ctx, id) + if err != nil { return errors.Wrap(svcerr.ErrRemoveEntity, err) } + if ok { + if _, err := svc.channels.RemoveClientConnections(ctx, &grpcChannelsV1.RemoveClientConnectionsReq{ClientId: id}); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + } - req := policies.Policy{ - Object: id, - ObjectType: policies.ThingType, + if _, err := svc.repo.ChangeStatus(ctx, Client{ID: id, Status: DeletedStatus}); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) } - if err := svc.policysvc.DeletePolicyFilter(ctx, req); err != nil { + if err := svc.cache.Remove(ctx, id); err != nil { return errors.Wrap(svcerr.ErrRemoveEntity, err) } - if err := svc.clients.Delete(ctx, id); err != nil { + filterDeletePolicies := []policies.Policy{ + { + SubjectType: policies.ClientType, + Subject: id, + }, + { + ObjectType: policies.ClientType, + Object: id, + }, + } + deletePolicies := []policies.Policy{ + { + SubjectType: policies.DomainType, + Subject: session.DomainID, + Relation: policies.DomainRelation, + ObjectType: policies.ClientType, + Object: id, + }, + } + + if err := svc.RemoveEntitiesRoles(ctx, session.DomainID, session.DomainUserID, []string{id}, filterDeletePolicies, deletePolicies); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + if err := svc.repo.Delete(ctx, id); err != nil { return errors.Wrap(svcerr.ErrRemoveEntity, err) } @@ -364,7 +459,7 @@ func (svc service) Delete(ctx context.Context, session authn.Session, id string) } func (svc service) changeClientStatus(ctx context.Context, session authn.Session, client Client) (Client, error) { - dbClient, err := svc.clients.RetrieveByID(ctx, client.ID) + dbClient, err := svc.repo.RetrieveByID(ctx, client.ID) if err != nil { return Client{}, errors.Wrap(svcerr.ErrViewEntity, err) } @@ -374,122 +469,9 @@ func (svc service) changeClientStatus(ctx context.Context, session authn.Session client.UpdatedBy = session.UserID - client, err = svc.clients.ChangeStatus(ctx, client) + client, err = svc.repo.ChangeStatus(ctx, client) if err != nil { return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err) } return client, nil } - -func (svc service) ListClientsByGroup(ctx context.Context, session authn.Session, groupID string, pm Page) (MembersPage, error) { - tids, err := svc.policysvc.ListAllObjects(ctx, policies.Policy{ - SubjectType: policies.GroupType, - Subject: groupID, - Permission: policies.GroupRelation, - ObjectType: policies.ThingType, - }) - if err != nil { - return MembersPage{}, errors.Wrap(svcerr.ErrNotFound, err) - } - - pm.IDs = tids.Policies - - cp, err := svc.clients.RetrieveAllByIDs(ctx, pm) - if err != nil { - return MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - if pm.ListPerms && len(cp.Clients) > 0 { - g, ctx := errgroup.WithContext(ctx) - - for i := range cp.Clients { - // Copying loop variable "i" to avoid "loop variable captured by func literal" - iter := i - g.Go(func() error { - return svc.retrievePermissions(ctx, session.DomainUserID, &cp.Clients[iter]) - }) - } - - if err := g.Wait(); err != nil { - return MembersPage{}, err - } - } - - return MembersPage{ - Page: cp.Page, - Members: cp.Clients, - }, nil -} - -func (svc service) Identify(ctx context.Context, key string) (string, error) { - id, err := svc.clientCache.ID(ctx, key) - if err == nil { - return id, nil - } - - client, err := svc.clients.RetrieveBySecret(ctx, key) - if err != nil { - return "", errors.Wrap(svcerr.ErrAuthorization, err) - } - if err := svc.clientCache.Save(ctx, key, client.ID); err != nil { - return "", errors.Wrap(svcerr.ErrAuthorization, err) - } - - return client.ID, nil -} - -func (svc service) addClientPolicies(ctx context.Context, userID, domainID string, clients []Client) error { - policyList := []policies.Policy{} - for _, client := range clients { - policyList = append(policyList, policies.Policy{ - Domain: domainID, - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.AdministratorRelation, - ObjectKind: policies.NewThingKind, - ObjectType: policies.ThingType, - Object: client.ID, - }) - policyList = append(policyList, policies.Policy{ - Domain: domainID, - SubjectType: policies.DomainType, - Subject: domainID, - Relation: policies.DomainRelation, - ObjectType: policies.ThingType, - Object: client.ID, - }) - } - if err := svc.policysvc.AddPolicies(ctx, policyList); err != nil { - return errors.Wrap(svcerr.ErrCreateEntity, err) - } - - return nil -} - -func (svc service) addClientPoliciesRollback(ctx context.Context, userID, domainID string, clients []Client) error { - policyList := []policies.Policy{} - for _, client := range clients { - policyList = append(policyList, policies.Policy{ - Domain: domainID, - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.AdministratorRelation, - ObjectKind: policies.NewThingKind, - ObjectType: policies.ThingType, - Object: client.ID, - }) - policyList = append(policyList, policies.Policy{ - Domain: domainID, - SubjectType: policies.DomainType, - Subject: domainID, - Relation: policies.DomainRelation, - ObjectType: policies.ThingType, - Object: client.ID, - }) - } - if err := svc.policysvc.DeletePolicies(ctx, policyList); err != nil { - return errors.Wrap(svcerr.ErrRemoveEntity, err) - } - - return nil -} diff --git a/clients/service_test.go b/clients/service_test.go new file mode 100644 index 0000000000..e0c3b7be59 --- /dev/null +++ b/clients/service_test.go @@ -0,0 +1,948 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package clients_test + +import ( + "context" + "fmt" + "testing" + + chmocks "github.com/absmach/magistrala/channels/mocks" + "github.com/absmach/magistrala/clients" + climocks "github.com/absmach/magistrala/clients/mocks" + gpmocks "github.com/absmach/magistrala/groups/mocks" + "github.com/absmach/magistrala/internal/testsutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + policysvc "github.com/absmach/magistrala/pkg/policies" + policymocks "github.com/absmach/magistrala/pkg/policies/mocks" + "github.com/absmach/magistrala/pkg/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var ( + secret = "strongsecret" + validTMetadata = clients.Metadata{"role": "client"} + ID = "6e5e10b3-d4df-4758-b426-4929d55ad740" + client = clients.Client{ + ID: ID, + Name: "clientname", + Tags: []string{"tag1", "tag2"}, + Credentials: clients.Credentials{Identity: "clientidentity", Secret: secret}, + Metadata: validTMetadata, + Status: clients.EnabledStatus, + } + validToken = "token" + valid = "valid" + invalid = "invalid" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" + wrongID = testsutil.GenerateUUID(&testing.T{}) + errRemovePolicies = errors.New("failed to delete policies") +) + +var ( + pService *policymocks.Service + pEvaluator *policymocks.Evaluator + cache *climocks.Cache + repo *climocks.Repository +) + +func newService() clients.Service { + pService = new(policymocks.Service) + cache = new(climocks.Cache) + idProvider := uuid.NewMock() + sidProvider := uuid.NewMock() + repo = new(climocks.Repository) + chgRPCClient := new(chmocks.ChannelsServiceClient) + gpgRPCClient := new(gpmocks.GroupsServiceClient) + tsv, _ := clients.NewService(repo, pService, cache, chgRPCClient, gpgRPCClient, idProvider, sidProvider) + return tsv +} + +func TestCreateClients(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + client clients.Client + token string + addPolicyErr error + deletePolicyErr error + saveErr error + err error + }{ + { + desc: "create a new client successfully", + client: client, + token: validToken, + err: nil, + }, + { + desc: "create an existing client", + client: client, + token: validToken, + saveErr: repoerr.ErrConflict, + err: repoerr.ErrConflict, + }, + { + desc: "create a new client without secret", + client: clients.Client{ + Name: "clientWithoutSecret", + Credentials: clients.Credentials{ + Identity: "newclientwithoutsecret@example.com", + }, + Status: clients.EnabledStatus, + }, + token: validToken, + err: nil, + }, + { + desc: "create a new client without identity", + client: clients.Client{ + Name: "clientWithoutIdentity", + Credentials: clients.Credentials{ + Identity: "newclientwithoutsecret@example.com", + }, + Status: clients.EnabledStatus, + }, + token: validToken, + err: nil, + }, + { + desc: "create a new enabled client with name", + client: clients.Client{ + Name: "clientWithName", + Credentials: clients.Credentials{ + Identity: "newclientwithname@example.com", + Secret: secret, + }, + Status: clients.EnabledStatus, + }, + token: validToken, + err: nil, + }, + + { + desc: "create a new disabled client with name", + client: clients.Client{ + Name: "clientWithName", + Credentials: clients.Credentials{ + Identity: "newclientwithname@example.com", + Secret: secret, + }, + }, + token: validToken, + err: nil, + }, + { + desc: "create 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, + }, + token: validToken, + err: nil, + }, + { + desc: "create 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, + }, + token: validToken, + err: nil, + }, + { + desc: "create a new enabled client with metadata", + client: clients.Client{ + Credentials: clients.Credentials{ + Identity: "newclientwithmetadata@example.com", + Secret: secret, + }, + Metadata: validTMetadata, + Status: clients.EnabledStatus, + }, + token: validToken, + err: nil, + }, + { + desc: "create a new disabled client with metadata", + client: clients.Client{ + Credentials: clients.Credentials{ + Identity: "newclientwithmetadata@example.com", + Secret: secret, + }, + Metadata: validTMetadata, + }, + token: validToken, + err: nil, + }, + { + desc: "create a new disabled client", + client: clients.Client{ + Credentials: clients.Credentials{ + Identity: "newclientwithvalidstatus@example.com", + Secret: secret, + }, + }, + token: validToken, + err: nil, + }, + { + desc: "create a new client with valid disabled status", + client: clients.Client{ + Credentials: clients.Credentials{ + Identity: "newclientwithvalidstatus@example.com", + Secret: secret, + }, + Status: clients.DisabledStatus, + }, + token: validToken, + err: nil, + }, + { + desc: "create 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, + }, + token: validToken, + err: nil, + }, + { + desc: "create a new client with invalid status", + client: clients.Client{ + Credentials: clients.Credentials{ + Identity: "newclientwithinvalidstatus@example.com", + Secret: secret, + }, + Status: clients.AllStatus, + }, + token: validToken, + err: svcerr.ErrInvalidStatus, + }, + { + desc: "create a new client with failed add policies response", + client: clients.Client{ + Credentials: clients.Credentials{ + Identity: "newclientwithfailedpolicy@example.com", + Secret: secret, + }, + Status: clients.EnabledStatus, + }, + token: validToken, + addPolicyErr: svcerr.ErrInvalidPolicy, + err: svcerr.ErrInvalidPolicy, + }, + { + desc: "create a new client with failed delete policies response", + client: clients.Client{ + Credentials: clients.Credentials{ + Identity: "newclientwithfailedpolicy@example.com", + Secret: secret, + }, + Status: clients.EnabledStatus, + }, + token: validToken, + saveErr: repoerr.ErrConflict, + deletePolicyErr: svcerr.ErrInvalidPolicy, + err: repoerr.ErrConflict, + }, + } + + for _, tc := range cases { + repoCall := repo.On("Save", context.Background(), mock.Anything).Return([]clients.Client{tc.client}, tc.saveErr) + policyCall := pService.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPolicyErr) + policyCall1 := pService.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePolicyErr) + expected, err := svc.CreateClients(context.Background(), mgauthn.Session{}, 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 { + tc.client.ID = expected[0].ID + tc.client.CreatedAt = expected[0].CreatedAt + tc.client.UpdatedAt = expected[0].UpdatedAt + tc.client.Credentials.Secret = expected[0].Credentials.Secret + tc.client.Domain = expected[0].Domain + tc.client.UpdatedBy = expected[0].UpdatedBy + assert.Equal(t, tc.client, expected[0], fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.client, expected[0])) + } + repoCall.Unset() + policyCall.Unset() + policyCall1.Unset() + } +} + +func TestViewClient(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + clientID string + response clients.Client + retrieveErr error + err error + }{ + { + desc: "view client successfully", + response: client, + clientID: client.ID, + err: nil, + }, + { + desc: "view client with an invalid token", + response: clients.Client{}, + clientID: "", + err: svcerr.ErrAuthorization, + }, + { + desc: "view client with valid token and invalid client id", + response: clients.Client{}, + clientID: wrongID, + retrieveErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "view client with an invalid token and invalid client id", + response: clients.Client{}, + clientID: wrongID, + err: svcerr.ErrAuthorization, + }, + } + + for _, tc := range cases { + repoCall1 := repo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.response, tc.err) + rClient, err := svc.View(context.Background(), mgauthn.Session{}, 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)) + repoCall1.Unset() + } +} + +func TestListClients(t *testing.T) { + svc := newService() + + adminID := testsutil.GenerateUUID(t) + domainID := testsutil.GenerateUUID(t) + nonAdminID := testsutil.GenerateUUID(t) + client.Permissions = []string{"read", "write"} + + cases := []struct { + desc string + userKind string + session mgauthn.Session + page clients.Page + listObjectsResponse policysvc.PolicyPage + retrieveAllResponse clients.ClientsPage + listPermissionsResponse policysvc.Permissions + response clients.ClientsPage + id string + size uint64 + listObjectsErr error + retrieveAllErr error + listPermissionsErr error + err error + }{ + { + desc: "list all clients successfully as non admin", + userKind: "non-admin", + session: mgauthn.Session{UserID: nonAdminID, DomainID: domainID, SuperAdmin: false}, + id: nonAdminID, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + }, + listObjectsResponse: policysvc.PolicyPage{Policies: []string{client.ID, client.ID}}, + retrieveAllResponse: clients.ClientsPage{ + Page: clients.Page{ + Total: 2, + Offset: 0, + Limit: 100, + }, + Clients: []clients.Client{client, client}, + }, + listPermissionsResponse: []string{"read", "write"}, + response: clients.ClientsPage{ + Page: clients.Page{ + Total: 2, + Offset: 0, + Limit: 100, + }, + Clients: []clients.Client{client, client}, + }, + err: nil, + }, + { + desc: "list all clients as non admin with failed to retrieve all", + userKind: "non-admin", + session: mgauthn.Session{UserID: nonAdminID, DomainID: domainID, SuperAdmin: false}, + id: nonAdminID, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + }, + listObjectsResponse: policysvc.PolicyPage{Policies: []string{client.ID, client.ID}}, + retrieveAllResponse: clients.ClientsPage{}, + response: clients.ClientsPage{}, + retrieveAllErr: repoerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "list all clients as non admin with failed to list permissions", + userKind: "non-admin", + session: mgauthn.Session{UserID: nonAdminID, DomainID: domainID, SuperAdmin: false}, + id: nonAdminID, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + }, + listObjectsResponse: policysvc.PolicyPage{Policies: []string{client.ID, client.ID}}, + retrieveAllResponse: clients.ClientsPage{ + Page: clients.Page{ + Total: 2, + Offset: 0, + Limit: 100, + }, + Clients: []clients.Client{client, client}, + }, + listPermissionsResponse: []string{}, + response: clients.ClientsPage{}, + listPermissionsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "list all clients as non admin with failed super admin", + userKind: "non-admin", + session: mgauthn.Session{UserID: nonAdminID, DomainID: domainID, SuperAdmin: false}, + id: nonAdminID, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + }, + response: clients.ClientsPage{}, + listObjectsResponse: policysvc.PolicyPage{}, + err: nil, + }, + { + desc: "list all clients as non admin with failed to list objects", + userKind: "non-admin", + id: nonAdminID, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + }, + response: clients.ClientsPage{}, + listObjectsResponse: policysvc.PolicyPage{}, + listObjectsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + } + + for _, tc := range cases { + listAllObjectsCall := pService.On("ListAllObjects", mock.Anything, mock.Anything).Return(tc.listObjectsResponse, tc.listObjectsErr) + retrieveAllCall := repo.On("SearchClients", mock.Anything, mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) + listPermissionsCall := pService.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionsErr) + page, err := svc.ListClients(context.Background(), tc.session, tc.id, 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)) + listAllObjectsCall.Unset() + retrieveAllCall.Unset() + listPermissionsCall.Unset() + } + + cases2 := []struct { + desc string + userKind string + session mgauthn.Session + page clients.Page + listObjectsResponse policysvc.PolicyPage + retrieveAllResponse clients.ClientsPage + listPermissionsResponse policysvc.Permissions + response clients.ClientsPage + id string + size uint64 + listObjectsErr error + retrieveAllErr error + listPermissionsErr error + err error + }{ + { + desc: "list all clients as admin successfully", + userKind: "admin", + id: adminID, + session: mgauthn.Session{UserID: adminID, DomainID: domainID, SuperAdmin: true}, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + Domain: domainID, + }, + listObjectsResponse: policysvc.PolicyPage{Policies: []string{client.ID, client.ID}}, + retrieveAllResponse: clients.ClientsPage{ + Page: clients.Page{ + Total: 2, + Offset: 0, + Limit: 100, + }, + Clients: []clients.Client{client, client}, + }, + listPermissionsResponse: []string{"read", "write"}, + response: clients.ClientsPage{ + Page: clients.Page{ + Total: 2, + Offset: 0, + Limit: 100, + }, + Clients: []clients.Client{client, client}, + }, + err: nil, + }, + { + desc: "list all clients as admin with failed to retrieve all", + userKind: "admin", + id: adminID, + session: mgauthn.Session{UserID: adminID, DomainID: domainID, SuperAdmin: true}, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + Domain: domainID, + }, + listObjectsResponse: policysvc.PolicyPage{}, + retrieveAllResponse: clients.ClientsPage{}, + retrieveAllErr: repoerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "list all clients as admin with failed to list permissions", + userKind: "admin", + id: adminID, + session: mgauthn.Session{UserID: adminID, DomainID: domainID, SuperAdmin: true}, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + Domain: domainID, + }, + listObjectsResponse: policysvc.PolicyPage{}, + retrieveAllResponse: clients.ClientsPage{ + Page: clients.Page{ + Total: 2, + Offset: 0, + Limit: 100, + }, + Clients: []clients.Client{client, client}, + }, + listPermissionsResponse: []string{}, + listPermissionsErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "list all clients as admin with failed to list clients", + userKind: "admin", + id: adminID, + session: mgauthn.Session{UserID: adminID, DomainID: domainID, SuperAdmin: true}, + page: clients.Page{ + Offset: 0, + Limit: 100, + ListPerms: true, + Domain: domainID, + }, + retrieveAllResponse: clients.ClientsPage{}, + retrieveAllErr: repoerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + } + + for _, tc := range cases2 { + listAllObjectsCall := pService.On("ListAllObjects", context.Background(), policysvc.Policy{ + SubjectType: policysvc.UserType, + Subject: tc.session.DomainID + "_" + adminID, + Permission: "", + ObjectType: policysvc.ClientType, + }).Return(tc.listObjectsResponse, tc.listObjectsErr) + listAllObjectsCall2 := pService.On("ListAllObjects", context.Background(), policysvc.Policy{ + SubjectType: policysvc.UserType, + Subject: tc.session.UserID, + Permission: "", + ObjectType: policysvc.ClientType, + }).Return(tc.listObjectsResponse, tc.listObjectsErr) + retrieveAllCall := repo.On("SearchClients", mock.Anything, mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) + listPermissionsCall := pService.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionsErr) + page, err := svc.ListClients(context.Background(), tc.session, tc.id, 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)) + listAllObjectsCall.Unset() + listAllObjectsCall2.Unset() + retrieveAllCall.Unset() + listPermissionsCall.Unset() + } +} + +func TestUpdateClient(t *testing.T) { + svc := newService() + + client1 := client + client2 := client + client1.Name = "Updated client" + client2.Metadata = clients.Metadata{"role": "test"} + + cases := []struct { + desc string + client clients.Client + session mgauthn.Session + updateResponse clients.Client + updateErr error + err error + }{ + { + desc: "update client name successfully", + client: client1, + session: mgauthn.Session{UserID: validID}, + updateResponse: client1, + err: nil, + }, + { + desc: "update client metadata with valid token", + client: client2, + updateResponse: client2, + session: mgauthn.Session{UserID: validID}, + err: nil, + }, + { + desc: "update client with failed to update repo", + client: client1, + updateResponse: clients.Client{}, + session: mgauthn.Session{UserID: validID}, + updateErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, + }, + } + + for _, tc := range cases { + repoCall1 := repo.On("Update", context.Background(), mock.Anything).Return(tc.updateResponse, tc.updateErr) + updatedClient, err := svc.Update(context.Background(), tc.session, 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.updateResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateResponse, updatedClient)) + repoCall1.Unset() + } +} + +func TestUpdateTags(t *testing.T) { + svc := newService() + + client.Tags = []string{"updated"} + + cases := []struct { + desc string + client clients.Client + session mgauthn.Session + updateResponse clients.Client + updateErr error + err error + }{ + { + desc: "update client tags successfully", + client: client, + session: mgauthn.Session{UserID: validID}, + updateResponse: client, + err: nil, + }, + { + desc: "update client tags with failed to update repo", + client: client, + updateResponse: clients.Client{}, + session: mgauthn.Session{UserID: validID}, + updateErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, + }, + } + + for _, tc := range cases { + repoCall1 := repo.On("UpdateTags", context.Background(), mock.Anything).Return(tc.updateResponse, tc.updateErr) + updatedClient, err := svc.UpdateTags(context.Background(), tc.session, 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.updateResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateResponse, updatedClient)) + repoCall1.Unset() + } +} + +func TestUpdateSecret(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + client clients.Client + newSecret string + updateSecretResponse clients.Client + session mgauthn.Session + updateErr error + err error + }{ + { + desc: "update client secret successfully", + client: client, + newSecret: "newSecret", + session: mgauthn.Session{UserID: validID}, + updateSecretResponse: clients.Client{ + ID: client.ID, + Credentials: clients.Credentials{ + Identity: client.Credentials.Identity, + Secret: "newSecret", + }, + }, + err: nil, + }, + { + desc: "update client secret with failed to update repo", + client: client, + newSecret: "newSecret", + session: mgauthn.Session{UserID: validID}, + updateSecretResponse: clients.Client{}, + updateErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, + }, + } + + for _, tc := range cases { + repoCall := repo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.updateSecretResponse, tc.updateErr) + updatedClient, err := svc.UpdateSecret(context.Background(), tc.session, tc.client.ID, 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.updateSecretResponse, updatedClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateSecretResponse, updatedClient)) + repoCall.Unset() + } +} + +func TestEnable(t *testing.T) { + svc := newService() + + enabledClient1 := clients.Client{ID: ID, Credentials: clients.Credentials{Identity: "client1@example.com", Secret: "password"}, Status: clients.EnabledStatus} + disabledClient1 := clients.Client{ID: ID, Credentials: clients.Credentials{Identity: "client3@example.com", Secret: "password"}, Status: clients.DisabledStatus} + endisabledClient1 := disabledClient1 + endisabledClient1.Status = clients.EnabledStatus + + cases := []struct { + desc string + id string + session mgauthn.Session + client clients.Client + changeStatusResponse clients.Client + retrieveByIDResponse clients.Client + changeStatusErr error + retrieveIDErr error + err error + }{ + { + desc: "enable disabled client", + id: disabledClient1.ID, + session: mgauthn.Session{UserID: validID}, + client: disabledClient1, + changeStatusResponse: endisabledClient1, + retrieveByIDResponse: disabledClient1, + err: nil, + }, + { + desc: "enable disabled client with failed to update repo", + id: disabledClient1.ID, + session: mgauthn.Session{UserID: validID}, + client: disabledClient1, + changeStatusResponse: clients.Client{}, + retrieveByIDResponse: disabledClient1, + changeStatusErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, + }, + { + desc: "enable enabled client", + id: enabledClient1.ID, + session: mgauthn.Session{UserID: validID}, + client: enabledClient1, + changeStatusResponse: enabledClient1, + retrieveByIDResponse: enabledClient1, + changeStatusErr: errors.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, + }, + { + desc: "enable non-existing client", + id: wrongID, + session: mgauthn.Session{UserID: validID}, + client: clients.Client{}, + changeStatusResponse: clients.Client{}, + retrieveByIDResponse: clients.Client{}, + retrieveIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + repoCall := repo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveIDErr) + repoCall1 := repo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) + _, err := svc.Enable(context.Background(), tc.session, 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() + } +} + +func TestDisable(t *testing.T) { + svc := newService() + + enabledClient1 := clients.Client{ID: ID, Credentials: clients.Credentials{Identity: "client1@example.com", Secret: "password"}, Status: clients.EnabledStatus} + disabledClient1 := clients.Client{ID: ID, Credentials: clients.Credentials{Identity: "client3@example.com", Secret: "password"}, Status: clients.DisabledStatus} + disenabledClient1 := enabledClient1 + disenabledClient1.Status = clients.DisabledStatus + + cases := []struct { + desc string + id string + session mgauthn.Session + client clients.Client + changeStatusResponse clients.Client + retrieveByIDResponse clients.Client + changeStatusErr error + retrieveIDErr error + removeErr error + err error + }{ + { + desc: "disable enabled client", + id: enabledClient1.ID, + session: mgauthn.Session{UserID: validID}, + client: enabledClient1, + changeStatusResponse: disenabledClient1, + retrieveByIDResponse: enabledClient1, + err: nil, + }, + { + desc: "disable client with failed to update repo", + id: enabledClient1.ID, + session: mgauthn.Session{UserID: validID}, + client: enabledClient1, + changeStatusResponse: clients.Client{}, + retrieveByIDResponse: enabledClient1, + changeStatusErr: repoerr.ErrMalformedEntity, + err: svcerr.ErrUpdateEntity, + }, + { + desc: "disable disabled client", + id: disabledClient1.ID, + session: mgauthn.Session{UserID: validID}, + client: disabledClient1, + changeStatusResponse: clients.Client{}, + retrieveByIDResponse: disabledClient1, + changeStatusErr: errors.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, + }, + { + desc: "disable non-existing client", + id: wrongID, + client: clients.Client{}, + session: mgauthn.Session{UserID: validID}, + changeStatusResponse: clients.Client{}, + retrieveByIDResponse: clients.Client{}, + retrieveIDErr: repoerr.ErrNotFound, + err: repoerr.ErrNotFound, + }, + { + desc: "disable client with failed to remove from cache", + id: enabledClient1.ID, + session: mgauthn.Session{UserID: validID}, + client: disabledClient1, + changeStatusResponse: disenabledClient1, + retrieveByIDResponse: enabledClient1, + removeErr: svcerr.ErrRemoveEntity, + err: svcerr.ErrRemoveEntity, + }, + } + + for _, tc := range cases { + repoCall := repo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveIDErr) + repoCall1 := repo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) + repoCall2 := cache.On("Remove", mock.Anything, mock.Anything).Return(tc.removeErr) + _, err := svc.Disable(context.Background(), tc.session, 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() + } +} + +func TestDelete(t *testing.T) { + svc := newService() + + client := clients.Client{ + ID: testsutil.GenerateUUID(t), + } + + cases := []struct { + desc string + clientID string + removeErr error + deleteErr error + deletePolicyErr error + err error + }{ + { + desc: "Delete client successfully", + clientID: client.ID, + err: nil, + }, + { + desc: "Delete non-existing client", + clientID: wrongID, + deleteErr: repoerr.ErrNotFound, + err: svcerr.ErrRemoveEntity, + }, + { + desc: "Delete client with repo error ", + clientID: client.ID, + deleteErr: repoerr.ErrRemoveEntity, + err: repoerr.ErrRemoveEntity, + }, + { + desc: "Delete client with cache error ", + clientID: client.ID, + removeErr: svcerr.ErrRemoveEntity, + err: repoerr.ErrRemoveEntity, + }, + { + desc: "Delete client with failed to delete policies", + clientID: client.ID, + deletePolicyErr: errRemovePolicies, + err: errRemovePolicies, + }, + } + + for _, tc := range cases { + repoCall := cache.On("Remove", mock.Anything, tc.clientID).Return(tc.removeErr) + policyCall := pService.On("DeletePolicyFilter", context.Background(), mock.Anything).Return(tc.deletePolicyErr) + repoCall1 := repo.On("Delete", context.Background(), tc.clientID).Return(tc.deleteErr) + err := svc.Delete(context.Background(), mgauthn.Session{}, tc.clientID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + policyCall.Unset() + repoCall1.Unset() + } +} diff --git a/things/standalone/doc.go b/clients/standalone/doc.go similarity index 69% rename from things/standalone/doc.go rename to clients/standalone/doc.go index 68ca6a78d6..9ad956bafe 100644 --- a/things/standalone/doc.go +++ b/clients/standalone/doc.go @@ -3,7 +3,7 @@ // Package standalone contains implementation for auth service in // single-user scenario. Running with a single user provides -// Things as a standalone service with one admin user who -// manages all the Things and Channels and does not +// Clients as a standalone service with one admin user who +// manages all the Clients and Channels and does not // require connection to Auth service. package standalone diff --git a/things/standalone/standalone.go b/clients/standalone/standalone.go similarity index 100% rename from things/standalone/standalone.go rename to clients/standalone/standalone.go diff --git a/things/status.go b/clients/status.go similarity index 99% rename from things/status.go rename to clients/status.go index f34ed99beb..b66bf2e3ac 100644 --- a/things/status.go +++ b/clients/status.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package things +package clients import ( "encoding/json" diff --git a/things/status_test.go b/clients/status_test.go similarity index 78% rename from things/status_test.go rename to clients/status_test.go index 9df845bf55..29016e3e66 100644 --- a/things/status_test.go +++ b/clients/status_test.go @@ -1,45 +1,45 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package things_test +package clients_test import ( "testing" + "github.com/absmach/magistrala/clients" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/things" "github.com/stretchr/testify/assert" ) func TestStatusString(t *testing.T) { cases := []struct { desc string - status things.Status + status clients.Status expected string }{ { desc: "Enabled", - status: things.EnabledStatus, + status: clients.EnabledStatus, expected: "enabled", }, { desc: "Disabled", - status: things.DisabledStatus, + status: clients.DisabledStatus, expected: "disabled", }, { desc: "Deleted", - status: things.DeletedStatus, + status: clients.DeletedStatus, expected: "deleted", }, { desc: "All", - status: things.AllStatus, + status: clients.AllStatus, expected: "all", }, { desc: "Unknown", - status: things.Status(100), + status: clients.Status(100), expected: "unknown", }, } @@ -56,44 +56,44 @@ func TestToStatus(t *testing.T) { cases := []struct { desc string status string - expetcted things.Status + expetcted clients.Status err error }{ { desc: "Enabled", status: "enabled", - expetcted: things.EnabledStatus, + expetcted: clients.EnabledStatus, err: nil, }, { desc: "Disabled", status: "disabled", - expetcted: things.DisabledStatus, + expetcted: clients.DisabledStatus, err: nil, }, { desc: "Deleted", status: "deleted", - expetcted: things.DeletedStatus, + expetcted: clients.DeletedStatus, err: nil, }, { desc: "All", status: "all", - expetcted: things.AllStatus, + expetcted: clients.AllStatus, err: nil, }, { desc: "Unknown", status: "unknown", - expetcted: things.Status(0), + expetcted: clients.Status(0), err: svcerr.ErrInvalidStatus, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - got, err := things.ToStatus(tc.status) + got, err := clients.ToStatus(tc.status) assert.Equal(t, tc.err, err, "ToStatus() error = %v, expected %v", err, tc.err) assert.Equal(t, tc.expetcted, got, "ToStatus() = %v, expected %v", got, tc.expetcted) }) @@ -104,37 +104,37 @@ func TestStatusMarshalJSON(t *testing.T) { cases := []struct { desc string expected []byte - status things.Status + status clients.Status err error }{ { desc: "Enabled", expected: []byte(`"enabled"`), - status: things.EnabledStatus, + status: clients.EnabledStatus, err: nil, }, { desc: "Disabled", expected: []byte(`"disabled"`), - status: things.DisabledStatus, + status: clients.DisabledStatus, err: nil, }, { desc: "Deleted", expected: []byte(`"deleted"`), - status: things.DeletedStatus, + status: clients.DeletedStatus, err: nil, }, { desc: "All", expected: []byte(`"all"`), - status: things.AllStatus, + status: clients.AllStatus, err: nil, }, { desc: "Unknown", expected: []byte(`"unknown"`), - status: things.Status(100), + status: clients.Status(100), err: nil, }, } @@ -151,37 +151,37 @@ func TestStatusMarshalJSON(t *testing.T) { func TestStatusUnmarshalJSON(t *testing.T) { cases := []struct { desc string - expected things.Status + expected clients.Status status []byte err error }{ { desc: "Enabled", - expected: things.EnabledStatus, + expected: clients.EnabledStatus, status: []byte(`"enabled"`), err: nil, }, { desc: "Disabled", - expected: things.DisabledStatus, + expected: clients.DisabledStatus, status: []byte(`"disabled"`), err: nil, }, { desc: "Deleted", - expected: things.DeletedStatus, + expected: clients.DeletedStatus, status: []byte(`"deleted"`), err: nil, }, { desc: "All", - expected: things.AllStatus, + expected: clients.AllStatus, status: []byte(`"all"`), err: nil, }, { desc: "Unknown", - expected: things.Status(0), + expected: clients.Status(0), status: []byte(`"unknown"`), err: svcerr.ErrInvalidStatus, }, @@ -189,7 +189,7 @@ func TestStatusUnmarshalJSON(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - var s things.Status + var s clients.Status err := s.UnmarshalJSON(tc.status) assert.Equal(t, tc.err, err, "UnmarshalJSON() error = %v, expected %v", err, tc.err) assert.Equal(t, tc.expected, s, "UnmarshalJSON() = %v, expected %v", s, tc.expected) @@ -201,37 +201,37 @@ func TestUserMarshalJSON(t *testing.T) { cases := []struct { desc string expected []byte - user things.Client + user clients.Client err error }{ { desc: "Enabled", expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"enabled"}`), - user: things.Client{Status: things.EnabledStatus}, + user: clients.Client{Status: clients.EnabledStatus}, err: nil, }, { desc: "Disabled", expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"disabled"}`), - user: things.Client{Status: things.DisabledStatus}, + user: clients.Client{Status: clients.DisabledStatus}, err: nil, }, { desc: "Deleted", expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"deleted"}`), - user: things.Client{Status: things.DeletedStatus}, + user: clients.Client{Status: clients.DeletedStatus}, err: nil, }, { desc: "All", expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"all"}`), - user: things.Client{Status: things.AllStatus}, + user: clients.Client{Status: clients.AllStatus}, err: nil, }, { desc: "Unknown", expected: []byte(`{"id":"","credentials":{},"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","status":"unknown"}`), - user: things.Client{Status: things.Status(100)}, + user: clients.Client{Status: clients.Status(100)}, err: nil, }, } diff --git a/clients/tracing/doc.go b/clients/tracing/doc.go new file mode 100644 index 0000000000..42a4ad48e5 --- /dev/null +++ b/clients/tracing/doc.go @@ -0,0 +1,12 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package tracing provides tracing instrumentation for Magistrala clients clients service. +// +// This package provides tracing middleware for Magistrala clients clients service. +// It can be used to trace incoming requests and add tracing capabilities to +// Magistrala clients clients service. +// +// For more details about tracing instrumentation for Magistrala messaging refer +// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. +package tracing diff --git a/clients/tracing/tracing.go b/clients/tracing/tracing.go new file mode 100644 index 0000000000..e7d600aaed --- /dev/null +++ b/clients/tracing/tracing.go @@ -0,0 +1,118 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/pkg/authn" + rmTrace "github.com/absmach/magistrala/pkg/roles/rolemanager/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +var _ clients.Service = (*tracingMiddleware)(nil) + +type tracingMiddleware struct { + tracer trace.Tracer + svc clients.Service + rmTrace.RoleManagerTracing +} + +// New returns a new group service with tracing capabilities. +func New(svc clients.Service, tracer trace.Tracer) clients.Service { + return &tracingMiddleware{ + tracer: tracer, + svc: svc, + RoleManagerTracing: rmTrace.NewRoleManagerTracing("group", svc, tracer), + } +} + +// CreateClients traces the "CreateClients" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) CreateClients(ctx context.Context, session authn.Session, cli ...clients.Client) ([]clients.Client, error) { + ctx, span := tm.tracer.Start(ctx, "svc_create_client") + defer span.End() + + return tm.svc.CreateClients(ctx, session, cli...) +} + +// View traces the "View" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) View(ctx context.Context, session authn.Session, 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.View(ctx, session, id) +} + +// ListClients traces the "ListClients" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm clients.Page) (clients.ClientsPage, error) { + ctx, span := tm.tracer.Start(ctx, "svc_list_clients") + defer span.End() + return tm.svc.ListClients(ctx, session, reqUserID, pm) +} + +// Update traces the "Update" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) Update(ctx context.Context, session authn.Session, cli clients.Client) (clients.Client, error) { + ctx, span := tm.tracer.Start(ctx, "svc_update_client", trace.WithAttributes(attribute.String("id", cli.ID))) + defer span.End() + + return tm.svc.Update(ctx, session, cli) +} + +// UpdateTags traces the "UpdateTags" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) UpdateTags(ctx context.Context, session authn.Session, cli clients.Client) (clients.Client, error) { + ctx, span := tm.tracer.Start(ctx, "svc_update_client_tags", trace.WithAttributes( + attribute.String("id", cli.ID), + attribute.StringSlice("tags", cli.Tags), + )) + defer span.End() + + return tm.svc.UpdateTags(ctx, session, cli) +} + +// UpdateSecret traces the "UpdateSecret" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (clients.Client, error) { + ctx, span := tm.tracer.Start(ctx, "svc_update_client_secret") + defer span.End() + + return tm.svc.UpdateSecret(ctx, session, oldSecret, newSecret) +} + +// Enable traces the "Enable" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) Enable(ctx context.Context, session authn.Session, 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.Enable(ctx, session, id) +} + +// Disable traces the "Disable" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) Disable(ctx context.Context, session authn.Session, 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.Disable(ctx, session, id) +} + +// Delete traces the "Delete" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { + ctx, span := tm.tracer.Start(ctx, "delete_client", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + return tm.svc.Delete(ctx, session, id) +} + +func (tm *tracingMiddleware) SetParentGroup(ctx context.Context, session authn.Session, parentGroupID string, id string) error { + ctx, span := tm.tracer.Start(ctx, "set_parent_group", trace.WithAttributes( + attribute.String("id", id), + attribute.String("parent_group_id", parentGroupID), + )) + defer span.End() + return tm.svc.SetParentGroup(ctx, session, parentGroupID, id) +} + +func (tm *tracingMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + ctx, span := tm.tracer.Start(ctx, "remove_parent_group", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + return tm.svc.RemoveParentGroup(ctx, session, id) +} diff --git a/cmd/auth/main.go b/cmd/auth/main.go index a29477830c..f75691b2b9 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -17,13 +17,13 @@ import ( "github.com/absmach/magistrala/auth" api "github.com/absmach/magistrala/auth/api" authgrpcapi "github.com/absmach/magistrala/auth/api/grpc/auth" - domainsgrpcapi "github.com/absmach/magistrala/auth/api/grpc/domains" tokengrpcapi "github.com/absmach/magistrala/auth/api/grpc/token" httpapi "github.com/absmach/magistrala/auth/api/http" - "github.com/absmach/magistrala/auth/events" "github.com/absmach/magistrala/auth/jwt" apostgres "github.com/absmach/magistrala/auth/postgres" "github.com/absmach/magistrala/auth/tracing" + grpcAuthV1 "github.com/absmach/magistrala/internal/grpc/auth/v1" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/jaeger" "github.com/absmach/magistrala/pkg/policies/spicedb" @@ -103,7 +103,8 @@ func main() { logger.Error(err.Error()) } - db, err := pgclient.Setup(dbConfig, *apostgres.Migration()) + am := apostgres.Migration() + db, err := pgclient.Setup(dbConfig, *am) if err != nil { logger.Error(err.Error()) exitCode = 1 @@ -130,17 +131,8 @@ func main() { exitCode = 1 return } - svc := newService(ctx, db, tracer, cfg, dbConfig, logger, spicedbclient) - httpServerConfig := server.Config{Port: defSvcHTTPPort} - if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err.Error())) - exitCode = 1 - return - } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, logger, cfg.InstanceID), logger) - grpcServerConfig := server.Config{Port: defSvcGRPCPort} if err := env.ParseWithOptions(&grpcServerConfig, env.Options{Prefix: envPrefixGrpc}); err != nil { logger.Error(fmt.Sprintf("failed to load %s gRPC server configuration : %s", svcName, err.Error())) @@ -149,9 +141,8 @@ func main() { } registerAuthServiceServer := func(srv *grpc.Server) { reflection.Register(srv) - magistrala.RegisterTokenServiceServer(srv, tokengrpcapi.NewTokenServer(svc)) - magistrala.RegisterDomainsServiceServer(srv, domainsgrpcapi.NewDomainsServer(svc)) - magistrala.RegisterAuthServiceServer(srv, authgrpcapi.NewAuthServer(svc)) + grpcTokenV1.RegisterTokenServiceServer(srv, tokengrpcapi.NewTokenServer(svc)) + grpcAuthV1.RegisterAuthServiceServer(srv, authgrpcapi.NewAuthServer(svc)) } gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerAuthServiceServer, logger) @@ -160,12 +151,25 @@ func main() { chc := chclient.New(svcName, magistrala.Version, logger, cancel) go chc.CallHome(ctx) } - g.Go(func() error { - return hs.Start() + return gs.Start() }) + + httpServerConfig := server.Config{Port: defSvcHTTPPort} + if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, logger, cfg.InstanceID), logger) + + if cfg.SendTelemetry { + chc := chclient.New(svcName, magistrala.Version, logger, cancel) + go chc.CallHome(ctx) + } + g.Go(func() error { - return gs.Start() + return hs.Start() }) g.Go(func() error { @@ -207,10 +211,9 @@ func initSchema(ctx context.Context, client *authzed.ClientWithExperimental, sch return nil } -func newService(ctx context.Context, db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental) auth.Service { +func newService(_ context.Context, db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental) auth.Service { database := postgres.NewDatabase(db, dbConfig, tracer) keysRepo := apostgres.New(database) - domainsRepo := apostgres.NewDomainRepository(database) idProvider := uuid.New() pEvaluator := spicedb.NewPolicyEvaluator(spicedbClient, logger) @@ -218,14 +221,9 @@ func newService(ctx context.Context, db *sqlx.DB, tracer trace.Tracer, cfg confi t := jwt.New([]byte(cfg.SecretKey)) - svc := auth.New(keysRepo, domainsRepo, idProvider, t, pEvaluator, pService, cfg.AccessDuration, cfg.RefreshDuration, cfg.InvitationDuration) - svc, err := events.NewEventStoreMiddleware(ctx, svc, cfg.ESURL) - if err != nil { - logger.Error(fmt.Sprintf("failed to init event store middleware : %s", err)) - return nil - } + svc := auth.New(keysRepo, idProvider, t, pEvaluator, pService, cfg.AccessDuration, cfg.RefreshDuration, cfg.InvitationDuration) svc = api.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics("groups", "api") + counter, latency := prometheus.MakeMetrics("auth", "api") svc = api.MetricsMiddleware(svc, counter, latency) svc = tracing.New(svc, tracer) diff --git a/cmd/bootstrap/main.go b/cmd/bootstrap/main.go index cfe998b41e..d468d68baf 100644 --- a/cmd/bootstrap/main.go +++ b/cmd/bootstrap/main.go @@ -55,15 +55,15 @@ const ( defDB = "bootstrap" defSvcHTTPPort = "9013" - thingsStream = "events.magistrala.things" - streamID = "magistrala.bootstrap" + stream = "events.magistrala.clients" + streamID = "magistrala.bootstrap" ) type config struct { LogLevel string `env:"MG_BOOTSTRAP_LOG_LEVEL" envDefault:"info"` EncKey string `env:"MG_BOOTSTRAP_ENCRYPT_KEY" envDefault:"12345678910111213141516171819202"` ESConsumerName string `env:"MG_BOOTSTRAP_EVENT_CONSUMER" envDefault:"bootstrap"` - ThingsURL string `env:"MG_THINGS_URL" envDefault:"http://localhost:9000"` + ClientsURL string `env:"MG_CLIENTS_URL" envDefault:"http://localhost:9000"` JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` InstanceID string `env:"MG_BOOTSTRAP_INSTANCE_ID" envDefault:""` @@ -165,8 +165,8 @@ func main() { return } - if err = subscribeToThingsES(ctx, svc, cfg, logger); err != nil { - logger.Error(fmt.Sprintf("failed to subscribe to things event store: %s", err)) + if err = subscribeToClientsES(ctx, svc, cfg, logger); err != nil { + logger.Error(fmt.Sprintf("failed to subscribe to clients event store: %s", err)) exitCode = 1 return } @@ -205,7 +205,7 @@ func newService(ctx context.Context, authz mgauthz.Authorization, policySvc poli repoConfig := bootstrappg.NewConfigRepository(database, logger) config := mgsdk.Config{ - ThingsURL: cfg.ThingsURL, + ClientsURL: cfg.ClientsURL, } sdk := mgsdk.NewSDK(config) @@ -228,14 +228,14 @@ func newService(ctx context.Context, authz mgauthz.Authorization, policySvc poli return svc, nil } -func subscribeToThingsES(ctx context.Context, svc bootstrap.Service, cfg config, logger *slog.Logger) error { +func subscribeToClientsES(ctx context.Context, svc bootstrap.Service, cfg config, logger *slog.Logger) error { subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, logger) if err != nil { return err } subConfig := events.SubscriberConfig{ - Stream: thingsStream, + Stream: stream, Consumer: cfg.ESConsumerName, Handler: consumer.NewEventHandler(svc), } diff --git a/cmd/certs/main.go b/cmd/certs/main.go index 1b75d80d6f..b910fecf4e 100644 --- a/cmd/certs/main.go +++ b/cmd/certs/main.go @@ -43,7 +43,7 @@ const ( type config struct { LogLevel string `env:"MG_CERTS_LOG_LEVEL" envDefault:"info"` - ThingsURL string `env:"MG_THINGS_URL" envDefault:"http://localhost:9000"` + ClientsURL string `env:"MG_CLIENTS_URL" envDefault:"http://localhost:9000"` JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` InstanceID string `env:"MG_CERTS_INSTANCE_ID" envDefault:""` @@ -155,7 +155,7 @@ func main() { func newService(tracer trace.Tracer, logger *slog.Logger, cfg config, pkiAgent pki.Agent) certs.Service { config := mgsdk.Config{ - ThingsURL: cfg.ThingsURL, + ClientsURL: cfg.ClientsURL, } sdk := mgsdk.NewSDK(config) svc := certs.New(sdk, pkiAgent) diff --git a/cmd/channels/main.go b/cmd/channels/main.go new file mode 100644 index 0000000000..776c08e19f --- /dev/null +++ b/cmd/channels/main.go @@ -0,0 +1,307 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package main contains clients main function to start the clients service. +package main + +import ( + "context" + "fmt" + "log" + "log/slog" + "net/url" + "os" + + chclient "github.com/absmach/callhome/pkg/client" + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/channels" + grpcapi "github.com/absmach/magistrala/channels/api/grpc" + httpapi "github.com/absmach/magistrala/channels/api/http" + "github.com/absmach/magistrala/channels/events" + "github.com/absmach/magistrala/channels/middleware" + "github.com/absmach/magistrala/channels/postgres" + pChannels "github.com/absmach/magistrala/channels/private" + "github.com/absmach/magistrala/channels/tracing" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + grpcGroupsV1 "github.com/absmach/magistrala/internal/grpc/groups/v1" + mglog "github.com/absmach/magistrala/logger" + authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" + mgauthz "github.com/absmach/magistrala/pkg/authz" + authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc" + "github.com/absmach/magistrala/pkg/grpcclient" + jaegerclient "github.com/absmach/magistrala/pkg/jaeger" + "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/policies/spicedb" + pg "github.com/absmach/magistrala/pkg/postgres" + pgclient "github.com/absmach/magistrala/pkg/postgres" + "github.com/absmach/magistrala/pkg/prometheus" + "github.com/absmach/magistrala/pkg/server" + grpcserver "github.com/absmach/magistrala/pkg/server/grpc" + httpserver "github.com/absmach/magistrala/pkg/server/http" + "github.com/absmach/magistrala/pkg/sid" + "github.com/absmach/magistrala/pkg/uuid" + "github.com/authzed/authzed-go/v1" + "github.com/authzed/grpcutil" + "github.com/caarlos0/env/v11" + "github.com/go-chi/chi/v5" + "github.com/jmoiron/sqlx" + "go.opentelemetry.io/otel/trace" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/reflection" +) + +const ( + svcName = "channels" + envPrefixDB = "MG_CHANNELS_DB_" + envPrefixHTTP = "MG_CHANNELS_HTTP_" + envPrefixGRPC = "MG_CHANNELS_GRPC_" + envPrefixAuth = "MG_AUTH_GRPC_" + envPrefixClients = "MG_CLIENTS_AUTH_GRPC_" + envPrefixGroups = "MG_GROUPS_GRPC_" + defDB = "channels" + defSvcHTTPPort = "9005" + defSvcGRPCPort = "7005" +) + +type config struct { + LogLevel string `env:"MG_CHANNELS_LOG_LEVEL" envDefault:"info"` + InstanceID string `env:"MG_CHANNELS_INSTANCE_ID" envDefault:""` + JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` + ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` + TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` + SpicedbHost string `env:"MG_SPICEDB_HOST" envDefault:"localhost"` + SpicedbPort string `env:"MG_SPICEDB_PORT" envDefault:"50051"` + SpicedbPreSharedKey string `env:"MG_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"` +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + g, ctx := errgroup.WithContext(ctx) + + // Create new channels configuration + cfg := config{} + if err := env.Parse(&cfg); err != nil { + log.Fatalf("failed to load %s configuration : %s", svcName, err) + } + + var logger *slog.Logger + logger, err := mglog.New(os.Stdout, cfg.LogLevel) + if err != nil { + log.Fatalf("failed to init logger: %s", err.Error()) + } + + var exitCode int + defer mglog.ExitWithError(&exitCode) + + if cfg.InstanceID == "" { + if cfg.InstanceID, err = uuid.New().ID(); err != nil { + logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err)) + exitCode = 1 + return + } + } + + // Create new database for clients + dbConfig := pgclient.Config{Name: defDB} + if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + migrations, err := postgres.Migration() + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + db, err := pgclient.Setup(dbConfig, *migrations) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer db.Close() + + tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio) + if err != nil { + logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err)) + exitCode = 1 + return + } + defer func() { + if err := tp.Shutdown(ctx); err != nil { + logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err)) + } + }() + tracer := tp.Tracer(svcName) + + policyEvaluator, policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + logger.Info("Policy service are successfully connected to SpiceDB gRPC server") + + grpcCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil { + logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) + exitCode = 1 + return + } + authn, authnClient, err := authsvcAuthn.NewAuthentication(ctx, grpcCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer authnClient.Close() + logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure()) + + authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer authzClient.Close() + logger.Info("AuthZ successfully connected to auth gRPC server " + authzClient.Secure()) + + thgrpcCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&thgrpcCfg, env.Options{Prefix: envPrefixClients}); err != nil { + logger.Error(fmt.Sprintf("failed to load clients gRPC client configuration : %s", err)) + exitCode = 1 + return + } + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, thgrpcCfg) + if err != nil { + logger.Error(fmt.Sprintf("failed to connect to clients gRPC server: %s", err)) + exitCode = 1 + return + } + defer clientsHandler.Close() + logger.Info("Clients gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) + + groupsgRPCCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&groupsgRPCCfg, env.Options{Prefix: envPrefixGroups}); err != nil { + logger.Error(fmt.Sprintf("failed to load groups gRPC client configuration : %s", err)) + exitCode = 1 + return + } + groupsClient, groupsHandler, err := grpcclient.SetupGroupsClient(ctx, groupsgRPCCfg) + if err != nil { + logger.Error(fmt.Sprintf("failed to connect to groups gRPC server: %s", err)) + exitCode = 1 + return + } + defer groupsHandler.Close() + logger.Info("Groups gRPC client successfully connected to groups gRPC server " + groupsHandler.Secure()) + + svc, psvc, err := newService(ctx, db, dbConfig, authz, policyEvaluator, policyService, cfg.ESURL, tracer, clientsClient, groupsClient, logger) + if err != nil { + logger.Error(fmt.Sprintf("failed to create services: %s", err)) + exitCode = 1 + return + } + + grpcServerConfig := server.Config{Port: defSvcGRPCPort} + if err := env.ParseWithOptions(&grpcServerConfig, env.Options{Prefix: envPrefixGRPC}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s gRPC server configuration : %s", svcName, err)) + exitCode = 1 + return + } + registerChannelsServer := func(srv *grpc.Server) { + reflection.Register(srv) + grpcChannelsV1.RegisterChannelsServiceServer(srv, grpcapi.NewServer(psvc)) + } + + gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerChannelsServer, logger) + + httpServerConfig := server.Config{Port: defSvcHTTPPort} + if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err)) + exitCode = 1 + return + } + mux := chi.NewRouter() + httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, mux, logger, cfg.InstanceID), logger) + + if cfg.SendTelemetry { + chc := chclient.New(svcName, magistrala.Version, logger, cancel) + go chc.CallHome(ctx) + } + + // Start all servers + g.Go(func() error { + return httpSvc.Start() + }) + + g.Go(func() error { + return gs.Start() + }) + + g.Go(func() error { + return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSvc) + }) + + if err := g.Wait(); err != nil { + logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err)) + } +} + +func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authz mgauthz.Authorization, + pe policies.Evaluator, ps policies.Service, esURL string, tracer trace.Tracer, clientsClient grpcClientsV1.ClientsServiceClient, + groupsClient grpcGroupsV1.GroupsServiceClient, logger *slog.Logger, +) (channels.Service, pChannels.Service, error) { + database := pg.NewDatabase(db, dbConfig, tracer) + repo := postgres.NewRepository(database) + + idp := uuid.New() + sidp, err := sid.New() + if err != nil { + return nil, nil, err + } + + svc, err := channels.New(repo, ps, idp, clientsClient, groupsClient, sidp) + if err != nil { + return nil, nil, err + } + + svc, err = events.NewEventStoreMiddleware(ctx, svc, esURL) + if err != nil { + return nil, nil, err + } + + svc = tracing.New(svc, tracer) + + counter, latency := prometheus.MakeMetrics("channels", "api") + svc = middleware.MetricsMiddleware(svc, counter, latency) + + svc, err = middleware.AuthorizationMiddleware(svc, repo, authz, channels.NewOperationPermissionMap(), channels.NewRolesOperationPermissionMap(), channels.NewExternalOperationPermissionMap()) + if err != nil { + return nil, nil, err + } + svc = middleware.LoggingMiddleware(svc, logger) + + psvc := pChannels.New(repo, pe, ps) + return svc, psvc, err +} + +func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Evaluator, policies.Service, error) { + client, err := authzed.NewClientWithExperimentalAPIs( + fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey), + ) + if err != nil { + return nil, nil, err + } + ps := spicedb.NewPolicyService(client, logger) + + pe := spicedb.NewPolicyEvaluator(client, logger) + return pe, ps, nil +} diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 7ed42dfbb5..8e7a708c7e 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -37,7 +37,7 @@ func main() { healthCmd := cli.NewHealthCmd() usersCmd := cli.NewUsersCmd() domainsCmd := cli.NewDomainsCmd() - thingsCmd := cli.NewThingsCmd() + clientsCmd := cli.NewClientsCmd() groupsCmd := cli.NewGroupsCmd() channelsCmd := cli.NewChannelsCmd() messagesCmd := cli.NewMessagesCmd() @@ -54,7 +54,7 @@ func main() { rootCmd.AddCommand(usersCmd) rootCmd.AddCommand(domainsCmd) rootCmd.AddCommand(groupsCmd) - rootCmd.AddCommand(thingsCmd) + rootCmd.AddCommand(clientsCmd) rootCmd.AddCommand(channelsCmd) rootCmd.AddCommand(messagesCmd) rootCmd.AddCommand(provisionCmd) @@ -83,11 +83,11 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &sdkConf.ThingsURL, - "things-url", + &sdkConf.ClientsURL, + "clients-url", "t", - sdkConf.ThingsURL, - "Things service URL", + sdkConf.ClientsURL, + "Clients service URL", ) rootCmd.PersistentFlags().StringVarP( diff --git a/cmd/things/main.go b/cmd/clients/main.go similarity index 61% rename from cmd/things/main.go rename to cmd/clients/main.go index 6410e6abc2..6188ceaac7 100644 --- a/cmd/things/main.go +++ b/cmd/clients/main.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package main contains things main function to start the things service. +// Package main contains clients main function to start the clients service. package main import ( @@ -15,36 +15,35 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" + "github.com/absmach/magistrala/clients" + grpcapi "github.com/absmach/magistrala/clients/api/grpc" + httpapi "github.com/absmach/magistrala/clients/api/http" + "github.com/absmach/magistrala/clients/cache" + "github.com/absmach/magistrala/clients/events" + "github.com/absmach/magistrala/clients/middleware" + "github.com/absmach/magistrala/clients/postgres" + pClients "github.com/absmach/magistrala/clients/private" + "github.com/absmach/magistrala/clients/tracing" redisclient "github.com/absmach/magistrala/internal/clients/redis" - mggroups "github.com/absmach/magistrala/internal/groups" - gevents "github.com/absmach/magistrala/internal/groups/events" - gmiddleware "github.com/absmach/magistrala/internal/groups/middleware" - gpostgres "github.com/absmach/magistrala/internal/groups/postgres" - gtracing "github.com/absmach/magistrala/internal/groups/tracing" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + grpcGroupsV1 "github.com/absmach/magistrala/internal/grpc/groups/v1" mglog "github.com/absmach/magistrala/logger" authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" mgauthz "github.com/absmach/magistrala/pkg/authz" authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc" - "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/grpcclient" jaegerclient "github.com/absmach/magistrala/pkg/jaeger" "github.com/absmach/magistrala/pkg/policies" "github.com/absmach/magistrala/pkg/policies/spicedb" - "github.com/absmach/magistrala/pkg/postgres" + pg "github.com/absmach/magistrala/pkg/postgres" pgclient "github.com/absmach/magistrala/pkg/postgres" "github.com/absmach/magistrala/pkg/prometheus" "github.com/absmach/magistrala/pkg/server" grpcserver "github.com/absmach/magistrala/pkg/server/grpc" httpserver "github.com/absmach/magistrala/pkg/server/http" + "github.com/absmach/magistrala/pkg/sid" "github.com/absmach/magistrala/pkg/uuid" - "github.com/absmach/magistrala/things" - grpcapi "github.com/absmach/magistrala/things/api/grpc" - httpapi "github.com/absmach/magistrala/things/api/http" - thcache "github.com/absmach/magistrala/things/cache" - thevents "github.com/absmach/magistrala/things/events" - tmiddleware "github.com/absmach/magistrala/things/middleware" - thingspg "github.com/absmach/magistrala/things/postgres" - ctracing "github.com/absmach/magistrala/things/tracing" "github.com/authzed/authzed-go/v1" "github.com/authzed/grpcutil" "github.com/caarlos0/env/v11" @@ -59,28 +58,28 @@ import ( ) const ( - svcName = "things" - envPrefixDB = "MG_THINGS_DB_" - envPrefixHTTP = "MG_THINGS_HTTP_" - envPrefixGRPC = "MG_THINGS_AUTH_GRPC_" + svcName = "clients" + envPrefixDB = "MG_CLIENTS_DB_" + envPrefixHTTP = "MG_CLIENTS_HTTP_" + envPrefixGRPC = "MG_CLIENTS_AUTH_GRPC_" envPrefixAuth = "MG_AUTH_GRPC_" - defDB = "things" + envPrefixChannels = "MG_CHANNELS_GRPC_" + envPrefixGroups = "MG_GROUPS_GRPC_" + defDB = "clients" defSvcHTTPPort = "9000" defSvcAuthGRPCPort = "7000" - - streamID = "magistrala.things" ) type config struct { - LogLevel string `env:"MG_THINGS_LOG_LEVEL" envDefault:"info"` - StandaloneID string `env:"MG_THINGS_STANDALONE_ID" envDefault:""` - StandaloneToken string `env:"MG_THINGS_STANDALONE_TOKEN" envDefault:""` + InstanceID string `env:"MG_CLIENTS_INSTANCE_ID" envDefault:""` + LogLevel string `env:"MG_CLIENTS_LOG_LEVEL" envDefault:"info"` + StandaloneID string `env:"MG_CLIENTS_STANDALONE_ID" envDefault:""` + StandaloneToken string `env:"MG_CLIENTS_STANDALONE_TOKEN" envDefault:""` + CacheURL string `env:"MG_CLIENTS_CACHE_URL" envDefault:"redis://localhost:6379/0"` + CacheKeyDuration time.Duration `env:"MG_CLIENTS_CACHE_KEY_DURATION" envDefault:"10m"` JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` - CacheKeyDuration time.Duration `env:"MG_THINGS_CACHE_KEY_DURATION" envDefault:"10m"` SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"MG_THINGS_INSTANCE_ID" envDefault:""` ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` - CacheURL string `env:"MG_THINGS_CACHE_URL" envDefault:"redis://localhost:6379/0"` TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` SpicedbHost string `env:"MG_SPICEDB_HOST" envDefault:"localhost"` SpicedbPort string `env:"MG_SPICEDB_PORT" envDefault:"50051"` @@ -91,7 +90,7 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) g, ctx := errgroup.WithContext(ctx) - // Create new things configuration + // Create new clients configuration cfg := config{} if err := env.Parse(&cfg); err != nil { log.Fatalf("failed to load %s configuration : %s", svcName, err) @@ -114,16 +113,19 @@ func main() { } } - // Create new database for things + // Create new database for clients dbConfig := pgclient.Config{Name: defDB} if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { logger.Error(err.Error()) exitCode = 1 return } - tm := thingspg.Migration() - gm := gpostgres.Migration() - tm.Migrations = append(tm.Migrations, gm.Migrations...) + tm, err := postgres.Migration() + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } db, err := pgclient.Setup(dbConfig, *tm) if err != nil { logger.Error(err.Error()) @@ -186,7 +188,37 @@ func main() { defer authzClient.Close() logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure()) - csvc, gsvc, err := newService(ctx, db, dbConfig, authz, policyEvaluator, policyService, cacheclient, cfg.CacheKeyDuration, cfg.ESURL, tracer, logger) + chgrpccfg := grpcclient.Config{} + if err := env.ParseWithOptions(&chgrpccfg, env.Options{Prefix: envPrefixChannels}); err != nil { + logger.Error(fmt.Sprintf("failed to load channels gRPC client configuration : %s", err)) + exitCode = 1 + return + } + channelsgRPC, channelsClient, err := grpcclient.SetupChannelsClient(ctx, chgrpccfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + logger.Info("Channels gRPC client successfully connected to channels gRPC server " + channelsClient.Secure()) + defer channelsClient.Close() + + groupsgRPCCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&groupsgRPCCfg, env.Options{Prefix: envPrefixGroups}); err != nil { + logger.Error(fmt.Sprintf("failed to load groups gRPC client configuration : %s", err)) + exitCode = 1 + return + } + groupsClient, groupsHandler, err := grpcclient.SetupGroupsClient(ctx, groupsgRPCCfg) + if err != nil { + logger.Error(fmt.Sprintf("failed to connect to groups gRPC server: %s", err)) + exitCode = 1 + return + } + defer groupsHandler.Close() + logger.Info("Groups gRPC client successfully connected to groups gRPC server " + groupsHandler.Secure()) + + svc, psvc, err := newService(ctx, db, dbConfig, authz, policyEvaluator, policyService, cacheclient, cfg.CacheKeyDuration, cfg.ESURL, channelsgRPC, groupsClient, tracer, logger) if err != nil { logger.Error(fmt.Sprintf("failed to create services: %s", err)) exitCode = 1 @@ -200,7 +232,7 @@ func main() { return } mux := chi.NewRouter() - httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(csvc, gsvc, authn, mux, logger, cfg.InstanceID), logger) + httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, mux, logger, cfg.InstanceID), logger) grpcServerConfig := server.Config{Port: defSvcAuthGRPCPort} if err := env.ParseWithOptions(&grpcServerConfig, env.Options{Prefix: envPrefixGRPC}); err != nil { @@ -208,11 +240,12 @@ func main() { exitCode = 1 return } - registerThingsServer := func(srv *grpc.Server) { + + registerClientsServer := func(srv *grpc.Server) { reflection.Register(srv) - magistrala.RegisterThingsServiceServer(srv, grpcapi.NewServer(csvc)) + grpcClientsV1.RegisterClientsServiceServer(srv, grpcapi.NewServer(psvc)) } - gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerThingsServer, logger) + gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerClientsServer, logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) @@ -237,42 +270,44 @@ func main() { } } -func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authz mgauthz.Authorization, pe policies.Evaluator, ps policies.Service, cacheClient *redis.Client, keyDuration time.Duration, esURL string, tracer trace.Tracer, logger *slog.Logger) (things.Service, groups.Service, error) { - database := postgres.NewDatabase(db, dbConfig, tracer) - cRepo := thingspg.NewRepository(database) - gRepo := gpostgres.New(database) +func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authz mgauthz.Authorization, pe policies.Evaluator, ps policies.Service, cacheClient *redis.Client, keyDuration time.Duration, esURL string, channels grpcChannelsV1.ChannelsServiceClient, groups grpcGroupsV1.GroupsServiceClient, tracer trace.Tracer, logger *slog.Logger) (clients.Service, pClients.Service, error) { + database := pg.NewDatabase(db, dbConfig, tracer) + repo := postgres.NewRepository(database) idp := uuid.New() + sidp, err := sid.New() + if err != nil { + return nil, nil, err + } - thingCache := thcache.NewCache(cacheClient, keyDuration) - - csvc := things.NewService(pe, ps, cRepo, thingCache, idp) - gsvc := mggroups.NewService(gRepo, idp, ps) + // Clients service + cache := cache.NewCache(cacheClient, keyDuration) - csvc, err := thevents.NewEventStoreMiddleware(ctx, csvc, esURL) + csvc, err := clients.NewService(repo, ps, cache, channels, groups, idp, sidp) if err != nil { return nil, nil, err } - gsvc, err = gevents.NewEventStoreMiddleware(ctx, gsvc, esURL, streamID) + csvc, err = events.NewEventStoreMiddleware(ctx, csvc, esURL) if err != nil { return nil, nil, err } - csvc = tmiddleware.AuthorizationMiddleware(csvc, authz) - gsvc = gmiddleware.AuthorizationMiddleware(gsvc, authz) + csvc = tracing.New(csvc, tracer) - csvc = ctracing.New(csvc, tracer) - csvc = tmiddleware.LoggingMiddleware(csvc, logger) counter, latency := prometheus.MakeMetrics(svcName, "api") - csvc = tmiddleware.MetricsMiddleware(csvc, counter, latency) + csvc = middleware.MetricsMiddleware(csvc, counter, latency) + csvc = middleware.MetricsMiddleware(csvc, counter, latency) + + csvc, err = middleware.AuthorizationMiddleware(policies.ClientType, csvc, authz, repo, clients.NewOperationPermissionMap(), clients.NewRolesOperationPermissionMap(), clients.NewExternalOperationPermissionMap()) + if err != nil { + return nil, nil, err + } + csvc = middleware.LoggingMiddleware(csvc, logger) - gsvc = gtracing.New(gsvc, tracer) - gsvc = gmiddleware.LoggingMiddleware(gsvc, logger) - counter, latency = prometheus.MakeMetrics(fmt.Sprintf("%s_groups", svcName), "api") - gsvc = gmiddleware.MetricsMiddleware(gsvc, counter, latency) + isvc := pClients.New(repo, cache, pe, ps) - return csvc, gsvc, err + return csvc, isvc, err } func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Evaluator, policies.Service, error) { diff --git a/cmd/coap/main.go b/cmd/coap/main.go index ad16e992c1..15b916cf94 100644 --- a/cmd/coap/main.go +++ b/cmd/coap/main.go @@ -31,12 +31,13 @@ import ( ) const ( - svcName = "coap_adapter" - envPrefix = "MG_COAP_ADAPTER_" - envPrefixHTTP = "MG_COAP_ADAPTER_HTTP_" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - defSvcHTTPPort = "5683" - defSvcCoAPPort = "5683" + svcName = "coap_adapter" + envPrefix = "MG_COAP_ADAPTER_" + envPrefixHTTP = "MG_COAP_ADAPTER_HTTP_" + envPrefixClients = "MG_CLIENTS_AUTH_GRPC_" + envPrefixChannels = "MG_CHANNELS_GRPC_" + defSvcHTTPPort = "5683" + defSvcCoAPPort = "5683" ) type config struct { @@ -87,22 +88,38 @@ func main() { return } - thingsClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&thingsClientCfg, env.Options{Prefix: envPrefixThings}); err != nil { + clientsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientsClientCfg, env.Options{Prefix: envPrefixClients}); err != nil { logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) exitCode = 1 return } - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, clientsClientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer thingsHandler.Close() + defer clientsHandler.Close() - logger.Info("Things service gRPC client successfully connected to things gRPC server " + thingsHandler.Secure()) + logger.Info("Clients service gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) + + channelsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&channelsClientCfg, env.Options{Prefix: envPrefixChannels}); err != nil { + logger.Error(fmt.Sprintf("failed to load channels gRPC client configuration : %s", err)) + exitCode = 1 + return + } + + channelsClient, channelsHandler, err := grpcclient.SetupChannelsClient(ctx, channelsClientCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer channelsHandler.Close() + logger.Info("Channels service gRPC client successfully connected to channels gRPC server " + channelsHandler.Secure()) tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio) if err != nil { @@ -126,7 +143,7 @@ func main() { defer nps.Close() nps = brokerstracing.NewPubSub(coapServerConfig, tracer, nps) - svc := coap.New(thingsClient, nps) + svc := coap.New(clientsClient, channelsClient, nps) svc = tracing.New(tracer, svc) diff --git a/cmd/domains/main.go b/cmd/domains/main.go new file mode 100644 index 0000000000..d5bd9ac9d8 --- /dev/null +++ b/cmd/domains/main.go @@ -0,0 +1,265 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "fmt" + "log" + "log/slog" + "net/url" + "os" + "time" + + chclient "github.com/absmach/callhome/pkg/client" + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/domains" + domainsSvc "github.com/absmach/magistrala/domains" + domainsgrpcapi "github.com/absmach/magistrala/domains/api/grpc" + httpapi "github.com/absmach/magistrala/domains/api/http" + "github.com/absmach/magistrala/domains/events" + dmw "github.com/absmach/magistrala/domains/middleware" + dpostgres "github.com/absmach/magistrala/domains/postgres" + dtracing "github.com/absmach/magistrala/domains/tracing" + grpcDomainsV1 "github.com/absmach/magistrala/internal/grpc/domains/v1" + mglog "github.com/absmach/magistrala/logger" + authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" + "github.com/absmach/magistrala/pkg/authz" + authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc" + "github.com/absmach/magistrala/pkg/grpcclient" + "github.com/absmach/magistrala/pkg/jaeger" + "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/policies/spicedb" + "github.com/absmach/magistrala/pkg/postgres" + pgclient "github.com/absmach/magistrala/pkg/postgres" + "github.com/absmach/magistrala/pkg/prometheus" + "github.com/absmach/magistrala/pkg/server" + grpcserver "github.com/absmach/magistrala/pkg/server/grpc" + httpserver "github.com/absmach/magistrala/pkg/server/http" + "github.com/absmach/magistrala/pkg/sid" + "github.com/absmach/magistrala/pkg/uuid" + "github.com/authzed/authzed-go/v1" + "github.com/authzed/grpcutil" + "github.com/caarlos0/env/v11" + "github.com/go-chi/chi/v5" + "github.com/jmoiron/sqlx" + "go.opentelemetry.io/otel/trace" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/reflection" +) + +const ( + svcName = "domains" + envPrefixHTTP = "MG_DOMAINS_HTTP_" + envPrefixGrpc = "MG_DOMAINS_GRPC_" + envPrefixDB = "MG_DOMAINS_DB_" + envPrefixAuth = "MG_AUTH_GRPC_" + defDB = "domains" + defSvcHTTPPort = "9004" + defSvcGRPCPort = "7004" +) + +type config struct { + LogLevel string `env:"MG_DOMAINS_LOG_LEVEL" envDefault:"info"` + JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"MG_DOMAINS_INSTANCE_ID" envDefault:""` + SpicedbHost string `env:"MG_SPICEDB_HOST" envDefault:"localhost"` + SpicedbPort string `env:"MG_SPICEDB_PORT" envDefault:"50051"` + SpicedbPreSharedKey string `env:"MG_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"` + TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` + ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + g, ctx := errgroup.WithContext(ctx) + + cfg := config{} + if err := env.Parse(&cfg); err != nil { + log.Fatalf("failed to load %s configuration : %s", svcName, err.Error()) + } + + logger, err := mglog.New(os.Stdout, cfg.LogLevel) + if err != nil { + log.Fatalf("failed to init logger: %s", err.Error()) + } + + var exitCode int + defer mglog.ExitWithError(&exitCode) + + if cfg.InstanceID == "" { + if cfg.InstanceID, err = uuid.New().ID(); err != nil { + logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err)) + exitCode = 1 + return + } + } + + dbConfig := pgclient.Config{Name: defDB} + if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { + logger.Error(err.Error()) + } + + dm, err := dpostgres.Migration() + if err != nil { + logger.Error(fmt.Sprintf("failed create migrations for domain: %s", err.Error())) + exitCode = 1 + return + } + + db, err := pgclient.Setup(dbConfig, *dm) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer db.Close() + + tp, err := jaeger.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio) + if err != nil { + logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err)) + exitCode = 1 + return + } + defer func() { + if err := tp.Shutdown(ctx); err != nil { + logger.Error(fmt.Sprintf("error shutting down tracer provider: %v", err)) + } + }() + tracer := tp.Tracer(svcName) + + time.Sleep(1 * time.Second) + + clientConfig := grpcclient.Config{} + if err := env.ParseWithOptions(&clientConfig, env.Options{Prefix: envPrefixAuth}); err != nil { + logger.Error(fmt.Sprintf("failed to load auth gRPC server configuration : %s", err)) + exitCode = 1 + return + } + + authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, clientConfig) + if err != nil { + logger.Error(fmt.Sprintf("authn failed to connect to auth gRPC server : %s", err.Error())) + exitCode = 1 + return + } + defer authnHandler.Close() + logger.Info("Authn successfully connected to auth gRPC server " + authnHandler.Secure()) + + authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, clientConfig) + if err != nil { + logger.Error(fmt.Sprintf("authz failed to connect to auth gRPC server : %s", err.Error())) + exitCode = 1 + return + } + defer authzHandler.Close() + logger.Info("Authz successfully connected to auth gRPC server " + authzHandler.Secure()) + + policyService, err := newPolicyService(cfg, logger) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + logger.Info("Policy client successfully connected to spicedb gRPC server") + + svc, err := newDomainService(ctx, db, tracer, cfg, dbConfig, authz, policyService, logger) + if err != nil { + logger.Error(fmt.Sprintf("failed to create %s service: %s", svcName, err.Error())) + exitCode = 1 + return + } + + grpcServerConfig := server.Config{Port: defSvcGRPCPort} + if err := env.ParseWithOptions(&grpcServerConfig, env.Options{Prefix: envPrefixGrpc}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s gRPC server configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + registerDomainsServiceServer := func(srv *grpc.Server) { + reflection.Register(srv) + grpcDomainsV1.RegisterDomainsServiceServer(srv, domainsgrpcapi.NewDomainsServer(svc)) + } + + gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerDomainsServiceServer, logger) + + g.Go(func() error { + return gs.Start() + }) + + httpServerConfig := server.Config{Port: defSvcHTTPPort} + if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + mux := chi.NewMux() + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, mux, logger, cfg.InstanceID), logger) + + g.Go(func() error { + return hs.Start() + }) + + if cfg.SendTelemetry { + chc := chclient.New(svcName, magistrala.Version, logger, cancel) + go chc.CallHome(ctx) + } + + g.Go(func() error { + return server.StopSignalHandler(ctx, cancel, logger, svcName, hs, gs) + }) + + if err := g.Wait(); err != nil { + logger.Error(fmt.Sprintf("domains service terminated: %s", err)) + } +} + +func newDomainService(ctx context.Context, db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, authz authz.Authorization, policiessvc policies.Service, logger *slog.Logger) (domains.Service, error) { + database := postgres.NewDatabase(db, dbConfig, tracer) + domainsRepo := dpostgres.New(database) + + idProvider := uuid.New() + sidProvider, err := sid.New() + if err != nil { + return nil, fmt.Errorf("failed to init short id provider : %w", err) + } + svc, err := domainsSvc.New(domainsRepo, policiessvc, idProvider, sidProvider) + if err != nil { + return nil, fmt.Errorf("failed to init domain service: %w", err) + } + svc, err = events.NewEventStoreMiddleware(ctx, svc, cfg.ESURL) + if err != nil { + return nil, fmt.Errorf("failed to init domain event store middleware: %w", err) + } + + svc, err = dmw.AuthorizationMiddleware(policies.DomainType, svc, authz, domains.NewOperationPermissionMap(), domains.NewRolesOperationPermissionMap()) + if err != nil { + return nil, err + } + + counter, latency := prometheus.MakeMetrics("domains", "api") + svc = dmw.MetricsMiddleware(svc, counter, latency) + + svc = dmw.LoggingMiddleware(svc, logger) + + svc = dtracing.New(svc, tracer) + return svc, nil +} + +func newPolicyService(cfg config, logger *slog.Logger) (policies.Service, error) { + client, err := authzed.NewClientWithExperimentalAPIs( + fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey), + ) + if err != nil { + return nil, err + } + policySvc := spicedb.NewPolicyService(client, logger) + + return policySvc, nil +} diff --git a/cmd/groups/main.go b/cmd/groups/main.go new file mode 100644 index 0000000000..0a4f4d8fe7 --- /dev/null +++ b/cmd/groups/main.go @@ -0,0 +1,301 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package main contains groups main function to start the groups service. +package main + +import ( + "context" + "fmt" + "log" + "log/slog" + "net/url" + "os" + + chclient "github.com/absmach/callhome/pkg/client" + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/groups" + gpsvc "github.com/absmach/magistrala/groups" + grpcapi "github.com/absmach/magistrala/groups/api/grpc" + httpapi "github.com/absmach/magistrala/groups/api/http" + "github.com/absmach/magistrala/groups/events" + "github.com/absmach/magistrala/groups/middleware" + "github.com/absmach/magistrala/groups/postgres" + pgroups "github.com/absmach/magistrala/groups/private" + "github.com/absmach/magistrala/groups/tracing" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + grpcGroupsV1 "github.com/absmach/magistrala/internal/grpc/groups/v1" + mglog "github.com/absmach/magistrala/logger" + authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" + mgauthz "github.com/absmach/magistrala/pkg/authz" + authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc" + "github.com/absmach/magistrala/pkg/grpcclient" + jaegerclient "github.com/absmach/magistrala/pkg/jaeger" + "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/policies/spicedb" + pg "github.com/absmach/magistrala/pkg/postgres" + pgclient "github.com/absmach/magistrala/pkg/postgres" + "github.com/absmach/magistrala/pkg/prometheus" + "github.com/absmach/magistrala/pkg/server" + grpcserver "github.com/absmach/magistrala/pkg/server/grpc" + httpserver "github.com/absmach/magistrala/pkg/server/http" + "github.com/absmach/magistrala/pkg/sid" + "github.com/absmach/magistrala/pkg/uuid" + "github.com/authzed/authzed-go/v1" + "github.com/authzed/grpcutil" + "github.com/caarlos0/env/v11" + "github.com/go-chi/chi/v5" + "github.com/jmoiron/sqlx" + "go.opentelemetry.io/otel/trace" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/reflection" +) + +const ( + svcName = "groups" + envPrefixDB = "MG_GROUPS_DB_" + envPrefixHTTP = "MG_GROUPS_HTTP_" + envPrefixgRPC = "MG_GROUPS_GRPC_" + envPrefixAuth = "MG_AUTH_GRPC_" + envPrefixDomains = "MG_DOMAINS_GRPC_" + envPrefixChannels = "MG_CHANNELS_GRPC_" + envPrefixClients = "MG_CLIENTS_AUTH_GRPC_" + defDB = "groups" + defSvcHTTPPort = "9004" + defSvcgRPCPort = "7004" +) + +type config struct { + LogLevel string `env:"MG_GROUPS_LOG_LEVEL" envDefault:"info"` + InstanceID string `env:"MG_GROUPS_INSTANCE_ID" envDefault:""` + JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` + ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` + TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` + SpicedbHost string `env:"MG_SPICEDB_HOST" envDefault:"localhost"` + SpicedbPort string `env:"MG_SPICEDB_PORT" envDefault:"50051"` + SpicedbPreSharedKey string `env:"MG_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"` +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + g, ctx := errgroup.WithContext(ctx) + + cfg := config{} + if err := env.Parse(&cfg); err != nil { + log.Fatalf("failed to load %s configuration : %s", svcName, err.Error()) + } + + logger, err := mglog.New(os.Stdout, cfg.LogLevel) + if err != nil { + log.Fatalf("failed to init logger: %s", err.Error()) + } + + var exitCode int + defer mglog.ExitWithError(&exitCode) + + if cfg.InstanceID == "" { + if cfg.InstanceID, err = uuid.New().ID(); err != nil { + logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err)) + exitCode = 1 + return + } + } + + dbConfig := pgclient.Config{Name: defDB} + if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + gm, err := postgres.Migration() + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + db, err := pgclient.Setup(dbConfig, *gm) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer db.Close() + + tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio) + if err != nil { + logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err)) + exitCode = 1 + return + } + defer func() { + if err := tp.Shutdown(ctx); err != nil { + logger.Error(fmt.Sprintf("error shutting down tracer provider: %v", err)) + } + }() + tracer := tp.Tracer(svcName) + + authClientConfig := grpcclient.Config{} + if err := env.ParseWithOptions(&authClientConfig, env.Options{Prefix: envPrefixAuth}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) + exitCode = 1 + return + } + + authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, authClientConfig) + if err != nil { + logger.Error("failed to create authn " + err.Error()) + exitCode = 1 + return + } + defer authnHandler.Close() + logger.Info("Authn successfully connected to auth gRPC server " + authnHandler.Secure()) + + authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientConfig) + if err != nil { + logger.Error("failed to create authz " + err.Error()) + exitCode = 1 + return + } + defer authzHandler.Close() + logger.Info("Authz successfully connected to auth gRPC server " + authzHandler.Secure()) + + policyService, err := newPolicyService(cfg, logger) + if err != nil { + logger.Error("failed to create new policies service " + err.Error()) + exitCode = 1 + return + } + logger.Info("Policy client successfully connected to spicedb gRPC server") + + chgrpcCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&chgrpcCfg, env.Options{Prefix: envPrefixChannels}); err != nil { + logger.Error(fmt.Sprintf("failed to load channels gRPC client configuration : %s", err)) + exitCode = 1 + return + } + channelsClient, channelsHandler, err := grpcclient.SetupChannelsClient(ctx, chgrpcCfg) + if err != nil { + logger.Error(fmt.Sprintf("failed to connect to channels gRPC server: %s", err)) + exitCode = 1 + return + } + defer channelsHandler.Close() + logger.Info("Groups gRPC client successfully connected to channels gRPC server " + channelsHandler.Secure()) + + thgrpcCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&thgrpcCfg, env.Options{Prefix: envPrefixClients}); err != nil { + logger.Error(fmt.Sprintf("failed to load clients gRPC client configuration : %s", err)) + exitCode = 1 + return + } + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, thgrpcCfg) + if err != nil { + logger.Error(fmt.Sprintf("failed to connect to clients gRPC server: %s", err)) + exitCode = 1 + return + } + defer clientsHandler.Close() + logger.Info("Clients gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) + + svc, psvc, err := newService(ctx, authz, policyService, db, dbConfig, channelsClient, clientsClient, tracer, logger, cfg) + if err != nil { + logger.Error(fmt.Sprintf("failed to setup service: %s", err)) + exitCode = 1 + return + } + + httpServerConfig := server.Config{Port: defSvcHTTPPort} + if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + + mux := chi.NewRouter() + httpSrv := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, mux, logger, cfg.InstanceID), logger) + + grpcServerConfig := server.Config{} + if err := env.ParseWithOptions(&grpcServerConfig, env.Options{Prefix: envPrefixgRPC}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s gRPC server configuration : %s", svcName, err)) + exitCode = 1 + return + } + + registerGroupsServer := func(srv *grpc.Server) { + reflection.Register(srv) + grpcGroupsV1.RegisterGroupsServiceServer(srv, grpcapi.NewServer(psvc)) + } + gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerGroupsServer, logger) + + if cfg.SendTelemetry { + chc := chclient.New(svcName, magistrala.Version, logger, cancel) + go chc.CallHome(ctx) + } + + g.Go(func() error { + return gs.Start() + }) + + g.Go(func() error { + return httpSrv.Start() + }) + + g.Go(func() error { + return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSrv) + }) + + if err := g.Wait(); err != nil { + logger.Error(fmt.Sprintf("groups service terminated: %s", err)) + } +} + +func newService(ctx context.Context, authz mgauthz.Authorization, policy policies.Service, db *sqlx.DB, dbConfig pgclient.Config, channels grpcChannelsV1.ChannelsServiceClient, clients grpcClientsV1.ClientsServiceClient, tracer trace.Tracer, logger *slog.Logger, c config) (groups.Service, pgroups.Service, error) { + database := pg.NewDatabase(db, dbConfig, tracer) + idp := uuid.New() + sid, err := sid.New() + if err != nil { + return nil, nil, err + } + + // Creating groups service + repo := postgres.New(database) + svc, err := gpsvc.NewService(repo, policy, idp, channels, clients, sid) + if err != nil { + return nil, nil, err + } + svc, err = events.New(ctx, svc, c.ESURL) + if err != nil { + return nil, nil, err + } + + svc, err = middleware.AuthorizationMiddleware(policies.GroupType, svc, authz, groups.NewOperationPermissionMap(), groups.NewRolesOperationPermissionMap()) + if err != nil { + return nil, nil, err + } + + svc = tracing.New(svc, tracer) + svc = middleware.LoggingMiddleware(svc, logger) + counter, latency := prometheus.MakeMetrics("groups", "api") + svc = middleware.MetricsMiddleware(svc, counter, latency) + + psvc := pgroups.New(repo) + return svc, psvc, err +} + +func newPolicyService(cfg config, logger *slog.Logger) (policies.Service, error) { + client, err := authzed.NewClientWithExperimentalAPIs( + fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey), + ) + if err != nil { + return nil, err + } + policySvc := spicedb.NewPolicyService(client, logger) + + return policySvc, nil +} diff --git a/cmd/http/main.go b/cmd/http/main.go index 4bf25efa9a..57f5d89ff4 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -18,7 +18,11 @@ import ( "github.com/absmach/magistrala" adapter "github.com/absmach/magistrala/http" "github.com/absmach/magistrala/http/api" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" mglog "github.com/absmach/magistrala/logger" + mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/authn/authsvc" "github.com/absmach/magistrala/pkg/grpcclient" jaegerclient "github.com/absmach/magistrala/pkg/jaeger" "github.com/absmach/magistrala/pkg/messaging" @@ -38,12 +42,14 @@ import ( ) const ( - svcName = "http_adapter" - envPrefix = "MG_HTTP_ADAPTER_" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - defSvcHTTPPort = "80" - targetHTTPPort = "81" - targetHTTPHost = "http://localhost" + svcName = "http_adapter" + envPrefix = "MG_HTTP_ADAPTER_" + envPrefixClients = "MG_CLIENTS_AUTH_GRPC_" + envPrefixChannels = "MG_CHANNELS_GRPC_" + envPrefixAuth = "MG_AUTH_GRPC_" + defSvcHTTPPort = "80" + targetHTTPPort = "81" + targetHTTPHost = "http://localhost" ) type config struct { @@ -87,22 +93,53 @@ func main() { return } - thingsClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&thingsClientCfg, env.Options{Prefix: envPrefixThings}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) + clientsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientsClientCfg, env.Options{Prefix: envPrefixClients}); err != nil { + logger.Error(fmt.Sprintf("failed to load clients gRPC client configuration : %s", err)) exitCode = 1 return } - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, clientsClientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer thingsHandler.Close() + defer clientsHandler.Close() + logger.Info("Clients service gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) - logger.Info("Things service gRPC client successfully connected to things gRPC server " + thingsHandler.Secure()) + channelsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&channelsClientCfg, env.Options{Prefix: envPrefixChannels}); err != nil { + logger.Error(fmt.Sprintf("failed to load channels gRPC client configuration : %s", err)) + exitCode = 1 + return + } + + channelsClient, channelsHandler, err := grpcclient.SetupChannelsClient(ctx, channelsClientCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer channelsHandler.Close() + logger.Info("Channels service gRPC client successfully connected to channels gRPC server " + channelsHandler.Secure()) + + authnCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&authnCfg, env.Options{Prefix: envPrefixAuth}); err != nil { + logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) + exitCode = 1 + return + } + + authn, authnHandler, err := authsvc.NewAuthentication(ctx, authnCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer authnHandler.Close() + logger.Info("authn successfully connected to auth gRPC server " + authnHandler.Secure()) tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio) if err != nil { @@ -126,7 +163,7 @@ func main() { defer pub.Close() pub = brokerstracing.NewPublisher(httpServerConfig, tracer, pub) - svc := newService(pub, thingsClient, logger, tracer) + svc := newService(pub, authn, clientsClient, channelsClient, logger, tracer) targetServerCfg := server.Config{Port: targetHTTPPort} hs := httpserver.NewServer(ctx, cancel, svcName, targetServerCfg, api.MakeHandler(logger, cfg.InstanceID), logger) @@ -153,8 +190,8 @@ func main() { } } -func newService(pub messaging.Publisher, tc magistrala.ThingsServiceClient, logger *slog.Logger, tracer trace.Tracer) session.Handler { - svc := adapter.NewHandler(pub, logger, tc) +func newService(pub messaging.Publisher, authn mgauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient, logger *slog.Logger, tracer trace.Tracer) session.Handler { + svc := adapter.NewHandler(pub, authn, clients, channels, logger) svc = handler.NewTracing(tracer, svc) svc = handler.LoggingMiddleware(svc, logger) counter, latency := prometheus.MakeMetrics(svcName, "api") diff --git a/cmd/invitations/main.go b/cmd/invitations/main.go index 8f79da3915..2e7b79df16 100644 --- a/cmd/invitations/main.go +++ b/cmd/invitations/main.go @@ -14,6 +14,7 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/invitations" "github.com/absmach/magistrala/invitations/api" "github.com/absmach/magistrala/invitations/middleware" @@ -175,7 +176,7 @@ func main() { } } -func newService(db *sqlx.DB, dbConfig clientspg.Config, authz mgauthz.Authorization, token magistrala.TokenServiceClient, tracer trace.Tracer, conf config, logger *slog.Logger) (invitations.Service, error) { +func newService(db *sqlx.DB, dbConfig clientspg.Config, authz mgauthz.Authorization, token grpcTokenV1.TokenServiceClient, tracer trace.Tracer, conf config, logger *slog.Logger) (invitations.Service, error) { database := postgres.NewDatabase(db, dbConfig, tracer) repo := invitationspg.NewRepository(database) diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go index 1d226543d4..911d525da6 100644 --- a/cmd/mqtt/main.go +++ b/cmd/mqtt/main.go @@ -42,9 +42,10 @@ import ( ) const ( - svcName = "mqtt" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - wsPathPrefix = "/mqtt" + svcName = "mqtt" + envPrefixClients = "MG_CLIENTS_AUTH_GRPC_" + envPrefixChannels = "MG_CHANNELS_GRPC_" + wsPathPrefix = "/mqtt" ) type config struct { @@ -165,24 +166,39 @@ func main() { return } - thingsClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&thingsClientCfg, env.Options{Prefix: envPrefixThings}); err != nil { + clientsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientsClientCfg, env.Options{Prefix: envPrefixClients}); err != nil { logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) exitCode = 1 return } - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, clientsClientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer thingsHandler.Close() + defer clientsHandler.Close() + logger.Info("Clients service gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) - logger.Info("Things service gRPC client successfully connected to things gRPC server " + thingsHandler.Secure()) + channelsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&channelsClientCfg, env.Options{Prefix: envPrefixChannels}); err != nil { + logger.Error(fmt.Sprintf("failed to load channels gRPC client configuration : %s", err)) + exitCode = 1 + return + } + + channelsClient, channelsHandler, err := grpcclient.SetupChannelsClient(ctx, channelsClientCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer channelsHandler.Close() + logger.Info("Channels service gRPC client successfully connected to channels gRPC server " + channelsHandler.Secure()) - h := mqtt.NewHandler(np, es, logger, thingsClient) + h := mqtt.NewHandler(np, es, logger, clientsClient, channelsClient) h = handler.NewTracing(tracer, h) if cfg.SendTelemetry { diff --git a/cmd/postgres-reader/main.go b/cmd/postgres-reader/main.go index 0bbb62554e..5487d7670a 100644 --- a/cmd/postgres-reader/main.go +++ b/cmd/postgres-reader/main.go @@ -14,8 +14,7 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" mglog "github.com/absmach/magistrala/logger" - authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" - "github.com/absmach/magistrala/pkg/authz/authsvc" + "github.com/absmach/magistrala/pkg/authn/authsvc" "github.com/absmach/magistrala/pkg/grpcclient" pgclient "github.com/absmach/magistrala/pkg/postgres" "github.com/absmach/magistrala/pkg/prometheus" @@ -31,13 +30,14 @@ import ( ) const ( - svcName = "postgres-reader" - envPrefixDB = "MG_POSTGRES_" - envPrefixHTTP = "MG_POSTGRES_READER_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - defDB = "magistrala" - defSvcHTTPPort = "9009" + svcName = "postgres-reader" + envPrefixDB = "MG_POSTGRES_" + envPrefixHTTP = "MG_POSTGRES_READER_HTTP_" + envPrefixAuth = "MG_AUTH_GRPC_" + envPrefixClients = "MG_CLIENTS_AUTH_GRPC_" + envPrefixChannels = "MG_CHANNELS_GRPC_" + defDB = "magistrala" + defSvcHTTPPort = "9009" ) type config struct { @@ -85,47 +85,53 @@ func main() { } defer db.Close() - clientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&clientCfg, env.Options{Prefix: envPrefixAuth}); err != nil { - logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) + clientsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientsClientCfg, env.Options{Prefix: envPrefixClients}); err != nil { + logger.Error(fmt.Sprintf("failed to load clients gRPC client configuration : %s", err)) exitCode = 1 return } - authz, authzHandler, err := authsvc.NewAuthorization(ctx, clientCfg) + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, clientsClientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer authzHandler.Close() - logger.Info("AuthZ successfully connected to auth gRPC server " + authzHandler.Secure()) + defer clientsHandler.Close() + logger.Info("Clients service gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) + + channelsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&channelsClientCfg, env.Options{Prefix: envPrefixChannels}); err != nil { + logger.Error(fmt.Sprintf("failed to load channels gRPC client configuration : %s", err)) + exitCode = 1 + return + } - authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, clientCfg) + channelsClient, channelsHandler, err := grpcclient.SetupChannelsClient(ctx, channelsClientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer authnHandler.Close() - logger.Info("AuthN successfully connected to auth gRPC server " + authnHandler.Secure()) + defer channelsHandler.Close() + logger.Info("Channels service gRPC client successfully connected to channels gRPC server " + channelsHandler.Secure()) - thingsClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&thingsClientCfg, env.Options{Prefix: envPrefixThings}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) + authnCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&authnCfg, env.Options{Prefix: envPrefixAuth}); err != nil { + logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) exitCode = 1 return } - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) + authn, authnHandler, err := authsvc.NewAuthentication(ctx, authnCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer thingsHandler.Close() - - logger.Info("Things service gRPC client successfully connected to things gRPC server " + thingsHandler.Secure()) + defer authnHandler.Close() + logger.Info("authn successfully connected to auth gRPC server " + authnHandler.Secure()) repo := newService(db, logger) @@ -135,7 +141,7 @@ func main() { exitCode = 1 return } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authn, authz, thingsClient, svcName, cfg.InstanceID), logger) + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authn, clientsClient, channelsClient, svcName, cfg.InstanceID), logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) diff --git a/cmd/provision/main.go b/cmd/provision/main.go index 986f7acf74..a8525a4e42 100644 --- a/cmd/provision/main.go +++ b/cmd/provision/main.go @@ -14,16 +14,16 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/clients" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" - mggroups "github.com/absmach/magistrala/pkg/groups" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/pkg/server" httpserver "github.com/absmach/magistrala/pkg/server/http" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/provision" "github.com/absmach/magistrala/provision/api" - "github.com/absmach/magistrala/things" "github.com/caarlos0/env/v11" "golang.org/x/sync/errgroup" ) @@ -75,7 +75,7 @@ func main() { SDKCfg := mgsdk.Config{ UsersURL: cfg.Server.UsersURL, - ThingsURL: cfg.Server.ThingsURL, + ClientsURL: cfg.Server.ClientsURL, BootstrapURL: cfg.Server.MgBSURL, CertsURL: cfg.Server.MgCertsURL, MsgContentType: contentType, @@ -138,7 +138,7 @@ func loadConfig() (provision.Config, error) { cfg.Bootstrap.Content = content // This is default conf for provision if there is no config file - cfg.Channels = []mggroups.Group{ + cfg.Channels = []channels.Channel{ { Name: "control-channel", Metadata: map[string]interface{}{"type": "control"}, @@ -147,9 +147,9 @@ func loadConfig() (provision.Config, error) { Metadata: map[string]interface{}{"type": "data"}, }, } - cfg.Things = []things.Client{ + cfg.Clients = []clients.Client{ { - Name: "thing", + Name: "client", Metadata: map[string]interface{}{"external_id": "xxxxxx"}, }, } diff --git a/cmd/timescale-reader/main.go b/cmd/timescale-reader/main.go index 066291a7f4..24f3db32bf 100644 --- a/cmd/timescale-reader/main.go +++ b/cmd/timescale-reader/main.go @@ -14,8 +14,7 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" mglog "github.com/absmach/magistrala/logger" - authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" - "github.com/absmach/magistrala/pkg/authz/authsvc" + "github.com/absmach/magistrala/pkg/authn/authsvc" "github.com/absmach/magistrala/pkg/grpcclient" pgclient "github.com/absmach/magistrala/pkg/postgres" "github.com/absmach/magistrala/pkg/prometheus" @@ -31,13 +30,14 @@ import ( ) const ( - svcName = "timescaledb-reader" - envPrefixDB = "MG_TIMESCALE_" - envPrefixHTTP = "MG_TIMESCALE_READER_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - defDB = "messages" - defSvcHTTPPort = "9011" + svcName = "timescaledb-reader" + envPrefixDB = "MG_TIMESCALE_" + envPrefixHTTP = "MG_TIMESCALE_READER_HTTP_" + envPrefixAuth = "MG_AUTH_GRPC_" + envPrefixClients = "MG_CLIENTS_AUTH_GRPC_" + envPrefixChannels = "MG_CHANNELS_GRPC_" + defDB = "messages" + defSvcHTTPPort = "9011" ) type config struct { @@ -85,47 +85,54 @@ func main() { repo := newService(db, logger) - clientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&clientCfg, env.Options{Prefix: envPrefixAuth}); err != nil { - logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) + clientsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientsClientCfg, env.Options{Prefix: envPrefixClients}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) exitCode = 1 return } - authz, authzHandler, err := authsvc.NewAuthorization(ctx, clientCfg) + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, clientsClientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer authzHandler.Close() - logger.Info("AuthZ successfully connected to auth gRPC server " + authzHandler.Secure()) + defer clientsHandler.Close() + + logger.Info("ClientsService gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) + + channelsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&channelsClientCfg, env.Options{Prefix: envPrefixChannels}); err != nil { + logger.Error(fmt.Sprintf("failed to load channels gRPC client configuration : %s", err)) + exitCode = 1 + return + } - authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, clientCfg) + channelsClient, channelsHandler, err := grpcclient.SetupChannelsClient(ctx, channelsClientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer authnHandler.Close() - logger.Info("AuthN successfully connected to auth gRPC server " + authnHandler.Secure()) + defer channelsHandler.Close() + logger.Info("Channels service gRPC client successfully connected to channels gRPC server " + channelsHandler.Secure()) - thingsClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&thingsClientCfg, env.Options{Prefix: envPrefixThings}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) + authnCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&authnCfg, env.Options{Prefix: envPrefixAuth}); err != nil { + logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) exitCode = 1 return } - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) + authn, authnHandler, err := authsvc.NewAuthentication(ctx, authnCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer thingsHandler.Close() - - logger.Info("Things service gRPC client successfully connected to things gRPC server " + thingsHandler.Secure()) + defer authnHandler.Close() + logger.Info("authn successfully connected to auth gRPC server " + authnHandler.Secure()) httpServerConfig := server.Config{Port: defSvcHTTPPort} if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { @@ -133,7 +140,7 @@ func main() { exitCode = 1 return } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authn, authz, thingsClient, svcName, cfg.InstanceID), logger) + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authn, clientsClient, channelsClient, svcName, cfg.InstanceID), logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) diff --git a/cmd/users/main.go b/cmd/users/main.go index 3d285b9af0..67f7fb2f61 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -17,36 +17,32 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/email" - mggroups "github.com/absmach/magistrala/internal/groups" - gevents "github.com/absmach/magistrala/internal/groups/events" - gmiddleware "github.com/absmach/magistrala/internal/groups/middleware" - gpostgres "github.com/absmach/magistrala/internal/groups/postgres" - gtracing "github.com/absmach/magistrala/internal/groups/tracing" + grpcDomainsV1 "github.com/absmach/magistrala/internal/grpc/domains/v1" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" mglog "github.com/absmach/magistrala/logger" authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" mgauthz "github.com/absmach/magistrala/pkg/authz" authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc" - "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/grpcclient" jaegerclient "github.com/absmach/magistrala/pkg/jaeger" "github.com/absmach/magistrala/pkg/oauth2" googleoauth "github.com/absmach/magistrala/pkg/oauth2/google" "github.com/absmach/magistrala/pkg/policies" "github.com/absmach/magistrala/pkg/policies/spicedb" - "github.com/absmach/magistrala/pkg/postgres" + pg "github.com/absmach/magistrala/pkg/postgres" pgclient "github.com/absmach/magistrala/pkg/postgres" "github.com/absmach/magistrala/pkg/prometheus" "github.com/absmach/magistrala/pkg/server" httpserver "github.com/absmach/magistrala/pkg/server/http" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/users" - capi "github.com/absmach/magistrala/users/api" + "github.com/absmach/magistrala/users/api" "github.com/absmach/magistrala/users/emailer" - uevents "github.com/absmach/magistrala/users/events" + "github.com/absmach/magistrala/users/events" "github.com/absmach/magistrala/users/hasher" - cmiddleware "github.com/absmach/magistrala/users/middleware" - clientspg "github.com/absmach/magistrala/users/postgres" - ctracing "github.com/absmach/magistrala/users/tracing" + "github.com/absmach/magistrala/users/middleware" + "github.com/absmach/magistrala/users/postgres" + "github.com/absmach/magistrala/users/tracing" "github.com/authzed/authzed-go/v1" "github.com/authzed/grpcutil" "github.com/caarlos0/env/v11" @@ -59,15 +55,14 @@ import ( ) const ( - svcName = "users" - envPrefixDB = "MG_USERS_DB_" - envPrefixHTTP = "MG_USERS_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - envPrefixGoogle = "MG_GOOGLE_" - defDB = "users" - defSvcHTTPPort = "9002" - - streamID = "magistrala.users" + svcName = "users" + envPrefixDB = "MG_USERS_DB_" + envPrefixHTTP = "MG_USERS_HTTP_" + envPrefixAuth = "MG_AUTH_GRPC_" + envPrefixDomains = "MG_DOMAINS_GRPC_" + envPrefixGoogle = "MG_GOOGLE_" + defDB = "users" + defSvcHTTPPort = "9002" ) type config struct { @@ -138,10 +133,9 @@ func main() { exitCode = 1 return } - cm := clientspg.Migration() - gm := gpostgres.Migration() - cm.Migrations = append(cm.Migrations, gm.Migrations...) - db, err := pgclient.Setup(dbConfig, *cm) + + migration := postgres.Migration() + db, err := pgclient.Setup(dbConfig, *migration) if err != nil { logger.Error(err.Error()) exitCode = 1 @@ -162,58 +156,65 @@ func main() { }() tracer := tp.Tracer(svcName) - clientConfig := grpcclient.Config{} - if err := env.ParseWithOptions(&clientConfig, env.Options{Prefix: envPrefixAuth}); err != nil { + authClientConfig := grpcclient.Config{} + if err := env.ParseWithOptions(&authClientConfig, env.Options{Prefix: envPrefixAuth}); err != nil { logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) exitCode = 1 return } - tokenClient, tokenHandler, err := grpcclient.SetupTokenClient(ctx, clientConfig) + tokenClient, tokenHandler, err := grpcclient.SetupTokenClient(ctx, authClientConfig) if err != nil { - logger.Error(err.Error()) + logger.Error("failed to create token gRPC client " + err.Error()) exitCode = 1 return } defer tokenHandler.Close() logger.Info("Token service client successfully connected to auth gRPC server " + tokenHandler.Secure()) - authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, clientConfig) + authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, authClientConfig) if err != nil { - logger.Error(err.Error()) + logger.Error("failed to create authn " + err.Error()) exitCode = 1 return } defer authnHandler.Close() logger.Info("AuthN successfully connected to auth gRPC server " + authnHandler.Secure()) - authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, clientConfig) + authz, authzHandler, err := authsvcAuthz.NewAuthorization(ctx, authClientConfig) if err != nil { - logger.Error(err.Error()) + logger.Error("failed to create authz " + err.Error()) exitCode = 1 return } defer authzHandler.Close() logger.Info("AuthZ successfully connected to auth gRPC server " + authzHandler.Secure()) - domainsClient, domainsHandler, err := grpcclient.SetupDomainsClient(ctx, clientConfig) + domainsClientConfig := grpcclient.Config{} + if err := env.ParseWithOptions(&domainsClientConfig, env.Options{Prefix: envPrefixDomains}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) + exitCode = 1 + return + } + + domainsClient, domainsHandler, err := grpcclient.SetupDomainsClient(ctx, domainsClientConfig) if err != nil { - logger.Error(err.Error()) + logger.Error("failed to setup domain gRPC clients " + err.Error()) exitCode = 1 return } defer domainsHandler.Close() - logger.Info("DomainsService gRPC client successfully connected to auth gRPC server " + domainsHandler.Secure()) + logger.Info("DomainsService gRPC client successfully connected to domains gRPC server " + domainsHandler.Secure()) policyService, err := newPolicyService(cfg, logger) if err != nil { - logger.Error(err.Error()) + logger.Error("failed to create new policies service " + err.Error()) exitCode = 1 return } logger.Info("Policy client successfully connected to spicedb gRPC server") - csvc, gsvc, err := newService(ctx, authz, tokenClient, policyService, domainsClient, db, dbConfig, tracer, cfg, ec, logger) + csvc, err := newService(ctx, authz, tokenClient, policyService, domainsClient, db, dbConfig, tracer, cfg, ec, logger) if err != nil { logger.Error(fmt.Sprintf("failed to setup service: %s", err)) exitCode = 1 @@ -236,7 +237,7 @@ func main() { oauthProvider := googleoauth.NewProvider(oauthConfig, cfg.OAuthUIRedirectURL, cfg.OAuthUIErrorURL) mux := chi.NewRouter() - httpSrv := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, capi.MakeHandler(csvc, authn, tokenClient, cfg.SelfRegister, gsvc, mux, logger, cfg.InstanceID, cfg.PassRegex, oauthProvider), logger) + httpSrv := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(csvc, authn, tokenClient, cfg.SelfRegister, mux, logger, cfg.InstanceID, cfg.PassRegex, oauthProvider), logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) @@ -256,59 +257,46 @@ func main() { } } -func newService(ctx context.Context, authz mgauthz.Authorization, token magistrala.TokenServiceClient, policyService policies.Service, domainsClient magistrala.DomainsServiceClient, db *sqlx.DB, dbConfig pgclient.Config, tracer trace.Tracer, c config, ec email.Config, logger *slog.Logger) (users.Service, groups.Service, error) { - database := postgres.NewDatabase(db, dbConfig, tracer) - - cRepo := clientspg.NewRepository(database) - gRepo := gpostgres.New(database) +func newService(ctx context.Context, authz mgauthz.Authorization, token grpcTokenV1.TokenServiceClient, policyService policies.Service, domainsClient grpcDomainsV1.DomainsServiceClient, db *sqlx.DB, dbConfig pgclient.Config, tracer trace.Tracer, c config, ec email.Config, logger *slog.Logger) (users.Service, error) { + database := pg.NewDatabase(db, dbConfig, tracer) idp := uuid.New() hsr := hasher.New() + // Creating users service + repo := postgres.NewRepository(database) emailerClient, err := emailer.New(c.ResetURL, &ec) if err != nil { logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error())) } - csvc := users.NewService(token, cRepo, policyService, emailerClient, hsr, idp) - gsvc := mggroups.NewService(gRepo, idp, policyService) + svc := users.NewService(token, repo, policyService, emailerClient, hsr, idp) - csvc, err = uevents.NewEventStoreMiddleware(ctx, csvc, c.ESURL) - if err != nil { - return nil, nil, err - } - gsvc, err = gevents.NewEventStoreMiddleware(ctx, gsvc, c.ESURL, streamID) + svc, err = events.NewEventStoreMiddleware(ctx, svc, c.ESURL) if err != nil { - return nil, nil, err + return nil, err } + svc = middleware.AuthorizationMiddleware(svc, authz, c.SelfRegister) - csvc = cmiddleware.AuthorizationMiddleware(csvc, authz, c.SelfRegister) - gsvc = gmiddleware.AuthorizationMiddleware(gsvc, authz) - - csvc = ctracing.New(csvc, tracer) - csvc = cmiddleware.LoggingMiddleware(csvc, logger) + svc = tracing.New(svc, tracer) + svc = middleware.LoggingMiddleware(svc, logger) counter, latency := prometheus.MakeMetrics(svcName, "api") - csvc = cmiddleware.MetricsMiddleware(csvc, counter, latency) + svc = middleware.MetricsMiddleware(svc, counter, latency) - gsvc = gtracing.New(gsvc, tracer) - gsvc = gmiddleware.LoggingMiddleware(gsvc, logger) - counter, latency = prometheus.MakeMetrics("groups", "api") - gsvc = gmiddleware.MetricsMiddleware(gsvc, counter, latency) - - userID, err := createAdmin(ctx, c, cRepo, hsr, csvc) + userID, err := createAdmin(ctx, c, repo, hsr, svc) if err != nil { logger.Error(fmt.Sprintf("failed to create admin client: %s", err)) } if err := createAdminPolicy(ctx, userID, authz, policyService); err != nil { - return nil, nil, err + return nil, err } - users.NewDeleteHandler(ctx, cRepo, policyService, domainsClient, c.DeleteInterval, c.DeleteAfter, logger) + users.NewDeleteHandler(ctx, repo, policyService, domainsClient, c.DeleteInterval, c.DeleteAfter, logger) - return csvc, gsvc, err + return svc, err } -func createAdmin(ctx context.Context, c config, urepo users.Repository, hsr users.Hasher, svc users.Service) (string, error) { +func createAdmin(ctx context.Context, c config, repo users.Repository, hsr users.Hasher, svc users.Service) (string, error) { id, err := uuid.New().ID() if err != nil { return "", err @@ -336,12 +324,12 @@ func createAdmin(ctx context.Context, c config, urepo users.Repository, hsr user Status: users.EnabledStatus, } - if u, err := urepo.RetrieveByEmail(ctx, user.Email); err == nil { + if u, err := repo.RetrieveByEmail(ctx, user.Email); err == nil { return u.ID, nil } // Create an admin - if _, err = urepo.Save(ctx, user); err != nil { + if _, err = repo.Save(ctx, user); err != nil { return "", err } if _, err = svc.IssueToken(ctx, c.AdminUsername, c.AdminPassword); err != nil { diff --git a/cmd/ws/main.go b/cmd/ws/main.go index a2f1e57d59..3f2827b25a 100644 --- a/cmd/ws/main.go +++ b/cmd/ws/main.go @@ -14,7 +14,10 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" mglog "github.com/absmach/magistrala/logger" + "github.com/absmach/magistrala/pkg/authn/authsvc" "github.com/absmach/magistrala/pkg/grpcclient" jaegerclient "github.com/absmach/magistrala/pkg/jaeger" "github.com/absmach/magistrala/pkg/messaging" @@ -35,12 +38,14 @@ import ( ) const ( - svcName = "ws-adapter" - envPrefixHTTP = "MG_WS_ADAPTER_HTTP_" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - defSvcHTTPPort = "8190" - targetWSPort = "8191" - targetWSHost = "localhost" + svcName = "ws-adapter" + envPrefixHTTP = "MG_WS_ADAPTER_HTTP_" + envPrefixClients = "MG_CLIENTS_AUTH_GRPC_" + envPrefixChannels = "MG_CHANNELS_GRPC_" + envPrefixAuth = "MG_AUTH_GRPC_" + defSvcHTTPPort = "8190" + targetWSPort = "8191" + targetWSHost = "localhost" ) type config struct { @@ -89,22 +94,54 @@ func main() { Host: targetWSHost, } - thingsClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&thingsClientCfg, env.Options{Prefix: envPrefixThings}); err != nil { + clientsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientsClientCfg, env.Options{Prefix: envPrefixClients}); err != nil { logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) exitCode = 1 return } - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, clientsClientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 return } - defer thingsHandler.Close() + defer clientsHandler.Close() - logger.Info("Things service gRPC client successfully connected to things gRPC server " + thingsHandler.Secure()) + logger.Info("Clients service gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) + + channelsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&channelsClientCfg, env.Options{Prefix: envPrefixChannels}); err != nil { + logger.Error(fmt.Sprintf("failed to load channels gRPC client configuration : %s", err)) + exitCode = 1 + return + } + + channelsClient, channelsHandler, err := grpcclient.SetupChannelsClient(ctx, channelsClientCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer channelsHandler.Close() + logger.Info("Channels service gRPC client successfully connected to channels gRPC server " + channelsHandler.Secure()) + + authnCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&authnCfg, env.Options{Prefix: envPrefixAuth}); err != nil { + logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) + exitCode = 1 + return + } + + authn, authnHandler, err := authsvc.NewAuthentication(ctx, authnCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer authnHandler.Close() + logger.Info("authn successfully connected to auth gRPC server " + authnHandler.Secure()) tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio) if err != nil { @@ -128,7 +165,7 @@ func main() { defer nps.Close() nps = brokerstracing.NewPubSub(targetServerConfig, tracer, nps) - svc := newService(thingsClient, nps, logger, tracer) + svc := newService(clientsClient, channelsClient, nps, logger, tracer) hs := httpserver.NewServer(ctx, cancel, svcName, targetServerConfig, api.MakeHandler(ctx, svc, logger, cfg.InstanceID), logger) @@ -141,7 +178,7 @@ func main() { g.Go(func() error { return hs.Start() }) - handler := ws.NewHandler(nps, logger, thingsClient) + handler := ws.NewHandler(nps, logger, authn, clientsClient, channelsClient) return proxyWS(ctx, httpServerConfig, targetServerConfig, logger, handler) }) @@ -154,8 +191,8 @@ func main() { } } -func newService(thingsClient magistrala.ThingsServiceClient, nps messaging.PubSub, logger *slog.Logger, tracer trace.Tracer) ws.Service { - svc := ws.New(thingsClient, nps) +func newService(clientsClient grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient, nps messaging.PubSub, logger *slog.Logger, tracer trace.Tracer) ws.Service { + svc := ws.New(clientsClient, channels, nps) svc = tracing.New(tracer, svc) svc = api.LoggingMiddleware(svc, logger) counter, latency := prometheus.MakeMetrics("ws_adapter", "api") diff --git a/coap/README.md b/coap/README.md index 373bd866a0..5eb4c37c38 100644 --- a/coap/README.md +++ b/coap/README.md @@ -6,33 +6,33 @@ Magistrala CoAP adapter provides an [CoAP](http://coap.technology/) API for send The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| -------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------- | -| MG_COAP_ADAPTER_LOG_LEVEL | Log level for the CoAP Adapter (debug, info, warn, error) | info | -| MG_COAP_ADAPTER_HOST | CoAP service listening host | "" | -| MG_COAP_ADAPTER_PORT | CoAP service listening port | 5683 | -| MG_COAP_ADAPTER_SERVER_CERT | CoAP service server certificate | "" | -| MG_COAP_ADAPTER_SERVER_KEY | CoAP service server key | "" | -| MG_COAP_ADAPTER_HTTP_HOST | Service HTTP listening host | "" | -| MG_COAP_ADAPTER_HTTP_PORT | Service listening port | 5683 | -| MG_COAP_ADAPTER_HTTP_SERVER_CERT | Service server certificate | "" | -| MG_COAP_ADAPTER_HTTP_SERVER_KEY | Service server key | "" | -| MG_THINGS_AUTH_GRPC_URL | Things service Auth gRPC URL | | -| MG_THINGS_AUTH_GRPC_TIMEOUT | Things service Auth gRPC request timeout in seconds | 1s | -| MG_THINGS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded things service Auth gRPC client certificate file | "" | -| MG_THINGS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded things service Auth gRPC client key file | "" | -| MG_THINGS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded things server Auth gRPC server trusted CA certificate file | "" | -| MG_MESSAGE_BROKER_URL | Message broker instance URL | | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_COAP_ADAPTER_INSTANCE_ID | CoAP adapter instance ID | "" | +| Variable | Description | Default | +| --------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | +| MG_COAP_ADAPTER_LOG_LEVEL | Log level for the CoAP Adapter (debug, info, warn, error) | info | +| MG_COAP_ADAPTER_HOST | CoAP service listening host | "" | +| MG_COAP_ADAPTER_PORT | CoAP service listening port | 5683 | +| MG_COAP_ADAPTER_SERVER_CERT | CoAP service server certificate | "" | +| MG_COAP_ADAPTER_SERVER_KEY | CoAP service server key | "" | +| MG_COAP_ADAPTER_HTTP_HOST | Service HTTP listening host | "" | +| MG_COAP_ADAPTER_HTTP_PORT | Service listening port | 5683 | +| MG_COAP_ADAPTER_HTTP_SERVER_CERT | Service server certificate | "" | +| MG_COAP_ADAPTER_HTTP_SERVER_KEY | Service server key | "" | +| MG_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| MG_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| MG_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | +| MG_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | +| MG_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | +| MG_MESSAGE_BROKER_URL | Message broker instance URL | | +| MG_JAEGER_URL | Jaeger server URL | | +| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_COAP_ADAPTER_INSTANCE_ID | CoAP adapter instance ID | "" | ## Deployment The service itself is distributed as Docker container. Check the [`coap-adapter`](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yml) service section in docker-compose file to see how service is deployed. -Running this service outside of container requires working instance of the message broker service, things service and Jaeger server. +Running this service outside of container requires working instance of the message broker service, clients service and Jaeger server. To start the service outside of the container, execute the following shell script: ```bash @@ -57,11 +57,11 @@ MG_COAP_ADAPTER_HTTP_HOST=localhost \ MG_COAP_ADAPTER_HTTP_PORT=5683 \ MG_COAP_ADAPTER_HTTP_SERVER_CERT="" \ MG_COAP_ADAPTER_HTTP_SERVER_KEY="" \ -MG_THINGS_AUTH_GRPC_URL=localhost:7000 \ -MG_THINGS_AUTH_GRPC_TIMEOUT=1s \ -MG_THINGS_AUTH_GRPC_CLIENT_CERT="" \ -MG_THINGS_AUTH_GRPC_CLIENT_KEY="" \ -MG_THINGS_AUTH_GRPC_SERVER_CERTS="" \ +MG_CLIENTS_AUTH_GRPC_URL=localhost:7000 \ +MG_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ +MG_CLIENTS_AUTH_GRPC_CLIENT_CERT="" \ +MG_CLIENTS_AUTH_GRPC_CLIENT_KEY="" \ +MG_CLIENTS_AUTH_GRPC_SERVER_CERTS="" \ MG_MESSAGE_BROKER_URL=nats://localhost:4222 \ MG_JAEGER_URL=http://localhost:14268/api/traces \ MG_JAEGER_TRACE_RATIO=1.0 \ @@ -72,9 +72,9 @@ $GOBIN/magistrala-coap Setting `MG_COAP_ADAPTER_SERVER_CERT` and `MG_COAP_ADAPTER_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_COAP_ADAPTER_HTTP_SERVER_CERT` and `MG_COAP_ADAPTER_HTTP_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key. -Setting `MG_THINGS_AUTH_GRPC_CLIENT_CERT` and `MG_THINGS_AUTH_GRPC_CLIENT_KEY` will enable TLS against the things service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_THINGS_AUTH_GRPC_SERVER_CERTS` will enable TLS against the things service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. +Setting `MG_CLIENTS_AUTH_GRPC_CLIENT_CERT` and `MG_CLIENTS_AUTH_GRPC_CLIENT_KEY` will enable TLS against the clients service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_CLIENTS_AUTH_GRPC_SERVER_CERTS` will enable TLS against the clients service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. ## Usage -If CoAP adapter is running locally (on default 5683 port), a valid URL would be: `coap://localhost/channels//messages?auth=`. -Since CoAP protocol does not support `Authorization` header (option) and options have limited size, in order to send CoAP messages, valid `auth` value (a valid Thing key) must be present in `Uri-Query` option. +If CoAP adapter is running locally (on default 5683 port), a valid URL would be: `coap://localhost/channels//messages?auth=`. +Since CoAP protocol does not support `Authorization` header (option) and options have limited size, in order to send CoAP messages, valid `auth` value (a valid Client key) must be present in `Uri-Query` option. diff --git a/coap/adapter.go b/coap/adapter.go index 2d25b3c0c6..505a65a48d 100644 --- a/coap/adapter.go +++ b/coap/adapter.go @@ -10,13 +10,17 @@ import ( "context" "fmt" - "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + "github.com/absmach/magistrala/pkg/connections" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/magistrala/pkg/policies" ) +var errFailedToDisconnectClient = errors.New("failed to disconnect client") + const chansPrefix = "channels" // Service specifies CoAP service API. @@ -31,82 +35,124 @@ type Service interface { // Unsubscribe method is used to stop observing resource. Unsubscribe(ctx context.Context, key, chanID, subptopic, token string) error + + // DisconnectHandler method is used to disconnected the client + DisconnectHandler(ctx context.Context, chanID, subptopic, token string) error } var _ Service = (*adapterService)(nil) // Observers is a map of maps,. type adapterService struct { - things magistrala.ThingsServiceClient - pubsub messaging.PubSub + clients grpcClientsV1.ClientsServiceClient + channels grpcChannelsV1.ChannelsServiceClient + pubsub messaging.PubSub } // New instantiates the CoAP adapter implementation. -func New(thingsClient magistrala.ThingsServiceClient, pubsub messaging.PubSub) Service { +func New(clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient, pubsub messaging.PubSub) Service { as := &adapterService{ - things: thingsClient, - pubsub: pubsub, + clients: clients, + channels: channels, + pubsub: pubsub, } return as } func (svc *adapterService) Publish(ctx context.Context, key string, msg *messaging.Message) error { - ar := &magistrala.ThingsAuthzReq{ - Permission: policies.PublishPermission, - ThingKey: key, - ChannelId: msg.GetChannel(), + authnRes, err := svc.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ + ClientSecret: key, + }) + if err != nil { + return errors.Wrap(svcerr.ErrAuthentication, err) + } + if !authnRes.Authenticated { + return svcerr.ErrAuthentication } - res, err := svc.things.Authorize(ctx, ar) + + authzRes, err := svc.channels.Authorize(ctx, &grpcChannelsV1.AuthzReq{ + ClientId: authnRes.GetId(), + ClientType: policies.ClientType, + Type: uint32(connections.Publish), + ChannelId: msg.GetChannel(), + }) if err != nil { return errors.Wrap(svcerr.ErrAuthorization, err) } - if !res.GetAuthorized() { + if !authzRes.Authorized { return svcerr.ErrAuthorization } - msg.Publisher = res.GetId() + + msg.Publisher = authnRes.GetId() return svc.pubsub.Publish(ctx, msg.GetChannel(), msg) } func (svc *adapterService) Subscribe(ctx context.Context, key, chanID, subtopic string, c Client) error { - ar := &magistrala.ThingsAuthzReq{ - Permission: policies.SubscribePermission, - ThingKey: key, - ChannelId: chanID, + authnRes, err := svc.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ + ClientSecret: key, + }) + if err != nil { + return errors.Wrap(svcerr.ErrAuthentication, err) + } + if !authnRes.Authenticated { + return svcerr.ErrAuthentication } - res, err := svc.things.Authorize(ctx, ar) + + clientID := authnRes.GetId() + authzRes, err := svc.channels.Authorize(ctx, &grpcChannelsV1.AuthzReq{ + ClientId: clientID, + ClientType: policies.ClientType, + Type: uint32(connections.Subscribe), + ChannelId: chanID, + }) if err != nil { return errors.Wrap(svcerr.ErrAuthorization, err) } - if !res.GetAuthorized() { + if !authzRes.Authorized { return svcerr.ErrAuthorization } + subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) if subtopic != "" { subject = fmt.Sprintf("%s.%s", subject, subtopic) } + + authzc := newAuthzClient(clientID, chanID, subtopic, svc.channels, c) subCfg := messaging.SubscriberConfig{ ID: c.Token(), Topic: subject, - Handler: c, + Handler: authzc, } return svc.pubsub.Subscribe(ctx, subCfg) } func (svc *adapterService) Unsubscribe(ctx context.Context, key, chanID, subtopic, token string) error { - ar := &magistrala.ThingsAuthzReq{ - Permission: policies.SubscribePermission, - ThingKey: key, - ChannelId: chanID, + authnRes, err := svc.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ + ClientSecret: key, + }) + if err != nil { + return errors.Wrap(svcerr.ErrAuthentication, err) + } + if !authnRes.Authenticated { + return svcerr.ErrAuthentication } - res, err := svc.things.Authorize(ctx, ar) + + authzRes, err := svc.channels.Authorize(ctx, &grpcChannelsV1.AuthzReq{ + DomainId: "", + ClientId: authnRes.GetId(), + ClientType: policies.ClientType, + Type: uint32(connections.Subscribe), + ChannelId: chanID, + }) if err != nil { return errors.Wrap(svcerr.ErrAuthorization, err) } - if !res.GetAuthorized() { + if !authzRes.Authorized { return svcerr.ErrAuthorization } + subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) if subtopic != "" { subject = fmt.Sprintf("%s.%s", subject, subtopic) @@ -114,3 +160,54 @@ func (svc *adapterService) Unsubscribe(ctx context.Context, key, chanID, subtopi return svc.pubsub.Unsubscribe(ctx, token, subject) } + +func (svc *adapterService) DisconnectHandler(ctx context.Context, chanID, subtopic, token string) error { + subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) + if subtopic != "" { + subject = fmt.Sprintf("%s.%s", subject, subtopic) + } + + return svc.pubsub.Unsubscribe(ctx, token, subject) +} + +type authzClient interface { + // Handle handles incoming messages. + Handle(m *messaging.Message) error + + // Cancel cancels the client. + Cancel() error +} + +type ac struct { + clientID string + channelID string + subTopic string + channels grpcChannelsV1.ChannelsServiceClient + client Client +} + +func newAuthzClient(clientID, channelID, subTopic string, channels grpcChannelsV1.ChannelsServiceClient, client Client) authzClient { + return ac{clientID, channelID, subTopic, channels, client} +} + +func (a ac) Handle(m *messaging.Message) error { + res, err := a.channels.Authorize(context.Background(), &grpcChannelsV1.AuthzReq{ClientId: a.clientID, ClientType: policies.ClientType, ChannelId: a.channelID, Type: uint32(connections.Subscribe)}) + if err != nil { + if disErr := a.Cancel(); disErr != nil { + return errors.Wrap(err, errors.Wrap(errFailedToDisconnectClient, disErr)) + } + return err + } + if !res.GetAuthorized() { + err := svcerr.ErrAuthorization + if disErr := a.Cancel(); disErr != nil { + return errors.Wrap(err, errors.Wrap(errFailedToDisconnectClient, disErr)) + } + return err + } + return a.client.Handle(m) +} + +func (a ac) Cancel() error { + return a.client.Cancel() +} diff --git a/coap/api/logging.go b/coap/api/logging.go index 2f81f77f92..079dd0665d 100644 --- a/coap/api/logging.go +++ b/coap/api/logging.go @@ -91,3 +91,26 @@ func (lm *loggingMiddleware) Unsubscribe(ctx context.Context, key, chanID, subto return lm.svc.Unsubscribe(ctx, key, chanID, subtopic, token) } + +// DisconnectHandler logs the disconnect handler. It logs the channel ID, subtopic (if any) and the time it took to complete the request. +// If the request fails, it logs the error. +func (lm *loggingMiddleware) DisconnectHandler(ctx context.Context, chanID, subtopic, token string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("channel_id", chanID), + slog.String("token", token), + } + if subtopic != "" { + args = append(args, slog.String("subtopic", subtopic)) + } + if err != nil { + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Unsubscribe failed", args...) + return + } + lm.logger.Info("Unsubscribe completed successfully", args...) + }(time.Now()) + + return lm.svc.DisconnectHandler(ctx, chanID, subtopic, token) +} diff --git a/coap/api/metrics.go b/coap/api/metrics.go index e6bca32927..e75414309a 100644 --- a/coap/api/metrics.go +++ b/coap/api/metrics.go @@ -60,3 +60,13 @@ func (mm *metricsMiddleware) Unsubscribe(ctx context.Context, key, chanID, subto return mm.svc.Unsubscribe(ctx, key, chanID, subtopic, token) } + +// DisconnectHandler instruments DisconnectHandler method with metrics. +func (mm *metricsMiddleware) DisconnectHandler(ctx context.Context, chanID, subtopic, token string) error { + defer func(begin time.Time) { + mm.counter.With("method", "disconnect_handler").Add(1) + mm.latency.With("method", "disconnect_handler").Observe(time.Since(begin).Seconds()) + }(time.Now()) + + return mm.svc.DisconnectHandler(ctx, chanID, subtopic, token) +} diff --git a/coap/api/transport.go b/coap/api/transport.go index a2bbc8d1b9..7743baade8 100644 --- a/coap/api/transport.go +++ b/coap/api/transport.go @@ -132,18 +132,7 @@ func handleGet(m *mux.Message, w mux.ResponseWriter, msg *messaging.Message, key if obs == startObserve { c := coap.NewClient(w.Conn(), m.Token(), logger) w.Conn().AddOnClose(func() { - err := service.Unsubscribe(context.Background(), key, msg.GetChannel(), msg.GetSubtopic(), c.Token()) - args := []any{ - slog.String("channel_id", msg.GetChannel()), - slog.String("subtopic", msg.GetSubtopic()), - slog.String("token", c.Token()), - } - if err != nil { - args = append(args, slog.Any("error", err)) - logger.Warn("Unsubscribe idle client failed ", args...) - return - } - logger.Warn("Unsubscribe idle client completed successfully", args...) + _ = service.DisconnectHandler(context.Background(), msg.GetChannel(), msg.GetSubtopic(), c.Token()) }) return service.Subscribe(w.Conn().Context(), key, msg.GetChannel(), msg.GetSubtopic(), c) } diff --git a/coap/tracing/adapter.go b/coap/tracing/adapter.go index f2d3e92a4f..dee701f695 100644 --- a/coap/tracing/adapter.go +++ b/coap/tracing/adapter.go @@ -16,9 +16,10 @@ var _ coap.Service = (*tracingServiceMiddleware)(nil) // Operation names for tracing CoAP operations. const ( - publishOP = "publish_op" - subscribeOP = "subscribe_op" - unsubscribeOP = "unsubscribe_op" + publishOP = "publish_op" + subscribeOP = "subscribe_op" + unsubscribeOP = "unsubscribe_op" + disconnectHandlerOp = "disconnect_handler_op" ) // tracingServiceMiddleware is a middleware implementation for tracing CoAP service operations using OpenTelemetry. @@ -61,3 +62,13 @@ func (tm *tracingServiceMiddleware) Unsubscribe(ctx context.Context, key, chanID defer span.End() return tm.svc.Unsubscribe(ctx, key, chanID, subptopic, token) } + +// DisconnectHandler traces a CoAP disconnect operation. +func (tm *tracingServiceMiddleware) DisconnectHandler(ctx context.Context, chanID, subptopic, token string) error { + ctx, span := tm.tracer.Start(ctx, disconnectHandlerOp, trace.WithAttributes( + attribute.String("channel_id", chanID), + attribute.String("subtopic", subptopic), + )) + defer span.End() + return tm.svc.DisconnectHandler(ctx, chanID, subptopic, token) +} diff --git a/config.toml b/config.toml index 0745847311..cd96a917f8 100644 --- a/config.toml +++ b/config.toml @@ -18,6 +18,6 @@ user_token = "" http_adapter_url = "http://localhost:8008" invitations_url = "http://localhost:9020" reader_url = "http://localhost:9011" - things_url = "http://localhost:9000" + clients_url = "http://localhost:9000" tls_verification = false users_url = "http://localhost:9002" diff --git a/docker/.env b/docker/.env index 305d2c062d..19cf96e50d 100644 --- a/docker/.env +++ b/docker/.env @@ -76,11 +76,11 @@ MG_POSTGRES_MAX_CONNECTIONS=100 ### Auth MG_AUTH_LOG_LEVEL=debug MG_AUTH_HTTP_HOST=auth -MG_AUTH_HTTP_PORT=8189 +MG_AUTH_HTTP_PORT=9001 MG_AUTH_HTTP_SERVER_CERT= MG_AUTH_HTTP_SERVER_KEY= MG_AUTH_GRPC_HOST=auth -MG_AUTH_GRPC_PORT=8181 +MG_AUTH_GRPC_PORT=7001 MG_AUTH_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-server.crt}${GRPC_TLS:+./ssl/certs/auth-grpc-server.crt} MG_AUTH_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/auth-grpc-server.key}${GRPC_TLS:+./ssl/certs/auth-grpc-server.key} MG_AUTH_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} @@ -99,15 +99,41 @@ MG_AUTH_REFRESH_TOKEN_DURATION="24h" MG_AUTH_INVITATION_DURATION="168h" MG_AUTH_ADAPTER_INSTANCE_ID= -#### Auth GRPC Client Config -MG_AUTH_GRPC_URL=auth:8181 +#### Auth Client Config +MG_AUTH_URL=auth:9001 +MG_AUTH_GRPC_URL=auth:7001 MG_AUTH_GRPC_TIMEOUT=300s MG_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt} MG_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.key} MG_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} +### Domains +MG_DOMAINS_LOG_LEVEL=debug +MG_DOMAINS_HTTP_HOST=domains +MG_DOMAINS_HTTP_PORT=9003 +MG_DOMAINS_HTTP_SERVER_KEY= +MG_DOMAINS_HTTP_SERVER_CERT= +MG_DOMAINS_GRPC_HOST=domains +MG_DOMAINS_GRPC_PORT=7003 +MG_DOMAINS_DB_HOST=domains-db +MG_DOMAINS_DB_PORT=5432 +MG_DOMAINS_DB_NAME=domains +MG_DOMAINS_DB_USER=magistrala +MG_DOMAINS_DB_PASS=magistrala +MG_DOMAINS_DB_SSL_MODE= +MG_DOMAINS_DB_SSL_KEY= +MG_DOMAINS_DB_SSL_CERT= +MG_DOMAINS_DB_SSL_ROOT_CERT= +MG_DOMAINS_INSTANCE_ID= + #### Domains Client Config -MG_DOMAINS_URL=http://auth:8189 +MG_DOMAINS_URL=http://domains:9003 +MG_DOMAINS_GRPC_URL=domains:7003 +MG_DOMAINS_GRPC_TIMEOUT=300s +MG_DOMAINS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/domains-grpc-client.crt} +MG_DOMAINS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/domains-grpc-client.key} +MG_DOMAINS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} + ### SpiceDB Datastore config MG_SPICEDB_DB_USER=magistrala @@ -144,10 +170,10 @@ MG_UI_LOG_LEVEL=debug MG_UI_PORT=9095 MG_HTTP_ADAPTER_URL=http://http-adapter:8008 MG_READER_URL=http://timescale-reader:9011 -MG_THINGS_URL=http://things:9000 +MG_CLIENTS_URL=http://clients:9006 MG_USERS_URL=http://users:9002 MG_INVITATIONS_URL=http://invitations:9020 -MG_DOMAINS_URL=http://auth:8189 +MG_DOMAINS_URL=http://domains:9003 MG_BOOTSTRAP_URL=http://bootstrap:9013 MG_UI_HOST_URL=http://localhost:9095 MG_UI_VERIFICATION_TLS=false @@ -193,12 +219,22 @@ MG_USERS_DB_SSL_KEY= MG_USERS_DB_SSL_ROOT_CERT= MG_USERS_RESET_PWD_TEMPLATE=users.tmpl MG_USERS_INSTANCE_ID= +MG_USERS_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH +MG_USERS_ADMIN_EMAIL=admin@example.com +MG_USERS_ADMIN_PASSWORD=12345678 +MG_USERS_PASS_REGEX=^.{8,}$ +MG_USERS_ACCESS_TOKEN_DURATION=15m +MG_USERS_REFRESH_TOKEN_DURATION=24h +MG_TOKEN_RESET_ENDPOINT=/reset-request MG_USERS_ALLOW_SELF_REGISTER=true MG_OAUTH_UI_REDIRECT_URL=http://localhost:9095${MG_UI_PATH_PREFIX}/tokens/secure MG_OAUTH_UI_ERROR_URL=http://localhost:9095${MG_UI_PATH_PREFIX}/error MG_USERS_DELETE_INTERVAL=24h MG_USERS_DELETE_AFTER=720h +#### Users Client Config +MG_USERS_URL=users:9002 + ### Email utility MG_EMAIL_HOST=smtp.mailtrap.io MG_EMAIL_PORT=2525 @@ -214,37 +250,96 @@ MG_GOOGLE_CLIENT_SECRET= MG_GOOGLE_REDIRECT_URL= MG_GOOGLE_STATE= -### Things -MG_THINGS_LOG_LEVEL=debug -MG_THINGS_STANDALONE_ID= -MG_THINGS_STANDALONE_TOKEN= -MG_THINGS_CACHE_KEY_DURATION=10m -MG_THINGS_HTTP_HOST=things -MG_THINGS_HTTP_PORT=9000 -MG_THINGS_AUTH_GRPC_HOST=things -MG_THINGS_AUTH_GRPC_PORT=7000 -MG_THINGS_AUTH_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/things-grpc-server.crt}${GRPC_TLS:+./ssl/certs/things-grpc-server.crt} -MG_THINGS_AUTH_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/things-grpc-server.key}${GRPC_TLS:+./ssl/certs/things-grpc-server.key} -MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} -MG_THINGS_CACHE_URL=redis://things-redis:${MG_REDIS_TCP_PORT}/0 -MG_THINGS_DB_HOST=things-db -MG_THINGS_DB_PORT=5432 -MG_THINGS_DB_USER=magistrala -MG_THINGS_DB_PASS=magistrala -MG_THINGS_DB_NAME=things -MG_THINGS_DB_SSL_MODE=disable -MG_THINGS_DB_SSL_CERT= -MG_THINGS_DB_SSL_KEY= -MG_THINGS_DB_SSL_ROOT_CERT= -MG_THINGS_INSTANCE_ID= - -#### Things Client Config -MG_THINGS_URL=http://things:9000 -MG_THINGS_AUTH_GRPC_URL=things:7000 -MG_THINGS_AUTH_GRPC_TIMEOUT=1s -MG_THINGS_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/things-grpc-client.crt} -MG_THINGS_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/things-grpc-client.key} -MG_THINGS_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} +### Groups +MG_GROUPS_LOG_LEVEL=debug +MG_GROUPS_HTTP_HOST=groups +MG_GROUPS_HTTP_PORT=9004 +MG_GROUPS_HTTP_SERVER_CERT= +MG_GROUPS_HTTP_SERVER_KEY= +MG_GROUPS_GRPC_HOST=groups +MG_GROUPS_GRPC_PORT=7004 +MG_GROUPS_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/groups-grpc-server.crt}${GRPC_TLS:+./ssl/certs/groups-grpc-server.crt} +MG_GROUPS_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/groups-grpc-server.key}${GRPC_TLS:+./ssl/certs/groups-grpc-server.key} +MG_GROUPS_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} +MG_GROUPS_DB_HOST=groups-db +MG_GROUPS_DB_PORT=5432 +MG_GROUPS_DB_USER=magistrala +MG_GROUPS_DB_PASS=magistrala +MG_GROUPS_DB_NAME=groups +MG_GROUPS_DB_SSL_MODE=disable +MG_GROUPS_DB_SSL_CERT= +MG_GROUPS_DB_SSL_KEY= +MG_GROUPS_DB_SSL_ROOT_CERT= +MG_GROUPS_INSTANCE_ID= + +#### Groups Client Config +MG_GROUPS_URL=groups:9004 +MG_GROUPS_GRPC_URL=groups:7004 +MG_GROUPS_GRPC_TIMEOUT=300s +MG_GROUPS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/groups-grpc-client.crt} +MG_GROUPS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/groups-grpc-client.key} +MG_GROUPS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} + +### Clients +MG_CLIENTS_LOG_LEVEL=debug +MG_CLIENTS_STANDALONE_ID= +MG_CLIENTS_STANDALONE_TOKEN= +MG_CLIENTS_CACHE_KEY_DURATION=10m +MG_CLIENTS_HTTP_HOST=clients +MG_CLIENTS_HTTP_PORT=9006 +MG_CLIENTS_AUTH_GRPC_HOST=clients +MG_CLIENTS_AUTH_GRPC_PORT=7006 +MG_CLIENTS_AUTH_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/clients-grpc-server.crt}${GRPC_TLS:+./ssl/certs/clients-grpc-server.crt} +MG_CLIENTS_AUTH_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/clients-grpc-server.key}${GRPC_TLS:+./ssl/certs/clients-grpc-server.key} +MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} +MG_CLIENTS_CACHE_URL=redis://clients-redis:${MG_REDIS_TCP_PORT}/0 +MG_CLIENTS_DB_HOST=clients-db +MG_CLIENTS_DB_PORT=5432 +MG_CLIENTS_DB_USER=magistrala +MG_CLIENTS_DB_PASS=magistrala +MG_CLIENTS_DB_NAME=clients +MG_CLIENTS_DB_SSL_MODE=disable +MG_CLIENTS_DB_SSL_CERT= +MG_CLIENTS_DB_SSL_KEY= +MG_CLIENTS_DB_SSL_ROOT_CERT= +MG_CLIENTS_INSTANCE_ID= + +#### Clients Client Config +MG_CLIENTS_URL=http://clients:9006 +MG_CLIENTS_AUTH_GRPC_URL=clients:7006 +MG_CLIENTS_AUTH_GRPC_TIMEOUT=1s +MG_CLIENTS_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/clients-grpc-client.crt} +MG_CLIENTS_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/clients-grpc-client.key} +MG_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} + +### Channels +MG_CHANNELS_LOG_LEVEL=debug +MG_CHANNELS_HTTP_HOST=channels +MG_CHANNELS_HTTP_PORT=9005 +MG_CHANNELS_GRPC_HOST=channels +MG_CHANNELS_GRPC_PORT=7005 +MG_CHANNELS_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/channels-grpc-server.crt}${GRPC_TLS:+./ssl/certs/channels-grpc-server.crt} +MG_CHANNELS_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/channels-grpc-server.key}${GRPC_TLS:+./ssl/certs/channels-grpc-server.key} +MG_CHANNELS_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} +MG_CHANNELS_DB_HOST=channels-db +MG_CHANNELS_DB_PORT=5432 +MG_CHANNELS_DB_USER=magistrala +MG_CHANNELS_DB_PASS=magistrala +MG_CHANNELS_DB_NAME=channels +MG_CHANNELS_DB_SSL_MODE=disable +MG_CHANNELS_DB_SSL_CERT= +MG_CHANNELS_DB_SSL_KEY= +MG_CHANNELS_DB_SSL_ROOT_CERT= +MG_CHANNELS_INSTANCE_ID= + + +#### Channels Client Config +MG_CHANNELS_URL=http://channels:9005 +MG_CHANNELS_GRPC_URL=channels:7005 +MG_CHANNELS_GRPC_TIMEOUT=1s +MG_CHANNELS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/channels-grpc-client.crt} +MG_CHANNELS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/channels-grpc-client.key} +MG_CHANNELS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} ### HTTP MG_HTTP_ADAPTER_LOG_LEVEL=debug @@ -311,7 +406,7 @@ MG_PROVISION_ENV_CLIENTS_TLS=false MG_PROVISION_SERVER_CERT= MG_PROVISION_SERVER_KEY= MG_PROVISION_USERS_LOCATION=http://users:9002 -MG_PROVISION_THINGS_LOCATION=http://things:9000 +MG_PROVISION_CLIENTS_LOCATION=http://clients:9006 MG_PROVISION_USER= MG_PROVISION_USERNAME= MG_PROVISION_PASS= @@ -352,7 +447,7 @@ MG_VAULT_PKI_CLUSTER_AIA_PATH=http://localhost MG_VAULT_PKI_INT_PATH=pki_int MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME=magistrala_server_certs -MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME=magistrala_things_certs +MG_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME=magistrala_clients_certs MG_VAULT_PKI_INT_FILE_NAME=mg_int MG_VAULT_PKI_INT_CA_CN='Magistrala Intermediate Certificate Authority' MG_VAULT_PKI_INT_CA_OU='Magistrala' @@ -365,8 +460,8 @@ MG_VAULT_PKI_INT_CA_PO='75007' MG_VAULT_PKI_INT_CLUSTER_PATH=http://localhost MG_VAULT_PKI_INT_CLUSTER_AIA_PATH=http://localhost -MG_VAULT_THINGS_CERTS_ISSUER_ROLEID=magistrala -MG_VAULT_THINGS_CERTS_ISSUER_SECRET=magistrala +MG_VAULT_CLIENTS_CERTS_ISSUER_ROLEID=magistrala +MG_VAULT_CLIENTS_CERTS_ISSUER_SECRET=magistrala # Certs MG_CERTS_LOG_LEVEL=debug @@ -374,10 +469,10 @@ MG_CERTS_SIGN_CA_PATH=/etc/ssl/certs/ca.crt MG_CERTS_SIGN_CA_KEY_PATH=/etc/ssl/certs/ca.key MG_CERTS_VAULT_HOST=${MG_VAULT_ADDR} MG_CERTS_VAULT_NAMESPACE=${MG_VAULT_NAMESPACE} -MG_CERTS_VAULT_APPROLE_ROLEID=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} -MG_CERTS_VAULT_APPROLE_SECRET=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} -MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH=${MG_VAULT_PKI_INT_PATH} -MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME=${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} +MG_CERTS_VAULT_APPROLE_ROLEID=${MG_VAULT_CLIENTS_CERTS_ISSUER_ROLEID} +MG_CERTS_VAULT_APPROLE_SECRET=${MG_VAULT_CLIENTS_CERTS_ISSUER_SECRET} +MG_CERTS_VAULT_CLIENTS_CERTS_PKI_PATH=${MG_VAULT_PKI_INT_PATH} +MG_CERTS_VAULT_CLIENTS_CERTS_PKI_ROLE_NAME=${MG_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME} MG_CERTS_HTTP_HOST=certs MG_CERTS_HTTP_PORT=9019 MG_CERTS_HTTP_SERVER_CERT= diff --git a/docker/Dockerfile.livereload b/docker/Dockerfile.livereload new file mode 100644 index 0000000000..d0d482e946 --- /dev/null +++ b/docker/Dockerfile.livereload @@ -0,0 +1,3 @@ +FROM golang:1.23-alpine +RUN go install github.com/air-verse/air@latest +RUN apk update && apk add make git diff --git a/docker/addons/bootstrap/docker-compose.yml b/docker/addons/bootstrap/docker-compose.yml index d51df0533e..00e8463d6f 100644 --- a/docker/addons/bootstrap/docker-compose.yml +++ b/docker/addons/bootstrap/docker-compose.yml @@ -57,7 +57,7 @@ services: MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} - MG_THINGS_URL: ${MG_THINGS_URL} + MG_CLIENTS_URL: ${MG_CLIENTS_URL} MG_JAEGER_URL: ${MG_JAEGER_URL} MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} diff --git a/docker/addons/certs/docker-compose.yml b/docker/addons/certs/docker-compose.yml index 806ff03352..554ec13490 100644 --- a/docker/addons/certs/docker-compose.yml +++ b/docker/addons/certs/docker-compose.yml @@ -32,8 +32,8 @@ services: MG_CERTS_VAULT_NAMESPACE: ${MG_CERTS_VAULT_NAMESPACE} MG_CERTS_VAULT_APPROLE_ROLEID: ${MG_CERTS_VAULT_APPROLE_ROLEID} MG_CERTS_VAULT_APPROLE_SECRET: ${MG_CERTS_VAULT_APPROLE_SECRET} - MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH: ${MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH} - MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME: ${MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME} + MG_CERTS_VAULT_CLIENTS_CERTS_PKI_PATH: ${MG_CERTS_VAULT_CLIENTS_CERTS_PKI_PATH} + MG_CERTS_VAULT_CLIENTS_CERTS_PKI_ROLE_NAME: ${MG_CERTS_VAULT_CLIENTS_CERTS_PKI_ROLE_NAME} MG_CERTS_HTTP_HOST: ${MG_CERTS_HTTP_HOST} MG_CERTS_HTTP_PORT: ${MG_CERTS_HTTP_PORT} MG_CERTS_HTTP_SERVER_CERT: ${MG_CERTS_HTTP_SERVER_CERT} @@ -55,7 +55,7 @@ services: MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} - MG_THINGS_URL: ${MG_THINGS_URL} + MG_CLIENTS_URL: ${MG_CLIENTS_URL} MG_JAEGER_URL: ${MG_JAEGER_URL} MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} diff --git a/docker/addons/postgres-reader/docker-compose.yml b/docker/addons/postgres-reader/docker-compose.yml index 3b84d6c9bd..7895047caf 100644 --- a/docker/addons/postgres-reader/docker-compose.yml +++ b/docker/addons/postgres-reader/docker-compose.yml @@ -30,11 +30,16 @@ services: MG_POSTGRES_SSL_CERT: ${MG_POSTGRES_SSL_CERT} MG_POSTGRES_SSL_KEY: ${MG_POSTGRES_SSL_KEY} MG_POSTGRES_SSL_ROOT_CERT: ${MG_POSTGRES_SSL_ROOT_CERT} - MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL} - MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT} - MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt} - MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL} MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT} MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} @@ -62,19 +67,51 @@ services: target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt} + source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} bind: create_host_path: true - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key} + source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} bind: create_host_path: true - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Channels gRPC mTLS client certificates + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Auth gRPC mTLS client certificates + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true diff --git a/docker/addons/prometheus/grafana/example-dashboard.json b/docker/addons/prometheus/grafana/example-dashboard.json index 5604103129..63411e2963 100644 --- a/docker/addons/prometheus/grafana/example-dashboard.json +++ b/docker/addons/prometheus/grafana/example-dashboard.json @@ -398,7 +398,7 @@ }, "id": 35, "panels": [], - "title": "Things-Service", + "title": "Clients-Service", "type": "row" }, { @@ -452,14 +452,14 @@ "uid": "PBFA97CFB590B2093" }, "exemplar": true, - "expr": "things_api_request_count{}", + "expr": "clients_api_request_count{}", "instant": false, "interval": "", "legendFormat": "{{method}}", "refId": "A" } ], - "title": "Things Request Count", + "title": "Clients Request Count", "type": "gauge" }, { @@ -559,7 +559,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "label_replace(label_replace(label_replace(things_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", + "expr": "label_replace(label_replace(label_replace(clients_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", "format": "time_series", "instant": false, "interval": "", @@ -569,7 +569,7 @@ "refId": "A" } ], - "title": "Things Latency Quantiles", + "title": "Clients Latency Quantiles", "type": "timeseries" }, { diff --git a/docker/addons/prometheus/metrics/prometheus.yml b/docker/addons/prometheus/metrics/prometheus.yml index ecac123d6d..3ea4032c41 100644 --- a/docker/addons/prometheus/metrics/prometheus.yml +++ b/docker/addons/prometheus/metrics/prometheus.yml @@ -15,7 +15,7 @@ scrape_configs: enable_http2: true static_configs: - targets: - - magistrala-things:9000 + - magistrala-clients:9000 - magistrala-users:9002 - magistrala-http:8008 - magistrala-ws:8186 diff --git a/docker/addons/provision/configs/config.toml b/docker/addons/provision/configs/config.toml index ec1ee38bba..70e3114951 100644 --- a/docker/addons/provision/configs/config.toml +++ b/docker/addons/provision/configs/config.toml @@ -19,13 +19,13 @@ [bootstrap.content.agent.server] nats_url = "localhost:4222" port = "9000" - + [bootstrap.content.agent.heartbeat] interval = "30s" - + [bootstrap.content.agent.terminal] session_timeout = "30s" - + [bootstrap.content.export.exp] log_level = "debug" @@ -37,12 +37,12 @@ [bootstrap.content.export.mqtt] ca_path = "ca.crt" - cert_path = "thing.crt" + cert_path = "client.crt" channel = "" host = "tcp://localhost:1883" mtls = false password = "" - priv_key_path = "thing.key" + priv_key_path = "client.key" qos = 0 retain = false skip_tls_ver = false @@ -55,10 +55,10 @@ type = "plain" workers = 10 -[[things]] - name = "thing" +[[clients]] + name = "client" - [things.metadata] + [clients.metadata] external_id = "xxxxxx" [[channels]] diff --git a/docker/addons/provision/docker-compose.yml b/docker/addons/provision/docker-compose.yml index da8befad41..bf2fe29eb7 100644 --- a/docker/addons/provision/docker-compose.yml +++ b/docker/addons/provision/docker-compose.yml @@ -26,7 +26,7 @@ services: MG_PROVISION_SERVER_CERT: ${MG_PROVISION_SERVER_CERT} MG_PROVISION_SERVER_KEY: ${MG_PROVISION_SERVER_KEY} MG_PROVISION_USERS_LOCATION: ${MG_PROVISION_USERS_LOCATION} - MG_PROVISION_THINGS_LOCATION: ${MG_PROVISION_THINGS_LOCATION} + MG_PROVISION_CLIENTS_LOCATION: ${MG_PROVISION_CLIENTS_LOCATION} MG_PROVISION_USER: ${MG_PROVISION_USER} MG_PROVISION_USERNAME: ${MG_PROVISION_USERNAME} MG_PROVISION_PASS: ${MG_PROVISION_PASS} diff --git a/docker/addons/timescale-reader/docker-compose.yml b/docker/addons/timescale-reader/docker-compose.yml index 269e1c6025..11c0beca6b 100644 --- a/docker/addons/timescale-reader/docker-compose.yml +++ b/docker/addons/timescale-reader/docker-compose.yml @@ -30,11 +30,16 @@ services: MG_TIMESCALE_SSL_CERT: ${MG_TIMESCALE_SSL_CERT} MG_TIMESCALE_SSL_KEY: ${MG_TIMESCALE_SSL_KEY} MG_TIMESCALE_SSL_ROOT_CERT: ${MG_TIMESCALE_SSL_ROOT_CERT} - MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL} - MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT} - MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt} - MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL} MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT} MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} @@ -62,19 +67,51 @@ services: target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt} + source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} bind: create_host_path: true - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key} + source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} bind: create_host_path: true - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Channels gRPC mTLS client certificates + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Auth gRPC mTLS client certificates + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true diff --git a/docker/addons/vault/README.md b/docker/addons/vault/README.md index ab9f1fc73e..59e45ab60b 100644 --- a/docker/addons/vault/README.md +++ b/docker/addons/vault/README.md @@ -2,7 +2,7 @@ This is Vault service deployment to be used with Magistrala. -When the Vault service is started, some initialization steps need to be done to set things up. +When the Vault service is started, some initialization steps need to be done to set clients up. ## Configuration @@ -28,7 +28,7 @@ When the Vault service is started, some initialization steps need to be done to | MG_VAULT_PKI_CLUSTER_AIA_PATH | Vault Root CA Cluster AIA Path | http://localhost | | MG_VAULT_PKI_INT_PATH | Vault secrets engine path for Intermediate CA | pki_int | | MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue server certificate | magistrala_server_certs | -| MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue Things certificates | magistrala_things_certs | +| MG_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue Clients certificates | magistrala_clients_certs | | MG_VAULT_PKI_INT_FILE_NAME | Intermediate CA Certificates name used by`vault_set_pki.sh` | mg_root | | MG_VAULT_PKI_INT_CA_CN | Common name used for Intermediate CA creation by`vault_set_pki.sh` | Magistrala Root Certificate Authority | | MG_VAULT_PKI_INT_CA_OU | Organization unit used for Root CA creation by`vault_set_pki.sh` | Magistrala | @@ -40,8 +40,8 @@ When the Vault service is started, some initialization steps need to be done to | MG_VAULT_PKI_INT_CA_PO | Postal code used for Intermediate CA creation by`vault_set_pki.sh` | 75007 | | MG_VAULT_PKI_INT_CLUSTER_PATH | Vault Intermediate CA Cluster Path | http://localhost | | MG_VAULT_PKI_INT_CLUSTER_AIA_PATH | Vault Intermediate CA Cluster AIA Path | http://localhost | -| MG_VAULT_THINGS_CERTS_ISSUER_ROLEID | Vault Intermediate CA Things Certificate issuer AppRole authentication RoleID | magistrala | -| MG_VAULT_THINGS_CERTS_ISSUER_SECRET | Vault Intermediate CA Things Certificate issuer AppRole authentication Secret | magistrala | +| MG_VAULT_CLIENTS_CERTS_ISSUER_ROLEID | Vault Intermediate CA Clients Certificate issuer AppRole authentication RoleID | magistrala | +| MG_VAULT_CLIENTS_CERTS_ISSUER_SECRET | Vault Intermediate CA Clients Certificate issuer AppRole authentication Secret | magistrala | ## Setup @@ -169,19 +169,19 @@ token_policies ["root"] identity_policies [] policies ["root"] Creating new policy for AppRole -Successfully copied 2.56kB to magistrala-vault:/vault/magistrala_things_certs_issue.hcl -Success! Uploaded policy: magistrala_things_certs_issue +Successfully copied 2.56kB to magistrala-vault:/vault/magistrala_clients_certs_issue.hcl +Success! Uploaded policy: magistrala_clients_certs_issue Enabling AppRole Success! Enabled approle auth method at: approle/ Deleting old AppRole -Success! Data deleted (if it existed) at: auth/approle/role/magistrala_things_certs_issuer +Success! Data deleted (if it existed) at: auth/approle/role/magistrala_clients_certs_issuer Creating new AppRole -Success! Data written to: auth/approle/role/magistrala_things_certs_issuer +Success! Data written to: auth/approle/role/magistrala_clients_certs_issuer Writing custom role ID Key Value --- ----- role_id f23942b3-62b9-7456-784f-220ca3f703b9 -Success! Data written to: auth/approle/role/magistrala_things_certs_issuer/role-id +Success! Data written to: auth/approle/role/magistrala_clients_certs_issuer/role-id Writing custom secret Key Value --- ----- @@ -196,13 +196,13 @@ token token_accessor 9cuwS4mrLHKhJQMv0pl9Bbg9 token_duration 1h token_renewable true -token_policies ["default" "magistrala_things_certs_issue"] +token_policies ["default" "magistrala_clients_certs_issue"] identity_policies [] -policies ["default" "magistrala_things_certs_issue"] -token_meta_role_name magistrala_things_certs_issuer +policies ["default" "magistrala_clients_certs_issue"] +token_meta_role_name magistrala_clients_certs_issuer ``` -By default, the `vault_create_approle.sh` script tries to enable the AppRole authentication method. Certs service uses the approle credentials to issue and revoke things certificate from vault intermedate CA. If AppRole is already enabled, you can skip this step by passing the `--skip-enable-approle` argument: +By default, the `vault_create_approle.sh` script tries to enable the AppRole authentication method. Certs service uses the approle credentials to issue and revoke clients certificate from vault intermedate CA. If AppRole is already enabled, you can skip this step by passing the `--skip-enable-approle` argument: ```sh ./vault_create_approle.sh --skip-enable-approle diff --git a/docker/addons/vault/scripts/.gitignore b/docker/addons/vault/scripts/.gitignore index 4f14d396c2..feae90c5d7 100644 --- a/docker/addons/vault/scripts/.gitignore +++ b/docker/addons/vault/scripts/.gitignore @@ -2,4 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 data -magistrala_things_certs_issue.hcl +magistrala_clients_certs_issue.hcl diff --git a/docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl b/docker/addons/vault/scripts/magistrala_clients_certs_issue.template.hcl similarity index 89% rename from docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl rename to docker/addons/vault/scripts/magistrala_clients_certs_issue.template.hcl index 1b13f6db1b..ed7a9f546e 100644 --- a/docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl +++ b/docker/addons/vault/scripts/magistrala_clients_certs_issue.template.hcl @@ -1,6 +1,6 @@ # Allow issue certificate with role with default issuer from Intermediate PKI -path "${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME}" { +path "${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME}" { capabilities = ["create", "update"] } diff --git a/docker/addons/vault/scripts/vault_create_approle.sh b/docker/addons/vault/scripts/vault_create_approle.sh index c95eb742b0..0978f258ad 100755 --- a/docker/addons/vault/scripts/vault_create_approle.sh +++ b/docker/addons/vault/scripts/vault_create_approle.sh @@ -47,17 +47,17 @@ source "$scriptdir/vault_cmd.sh" vaultCreatePolicyFile() { envsubst ' ${MG_VAULT_PKI_INT_PATH} - ${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} - ' < "$scriptdir/magistrala_things_certs_issue.template.hcl" > "$scriptdir/magistrala_things_certs_issue.hcl" + ${MG_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME} + ' < "$scriptdir/magistrala_clients_certs_issue.template.hcl" > "$scriptdir/magistrala_clients_certs_issue.hcl" } vaultCreatePolicy() { echo "Creating new policy for AppRole" if is_container_running "magistrala-vault"; then - docker cp "$scriptdir/magistrala_things_certs_issue.hcl" magistrala-vault:/vault/magistrala_things_certs_issue.hcl - vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue /vault/magistrala_things_certs_issue.hcl + docker cp "$scriptdir/magistrala_clients_certs_issue.hcl" magistrala-vault:/vault/magistrala_clients_certs_issue.hcl + vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_clients_certs_issue /vault/magistrala_clients_certs_issue.hcl else - vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue "$scriptdir/magistrala_things_certs_issue.hcl" + vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_clients_certs_issue "$scriptdir/magistrala_clients_certs_issue.hcl" fi } @@ -72,33 +72,33 @@ vaultEnableAppRole() { vaultDeleteRole() { echo "Deleting old AppRole" - vault delete -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer + vault delete -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_clients_certs_issuer } vaultCreateRole() { echo "Creating new AppRole" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer \ - token_policies=magistrala_things_certs_issue secret_id_num_uses=0 \ + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_clients_certs_issuer \ + token_policies=magistrala_clients_certs_issue secret_id_num_uses=0 \ secret_id_ttl=0 token_ttl=1h token_max_ttl=3h token_num_uses=0 } vaultWriteCustomRoleID() { echo "Writing custom role id" - vault read -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/role-id - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/role-id role_id=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} + vault read -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_clients_certs_issuer/role-id + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_clients_certs_issuer/role-id role_id=${MG_VAULT_CLIENTS_CERTS_ISSUER_ROLEID} } vaultWriteCustomSecret() { echo "Writing custom secret" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -f auth/approle/role/magistrala_things_certs_issuer/secret-id - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/custom-secret-id secret_id=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} num_uses=0 ttl=0 + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -f auth/approle/role/magistrala_clients_certs_issuer/secret-id + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_clients_certs_issuer/custom-secret-id secret_id=${MG_VAULT_CLIENTS_CERTS_ISSUER_SECRET} num_uses=0 ttl=0 } vaultTestRoleLogin() { echo "Testing custom roleid secret by logging in" vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/login \ - role_id=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} \ - secret_id=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} + role_id=${MG_VAULT_CLIENTS_CERTS_ISSUER_ROLEID} \ + secret_id=${MG_VAULT_CLIENTS_CERTS_ISSUER_SECRET} } if ! command -v jq &> /dev/null; then diff --git a/docker/addons/vault/scripts/vault_set_pki.sh b/docker/addons/vault/scripts/vault_set_pki.sh index fb8f389414..7d289bb056 100755 --- a/docker/addons/vault/scripts/vault_set_pki.sh +++ b/docker/addons/vault/scripts/vault_set_pki.sh @@ -204,9 +204,9 @@ vaultGenerateServerCertificate() { fi } -vaultSetupThingCertsRole() { - echo "Setup Thing Certs role" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} \ +vaultSetupClientCertsRole() { + echo "Setup Client Certs role" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME} \ allow_subdomains=true \ allow_any_name=true \ max_ttl="2160h" @@ -245,7 +245,7 @@ vaultGenerateIntermediateCertificateBundle vaultSetupIntermediateIssuingURLs vaultSetupServerCertsRole vaultGenerateServerCertificate -vaultSetupThingCertsRole +vaultSetupClientCertsRole vaultCleanupFiles exit 0 diff --git a/docker/docker-compose-live.yaml b/docker/docker-compose-live.yaml new file mode 100644 index 0000000000..732356bca8 --- /dev/null +++ b/docker/docker-compose-live.yaml @@ -0,0 +1,96 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +services: + domains: + image: magistrala/domains-dev + build: + context: . + dockerfile: Dockerfile.livereload + volumes: + - ../:/go/src/github.com/absmach/magistrala + - $GOPATH/pkg/mod/cache:/go/pkg/mod/cache + working_dir: /go/src/github.com/absmach/magistrala + entrypoint: [ "air", + "--build.cmd", "BUILD_DIR=/tmp make domains", + "--build.bin", "/tmp/domains", + "--build.stop_on_error", "true", + "--build.send_interrupt", "true", + "--build.include_file","dockers/.env", + "--build.exclude_dir", ".vscode,.git,.docker,.github,api,build,tools,scripts", + "--build.exclude_regex", "[\"_test\\.go\"" , + "--tmp_dir", "/tmp",] + + users: + image: magistrala/users-dev + build: + context: . + dockerfile: Dockerfile.livereload + volumes: + - ../:/go/src/github.com/absmach/magistrala + - $GOPATH/pkg/mod/cache:/go/pkg/mod/cache + working_dir: /go/src/github.com/absmach/magistrala + entrypoint: [ "air", + "--build.cmd", "BUILD_DIR=/tmp make users", + "--build.bin", "/tmp/users", + "--build.stop_on_error", "true", + "--build.send_interrupt", "true", + "--build.exclude_dir", ".vscode,.git,.docker,.github,api,build,tools,scripts", + "--build.exclude_regex", "[\"_test\\.go\"" , + "--tmp_dir", "/tmp",] + clients: + image: magistrala/clients-dev + build: + context: . + dockerfile: Dockerfile.livereload + volumes: + - ../:/go/src/github.com/absmach/magistrala + - $GOPATH/pkg/mod/cache:/go/pkg/mod/cache + working_dir: /go/src/github.com/absmach/magistrala + entrypoint: [ "air", + "--build.cmd", "BUILD_DIR=/tmp make clients", + "--build.bin", "/tmp/clients", + "--build.stop_on_error", "true", + "--build.send_interrupt", "true", + "--build.exclude_dir", ".vscode,.git,.docker,.github,api,build,tools,scripts", + "--build.exclude_regex", "[\"_test\\.go\"" , + "-tmp_dir", "/tmp",] + + channels: + image: magistrala/channels-dev + build: + context: . + dockerfile: Dockerfile.livereload + volumes: + - ../:/go/src/github.com/absmach/magistrala + - $GOPATH/pkg/mod/cache:/go/pkg/mod/cache + working_dir: /go/src/github.com/absmach/magistrala + entrypoint: [ "air", + "--build.cmd", "BUILD_DIR=/tmp make channels", + "--build.bin", "/tmp/channels", + "--build.stop_on_error", "true", + "--build.send_interrupt", "true", + "--build.exclude_dir", ".vscode,.git,.docker,.github,api,build,tools,scripts", + "--build.exclude_regex", "[\"_test\\.go\"" , + "-tmp_dir", "/tmp",] + + channels-db: + command: ["postgres", "-c", "log_statement=all"] + + groups: + image: magistrala/groups-dev + build: + context: . + dockerfile: Dockerfile.livereload + volumes: + - ../:/go/src/github.com/absmach/magistrala + - $GOPATH/pkg/mod/cache:/go/pkg/mod/cache + working_dir: /go/src/github.com/absmach/magistrala + entrypoint: [ "air", + "--build.cmd", "BUILD_DIR=/tmp make groups", + "--build.bin", "/tmp/groups", + "--build.stop_on_error", "true", + "--build.send_interrupt", "true", + "--build.exclude_dir", ".vscode,.git,.docker,.github,api,build,tools,scripts", + "--build.exclude_regex", "[\"_test\\.go\"" , + "-tmp_dir", "/tmp",] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 804389ea8d..d8da7ccfeb 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,18 +9,21 @@ networks: volumes: magistrala-users-db-volume: - magistrala-things-db-volume: - magistrala-things-redis-volume: + magistrala-groups-db-volume: + magistrala-clients-db-volume: + magistrala-channels-db-volume: + magistrala-clients-redis-volume: magistrala-broker-volume: magistrala-mqtt-broker-volume: magistrala-spicedb-db-volume: magistrala-auth-db-volume: + magistrala-domains-db-volume: magistrala-invitations-db-volume: magistrala-ui-db-volume: services: spicedb: - image: "authzed/spicedb:v1.30.0" + image: "authzed/spicedb:v1.37.0" container_name: magistrala-spicedb command: "serve" restart: "always" @@ -38,7 +41,7 @@ services: - spicedb-migrate spicedb-migrate: - image: "authzed/spicedb:v1.30.0" + image: "authzed/spicedb:v1.37.0" container_name: magistrala-spicedb-migrate command: "migrate head" restart: "on-failure" @@ -69,7 +72,7 @@ services: container_name: magistrala-auth-db restart: on-failure ports: - - 6004:5432 + - 6001:5432 environment: POSTGRES_USER: ${MG_AUTH_DB_USER} POSTGRES_PASSWORD: ${MG_AUTH_DB_PASS} @@ -154,6 +157,111 @@ services: bind: create_host_path: true + domains-db: + image: postgres:16.2-alpine + container_name: magistrala-domains-db + restart: on-failure + ports: + - 6003:5432 + environment: + POSTGRES_USER: ${MG_DOMAINS_DB_USER} + POSTGRES_PASSWORD: ${MG_DOMAINS_DB_PASS} + POSTGRES_DB: ${MG_DOMAINS_DB_NAME} + networks: + - magistrala-base-net + volumes: + - magistrala-domains-db-volume:/var/lib/postgresql/data + + domains: + image: magistrala/domains:${MG_RELEASE_TAG} + container_name: magistrala-domains + depends_on: + - domains-db + - spicedb + expose: + - ${MG_DOMAINS_GRPC_PORT} + restart: on-failure + environment: + MG_DOMAINS_LOG_LEVEL: ${MG_DOMAINS_LOG_LEVEL} + MG_SPICEDB_PRE_SHARED_KEY: ${MG_SPICEDB_PRE_SHARED_KEY} + MG_SPICEDB_HOST: ${MG_SPICEDB_HOST} + MG_SPICEDB_PORT: ${MG_SPICEDB_PORT} + MG_DOMAINS_HTTP_HOST: ${MG_DOMAINS_HTTP_HOST} + MG_DOMAINS_HTTP_PORT: ${MG_DOMAINS_HTTP_PORT} + MG_DOMAINS_HTTP_SERVER_CERT: ${MG_DOMAINS_HTTP_SERVER_CERT} + MG_DOMAINS_HTTP_SERVER_KEY: ${MG_DOMAINS_HTTP_SERVER_KEY} + MG_DOMAINS_GRPC_HOST: ${MG_DOMAINS_GRPC_HOST} + MG_DOMAINS_GRPC_PORT: ${MG_DOMAINS_GRPC_PORT} + ## Compose supports parameter expansion in environment, + ## Eg: ${VAR:+replacement} or ${VAR+replacement} -> replacement if VAR is set and non-empty, otherwise empty + ## Eg :${VAR:-default} or ${VAR-default} -> value of VAR if set and non-empty, otherwise default + MG_DOMAINS_GRPC_SERVER_CERT: ${MG_DOMAINS_GRPC_SERVER_CERT:+/auth-grpc-server.crt} + MG_DOMAINS_GRPC_SERVER_KEY: ${MG_DOMAINS_GRPC_SERVER_KEY:+/auth-grpc-server.key} + MG_DOMAINS_GRPC_SERVER_CA_CERTS: ${MG_DOMAINS_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + MG_DOMAINS_GRPC_CLIENT_CA_CERTS: ${MG_DOMAINS_GRPC_CLIENT_CA_CERTS:+/auth-grpc-client-ca.crt} + MG_DOMAINS_DB_HOST: ${MG_DOMAINS_DB_HOST} + MG_DOMAINS_DB_PORT: ${MG_DOMAINS_DB_PORT} + MG_DOMAINS_DB_USER: ${MG_DOMAINS_DB_USER} + MG_DOMAINS_DB_PASS: ${MG_DOMAINS_DB_PASS} + MG_DOMAINS_DB_NAME: ${MG_DOMAINS_DB_NAME} + MG_DOMAINS_DB_SSL_MODE: ${MG_DOMAINS_DB_SSL_MODE} + MG_DOMAINS_DB_SSL_CERT: ${MG_DOMAINS_DB_SSL_CERT} + MG_DOMAINS_DB_SSL_KEY: ${MG_DOMAINS_DB_SSL_KEY} + MG_DOMAINS_DB_SSL_ROOT_CERT: ${MG_DOMAINS_DB_SSL_ROOT_CERT} + MG_DOMAINS_INSTANCE_ID: ${MG_DOMAINS_INSTANCE_ID} + MG_ES_URL: ${MG_ES_URL} + MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL} + MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT} + MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + MG_GROUPS_GRPC_URL: ${MG_GROUPS_GRPC_URL} + MG_GROUPS_GRPC_TIMEOUT: ${MG_GROUPS_GRPC_TIMEOUT} + MG_GROUPS_GRPC_CLIENT_CERT: ${MG_GROUPS_GRPC_CLIENT_CERT:+/groups-grpc-client.crt} + MG_GROUPS_GRPC_CLIENT_KEY: ${MG_GROUPS_GRPC_CLIENT_KEY:+/groups-grpc-client.key} + MG_GROUPS_GRPC_SERVER_CA_CERTS: ${MG_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt} + MG_CHANNELS_URL: ${MG_CHANNELS_URL} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_JAEGER_URL: ${MG_JAEGER_URL} + MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} + MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} + ports: + - ${MG_DOMAINS_HTTP_PORT}:${MG_DOMAINS_HTTP_PORT} + - ${MG_DOMAINS_GRPC_PORT}:${MG_DOMAINS_GRPC_PORT} + networks: + - magistrala-base-net + volumes: + # Auth gRPC mTLS server certificates + - type: bind + source: ${MG_DOMAINS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert} + target: /auth-grpc-server${MG_DOMAINS_GRPC_SERVER_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_DOMAINS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key} + target: /auth-grpc-server${MG_DOMAINS_GRPC_SERVER_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs} + target: /auth-grpc-server-ca${MG_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_DOMAINS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs} + target: /auth-grpc-client-ca${MG_DOMAINS_GRPC_CLIENT_CA_CERTS:+.crt} + bind: + create_host_path: true + invitations-db: image: postgres:16.2-alpine container_name: magistrala-invitations-db @@ -258,80 +366,92 @@ services: - .env depends_on: - auth - - things + - clients - users - mqtt-adapter - http-adapter - ws-adapter - coap-adapter - things-db: + clients-db: image: postgres:16.2-alpine - container_name: magistrala-things-db + container_name: magistrala-clients-db restart: on-failure command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" environment: - POSTGRES_USER: ${MG_THINGS_DB_USER} - POSTGRES_PASSWORD: ${MG_THINGS_DB_PASS} - POSTGRES_DB: ${MG_THINGS_DB_NAME} + POSTGRES_USER: ${MG_CLIENTS_DB_USER} + POSTGRES_PASSWORD: ${MG_CLIENTS_DB_PASS} + POSTGRES_DB: ${MG_CLIENTS_DB_NAME} MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} networks: - magistrala-base-net ports: - 6006:5432 volumes: - - magistrala-things-db-volume:/var/lib/postgresql/data + - magistrala-clients-db-volume:/var/lib/postgresql/data - things-redis: + clients-redis: image: redis:7.2.4-alpine - container_name: magistrala-things-redis + container_name: magistrala-clients-redis restart: on-failure networks: - magistrala-base-net volumes: - - magistrala-things-redis-volume:/data + - magistrala-clients-redis-volume:/data - things: - image: magistrala/things:${MG_RELEASE_TAG} - container_name: magistrala-things + clients: + image: magistrala/clients:${MG_RELEASE_TAG} + container_name: magistrala-clients depends_on: - - things-db + - clients-db - users - auth - nats restart: on-failure environment: - MG_THINGS_LOG_LEVEL: ${MG_THINGS_LOG_LEVEL} - MG_THINGS_STANDALONE_ID: ${MG_THINGS_STANDALONE_ID} - MG_THINGS_STANDALONE_TOKEN: ${MG_THINGS_STANDALONE_TOKEN} - MG_THINGS_CACHE_KEY_DURATION: ${MG_THINGS_CACHE_KEY_DURATION} - MG_THINGS_HTTP_HOST: ${MG_THINGS_HTTP_HOST} - MG_THINGS_HTTP_PORT: ${MG_THINGS_HTTP_PORT} - MG_THINGS_AUTH_GRPC_HOST: ${MG_THINGS_AUTH_GRPC_HOST} - MG_THINGS_AUTH_GRPC_PORT: ${MG_THINGS_AUTH_GRPC_PORT} + MG_CLIENTS_LOG_LEVEL: ${MG_CLIENTS_LOG_LEVEL} + MG_CLIENTS_STANDALONE_ID: ${MG_CLIENTS_STANDALONE_ID} + MG_CLIENTS_STANDALONE_TOKEN: ${MG_CLIENTS_STANDALONE_TOKEN} + MG_CLIENTS_CACHE_KEY_DURATION: ${MG_CLIENTS_CACHE_KEY_DURATION} + MG_CLIENTS_HTTP_HOST: ${MG_CLIENTS_HTTP_HOST} + MG_CLIENTS_HTTP_PORT: ${MG_CLIENTS_HTTP_PORT} + MG_CLIENTS_AUTH_GRPC_HOST: ${MG_CLIENTS_AUTH_GRPC_HOST} + MG_CLIENTS_AUTH_GRPC_PORT: ${MG_CLIENTS_AUTH_GRPC_PORT} ## Compose supports parameter expansion in environment, ## Eg: ${VAR:+replacement} or ${VAR+replacement} -> replacement if VAR is set and non-empty, otherwise empty ## Eg :${VAR:-default} or ${VAR-default} -> value of VAR if set and non-empty, otherwise default - MG_THINGS_AUTH_GRPC_SERVER_CERT: ${MG_THINGS_AUTH_GRPC_SERVER_CERT:+/things-grpc-server.crt} - MG_THINGS_AUTH_GRPC_SERVER_KEY: ${MG_THINGS_AUTH_GRPC_SERVER_KEY:+/things-grpc-server.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} - MG_THINGS_AUTH_GRPC_CLIENT_CA_CERTS: ${MG_THINGS_AUTH_GRPC_CLIENT_CA_CERTS:+/things-grpc-client-ca.crt} + MG_CLIENTS_AUTH_GRPC_SERVER_CERT: ${MG_CLIENTS_AUTH_GRPC_SERVER_CERT:+/clients-grpc-server.crt} + MG_CLIENTS_AUTH_GRPC_SERVER_KEY: ${MG_CLIENTS_AUTH_GRPC_SERVER_KEY:+/clients-grpc-server.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS:+/clients-grpc-client-ca.crt} MG_ES_URL: ${MG_ES_URL} - MG_THINGS_CACHE_URL: ${MG_THINGS_CACHE_URL} - MG_THINGS_DB_HOST: ${MG_THINGS_DB_HOST} - MG_THINGS_DB_PORT: ${MG_THINGS_DB_PORT} - MG_THINGS_DB_USER: ${MG_THINGS_DB_USER} - MG_THINGS_DB_PASS: ${MG_THINGS_DB_PASS} - MG_THINGS_DB_NAME: ${MG_THINGS_DB_NAME} - MG_THINGS_DB_SSL_MODE: ${MG_THINGS_DB_SSL_MODE} - MG_THINGS_DB_SSL_CERT: ${MG_THINGS_DB_SSL_CERT} - MG_THINGS_DB_SSL_KEY: ${MG_THINGS_DB_SSL_KEY} - MG_THINGS_DB_SSL_ROOT_CERT: ${MG_THINGS_DB_SSL_ROOT_CERT} + MG_CLIENTS_CACHE_URL: ${MG_CLIENTS_CACHE_URL} + MG_CLIENTS_DB_HOST: ${MG_CLIENTS_DB_HOST} + MG_CLIENTS_DB_PORT: ${MG_CLIENTS_DB_PORT} + MG_CLIENTS_DB_USER: ${MG_CLIENTS_DB_USER} + MG_CLIENTS_DB_PASS: ${MG_CLIENTS_DB_PASS} + MG_CLIENTS_DB_NAME: ${MG_CLIENTS_DB_NAME} + MG_CLIENTS_DB_SSL_MODE: ${MG_CLIENTS_DB_SSL_MODE} + MG_CLIENTS_DB_SSL_CERT: ${MG_CLIENTS_DB_SSL_CERT} + MG_CLIENTS_DB_SSL_KEY: ${MG_CLIENTS_DB_SSL_KEY} + MG_CLIENTS_DB_SSL_ROOT_CERT: ${MG_CLIENTS_DB_SSL_ROOT_CERT} MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL} MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT} MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + MG_CHANNELS_URL: ${MG_CHANNELS_URL} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + MG_GROUPS_URL: ${MG_GROUPS_URL} + MG_GROUPS_GRPC_URL: ${MG_GROUPS_GRPC_URL} + MG_GROUPS_GRPC_TIMEOUT: ${MG_GROUPS_GRPC_TIMEOUT} + MG_GROUPS_GRPC_CLIENT_CERT: ${MG_GROUPS_GRPC_CLIENT_CERT:+/groups-grpc-client.crt} + MG_GROUPS_GRPC_CLIENT_KEY: ${MG_GROUPS_GRPC_CLIENT_KEY:+/groups-grpc-client.key} + MG_GROUPS_GRPC_SERVER_CA_CERTS: ${MG_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt} MG_JAEGER_URL: ${MG_JAEGER_URL} MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} @@ -339,30 +459,30 @@ services: MG_SPICEDB_HOST: ${MG_SPICEDB_HOST} MG_SPICEDB_PORT: ${MG_SPICEDB_PORT} ports: - - ${MG_THINGS_HTTP_PORT}:${MG_THINGS_HTTP_PORT} - - ${MG_THINGS_AUTH_GRPC_PORT}:${MG_THINGS_AUTH_GRPC_PORT} + - ${MG_CLIENTS_HTTP_PORT}:${MG_CLIENTS_HTTP_PORT} + - ${MG_CLIENTS_AUTH_GRPC_PORT}:${MG_CLIENTS_AUTH_GRPC_PORT} networks: - magistrala-base-net volumes: - # Things gRPC server certificates + # Clients gRPC server certificates - type: bind - source: ${MG_THINGS_AUTH_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert} - target: /things-grpc-server${MG_THINGS_AUTH_GRPC_SERVER_CERT:+.crt} + source: ${MG_CLIENTS_AUTH_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert} + target: /clients-grpc-server${MG_CLIENTS_AUTH_GRPC_SERVER_CERT:+.crt} bind: create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key} - target: /things-grpc-server${MG_THINGS_AUTH_GRPC_SERVER_KEY:+.key} + source: ${MG_CLIENTS_AUTH_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key} + target: /clients-grpc-server${MG_CLIENTS_AUTH_GRPC_SERVER_KEY:+.key} bind: create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + source: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs} + target: /clients-grpc-server-ca${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs} - target: /things-grpc-client-ca${MG_THINGS_AUTH_GRPC_CLIENT_CA_CERTS:+.crt} + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs} + target: /clients-grpc-client-ca${MG_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS:+.crt} bind: create_host_path: true # Auth gRPC client certificates @@ -381,6 +501,116 @@ services: target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true + # Channel gRPC client certificates + - type: bind + source: ${MG_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${MG_CHANNELS_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${MG_CHANNELS_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + + channels-db: + image: postgres:16.2-alpine + container_name: magistrala-channels-db + restart: on-failure + command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" + environment: + POSTGRES_USER: ${MG_CHANNELS_DB_USER} + POSTGRES_PASSWORD: ${MG_CHANNELS_DB_PASS} + POSTGRES_DB: ${MG_CHANNELS_DB_NAME} + MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} + networks: + - magistrala-base-net + ports: + - 6005:5432 + volumes: + - magistrala-channels-db-volume:/var/lib/postgresql/data + + channels: + image: magistrala/channels:${MG_RELEASE_TAG} + container_name: magistrala-channels + depends_on: + - channels-db + - users + - auth + - nats + restart: on-failure + environment: + MG_CHANNELS_LOG_LEVEL: ${MG_CHANNELS_LOG_LEVEL} + MG_CHANNELS_INSTANCE_ID: ${MG_CHANNELS_INSTANCE_ID} + MG_CHANNELS_HTTP_HOST: ${MG_CHANNELS_HTTP_HOST} + MG_CHANNELS_HTTP_PORT: ${MG_CHANNELS_HTTP_PORT} + MG_CHANNELS_GRPC_HOST: ${MG_CHANNELS_GRPC_HOST} + MG_CHANNELS_GRPC_PORT: ${MG_CHANNELS_GRPC_PORT} + ## Compose supports parameter expansion in environment, + ## Eg: ${VAR:+replacement} or ${VAR+replacement} -> replacement if VAR is set and non-empty, otherwise empty + ## Eg :${VAR:-default} or ${VAR-default} -> value of VAR if set and non-empty, otherwise default + MG_CHANNELS_GRPC_SERVER_CERT: ${MG_CHANNELS_GRPC_SERVER_CERT:+/channels-grpc-server.crt} + MG_CHANNELS_GRPC_SERVER_KEY: ${MG_CHANNELS_GRPC_SERVER_KEY:+/channels-grpc-server.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + MG_CHANNELS_GRPC_CLIENT_CA_CERTS: ${MG_CHANNELS_GRPC_CLIENT_CA_CERTS:+/channels-grpc-client-ca.crt} + MG_CHANNELS_DB_HOST: ${MG_CHANNELS_DB_HOST} + MG_CHANNELS_DB_PORT: ${MG_CHANNELS_DB_PORT} + MG_CHANNELS_DB_USER: ${MG_CHANNELS_DB_USER} + MG_CHANNELS_DB_PASS: ${MG_CHANNELS_DB_PASS} + MG_CHANNELS_DB_NAME: ${MG_CHANNELS_DB_NAME} + MG_CHANNELS_DB_SSL_MODE: ${MG_CHANNELS_DB_SSL_MODE} + MG_CHANNELS_DB_SSL_CERT: ${MG_CHANNELS_DB_SSL_CERT} + MG_CHANNELS_DB_SSL_KEY: ${MG_CHANNELS_DB_SSL_KEY} + MG_CHANNELS_DB_SSL_ROOT_CERT: ${MG_CHANNELS_DB_SSL_ROOT_CERT} + MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL} + MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT} + MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_GROUPS_GRPC_URL: ${MG_GROUPS_GRPC_URL} + MG_GROUPS_GRPC_TIMEOUT: ${MG_GROUPS_GRPC_TIMEOUT} + MG_GROUPS_GRPC_CLIENT_CERT: ${MG_GROUPS_GRPC_CLIENT_CERT:+/groups-grpc-client.crt} + MG_GROUPS_GRPC_CLIENT_KEY: ${MG_GROUPS_GRPC_CLIENT_KEY:+/groups-grpc-client.key} + MG_GROUPS_GRPC_SERVER_CA_CERTS: ${MG_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt} + MG_ES_URL: ${MG_ES_URL} + MG_JAEGER_URL: ${MG_JAEGER_URL} + MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} + MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} + MG_SPICEDB_PRE_SHARED_KEY: ${MG_SPICEDB_PRE_SHARED_KEY} + MG_SPICEDB_HOST: ${MG_SPICEDB_HOST} + MG_SPICEDB_PORT: ${MG_SPICEDB_PORT} + ports: + - ${MG_CHANNELS_HTTP_PORT}:${MG_CHANNELS_HTTP_PORT} + - ${MG_CHANNELS_GRPC_PORT}:${MG_CHANNELS_GRPC_PORT} + networks: + - magistrala-base-net + volumes: + # Auth gRPC client certificates + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true users-db: image: postgres:16.2-alpine @@ -393,7 +623,7 @@ services: POSTGRES_DB: ${MG_USERS_DB_NAME} MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} ports: - - 6000:5432 + - 6002:5432 networks: - magistrala-base-net volumes: @@ -449,6 +679,11 @@ services: MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + MG_DOMAINS_GRPC_URL: ${MG_DOMAINS_GRPC_URL} + MG_DOMAINS_GRPC_TIMEOUT: ${MG_DOMAINS_GRPC_TIMEOUT} + MG_DOMAINS_GRPC_CLIENT_CERT: ${MG_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt} + MG_DOMAINS_GRPC_CLIENT_KEY: ${MG_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key} + MG_DOMAINS_GRPC_SERVER_CA_CERTS: ${MG_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt} MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} @@ -483,6 +718,103 @@ services: bind: create_host_path: true + + groups-db: + image: postgres:16.2-alpine + container_name: magistrala-groups-db + restart: on-failure + command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" + environment: + POSTGRES_USER: ${MG_GROUPS_DB_USER} + POSTGRES_PASSWORD: ${MG_GROUPS_DB_PASS} + POSTGRES_DB: ${MG_GROUPS_DB_NAME} + MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} + ports: + - 6004:5432 + networks: + - magistrala-base-net + volumes: + - magistrala-groups-db-volume:/var/lib/postgresql/data + + groups: + image: magistrala/groups:${MG_RELEASE_TAG} + container_name: magistrala-groups + depends_on: + - groups-db + - auth + - nats + restart: on-failure + environment: + MG_GROUPS_LOG_LEVEL: ${MG_GROUPS_LOG_LEVEL} + MG_GROUPS_HTTP_HOST: ${MG_GROUPS_HTTP_HOST} + MG_GROUPS_HTTP_PORT: ${MG_GROUPS_HTTP_PORT} + MG_GROUPS_HTTP_SERVER_CERT: ${MG_GROUPS_HTTP_SERVER_CERT} + MG_GROUPS_HTTP_SERVER_KEY: ${MG_GROUPS_HTTP_SERVER_KEY} + MG_GROUPS_GRPC_HOST: ${MG_GROUPS_GRPC_HOST} + MG_GROUPS_GRPC_PORT: ${MG_GROUPS_GRPC_PORT} + ## Compose supports parameter expansion in environment, + ## Eg: ${VAR:+replacement} or ${VAR+replacement} -> replacement if VAR is set and non-empty, otherwise empty + ## Eg :${VAR:-default} or ${VAR-default} -> value of VAR if set and non-empty, otherwise default + MG_GROUPS_GRPC_SERVER_CERT: ${MG_GROUPS_GRPC_SERVER_CERT:+/groups-grpc-server.crt} + MG_GROUPS_GRPC_SERVER_KEY: ${MG_GROUPS_GRPC_SERVER_KEY:+/groups-grpc-server.key} + MG_GROUPS_GRPC_SERVER_CA_CERTS: ${MG_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt} + MG_GROUPS_GRPC_CLIENT_CA_CERTS: ${MG_GROUPS_GRPC_CLIENT_CA_CERTS:+/groups-grpc-client-ca.crt} + MG_GROUPS_DB_HOST: ${MG_GROUPS_DB_HOST} + MG_GROUPS_DB_PORT: ${MG_GROUPS_DB_PORT} + MG_GROUPS_DB_USER: ${MG_GROUPS_DB_USER} + MG_GROUPS_DB_PASS: ${MG_GROUPS_DB_PASS} + MG_GROUPS_DB_NAME: ${MG_GROUPS_DB_NAME} + MG_GROUPS_DB_SSL_MODE: ${MG_GROUPS_DB_SSL_MODE} + MG_GROUPS_DB_SSL_CERT: ${MG_GROUPS_DB_SSL_CERT} + MG_GROUPS_DB_SSL_KEY: ${MG_GROUPS_DB_SSL_KEY} + MG_GROUPS_DB_SSL_ROOT_CERT: ${MG_GROUPS_DB_SSL_ROOT_CERT} + MG_CHANNELS_URL: ${MG_CHANNELS_URL} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_ES_URL: ${MG_ES_URL} + MG_JAEGER_URL: ${MG_JAEGER_URL} + MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} + MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} + MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL} + MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT} + MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + MG_SPICEDB_PRE_SHARED_KEY: ${MG_SPICEDB_PRE_SHARED_KEY} + MG_SPICEDB_HOST: ${MG_SPICEDB_HOST} + MG_SPICEDB_PORT: ${MG_SPICEDB_PORT} + ports: + - ${MG_GROUPS_HTTP_PORT}:${MG_GROUPS_HTTP_PORT} + - ${MG_GROUPS_GRPC_PORT}:${MG_GROUPS_GRPC_PORT} + networks: + - magistrala-base-net + volumes: + # Auth gRPC client certificates + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + + jaeger: image: jaegertracing/all-in-one:1.60 container_name: magistrala-jaeger @@ -499,7 +831,7 @@ services: image: magistrala/mqtt:${MG_RELEASE_TAG} container_name: magistrala-mqtt depends_on: - - things + - clients - vernemq - nats restart: on-failure @@ -518,11 +850,16 @@ services: MG_MQTT_ADAPTER_WS_TARGET_PATH: ${MG_MQTT_ADAPTER_WS_TARGET_PATH} MG_MQTT_ADAPTER_INSTANCE: ${MG_MQTT_ADAPTER_INSTANCE} MG_ES_URL: ${MG_ES_URL} - MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL} - MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT} - MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt} - MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} MG_JAEGER_URL: ${MG_JAEGER_URL} MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} @@ -530,20 +867,36 @@ services: networks: - magistrala-base-net volumes: - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates + - type: bind + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt} + source: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true + # Channels gRPC mTLS client certificates - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key} + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} bind: create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true @@ -551,7 +904,7 @@ services: image: magistrala/http:${MG_RELEASE_TAG} container_name: magistrala-http depends_on: - - things + - clients - nats restart: on-failure environment: @@ -560,11 +913,21 @@ services: MG_HTTP_ADAPTER_PORT: ${MG_HTTP_ADAPTER_PORT} MG_HTTP_ADAPTER_SERVER_CERT: ${MG_HTTP_ADAPTER_SERVER_CERT} MG_HTTP_ADAPTER_SERVER_KEY: ${MG_HTTP_ADAPTER_SERVER_KEY} - MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL} - MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT} - MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt} - MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL} + MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT} + MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} MG_JAEGER_URL: ${MG_JAEGER_URL} MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} @@ -575,20 +938,52 @@ services: networks: - magistrala-base-net volumes: - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates + - type: bind + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt} + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} bind: create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key} + source: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true + # Channels gRPC mTLS client certificates - type: bind - source: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Auth gRPC mTLS client certificates + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true @@ -596,7 +991,7 @@ services: image: magistrala/coap:${MG_RELEASE_TAG} container_name: magistrala-coap depends_on: - - things + - clients - nats restart: on-failure environment: @@ -609,11 +1004,16 @@ services: MG_COAP_ADAPTER_HTTP_PORT: ${MG_COAP_ADAPTER_HTTP_PORT} MG_COAP_ADAPTER_HTTP_SERVER_CERT: ${MG_COAP_ADAPTER_HTTP_SERVER_CERT} MG_COAP_ADAPTER_HTTP_SERVER_KEY: ${MG_COAP_ADAPTER_HTTP_SERVER_KEY} - MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL} - MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT} - MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt} - MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} MG_JAEGER_URL: ${MG_JAEGER_URL} MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} @@ -625,20 +1025,36 @@ services: networks: - magistrala-base-net volumes: - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates + - type: bind + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Channels gRPC mTLS client certificates - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt} + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} bind: create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key} + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} bind: create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + source: ${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true @@ -646,7 +1062,7 @@ services: image: magistrala/ws:${MG_RELEASE_TAG} container_name: magistrala-ws depends_on: - - things + - clients - nats restart: on-failure environment: @@ -655,11 +1071,21 @@ services: MG_WS_ADAPTER_HTTP_PORT: ${MG_WS_ADAPTER_HTTP_PORT} MG_WS_ADAPTER_HTTP_SERVER_CERT: ${MG_WS_ADAPTER_HTTP_SERVER_CERT} MG_WS_ADAPTER_HTTP_SERVER_KEY: ${MG_WS_ADAPTER_HTTP_SERVER_KEY} - MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL} - MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT} - MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt} - MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} + MG_CLIENTS_AUTH_GRPC_URL: ${MG_CLIENTS_AUTH_GRPC_URL} + MG_CLIENTS_AUTH_GRPC_TIMEOUT: ${MG_CLIENTS_AUTH_GRPC_TIMEOUT} + MG_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + MG_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + MG_CHANNELS_GRPC_URL: ${MG_CHANNELS_GRPC_URL} + MG_CHANNELS_GRPC_TIMEOUT: ${MG_CHANNELS_GRPC_TIMEOUT} + MG_CHANNELS_GRPC_CLIENT_CERT: ${MG_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + MG_CHANNELS_GRPC_CLIENT_KEY: ${MG_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + MG_CHANNELS_GRPC_SERVER_CA_CERTS: ${MG_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + MG_AUTH_GRPC_URL: ${MG_AUTH_GRPC_URL} + MG_AUTH_GRPC_TIMEOUT: ${MG_AUTH_GRPC_TIMEOUT} + MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} MG_JAEGER_URL: ${MG_JAEGER_URL} MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} @@ -670,20 +1096,52 @@ services: networks: - magistrala-base-net volumes: - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates + - type: bind + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt} + source: ${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${MG_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} bind: create_host_path: true - type: bind - source: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key} + source: ${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${MG_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true + # Channels gRPC mTLS client certificates - type: bind - source: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${MG_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${MG_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Auth gRPC mTLS client certificates + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true @@ -726,7 +1184,7 @@ services: MG_UI_PORT: ${MG_UI_PORT} MG_HTTP_ADAPTER_URL: ${MG_HTTP_ADAPTER_URL} MG_READER_URL: ${MG_READER_URL} - MG_THINGS_URL: ${MG_THINGS_URL} + MG_CLIENTS_URL: ${MG_CLIENTS_URL} MG_USERS_URL: ${MG_USERS_URL} MG_INVITATIONS_URL: ${MG_INVITATIONS_URL} MG_DOMAINS_URL: ${MG_DOMAINS_URL} diff --git a/docker/nginx/entrypoint.sh b/docker/nginx/entrypoint.sh index 6b90377035..eb2154a054 100755 --- a/docker/nginx/entrypoint.sh +++ b/docker/nginx/entrypoint.sh @@ -13,10 +13,12 @@ fi envsubst ' ${MG_NGINX_SERVER_NAME} - ${MG_AUTH_HTTP_PORT} + ${MG_DOMAINS_HTTP_PORT} + ${MG_GROUPS_HTTP_PORT} ${MG_USERS_HTTP_PORT} - ${MG_THINGS_HTTP_PORT} - ${MG_THINGS_AUTH_HTTP_PORT} + ${MG_CLIENTS_HTTP_PORT} + ${MG_CLIENTS_AUTH_HTTP_PORT} + ${MG_CHANNELS_HTTP_PORT} ${MG_HTTP_ADAPTER_PORT} ${MG_NGINX_MQTT_PORT} ${MG_NGINX_MQTTS_PORT} diff --git a/docker/nginx/nginx-key.conf b/docker/nginx/nginx-key.conf index 153a7b7a42..76df250c83 100644 --- a/docker/nginx/nginx-key.conf +++ b/docker/nginx/nginx-key.conf @@ -57,93 +57,39 @@ http { add_header Access-Control-Allow-Methods '*'; add_header Access-Control-Allow-Headers '*'; - location ~ ^/(channels)/(.+)/(things)/(.+) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - # Proxy pass to users & groups id to things service for listing of channels - # /users/{userID}/channels - Listing of channels belongs to userID - # /groups/{userGroupID}/channels - Listing of channels belongs to userGroupID - location ~ ^/(users|groups)/(.+)/(channels|things) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - break; - } - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - # Proxy pass to channel id to users service for listing of channels - # /channels/{channelID}/users - Listing of Users belongs to channelID - # /channels/{channelID}/groups - Listing of User Groups belongs to channelID - location ~ ^/(channels|things)/(.+)/(users|groups) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - break; - } - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - # Proxy pass to user id to auth service for listing of domains - # /users/{userID}/domains - Listing of Domains belongs to userID - location ~ ^/(users)/(.+)/(domains) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - break; - } - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - # Proxy pass to domain id to users service for listing of users - # /domains/{domainID}/users - Listing of Users belongs to domainID - location ~ ^/(domains)/(.+)/(users) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - break; - } - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - } - - - # Proxy pass to auth service + # Proxy pass to domains service location ~ ^/(domains) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; + proxy_pass http://domains:${MG_DOMAINS_HTTP_PORT}; } # Proxy pass to users service - location ~ ^/(users|groups|password|authorize|oauth/callback/[^/]+) { + location ~ ^/(users|password|authorize|oauth/callback/[^/]+) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; proxy_pass http://users:${MG_USERS_HTTP_PORT}; } - location ^~ /users/policies { + # Proxy pass to groups service + location ~ "^/([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})/(groups)" { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; - proxy_pass http://users:${MG_USERS_HTTP_PORT}/policies; + proxy_pass http://groups:${MG_GROUPS_HTTP_PORT}; } - # Proxy pass to things service - location ~ ^/(things|channels|connect|disconnect|identify) { + # Proxy pass to clients service + location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(clients)" { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + proxy_pass http://clients:${MG_CLIENTS_HTTP_PORT}; } - location ^~ /things/policies { + # Proxy pass to domains service + location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(channels)" { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}/policies; + proxy_pass http://channels:${MG_CHANNELS_HTTP_PORT}; } # Proxy pass to invitations service @@ -155,12 +101,12 @@ http { location /health { include snippets/proxy-headers.conf; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + proxy_pass http://clients:${MG_CLIENTS_HTTP_PORT}; } location /metrics { include snippets/proxy-headers.conf; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + proxy_pass http://clients:${MG_CLIENTS_HTTP_PORT}; } # Proxy pass to magistrala-http-adapter diff --git a/docker/nginx/nginx-x509.conf b/docker/nginx/nginx-x509.conf index 1da22b0fb4..1b2a63e900 100644 --- a/docker/nginx/nginx-x509.conf +++ b/docker/nginx/nginx-x509.conf @@ -66,67 +66,11 @@ http { add_header Access-Control-Allow-Methods '*'; add_header Access-Control-Allow-Headers '*'; - location ~ ^/(channels)/(.+)/(things)/(.+) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - # Proxy pass to users & groups id to things service for listing of channels - # /users/{userID}/channels - Listing of channels belongs to userID - # /groups/{userGroupID}/channels - Listing of channels belongs to userGroupID - location ~ ^/(users|groups)/(.+)/(channels|things) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - break; - } - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - # Proxy pass to channel id to users service for listing of channels - # /channels/{channelID}/users - Listing of Users belongs to channelID - # /channels/{channelID}/groups - Listing of User Groups belongs to channelID - location ~ ^/(channels|things)/(.+)/(users|groups) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - break; - } - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - # Proxy pass to user id to auth service for listing of domains - # /users/{userID}/domains - Listing of Domains belongs to userID - location ~ ^/(users)/(.+)/(domains) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - break; - } - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - # Proxy pass to domain id to users service for listing of users - # /domains/{domainID}/users - Listing of Users belongs to domainID - location ~ ^/(domains)/(.+)/(users) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - break; - } - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - } - - - # Proxy pass to auth service + # Proxy pass to domains service location ~ ^/(domains) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; + proxy_pass http://domains:${MG_DOMAINS_HTTP_PORT}; } # Proxy pass to users service @@ -136,23 +80,25 @@ http { proxy_pass http://users:${MG_USERS_HTTP_PORT}; } - location ^~ /users/policies { + # Proxy pass to groups service + location ~ "^/([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})/(groups)" { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; - proxy_pass http://users:${MG_USERS_HTTP_PORT}/policies; + proxy_pass http://groups:${MG_GROUPS_HTTP_PORT}; } - # Proxy pass to things service - location ~ ^/(things|channels|connect|disconnect|identify) { + # Proxy pass to clients service + location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(clients)" { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + proxy_pass http://clients:${MG_CLIENTS_HTTP_PORT}; } - location ^~ /things/policies { + # Proxy pass to domains service + location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(channels)" { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}/policies; + proxy_pass http://channels:${MG_CHANNELS_HTTP_PORT}; } # Proxy pass to invitations service @@ -164,12 +110,12 @@ http { location /health { include snippets/proxy-headers.conf; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + proxy_pass http://clients:${MG_CLIENTS_HTTP_PORT}; } location /metrics { include snippets/proxy-headers.conf; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; + proxy_pass http://clients:${MG_CLIENTS_HTTP_PORT}; } # Proxy pass to magistrala-http-adapter diff --git a/docker/nginx/snippets/http_access_log.conf b/docker/nginx/snippets/http_access_log.conf index d9adfa1958..4bc37166d6 100644 --- a/docker/nginx/snippets/http_access_log.conf +++ b/docker/nginx/snippets/http_access_log.conf @@ -4,5 +4,9 @@ log_format access_log_format 'HTTP/WS ' '$remote_addr: ' '"$request" $status; ' - 'request time=$request_time upstream connect time=$upstream_connect_time upstream response time=$upstream_response_time'; + 'request time=$request_time ' + 'upstream connect time=$upstream_connect_time ' + 'upstream address $upstream_addr ' + 'upstream status $upstream_status ' + 'upstream response time=$upstream_response_time'; access_log access.log access_log_format; diff --git a/docker/spicedb/schema.zed b/docker/spicedb/schema.zed index 215797a99c..f2eb65567e 100644 --- a/docker/spicedb/schema.zed +++ b/docker/spicedb/schema.zed @@ -1,74 +1,509 @@ definition user {} -definition thing { - relation administrator: user - relation group: group - relation domain: domain - - permission admin = administrator + group->admin + domain->admin - permission delete = admin - permission edit = admin + group->edit + domain->edit - permission view = edit + group->view + domain->view - permission share = edit - permission publish = group - permission subscribe = group - - // These permission are made for only list purpose. It helps to list users have only particular permission excluding other higher and lower permission. - permission admin_only = admin - permission edit_only = edit - admin - permission view_only = view - - // These permission are made for only list purpose. It helps to list users from external, users who are not in group but have permission on the group through parent group - permission ext_admin = admin - administrator // For list of external admin , not having direct relation with group, but have indirect relation from parent group -} -definition group { - relation administrator: user - relation editor: user - relation contributor: user +definition role { + relation entity: domain | group | channel | client relation member: user - relation guest: user + relation built_in_role: domain | group | channel | client + + permission delete = entity->manage_role_permission - built_in_role->manage_role_permission + permission update = entity->manage_role_permission - built_in_role->manage_role_permission + permission read = entity->manage_role_permission - built_in_role->manage_role_permission + + permission add_user = entity->add_role_users_permission + permission remove_user = entity->remove_role_users_permission + permission view_user = entity->view_role_users_permission +} + +definition client { + relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it safe to add domain + relation parent_group: group + + relation update: role#member + relation read: role#member + relation delete: role#member + relation set_parent_group: role#member + relation connect_to_channel: role#member + + relation manage_role: role#member + relation add_role_users: role#member + relation remove_role_users: role#member + relation view_role_users: role#member + + permission update_permission = update + parent_group->client_update_permission + domain->client_update_permission + permission read_permission = read + parent_group->client_read_permission + domain->client_read_permission + permission delete_permission = delete + parent_group->client_delete_permission + domain->client_delete_permission + permission set_parent_group_permission = set_parent_group + parent_group->client_set_parent_group_permission + domain->client_set_parent_group_permission + permission connect_to_channel_permission = connect_to_channel + parent_group->client_connect_to_channel + domain->client_connect_to_channel_permission + + permission manage_role_permission = manage_role + parent_group->client_manage_role_permission + domain->client_manage_role_permission + permission add_role_users_permission = add_role_users + parent_group->client_add_role_users_permission + domain->client_add_role_users_permission + permission remove_role_users_permission = remove_role_users + parent_group->client_remove_role_users_permission + domain->client_remove_role_users_permission + permission view_role_users_permission = view_role_users + parent_group->client_view_role_users_permission + domain->client_view_role_users_permission +} +definition channel { + relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it safe to add domain relation parent_group: group - relation domain: domain - - permission admin = administrator + parent_group->admin + domain->admin - permission delete = admin - permission edit = admin + editor + parent_group->edit + domain->edit - permission share = edit - permission view = contributor + edit + parent_group->view + domain->view + guest - permission membership = view + member - permission create = membership - guest - - // These permissions are made for listing purposes. They enable listing users who have only particular permission excluding higher-level permissions users. - permission admin_only = admin - permission edit_only = edit - admin - permission view_only = view - permission membership_only = membership - view - - // These permission are made for only list purpose. They enable listing users who have only particular permission from parent group excluding higher-level permissions. - permission ext_admin = admin - administrator // For list of external admin , not having direct relation with group, but have indirect relation from parent group - permission ext_edit = edit - editor // For list of external edit , not having direct relation with group, but have indirect relation from parent group - permission ext_view = view - contributor // For list of external view , not having direct relation with group, but have indirect relation from parent group + + relation update: role#member + relation read: role#member + relation delete: role#member + relation set_parent_group: role#member + relation connect_to_client: role#member + relation publish: role#member | client + relation subscribe: role#member | client + + relation manage_role: role#member + relation add_role_users: role#member + relation remove_role_users: role#member + relation view_role_users: role#member + + permission update_permission = update + parent_group->channel_update_permission + domain->channel_update_permission + permission read_permission = read + parent_group->channel_read_permission + domain->channel_read_permission + permission delete_permission = delete + parent_group->channel_delete_permission + domain->channel_delete_permission + permission set_parent_group_permission = set_parent_group + parent_group->channel_set_parent_group_permission + domain->channel_set_parent_group_permission + permission connect_to_client_permission = connect_to_client + parent_group->channel_connect_to_client_permission + domain->channel_connect_to_client + permission publish_permission = publish + parent_group->channel_publish_permission + domain->channel_publish_permission + permission subscribe_permission = subscribe + parent_group->channel_subscribe_permission + domain->channel_subscribe_permission + + permission manage_role_permission = manage_role + parent_group->channel_manage_role_permission + domain->channel_manage_role_permission + permission add_role_users_permission = add_role_users + parent_group->channel_add_role_users_permission + domain->channel_add_role_users_permission + permission remove_role_users_permission = remove_role_users + parent_group->channel_remove_role_users_permission + domain->channel_remove_role_users_permission + permission view_role_users_permission = view_role_users + parent_group->channel_view_role_users_permission + domain->channel_view_role_users_permission +} + +definition group { + relation domain: domain // This can't be clubbed with parent_group, but if parent_group is unassigned then we could not track belongs to which domain, so it is safe to add domain + relation parent_group: group + + relation update: role#member + relation read: role#member + relation membership: role#member + relation delete: role#member + relation set_child: role#member + relation set_parent: role#member + + relation manage_role: role#member + relation add_role_users: role#member + relation remove_role_users: role#member + relation view_role_users: role#member + + relation client_create: role#member + relation channel_create: role#member + // this allows to add parent for group during the new group creation + relation subgroup_create: role#member + relation subgroup_client_create: role#member + relation subgroup_channel_create: role#member + + relation client_update: role#member + relation client_read: role#member + relation client_delete: role#member + relation client_set_parent_group: role#member + relation client_connect_to_channel: role#member + + relation client_manage_role: role#member + relation client_add_role_users: role#member + relation client_remove_role_users: role#member + relation client_view_role_users: role#member + + relation channel_update: role#member + relation channel_read: role#member + relation channel_delete: role#member + relation channel_set_parent_group: role#member + relation channel_connect_to_client: role#member + relation channel_publish: role#member + relation channel_subscribe: role#member + + relation channel_manage_role: role#member + relation channel_add_role_users: role#member + relation channel_remove_role_users: role#member + relation channel_view_role_users: role#member + + relation subgroup_update: role#member + relation subgroup_read: role#member + relation subgroup_membership: role#member + relation subgroup_delete: role#member + relation subgroup_set_child: role#member + relation subgroup_set_parent: role#member + + relation subgroup_manage_role: role#member + relation subgroup_add_role_users: role#member + relation subgroup_remove_role_users: role#member + relation subgroup_view_role_users: role#member + + relation subgroup_client_update: role#member + relation subgroup_client_read: role#member + relation subgroup_client_delete: role#member + relation subgroup_client_set_parent_group: role#member + relation subgroup_client_connect_to_channel: role#member + + relation subgroup_client_manage_role: role#member + relation subgroup_client_add_role_users: role#member + relation subgroup_client_remove_role_users: role#member + relation subgroup_client_view_role_users: role#member + + relation subgroup_channel_update: role#member + relation subgroup_channel_read: role#member + relation subgroup_channel_delete: role#member + relation subgroup_channel_set_parent_group: role#member + relation subgroup_channel_connect_to_client: role#member + relation subgroup_channel_publish: role#member + relation subgroup_channel_subscribe: role#member + + relation subgroup_channel_manage_role: role#member + relation subgroup_channel_add_role_users: role#member + relation subgroup_channel_remove_role_users: role#member + relation subgroup_channel_view_role_users: role#member + + // Subgroup permission + permission subgroup_create_permission = subgroup_create + parent_group->subgroup_create_permission + permission subgroup_client_create_permission = subgroup_client_create + parent_group->subgroup_client_create_permission + permission subgroup_channel_create_permission = subgroup_channel_create + parent_group->subgroup_channel_create_permission + + permission subgroup_update_permission = subgroup_update + parent_group->subgroup_update_permission + permission subgroup_membership_permission = subgroup_membership + parent_group->subgroup_membership_permission + permission subgroup_read_permission = subgroup_read + parent_group->subgroup_read_permission + permission subgroup_delete_permission = subgroup_delete + parent_group->subgroup_delete_permission + permission subgroup_set_child_permission = subgroup_set_child + parent_group->subgroup_set_child_permission + permission subgroup_set_parent_permission = subgroup_set_parent + parent_group->subgroup_set_parent_permission + + permission subgroup_manage_role_permission = subgroup_manage_role + parent_group->subgroup_manage_role_permission + permission subgroup_add_role_users_permission = subgroup_add_role_users + parent_group->subgroup_add_role_users_permission + permission subgroup_remove_role_users_permission = subgroup_remove_role_users + parent_group->subgroup_remove_role_users_permission + permission subgroup_view_role_users_permission = subgroup_view_role_users + parent_group->subgroup_view_role_users_permission + + // Group permission + permission update_permission = update + parent_group->subgroup_create_permission + domain->group_update_permission + permission membership_permission = membership + parent_group->subgroup_membership_permission + domain->group_membership_permission + permission read_permission = read + parent_group->subgroup_read_permission + domain->group_read_permission + permission delete_permission = delete + parent_group->subgroup_delete_permission + domain->group_delete_permission + permission set_child_permission = set_child + parent_group->subgroup_set_child_permission + domain->group_set_child + permission set_parent_permission = set_parent + parent_group->subgroup_set_parent_permission + domain->group_set_parent + + permission manage_role_permission = manage_role + parent_group->subgroup_manage_role_permission + domain->group_manage_role_permission + permission add_role_users_permission = add_role_users + parent_group->subgroup_add_role_users_permission + domain->group_add_role_users_permission + permission remove_role_users_permission = remove_role_users + parent_group->subgroup_remove_role_users_permission + domain->group_remove_role_users_permission + permission view_role_users_permission = view_role_users + parent_group->subgroup_view_role_users_permission + domain->group_view_role_users_permission + + // Subgroup clients permisssion + permission subgroup_client_update_permission = subgroup_client_update + parent_group->subgroup_client_update_permission + permission subgroup_client_read_permission = subgroup_client_read + parent_group->subgroup_client_read_permission + permission subgroup_client_delete_permission = subgroup_client_delete + parent_group->subgroup_client_delete_permission + permission subgroup_client_set_parent_group_permission = subgroup_client_set_parent_group + parent_group->subgroup_client_set_parent_group_permission + permission subgroup_client_connect_to_channel_permission = subgroup_client_connect_to_channel + parent_group->subgroup_client_connect_to_channel_permission + + permission subgroup_client_manage_role_permission = subgroup_client_manage_role + parent_group->subgroup_client_manage_role_permission + permission subgroup_client_add_role_users_permission = subgroup_client_add_role_users + parent_group->subgroup_client_add_role_users_permission + permission subgroup_client_remove_role_users_permission = subgroup_client_remove_role_users + parent_group->subgroup_client_remove_role_users_permission + permission subgroup_client_view_role_users_permission = subgroup_client_view_role_users + parent_group->subgroup_client_view_role_users_permission + + // Group clients permisssion + permission client_create_permission = client_create + parent_group->subgroup_client_create + domain->client_create_permission + permission client_update_permission = client_update + parent_group->subgroup_client_update + domain->client_update_permission + permission client_read_permission = client_read + parent_group->subgroup_client_read + domain->client_read_permission + permission client_delete_permission = client_delete + parent_group->subgroup_client_delete + domain->client_delete_permission + permission client_set_parent_group_permission = client_set_parent_group + parent_group->subgroup_client_set_parent_group + domain->client_set_parent_group_permission + permission client_connect_to_channel_permission = client_connect_to_channel + parent_group->subgroup_client_connect_to_channel + domain->client_connect_to_channel_permission + + permission client_manage_role_permission = client_manage_role + parent_group->subgroup_client_manage_role + domain->client_manage_role_permission + permission client_add_role_users_permission = client_add_role_users + parent_group->subgroup_client_add_role_users + domain->client_add_role_users_permission + permission client_remove_role_users_permission = client_remove_role_users + parent_group->subgroup_client_remove_role_users + domain->client_remove_role_users_permission + permission client_view_role_users_permission = client_view_role_users + parent_group->subgroup_client_view_role_users + domain->client_view_role_users_permission + + // Subgroup channels permisssion + permission subgroup_channel_update_permission = subgroup_channel_update + parent_group->subgroup_channel_update_permission + permission subgroup_channel_read_permission = subgroup_channel_read + parent_group->subgroup_channel_read_permission + permission subgroup_channel_delete_permission = subgroup_channel_delete + parent_group->subgroup_channel_delete_permission + permission subgroup_channel_set_parent_group_permission = subgroup_channel_set_parent_group + parent_group->subgroup_channel_set_parent_group_permission + permission subgroup_channel_connect_to_client_permission = subgroup_channel_connect_to_client + parent_group->subgroup_channel_connect_to_client_permission + permission subgroup_channel_publish_permission = subgroup_channel_publish + parent_group->subgroup_channel_publish_permission + permission subgroup_channel_subscribe_permission = subgroup_channel_subscribe + parent_group->subgroup_channel_subscribe_permission + + permission subgroup_channel_manage_role_permission = subgroup_channel_manage_role + parent_group->subgroup_channel_manage_role_permission + permission subgroup_channel_add_role_users_permission = subgroup_channel_add_role_users + parent_group->subgroup_channel_add_role_users_permission + permission subgroup_channel_remove_role_users_permission = subgroup_channel_remove_role_users + parent_group->subgroup_channel_remove_role_users_permission + permission subgroup_channel_view_role_users_permission = subgroup_channel_view_role_users + parent_group->subgroup_channel_view_role_users_permission + + // Group channels permisssion + permission channel_create_permission = channel_create + parent_group->subgroup_channel_create_permission + domain->channel_create_permission + permission channel_update_permission = channel_update + parent_group->subgroup_channel_update + domain->channel_update_permission + permission channel_read_permission = channel_read + parent_group->subgroup_channel_read + domain->channel_read_permission + permission channel_delete_permission = channel_delete + parent_group->subgroup_channel_delete_permission + domain->channel_delete_permission + permission channel_set_parent_group_permission = channel_set_parent_group + parent_group->subgroup_channel_set_parent_group + domain->channel_set_parent_group_permission + permission channel_connect_to_client_permission = channel_connect_to_client + parent_group->subgroup_channel_connect_to_client + domain->channel_connect_to_client_permission + permission channel_publish_permission = channel_publish + parent_group->subgroup_channel_publish + domain->channel_publish_permission + permission channel_subscribe_permission = channel_subscribe + parent_group->subgroup_channel_subscribe + domain->channel_subscribe_permission + + permission channel_manage_role_permission = channel_manage_role + parent_group->subgroup_channel_manage_role + domain->channel_manage_role_permission + permission channel_add_role_users_permission = channel_add_role_users + parent_group->subgroup_channel_add_role_users + domain->channel_add_role_users_permission + permission channel_remove_role_users_permission = channel_remove_role_users + parent_group->subgroup_channel_remove_role_users + domain->channel_remove_role_users_permission + permission channel_view_role_users_permission = channel_view_role_users + parent_group->subgroup_channel_view_role_users + domain->channel_view_role_users_permission + + } definition domain { - relation administrator: user // combination domain + user id - relation editor: user - relation contributor: user - relation member: user - relation guest: user + //Replace platoform with organization in future + relation organization: platform + relation team: team + + relation update: role#member | team#member + relation enable: role#member | team#member + relation disable: role#member | team#member + relation membership: role#member | team#member + relation read: role#member | team#member + relation delete: role#member | team#member + + relation manage_role: role#member | team#member + relation add_role_users: role#member | team#member + relation remove_role_users: role#member | team#member + relation view_role_users: role#member | team#member + + relation client_create: role#member | team#member + relation channel_create: role#member | team#member + relation group_create: role#member | team#member + + relation client_update: role#member | team#member + relation client_read: role#member | team#member + relation client_delete: role#member | team#member + relation client_set_parent_group: role#member | team#member + relation client_connect_to_channel: role#member | team#member + relation client_manage_role: role#member | team#member + relation client_add_role_users: role#member | team#member + relation client_remove_role_users: role#member | team#member + relation client_view_role_users: role#member | team#member + + relation channel_update: role#member | team#member + relation channel_read: role#member | team#member + relation channel_delete: role#member | team#member + relation channel_set_parent_group: role#member | team#member + relation channel_connect_to_client: role#member | team#member + relation channel_publish: role#member | team#member + relation channel_subscribe: role#member | team#member + + relation channel_manage_role: role#member | team#member + relation channel_add_role_users: role#member | team#member + relation channel_remove_role_users: role#member | team#member + relation channel_view_role_users: role#member | team#member + + relation group_update: role#member | team#member + relation group_membership: role#member | team#member + relation group_read: role#member | team#member + relation group_delete: role#member | team#member + relation group_set_child: role#member | team#member + relation group_set_parent: role#member | team#member + + relation group_manage_role: role#member | team#member + relation group_add_role_users: role#member | team#member + relation group_remove_role_users: role#member | team#member + relation group_view_role_users: role#member | team#member + + permission update_permission = update + team->domain_update + organization->admin + permission read_permission = read + team->domain_read + organization->admin + permission enable_permission = enable + team->domain_update + organization->admin + permission disable_permission = disable + team->domain_update + organization->admin + permission membership_permission = membership + team->domain_membership + organization->admin + permission delete_permission = delete + team->domain_delete + organization->admin + + permission manage_role_permission = manage_role + team->domain_manage_role + organization->admin + permission add_role_users_permission = add_role_users + team->domain_add_role_users + organization->admin + permission remove_role_users_permission = remove_role_users + team->domain_remove_role_users + organization->admin + permission view_role_users_permission = view_role_users + team->domain_view_role_users + organization->admin + + permission client_create_permission = client_create + team->client_create + organization->admin + permission channel_create_permission = channel_create + team->channel_create + organization->admin + permission group_create_permission = group_create + team->group_create + organization->admin + + permission client_update_permission = client_update + team->client_update + organization->admin + permission client_read_permission = client_read + team->client_read + organization->admin + permission client_delete_permission = client_delete + team->client_delete + organization->admin + permission client_set_parent_group_permission = client_set_parent_group + team->client_set_parent_group + organization->admin + permission client_connect_to_channel_permission = client_connect_to_channel + team->client_connect_to_channel + organization->admin + + permission client_manage_role_permission = client_manage_role + team->client_manage_role + organization->admin + permission client_add_role_users_permission = client_add_role_users + team->client_add_role_users + organization->admin + permission client_remove_role_users_permission = client_remove_role_users + team->client_remove_role_users + organization->admin + permission client_view_role_users_permission = client_view_role_users + team->client_view_role_users + organization->admin + + permission channel_update_permission = channel_update + team->channel_update + organization->admin + permission channel_read_permission = channel_read + team->channel_read + organization->admin + permission channel_delete_permission = channel_delete + team->channel_delete + organization->admin + permission channel_set_parent_group_permission = channel_set_parent_group + team->channel_set_parent_group + organization->admin + permission channel_connect_to_client_permission = channel_connect_to_client + team->channel_connect_to_client + organization->admin + permission channel_publish_permission = channel_publish + team->channel_publish + organization->admin + permission channel_subscribe_permission = channel_subscribe + team->channel_subscribe + organization->admin + + permission channel_manage_role_permission = channel_manage_role + team->channel_manage_role + organization->admin + permission channel_add_role_users_permission = channel_add_role_users + team->channel_add_role_users + organization->admin + permission channel_remove_role_users_permission = channel_remove_role_users + team->channel_remove_role_users + organization->admin + permission channel_view_role_users_permission = channel_view_role_users + team->channel_view_role_users + organization->admin + + permission group_update_permission = group_update + team->group_update + organization->admin + permission group_membership_permission = group_membership + team->group_membership + organization->admin + permission group_read_permission = group_read + team->group_read + organization->admin + permission group_delete_permission = group_delete + team->group_delete + organization->admin + permission group_set_child_permission = group_set_child + team->group_set_child + organization->admin + permission group_set_parent_permission = group_set_parent + team->group_set_parent + organization->admin + + permission group_manage_role_permission = group_manage_role + team->group_manage_role + organization->admin + permission group_add_role_users_permission = group_add_role_users + team->group_add_role_users + organization->admin + permission group_remove_role_users_permission = group_remove_role_users + team->group_remove_role_users + organization->admin + permission group_view_role_users_permission = group_view_role_users + team->group_view_role_users + organization->admin + +} + +// Add this realtion and permission in future while adding orgnaization +definition team { + relation organization: organization + relation parent_team: team + + relation delete: role#member + relation enable: role#member | team#member + relation disable: role#member | team#member + relation update: role#member + relation read: role#member + + relation set_parent: role#member + relation set_child: role#member + + relation member: role#member + + relation manage_role: role#member + relation add_role_users: role#member + relation remove_role_users: role#member + relation view_role_users: role#member + + relation subteam_delete: role#member + relation subteam_update: role#member + relation subteam_read: role#member + + relation subteam_member: role#member + + relation subteam_set_child: role#member + relation subteam_set_parent: role#member + + relation subteam_manage_role: role#member + relation subteam_add_role_users: role#member + relation subteam_remove_role_users: role#member + relation subteam_view_role_users: role#member + + // Domain related permission + + relation domain_update: role#member | team#member + relation domain_read: role#member | team#member + relation domain_membership: role#member | team#member + relation domain_delete: role#member | team#member + + relation domain_manage_role: role#member | team#member + relation domain_add_role_users: role#member | team#member + relation domain_remove_role_users: role#member | team#member + relation domain_view_role_users: role#member | team#member + + relation client_create: role#member | team#member + relation channel_create: role#member | team#member + relation group_create: role#member | team#member + + relation client_update: role#member | team#member + relation client_read: role#member | team#member + relation client_delete: role#member | team#member + relation client_set_parent_group: role#member | team#member + relation client_connect_to_channel: role#member | team#member + + relation client_manage_role: role#member | team#member + relation client_add_role_users: role#member | team#member + relation client_remove_role_users: role#member | team#member + relation client_view_role_users: role#member | team#member + + relation channel_update: role#member | team#member + relation channel_read: role#member | team#member + relation channel_delete: role#member | team#member + relation channel_set_parent_group: role#member | team#member + relation channel_connect_to_client: role#member | team#member + relation channel_publish: role#member | team#member + relation channel_subscribe: role#member | team#member + + relation channel_manage_role: role#member | team#member + relation channel_add_role_users: role#member | team#member + relation channel_remove_role_users: role#member | team#member + relation channel_view_role_users: role#member | team#member + + relation group_update: role#member | team#member + relation group_membership: role#member | team#member + relation group_read: role#member | team#member + relation group_delete: role#member | team#member + relation group_set_child: role#member | team#member + relation group_set_parent: role#member | team#member + + relation group_manage_role: role#member | team#member + relation group_add_role_users: role#member | team#member + relation group_remove_role_users: role#member | team#member + relation group_view_role_users: role#member | team#member + + permission delete_permission = delete + organization->team_delete + parent_team->subteam_delete + organization->admin + permission update_permission = update + organization->team_update + parent_team->subteam_update + organization->admin + permission read_permission = read + organization->team_read + parent_team->subteam_read + organization->admin + + permission set_parent_permission = set_parent + organization->team_set_parent + parent_team->subteam_set_parent + organization->admin + permission set_child_permisssion = set_child + organization->team_set_child + parent_team->subteam_set_child + organization->admin + + permission membership = member + organization->team_member + parent_team->subteam_member + organization->admin + + permission manage_role_permission = manage_role + organization->team_manage_role + parent_team->subteam_manage_role + organization->admin + permission add_role_users_permission = add_role_users + organization->team_add_role_users + parent_team->subteam_add_role_users + organization->admin + permission remove_role_users_permission = remove_role_users + organization->team_remove_role_users + parent_team->subteam_remove_role_users + organization->admin + permission view_role_users_permission = view_role_users + organization->team_view_role_users + parent_team->subteam_view_role_users + organization->admin +} + + +definition organization { relation platform: platform + relation administrator: user + + relation delete: role#member + relation update: role#member + relation read: role#member + + relation member: role#member + + relation manage_role: role#member + relation add_role_users: role#member + relation remove_role_users: role#member + relation view_role_users: role#member + + relation team_create: role#member + + relation team_delete: role#member + relation team_update: role#member + relation team_read: role#member + + relation team_member: role#member // Will be member of all the teams in the organization - permission admin = administrator + platform->admin - permission edit = admin + editor - permission share = edit - permission view = edit + contributor + guest - permission membership = view + member - permission create = membership - guest + relation team_set_child: role#member + relation team_set_parent: role#member + + relation team_manage_role: role#member + relation team_add_role_users: role#member + relation team_remove_role_users: role#member + relation team_view_role_users: role#member + + permission admin = administrator + platform->administrator + permission delete_permission = admin + delete->member + permission update_permission = admin + update->member + permission read_permission = admin + read->member + + permission membership = admin + member->member + + permission team_create_permission = admin + team_create->member + + permission manage_role_permission = admin + manage_role + permission add_role_users_permisson = admin + add_role_users + permission remove_role_users_permission = admin + remove_role_users + permission view_role_users_permission = admin + view_role_users } + definition platform { relation administrator: user relation member: user diff --git a/docker/ssl/Makefile b/docker/ssl/Makefile index f0561b8726..1c11251ef7 100644 --- a/docker/ssl/Makefile +++ b/docker/ssl/Makefile @@ -8,14 +8,14 @@ OU_CRT = magistrala_crt EA = info@magistrala.com CN_CA = Magistrala_Self_Signed_CA CN_SRV = localhost -THING_SECRET = # e.g. 8f65ed04-0770-4ce4-a291-6d1bf2000f4d -CRT_FILE_NAME = thing -THINGS_GRPC_SERVER_CONF_FILE_NAME=thing-grpc-server.conf -THINGS_GRPC_CLIENT_CONF_FILE_NAME=thing-grpc-client.conf -THINGS_GRPC_SERVER_CN=things -THINGS_GRPC_CLIENT_CN=things-client -THINGS_GRPC_SERVER_CRT_FILE_NAME=things-grpc-server -THINGS_GRPC_CLIENT_CRT_FILE_NAME=things-grpc-client +CLIENT_SECRET = # e.g. 8f65ed04-0770-4ce4-a291-6d1bf2000f4d +CRT_FILE_NAME = client +CLIENTS_GRPC_SERVER_CONF_FILE_NAME=client-grpc-server.conf +CLIENTS_GRPC_CLIENT_CONF_FILE_NAME=client-grpc-client.conf +CLIENTS_GRPC_SERVER_CN=clients +CLIENTS_GRPC_CLIENT_CN=clients-client +CLIENTS_GRPC_SERVER_CRT_FILE_NAME=clients-grpc-server +CLIENTS_GRPC_CLIENT_CRT_FILE_NAME=clients-grpc-client AUTH_GRPC_SERVER_CONF_FILE_NAME=auth-grpc-server.conf AUTH_GRPC_CLIENT_CONF_FILE_NAME=auth-grpc-client.conf AUTH_GRPC_SERVER_CN=auth @@ -51,7 +51,7 @@ It can be downloaded from $(DOWNLOAD_URL). etc, etc. endef -all: clean_certs ca server_cert things_grpc_certs auth_grpc_certs +all: clean_certs ca server_cert clients_grpc_certs auth_grpc_certs # CA name and key is "ca". ca: @@ -70,10 +70,10 @@ server_cert: # Remove CSR. rm $(CRT_LOCATION)/magistrala-server.csr -thing_cert: +client_cert: # Create magistrala server key and CSR. openssl req -new -sha256 -newkey rsa:4096 -nodes -keyout $(CRT_LOCATION)/$(CRT_FILE_NAME).key \ - -out $(CRT_LOCATION)/$(CRT_FILE_NAME).csr -subj "/CN=$(THING_SECRET)/O=$(O)/OU=$(OU_CRT)/emailAddress=$(EA)" + -out $(CRT_LOCATION)/$(CRT_FILE_NAME).csr -subj "/CN=$(CLIENTS_SECRET)/O=$(O)/OU=$(OU_CRT)/emailAddress=$(EA)" # Sign client CSR. openssl x509 -req -days 730 -in $(CRT_LOCATION)/$(CRT_FILE_NAME).csr -CA $(CRT_LOCATION)/ca.crt -CAkey $(CRT_LOCATION)/ca.key -CAcreateserial -out $(CRT_LOCATION)/$(CRT_FILE_NAME).crt @@ -81,47 +81,47 @@ thing_cert: # Remove CSR. rm $(CRT_LOCATION)/$(CRT_FILE_NAME).csr -things_grpc_certs: - # Things server grpc certificates - $(file > $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).conf,$(subst <>,$(THINGS_GRPC_SERVER_CN),$(GRPC_CERT_CONFIG)) ) +clients_grpc_certs: + # Clients server grpc certificates + $(file > $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).conf,$(subst <>,$(CLIENTS_GRPC_SERVER_CN),$(GRPC_CERT_CONFIG)) ) openssl req -new -sha256 -newkey rsa:4096 -nodes \ - -keyout $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).key \ - -out $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).csr \ - -config $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).conf \ + -keyout $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).key \ + -out $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).csr \ + -config $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).conf \ -extensions v3_req openssl x509 -req -sha256 \ - -in $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).csr \ + -in $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).csr \ -CA $(CRT_LOCATION)/ca.crt \ -CAkey $(CRT_LOCATION)/ca.key \ -CAcreateserial \ - -out $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).crt \ + -out $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).crt \ -days 365 \ - -extfile $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).conf \ + -extfile $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).conf \ -extensions v3_req - rm -rf $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).conf - # Things client grpc certificates - $(file > $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).conf,$(subst <>,$(THINGS_GRPC_CLIENT_CN),$(GRPC_CERT_CONFIG)) ) + rm -rf $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(CLIENTS_GRPC_SERVER_CRT_FILE_NAME).conf + # Clients client grpc certificates + $(file > $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).conf,$(subst <>,$(CLIENTS_GRPC_CLIENT_CN),$(GRPC_CERT_CONFIG)) ) openssl req -new -sha256 -newkey rsa:4096 -nodes \ - -keyout $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).key \ - -out $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).csr \ - -config $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).conf \ + -keyout $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).key \ + -out $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).csr \ + -config $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).conf \ -extensions v3_req openssl x509 -req -sha256 \ - -in $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).csr \ + -in $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).csr \ -CA $(CRT_LOCATION)/ca.crt \ -CAkey $(CRT_LOCATION)/ca.key \ -CAcreateserial \ - -out $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).crt \ + -out $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).crt \ -days 365 \ - -extfile $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).conf \ + -extfile $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).conf \ -extensions v3_req - rm -rf $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).conf + rm -rf $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(CLIENTS_GRPC_CLIENT_CRT_FILE_NAME).conf auth_grpc_certs: # Auth gRPC server certificate @@ -164,7 +164,7 @@ auth_grpc_certs: -extensions v3_req rm -rf $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).conf - + clean_certs: rm -r $(CRT_LOCATION)/*.crt rm -r $(CRT_LOCATION)/*.key diff --git a/docker/ssl/authorization.js b/docker/ssl/authorization.js index 5bfedbe9c2..bf0652138e 100644 --- a/docker/ssl/authorization.js +++ b/docker/ssl/authorization.js @@ -170,7 +170,7 @@ function parseCert(cert, key) { for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].split('='); if (pair[0].toUpperCase() == key) { - return "Thing " + pair[1].replace("\\", "").trim(); + return "Client " + pair[1].replace("\\", "").trim(); } } } diff --git a/auth/api/grpc/domains/client.go b/domains/api/grpc/client.go similarity index 68% rename from auth/api/grpc/domains/client.go rename to domains/api/grpc/client.go index 1b952afc12..9f603b7ce8 100644 --- a/auth/api/grpc/domains/client.go +++ b/domains/api/grpc/client.go @@ -1,22 +1,22 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package grpc import ( "context" "time" - "github.com/absmach/magistrala" grpcapi "github.com/absmach/magistrala/auth/api/grpc" + grpcDomainsV1 "github.com/absmach/magistrala/internal/grpc/domains/v1" "github.com/go-kit/kit/endpoint" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc" ) -const domainsSvcName = "magistrala.DomainsService" +const domainsSvcName = "domains.v1.DomainsService" -var _ magistrala.DomainsServiceClient = (*domainsGrpcClient)(nil) +var _ grpcDomainsV1.DomainsServiceClient = (*domainsGrpcClient)(nil) type domainsGrpcClient struct { deleteUserFromDomains endpoint.Endpoint @@ -24,7 +24,7 @@ type domainsGrpcClient struct { } // NewDomainsClient returns new domains gRPC client instance. -func NewDomainsClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.DomainsServiceClient { +func NewDomainsClient(conn *grpc.ClientConn, timeout time.Duration) grpcDomainsV1.DomainsServiceClient { return &domainsGrpcClient{ deleteUserFromDomains: kitgrpc.NewClient( conn, @@ -32,14 +32,14 @@ func NewDomainsClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.D "DeleteUserFromDomains", encodeDeleteUserRequest, decodeDeleteUserResponse, - magistrala.DeleteUserRes{}, + grpcDomainsV1.DeleteUserRes{}, ).Endpoint(), timeout: timeout, } } -func (client domainsGrpcClient) DeleteUserFromDomains(ctx context.Context, in *magistrala.DeleteUserReq, opts ...grpc.CallOption) (*magistrala.DeleteUserRes, error) { +func (client domainsGrpcClient) DeleteUserFromDomains(ctx context.Context, in *grpcDomainsV1.DeleteUserReq, opts ...grpc.CallOption) (*grpcDomainsV1.DeleteUserRes, error) { ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() @@ -47,21 +47,21 @@ func (client domainsGrpcClient) DeleteUserFromDomains(ctx context.Context, in *m ID: in.GetId(), }) if err != nil { - return &magistrala.DeleteUserRes{}, grpcapi.DecodeError(err) + return &grpcDomainsV1.DeleteUserRes{}, grpcapi.DecodeError(err) } dpr := res.(deleteUserRes) - return &magistrala.DeleteUserRes{Deleted: dpr.deleted}, nil + return &grpcDomainsV1.DeleteUserRes{Deleted: dpr.deleted}, nil } func decodeDeleteUserResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.DeleteUserRes) + res := grpcRes.(*grpcDomainsV1.DeleteUserRes) return deleteUserRes{deleted: res.GetDeleted()}, nil } func encodeDeleteUserRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(deleteUserPoliciesReq) - return &magistrala.DeleteUserReq{ + return &grpcDomainsV1.DeleteUserReq{ Id: req.ID, }, nil } diff --git a/auth/api/grpc/domains/doc.go b/domains/api/grpc/doc.go similarity index 90% rename from auth/api/grpc/domains/doc.go rename to domains/api/grpc/doc.go index 4ae689977a..ecbe29d553 100644 --- a/auth/api/grpc/domains/doc.go +++ b/domains/api/grpc/doc.go @@ -2,4 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 // Package grpc contains implementation of Domains service gRPC API. -package domains +package grpc diff --git a/auth/api/grpc/domains/endpoint.go b/domains/api/grpc/endpoint.go similarity index 78% rename from auth/api/grpc/domains/endpoint.go rename to domains/api/grpc/endpoint.go index 5bbb047e6d..4ced78cbee 100644 --- a/auth/api/grpc/domains/endpoint.go +++ b/domains/api/grpc/endpoint.go @@ -1,16 +1,16 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package grpc import ( "context" - "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/domains" "github.com/go-kit/kit/endpoint" ) -func deleteUserFromDomainsEndpoint(svc auth.Service) endpoint.Endpoint { +func deleteUserFromDomainsEndpoint(svc domains.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(deleteUserPoliciesReq) if err := req.validate(); err != nil { diff --git a/auth/api/grpc/domains/endpoint_test.go b/domains/api/grpc/endpoint_test.go similarity index 75% rename from auth/api/grpc/domains/endpoint_test.go rename to domains/api/grpc/endpoint_test.go index 3bddb69141..e9efc902b5 100644 --- a/auth/api/grpc/domains/endpoint_test.go +++ b/domains/api/grpc/endpoint_test.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains_test +package grpc_test import ( "context" @@ -10,9 +10,9 @@ import ( "testing" "time" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - grpcapi "github.com/absmach/magistrala/auth/api/grpc/domains" + "github.com/absmach/magistrala/domains" + grpcapi "github.com/absmach/magistrala/domains/api/grpc" + grpcDomainsV1 "github.com/absmach/magistrala/internal/grpc/domains/v1" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" "github.com/stretchr/testify/assert" @@ -26,7 +26,7 @@ const ( secret = "secret" email = "test@example.com" id = "testID" - thingsType = "things" + clientsType = "clients" usersType = "users" description = "Description" groupName = "mgx" @@ -44,10 +44,10 @@ const ( var authAddr = fmt.Sprintf("localhost:%d", port) -func startGRPCServer(svc auth.Service, port int) *grpc.Server { +func startGRPCServer(svc domains.Service, port int) *grpc.Server { listener, _ := net.Listen("tcp", fmt.Sprintf(":%d", port)) server := grpc.NewServer() - magistrala.RegisterDomainsServiceServer(server, grpcapi.NewDomainsServer(svc)) + grpcDomainsV1.RegisterDomainsServiceServer(server, grpcapi.NewDomainsServer(svc)) go func() { err := server.Serve(listener) assert.Nil(&testing.T{}, err, fmt.Sprintf(`"Unexpected error creating auth server %s"`, err)) @@ -64,33 +64,33 @@ func TestDeleteUserFromDomains(t *testing.T) { cases := []struct { desc string token string - deleteUserReq *magistrala.DeleteUserReq - deleteUserRes *magistrala.DeleteUserRes + deleteUserReq *grpcDomainsV1.DeleteUserReq + deleteUserRes *grpcDomainsV1.DeleteUserRes err error }{ { desc: "delete valid req", token: validToken, - deleteUserReq: &magistrala.DeleteUserReq{ + deleteUserReq: &grpcDomainsV1.DeleteUserReq{ Id: id, }, - deleteUserRes: &magistrala.DeleteUserRes{Deleted: true}, + deleteUserRes: &grpcDomainsV1.DeleteUserRes{Deleted: true}, err: nil, }, { desc: "delete invalid req with invalid token", token: inValidToken, - deleteUserReq: &magistrala.DeleteUserReq{}, - deleteUserRes: &magistrala.DeleteUserRes{Deleted: false}, + deleteUserReq: &grpcDomainsV1.DeleteUserReq{}, + deleteUserRes: &grpcDomainsV1.DeleteUserRes{Deleted: false}, err: apiutil.ErrMissingID, }, { desc: "delete invalid req with invalid token", token: inValidToken, - deleteUserReq: &magistrala.DeleteUserReq{ + deleteUserReq: &grpcDomainsV1.DeleteUserReq{ Id: id, }, - deleteUserRes: &magistrala.DeleteUserRes{Deleted: false}, + deleteUserRes: &grpcDomainsV1.DeleteUserRes{Deleted: false}, err: apiutil.ErrMissingPolicyEntityType, }, } diff --git a/auth/api/grpc/domains/requests.go b/domains/api/grpc/requests.go similarity index 94% rename from auth/api/grpc/domains/requests.go rename to domains/api/grpc/requests.go index 8e98928798..ef9607dfff 100644 --- a/auth/api/grpc/domains/requests.go +++ b/domains/api/grpc/requests.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package grpc import ( "github.com/absmach/magistrala/pkg/apiutil" diff --git a/auth/api/grpc/domains/responses.go b/domains/api/grpc/responses.go similarity index 88% rename from auth/api/grpc/domains/responses.go rename to domains/api/grpc/responses.go index 09b883089a..c5749399dc 100644 --- a/auth/api/grpc/domains/responses.go +++ b/domains/api/grpc/responses.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package grpc type deleteUserRes struct { deleted bool diff --git a/auth/api/grpc/domains/server.go b/domains/api/grpc/server.go similarity index 62% rename from auth/api/grpc/domains/server.go rename to domains/api/grpc/server.go index fdfc55ce32..4831275705 100644 --- a/auth/api/grpc/domains/server.go +++ b/domains/api/grpc/server.go @@ -1,25 +1,25 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package grpc import ( "context" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" grpcapi "github.com/absmach/magistrala/auth/api/grpc" + "github.com/absmach/magistrala/domains" + grpcDomainsV1 "github.com/absmach/magistrala/internal/grpc/domains/v1" kitgrpc "github.com/go-kit/kit/transport/grpc" ) -var _ magistrala.DomainsServiceServer = (*domainsGrpcServer)(nil) +var _ grpcDomainsV1.DomainsServiceServer = (*domainsGrpcServer)(nil) type domainsGrpcServer struct { - magistrala.UnimplementedDomainsServiceServer + grpcDomainsV1.UnimplementedDomainsServiceServer deleteUserFromDomains kitgrpc.Handler } -func NewDomainsServer(svc auth.Service) magistrala.DomainsServiceServer { +func NewDomainsServer(svc domains.Service) grpcDomainsV1.DomainsServiceServer { return &domainsGrpcServer{ deleteUserFromDomains: kitgrpc.NewServer( (deleteUserFromDomainsEndpoint(svc)), @@ -30,7 +30,7 @@ func NewDomainsServer(svc auth.Service) magistrala.DomainsServiceServer { } func decodeDeleteUserRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.DeleteUserReq) + req := grpcReq.(*grpcDomainsV1.DeleteUserReq) return deleteUserPoliciesReq{ ID: req.GetId(), }, nil @@ -38,13 +38,13 @@ func decodeDeleteUserRequest(_ context.Context, grpcReq interface{}) (interface{ func encodeDeleteUserResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { res := grpcRes.(deleteUserRes) - return &magistrala.DeleteUserRes{Deleted: res.deleted}, nil + return &grpcDomainsV1.DeleteUserRes{Deleted: res.deleted}, nil } -func (s *domainsGrpcServer) DeleteUserFromDomains(ctx context.Context, req *magistrala.DeleteUserReq) (*magistrala.DeleteUserRes, error) { +func (s *domainsGrpcServer) DeleteUserFromDomains(ctx context.Context, req *grpcDomainsV1.DeleteUserReq) (*grpcDomainsV1.DeleteUserRes, error) { _, res, err := s.deleteUserFromDomains.ServeGRPC(ctx, req) if err != nil { return nil, grpcapi.EncodeError(err) } - return res.(*magistrala.DeleteUserRes), nil + return res.(*grpcDomainsV1.DeleteUserRes), nil } diff --git a/auth/api/grpc/domains/setup_test.go b/domains/api/grpc/setup_test.go similarity index 81% rename from auth/api/grpc/domains/setup_test.go rename to domains/api/grpc/setup_test.go index d65f23e793..220fc4904f 100644 --- a/auth/api/grpc/domains/setup_test.go +++ b/domains/api/grpc/setup_test.go @@ -1,13 +1,13 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains_test +package grpc_test import ( "os" "testing" - "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/domains/mocks" ) var svc *mocks.Service diff --git a/auth/api/http/domains/decode.go b/domains/api/http/decode.go similarity index 65% rename from auth/api/http/domains/decode.go rename to domains/api/http/decode.go index e0c58ecc52..543b4ffdd1 100644 --- a/auth/api/http/domains/decode.go +++ b/domains/api/http/decode.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package http import ( "context" @@ -9,7 +9,7 @@ import ( "net/http" "strings" - "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/domains" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" @@ -20,9 +20,7 @@ func decodeCreateDomainRequest(_ context.Context, r *http.Request) (interface{}, if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) } - req := createDomainReq{ - token: apiutil.ExtractBearerToken(r), - } + req := createDomainReq{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) } @@ -32,15 +30,6 @@ func decodeCreateDomainRequest(_ context.Context, r *http.Request) (interface{}, func decodeRetrieveDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { req := retrieveDomainRequest{ - token: apiutil.ExtractBearerToken(r), - domainID: chi.URLParam(r, "domainID"), - } - return req, nil -} - -func decodeRetrieveDomainPermissionsRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := retrieveDomainPermissionsRequest{ - token: apiutil.ExtractBearerToken(r), domainID: chi.URLParam(r, "domainID"), } return req, nil @@ -52,7 +41,6 @@ func decodeUpdateDomainRequest(_ context.Context, r *http.Request) (interface{}, } req := updateDomainReq{ - token: apiutil.ExtractBearerToken(r), domainID: chi.URLParam(r, "domainID"), } @@ -78,7 +66,6 @@ func decodeListDomainRequest(ctx context.Context, r *http.Request) (interface{}, func decodeEnableDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { req := enableDomainReq{ - token: apiutil.ExtractBearerToken(r), domainID: chi.URLParam(r, "domainID"), } return req, nil @@ -86,7 +73,6 @@ func decodeEnableDomainRequest(_ context.Context, r *http.Request) (interface{}, func decodeDisableDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { req := disableDomainReq{ - token: apiutil.ExtractBearerToken(r), domainID: chi.URLParam(r, "domainID"), } return req, nil @@ -94,63 +80,17 @@ func decodeDisableDomainRequest(_ context.Context, r *http.Request) (interface{} func decodeFreezeDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { req := freezeDomainReq{ - token: apiutil.ExtractBearerToken(r), domainID: chi.URLParam(r, "domainID"), } return req, nil } -func decodeAssignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := assignUsersReq{ - token: apiutil.ExtractBearerToken(r), - domainID: chi.URLParam(r, "domainID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - -func decodeUnassignUserRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := unassignUserReq{ - token: apiutil.ExtractBearerToken(r), - domainID: chi.URLParam(r, "domainID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - -func decodeListUserDomainsRequest(ctx context.Context, r *http.Request) (interface{}, error) { - page, err := decodePageRequest(ctx, r) - if err != nil { - return nil, err - } - req := listUserDomainsReq{ - token: apiutil.ExtractBearerToken(r), - userID: chi.URLParam(r, "userID"), - page: page, - } - return req, nil -} - func decodePageRequest(_ context.Context, r *http.Request) (page, error) { s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) if err != nil { return page{}, errors.Wrap(apiutil.ErrValidation, err) } - st, err := auth.ToStatus(s) + st, err := domains.ToStatus(s) if err != nil { return page{}, errors.Wrap(apiutil.ErrValidation, err) } diff --git a/domains/api/http/endpoint.go b/domains/api/http/endpoint.go new file mode 100644 index 0000000000..e332d82f08 --- /dev/null +++ b/domains/api/http/endpoint.go @@ -0,0 +1,182 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "context" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/go-kit/kit/endpoint" +) + +func createDomainEndpoint(svc domains.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createDomainReq) + if err := req.validate(); err != nil { + return nil, err + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthorization + } + + d := domains.Domain{ + Name: req.Name, + Metadata: req.Metadata, + Tags: req.Tags, + Alias: req.Alias, + } + domain, err := svc.CreateDomain(ctx, session, d) + if err != nil { + return nil, err + } + + return createDomainRes{domain}, nil + } +} + +func retrieveDomainEndpoint(svc domains.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(retrieveDomainRequest) + if err := req.validate(); err != nil { + return nil, err + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthorization + } + + domain, err := svc.RetrieveDomain(ctx, session, req.domainID) + if err != nil { + return nil, err + } + return retrieveDomainRes{domain}, nil + } +} + +func updateDomainEndpoint(svc domains.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateDomainReq) + if err := req.validate(); err != nil { + return nil, err + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthorization + } + + var metadata domains.Metadata + if req.Metadata != nil { + metadata = *req.Metadata + } + d := domains.DomainReq{ + Name: req.Name, + Metadata: &metadata, + Tags: req.Tags, + Alias: req.Alias, + } + domain, err := svc.UpdateDomain(ctx, session, req.domainID, d) + if err != nil { + return nil, err + } + + return updateDomainRes{domain}, nil + } +} + +func listDomainsEndpoint(svc domains.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listDomainsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthorization + } + + page := domains.Page{ + Offset: req.offset, + Limit: req.limit, + Name: req.name, + Metadata: req.metadata, + Order: req.order, + Dir: req.dir, + Tag: req.tag, + Permission: req.permission, + Status: req.status, + } + dp, err := svc.ListDomains(ctx, session, page) + if err != nil { + return nil, err + } + return listDomainsRes{dp}, nil + } +} + +func enableDomainEndpoint(svc domains.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(enableDomainReq) + if err := req.validate(); err != nil { + return nil, err + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthorization + } + + if _, err := svc.EnableDomain(ctx, session, req.domainID); err != nil { + return nil, err + } + return enableDomainRes{}, nil + } +} + +func disableDomainEndpoint(svc domains.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(disableDomainReq) + if err := req.validate(); err != nil { + return nil, err + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthorization + } + + if _, err := svc.DisableDomain(ctx, session, req.domainID); err != nil { + return nil, err + } + return disableDomainRes{}, nil + } +} + +func freezeDomainEndpoint(svc domains.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(freezeDomainReq) + if err := req.validate(); err != nil { + return nil, err + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthorization + } + + if _, err := svc.FreezeDomain(ctx, session, req.domainID); err != nil { + return nil, err + } + return freezeDomainRes{}, nil + } +} diff --git a/auth/api/http/domains/endpoint_test.go b/domains/api/http/endpoint_test.go similarity index 90% rename from auth/api/http/domains/endpoint_test.go rename to domains/api/http/endpoint_test.go index 2fe1fd7d38..ee24c481a0 100644 --- a/auth/api/http/domains/endpoint_test.go +++ b/domains/api/http/endpoint_test.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains_test +package http_test import ( "encoding/json" @@ -13,12 +13,14 @@ import ( "testing" "time" - "github.com/absmach/magistrala/auth" - httpapi "github.com/absmach/magistrala/auth/api/http/domains" - "github.com/absmach/magistrala/auth/mocks" + mgclients "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/domains" + httpapi "github.com/absmach/magistrala/domains/api/http" + "github.com/absmach/magistrala/domains/mocks" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" + authnmock "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" policies "github.com/absmach/magistrala/pkg/policies" @@ -28,14 +30,14 @@ import ( ) var ( - validCMetadata = auth.Metadata{"role": "client"} + validCMetadata = domains.Metadata{"role": "client"} ID = testsutil.GenerateUUID(&testing.T{}) - domain = auth.Domain{ + domain = domains.Domain{ ID: ID, Name: "domainname", Tags: []string{"tag1", "tag2"}, Metadata: validCMetadata, - Status: auth.EnabledStatus, + Status: domains.EnabledStatus, Alias: "mydomain", } validToken = "token" @@ -91,7 +93,9 @@ func newDomainsServer() (*httptest.Server, *mocks.Service) { logger := mglog.NewMock() mux := chi.NewRouter() svc := new(mocks.Service) - httpapi.MakeHandler(svc, mux, logger) + authn := new(authnmock.Authentication) + mux = chi.NewMux() + httpapi.MakeHandler(svc, authn, mux, logger, "") return httptest.NewServer(mux), svc } @@ -101,7 +105,7 @@ func TestCreateDomain(t *testing.T) { cases := []struct { desc string - domain auth.Domain + domain domains.Domain token string contentType string svcErr error @@ -110,10 +114,10 @@ func TestCreateDomain(t *testing.T) { }{ { desc: "register a new domain successfully", - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -124,10 +128,10 @@ func TestCreateDomain(t *testing.T) { }, { desc: "register a new domain with empty token", - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -138,10 +142,10 @@ func TestCreateDomain(t *testing.T) { }, { desc: "register a new domain with invalid token", - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -153,10 +157,10 @@ func TestCreateDomain(t *testing.T) { }, { desc: "register a new domain with an empty name", - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -167,10 +171,10 @@ func TestCreateDomain(t *testing.T) { }, { desc: "register a new domain with an empty alias", - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "", }, @@ -181,10 +185,10 @@ func TestCreateDomain(t *testing.T) { }, { desc: "register a new domain with invalid content type", - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -195,7 +199,7 @@ func TestCreateDomain(t *testing.T) { }, { desc: "register a new domain that cant be marshalled", - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", Metadata: map[string]interface{}{ @@ -222,7 +226,7 @@ func TestCreateDomain(t *testing.T) { body: strings.NewReader(data), } - svcCall := svc.On("CreateDomain", mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) + svcCall := svc.On("CreateDomain", mock.Anything, mock.Anything, mock.Anything).Return(domains.Domain{}, tc.svcErr) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var errRes respBody @@ -245,7 +249,7 @@ func TestListDomains(t *testing.T) { desc string token string query string - listDomainsRequest auth.DomainsPage + listDomainsRequest domains.DomainsPage status int svcErr error err error @@ -254,9 +258,9 @@ func TestListDomains(t *testing.T) { desc: "list domains with valid token", token: validToken, status: http.StatusOK, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, err: nil, }, @@ -276,9 +280,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with offset", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "offset=1", status: http.StatusOK, @@ -294,9 +298,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with limit", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "limit=1", status: http.StatusOK, @@ -312,9 +316,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with name", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "name=domainname", status: http.StatusOK, @@ -337,9 +341,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with status", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "status=enabled", status: http.StatusOK, @@ -362,9 +366,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with tags", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "tag=tag1,tag2", status: http.StatusOK, @@ -387,9 +391,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with metadata", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", status: http.StatusOK, @@ -412,9 +416,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with permissions", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "permission=view", status: http.StatusOK, @@ -437,9 +441,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with order", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "order=name", status: http.StatusOK, @@ -461,9 +465,9 @@ func TestListDomains(t *testing.T) { { desc: "list domains with dir", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "dir=asc", status: http.StatusOK, @@ -544,7 +548,7 @@ func TestViewDomain(t *testing.T) { token: tc.token, } - svcCall := svc.On("RetrieveDomain", mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) + svcCall := svc.On("RetrieveDomain", mock.Anything, mock.Anything, mock.Anything).Return(domains.Domain{}, tc.svcErr) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var errRes respBody @@ -633,7 +637,7 @@ func TestUpdateDomain(t *testing.T) { cases := []struct { desc string token string - domain auth.Domain + domain domains.Domain contentType string status int svcErr error @@ -642,10 +646,10 @@ func TestUpdateDomain(t *testing.T) { { desc: "update domain successfully", token: validToken, - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -656,10 +660,10 @@ func TestUpdateDomain(t *testing.T) { { desc: "update domain with empty token", token: "", - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -670,10 +674,10 @@ func TestUpdateDomain(t *testing.T) { { desc: "update domain with invalid token", token: inValidToken, - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -685,10 +689,10 @@ func TestUpdateDomain(t *testing.T) { { desc: "update domain with invalid content type", token: validToken, - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", - Metadata: auth.Metadata{"role": "domain"}, + Metadata: domains.Metadata{"role": "domain"}, Tags: []string{"tag1", "tag2"}, Alias: "test", }, @@ -699,7 +703,7 @@ func TestUpdateDomain(t *testing.T) { { desc: "update domain with data that cant be marshalled", token: validToken, - domain: auth.Domain{ + domain: domains.Domain{ ID: ID, Name: "test", Metadata: map[string]interface{}{ @@ -725,7 +729,7 @@ func TestUpdateDomain(t *testing.T) { token: tc.token, } - svcCall := svc.On("UpdateDomain", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) + svcCall := svc.On("UpdateDomain", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(domains.Domain{}, tc.svcErr) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var errRes respBody @@ -746,12 +750,12 @@ func TestEnableDomain(t *testing.T) { defer ds.Close() disabledDomain := domain - disabledDomain.Status = auth.DisabledStatus + disabledDomain.Status = domains.DisabledStatus cases := []struct { desc string - domain auth.Domain - response auth.Domain + domain domains.Domain + response domains.Domain token string status int svcErr error @@ -760,9 +764,9 @@ func TestEnableDomain(t *testing.T) { { desc: "enable domain with valid token", domain: disabledDomain, - response: auth.Domain{ + response: domains.Domain{ ID: domain.ID, - Status: auth.EnabledStatus, + Status: domains.EnabledStatus, }, token: validToken, status: http.StatusOK, @@ -785,7 +789,7 @@ func TestEnableDomain(t *testing.T) { }, { desc: "enable domain with empty id", - domain: auth.Domain{ + domain: domains.Domain{ ID: "", }, token: validToken, @@ -794,7 +798,7 @@ func TestEnableDomain(t *testing.T) { }, { desc: "enable domain with invalid id", - domain: auth.Domain{ + domain: domains.Domain{ ID: "invalid", }, token: validToken, @@ -828,8 +832,8 @@ func TestDisableDomain(t *testing.T) { cases := []struct { desc string - domain auth.Domain - response auth.Domain + domain domains.Domain + response domains.Domain token string status int svcErr error @@ -838,9 +842,9 @@ func TestDisableDomain(t *testing.T) { { desc: "disable domain with valid token", domain: domain, - response: auth.Domain{ + response: domains.Domain{ ID: domain.ID, - Status: auth.DisabledStatus, + Status: domains.DisabledStatus, }, token: validToken, status: http.StatusOK, @@ -863,7 +867,7 @@ func TestDisableDomain(t *testing.T) { }, { desc: "disable domain with empty id", - domain: auth.Domain{ + domain: domains.Domain{ ID: "", }, token: validToken, @@ -872,7 +876,7 @@ func TestDisableDomain(t *testing.T) { }, { desc: "disable domain with invalid id", - domain: auth.Domain{ + domain: domains.Domain{ ID: "invalid", }, token: validToken, @@ -906,8 +910,8 @@ func TestFreezeDomain(t *testing.T) { cases := []struct { desc string - domain auth.Domain - response auth.Domain + domain domains.Domain + response domains.Domain token string status int svcErr error @@ -916,9 +920,9 @@ func TestFreezeDomain(t *testing.T) { { desc: "freeze domain with valid token", domain: domain, - response: auth.Domain{ + response: domains.Domain{ ID: domain.ID, - Status: auth.FreezeStatus, + Status: domains.FreezeStatus, }, token: validToken, status: http.StatusOK, @@ -941,7 +945,7 @@ func TestFreezeDomain(t *testing.T) { }, { desc: "freeze domain with empty id", - domain: auth.Domain{ + domain: domains.Domain{ ID: "", }, token: validToken, @@ -950,7 +954,7 @@ func TestFreezeDomain(t *testing.T) { }, { desc: "freeze domain with invalid id", - domain: auth.Domain{ + domain: domains.Domain{ ID: "invalid", }, token: validToken, @@ -1205,7 +1209,7 @@ func TestListDomainsByUserID(t *testing.T) { desc string token string query string - listDomainsRequest auth.DomainsPage + listDomainsRequest domains.DomainsPage userID string status int svcErr error @@ -1215,9 +1219,9 @@ func TestListDomainsByUserID(t *testing.T) { desc: "list domains by user id with valid token", token: validToken, status: http.StatusOK, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, userID: validID, err: nil, @@ -1246,9 +1250,9 @@ func TestListDomainsByUserID(t *testing.T) { { desc: "list domains by user id with offset", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "offset=1", userID: validID, @@ -1265,9 +1269,9 @@ func TestListDomainsByUserID(t *testing.T) { { desc: "list domains by user id with limit", token: validToken, - listDomainsRequest: auth.DomainsPage{ + listDomainsRequest: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{domain}, + Domains: []domains.Domain{domain}, }, query: "limit=1", userID: validID, @@ -1300,11 +1304,11 @@ func TestListDomainsByUserID(t *testing.T) { } type respBody struct { - Err string `json:"error"` - Message string `json:"message"` - Total int `json:"total"` - Permissions []string `json:"permissions"` - ID string `json:"id"` - Tags []string `json:"tags"` - Status auth.Status `json:"status"` + Err string `json:"error"` + Message string `json:"message"` + Total int `json:"total"` + Permissions []string `json:"permissions"` + ID string `json:"id"` + Tags []string `json:"tags"` + Status mgclients.Status `json:"status"` } diff --git a/auth/api/http/domains/requests.go b/domains/api/http/requests.go similarity index 52% rename from auth/api/http/domains/requests.go rename to domains/api/http/requests.go index 5abbddd0df..722e4cfa32 100644 --- a/auth/api/http/domains/requests.go +++ b/domains/api/http/requests.go @@ -1,10 +1,10 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package http import ( - "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/domains" "github.com/absmach/magistrala/pkg/apiutil" ) @@ -17,11 +17,10 @@ type page struct { metadata map[string]interface{} tag string permission string - status auth.Status + status domains.Status } type createDomainReq struct { - token string Name string `json:"name"` Metadata map[string]interface{} `json:"metadata,omitempty"` Tags []string `json:"tags,omitempty"` @@ -29,10 +28,6 @@ type createDomainReq struct { } func (req createDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.Name == "" { return apiutil.ErrMissingName } @@ -45,32 +40,10 @@ func (req createDomainReq) validate() error { } type retrieveDomainRequest struct { - token string domainID string } func (req retrieveDomainRequest) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type retrieveDomainPermissionsRequest struct { - token string - domainID string -} - -func (req retrieveDomainPermissionsRequest) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.domainID == "" { return apiutil.ErrMissingID } @@ -79,7 +52,6 @@ func (req retrieveDomainPermissionsRequest) validate() error { } type updateDomainReq struct { - token string domainID string Name *string `json:"name,omitempty"` Metadata *map[string]interface{} `json:"metadata,omitempty"` @@ -88,10 +60,6 @@ type updateDomainReq struct { } func (req updateDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.domainID == "" { return apiutil.ErrMissingID } @@ -113,15 +81,10 @@ func (req listDomainsReq) validate() error { } type enableDomainReq struct { - token string domainID string } func (req enableDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.domainID == "" { return apiutil.ErrMissingID } @@ -130,15 +93,10 @@ func (req enableDomainReq) validate() error { } type disableDomainReq struct { - token string domainID string } func (req disableDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.domainID == "" { return apiutil.ErrMissingID } @@ -147,85 +105,13 @@ func (req disableDomainReq) validate() error { } type freezeDomainReq struct { - token string domainID string } func (req freezeDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type assignUsersReq struct { - token string - domainID string - UserIDs []string `json:"user_ids"` - Relation string `json:"relation"` -} - -func (req assignUsersReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.domainID == "" { return apiutil.ErrMissingID } - if len(req.UserIDs) == 0 { - return apiutil.ErrMissingID - } - - if req.Relation == "" { - return apiutil.ErrMissingRelation - } - - return nil -} - -type unassignUserReq struct { - token string - domainID string - UserID string `json:"user_id"` -} - -func (req unassignUserReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { - return apiutil.ErrMissingID - } - - if req.UserID == "" { - return apiutil.ErrMalformedPolicy - } - - return nil -} - -type listUserDomainsReq struct { - token string - userID string - page -} - -func (req listUserDomainsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.userID == "" { - return apiutil.ErrMissingID - } - return nil } diff --git a/auth/api/http/domains/responses.go b/domains/api/http/responses.go similarity index 95% rename from auth/api/http/domains/responses.go rename to domains/api/http/responses.go index 3eb277ef06..fd9fe227e2 100644 --- a/auth/api/http/domains/responses.go +++ b/domains/api/http/responses.go @@ -1,13 +1,13 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package http import ( "net/http" "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/domains" ) var ( @@ -19,7 +19,7 @@ var ( ) type createDomainRes struct { - auth.Domain + domains.Domain } func (res createDomainRes) Code() int { @@ -35,7 +35,7 @@ func (res createDomainRes) Empty() bool { } type retrieveDomainRes struct { - auth.Domain + domains.Domain } func (res retrieveDomainRes) Code() int { @@ -67,7 +67,7 @@ func (res retrieveDomainPermissionsRes) Empty() bool { } type updateDomainRes struct { - auth.Domain + domains.Domain } func (res updateDomainRes) Code() int { @@ -83,7 +83,7 @@ func (res updateDomainRes) Empty() bool { } type listDomainsRes struct { - auth.DomainsPage + domains.DomainsPage } func (res listDomainsRes) Code() int { @@ -169,7 +169,7 @@ func (res unassignUsersRes) Empty() bool { } type listUserDomainsRes struct { - auth.DomainsPage + domains.DomainsPage } func (res listUserDomainsRes) Code() int { diff --git a/auth/api/http/domains/transport.go b/domains/api/http/transport.go similarity index 53% rename from auth/api/http/domains/transport.go rename to domains/api/http/transport.go index 332e9b78ac..3c817ab2f1 100644 --- a/auth/api/http/domains/transport.go +++ b/domains/api/http/transport.go @@ -1,40 +1,51 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package domains +package http import ( "log/slog" - "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/domains" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/authn" + roleManagerHttp "github.com/absmach/magistrala/pkg/roles/rolemanager/api" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" + "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { +func MakeHandler(svc domains.Service, authn authn.Authentication, mux *chi.Mux, logger *slog.Logger, instanceID string) *chi.Mux { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } + d := roleManagerHttp.NewDecoder("domainID") mux.Route("/domains", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - createDomainEndpoint(svc), - decodeCreateDomainRequest, - api.EncodeResponse, - opts..., - ), "create_domain").ServeHTTP) + r.Group(func(r chi.Router) { + r.Use(api.AuthenticateMiddleware(authn, false)) + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + createDomainEndpoint(svc), + decodeCreateDomainRequest, + api.EncodeResponse, + opts..., + ), "create_domain").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + listDomainsEndpoint(svc), + decodeListDomainRequest, + api.EncodeResponse, + opts..., + ), "list_domains").ServeHTTP) - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - listDomainsEndpoint(svc), - decodeListDomainRequest, - api.EncodeResponse, - opts..., - ), "list_domains").ServeHTTP) + roleManagerHttp.EntityAvailableActionsRouter(svc, d, r, opts) + }) r.Route("/{domainID}", func(r chi.Router) { + r.Use(api.AuthenticateMiddleware(authn, true)) r.Get("/", otelhttp.NewHandler(kithttp.NewServer( retrieveDomainEndpoint(svc), decodeRetrieveDomainRequest, @@ -42,13 +53,6 @@ func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { opts..., ), "view_domain").ServeHTTP) - r.Get("/permissions", otelhttp.NewHandler(kithttp.NewServer( - retrieveDomainPermissionsEndpoint(svc), - decodeRetrieveDomainPermissionsRequest, - api.EncodeResponse, - opts..., - ), "view_domain_permissions").ServeHTTP) - r.Patch("/", otelhttp.NewHandler(kithttp.NewServer( updateDomainEndpoint(svc), decodeUpdateDomainRequest, @@ -76,30 +80,13 @@ func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { api.EncodeResponse, opts..., ), "freeze_domain").ServeHTTP) + roleManagerHttp.EntityRoleMangerRouter(svc, d, r, opts) - r.Route("/users", func(r chi.Router) { - r.Post("/assign", otelhttp.NewHandler(kithttp.NewServer( - assignDomainUsersEndpoint(svc), - decodeAssignUsersRequest, - api.EncodeResponse, - opts..., - ), "assign_domain_users").ServeHTTP) - - r.Post("/unassign", otelhttp.NewHandler(kithttp.NewServer( - unassignDomainUserEndpoint(svc), - decodeUnassignUserRequest, - api.EncodeResponse, - opts..., - ), "unassign_domain_users").ServeHTTP) - }) }) }) - mux.Get("/users/{userID}/domains", otelhttp.NewHandler(kithttp.NewServer( - listUserDomainsEndpoint(svc), - decodeListUserDomainsRequest, - api.EncodeResponse, - opts..., - ), "list_domains_by_user_id").ServeHTTP) + + mux.Get("/health", magistrala.Health("auth", instanceID)) + mux.Handle("/metrics", promhttp.Handler()) return mux } diff --git a/auth/domains.go b/domains/domains.go similarity index 55% rename from auth/domains.go rename to domains/domains.go index e9efc58034..4d2a6729af 100644 --- a/auth/domains.go +++ b/domains/domains.go @@ -1,7 +1,7 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package auth +package domains import ( "context" @@ -9,8 +9,10 @@ import ( "strings" "time" + "github.com/absmach/magistrala/pkg/authn" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/roles" + "github.com/absmach/magistrala/pkg/svcutil" ) // Status represents Domain status. @@ -24,6 +26,8 @@ const ( DisabledStatus // FreezeStatus represents domain is in freezed state. FreezeStatus + // DeletedStatus represents domain is in deleted state. + DeletedStatus // AllStatus is used for querying purposes to list Domains irrespective // of their status - enabled, disabled, freezed, deleting. It is never stored in the @@ -37,6 +41,7 @@ const ( Disabled = "disabled" Enabled = "enabled" Freezed = "freezed" + Deleted = "deleted" All = "all" Unknown = "unknown" ) @@ -52,6 +57,8 @@ func (s Status) String() string { return All case FreezeStatus: return Freezed + case DeletedStatus: + return Deleted default: return Unknown } @@ -66,6 +73,8 @@ func ToStatus(status string) (Status, error) { return DisabledStatus, nil case Freezed: return FreezeStatus, nil + case Deleted: + return DeletedStatus, nil case All: return AllStatus, nil } @@ -85,6 +94,34 @@ func (s *Status) UnmarshalJSON(data []byte) error { return err } +const ( + OpUpdateDomain svcutil.Operation = iota + OpRetrieveDomain + OpEnableDomain + OpDisableDomain +) + +var expectedOperations = []svcutil.Operation{ + OpRetrieveDomain, + OpUpdateDomain, + OpEnableDomain, + OpDisableDomain, +} + +var operationNames = []string{ + "OpRetrieveDomain", + "OpUpdateDomain", + "OpEnableDomain", + "OpDisableDomain", +} + +func NewOperationPerm() svcutil.OperationPerm { + return svcutil.NewOperationPerm(expectedOperations, operationNames) +} + +// Metadata represents arbitrary JSON. +type Metadata map[string]interface{} + type DomainReq struct { Name *string `json:"name,omitempty"` Metadata *Metadata `json:"metadata,omitempty"` @@ -106,9 +143,6 @@ type Domain struct { UpdatedAt time.Time `json:"updated_at,omitempty"` } -// Metadata represents arbitrary JSON. -type Metadata map[string]interface{} - type Page struct { Total uint64 `json:"total"` Offset uint64 `json:"offset"` @@ -157,32 +191,29 @@ type Policy struct { ObjectID string `json:"object_id,omitempty"` } -type Domains interface { - CreateDomain(ctx context.Context, token string, d Domain) (Domain, error) - RetrieveDomain(ctx context.Context, token string, id string) (Domain, error) - RetrieveDomainPermissions(ctx context.Context, token string, id string) (policies.Permissions, error) - UpdateDomain(ctx context.Context, token string, id string, d DomainReq) (Domain, error) - ChangeDomainStatus(ctx context.Context, token string, id string, d DomainReq) (Domain, error) - ListDomains(ctx context.Context, token string, page Page) (DomainsPage, error) - AssignUsers(ctx context.Context, token string, id string, userIds []string, relation string) error - UnassignUser(ctx context.Context, token string, id string, userID string) error - ListUserDomains(ctx context.Context, token string, userID string, page Page) (DomainsPage, error) +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" +type Service interface { + CreateDomain(ctx context.Context, sesssion authn.Session, d Domain) (Domain, error) + RetrieveDomain(ctx context.Context, sesssion authn.Session, id string) (Domain, error) + UpdateDomain(ctx context.Context, sesssion authn.Session, id string, d DomainReq) (Domain, error) + EnableDomain(ctx context.Context, sesssion authn.Session, id string) (Domain, error) + DisableDomain(ctx context.Context, sesssion authn.Session, id string) (Domain, error) + FreezeDomain(ctx context.Context, sesssion authn.Session, id string) (Domain, error) + ListDomains(ctx context.Context, sesssion authn.Session, page Page) (DomainsPage, error) DeleteUserFromDomains(ctx context.Context, id string) error + roles.RoleManager } -// DomainsRepository specifies Domain persistence API. +// Repository specifies Domain persistence API. // -//go:generate mockery --name DomainsRepository --output=./mocks --filename domains.go --quiet --note "Copyright (c) Abstract Machines" -type DomainsRepository interface { +//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" +type Repository interface { // Save creates db insert transaction for the given domain. Save(ctx context.Context, d Domain) (Domain, error) // RetrieveByID retrieves Domain by its unique ID. RetrieveByID(ctx context.Context, id string) (Domain, error) - // RetrievePermissions retrieves domain permissions. - RetrievePermissions(ctx context.Context, subject, id string) ([]string, error) - // RetrieveAllByIDs retrieves for given Domain IDs. RetrieveAllByIDs(ctx context.Context, pm Page) (DomainsPage, error) @@ -192,18 +223,60 @@ type DomainsRepository interface { // Delete Delete(ctx context.Context, id string) error - // SavePolicies save policies in domains database - SavePolicies(ctx context.Context, pcs ...Policy) error - - // DeletePolicies delete policies from domains database - DeletePolicies(ctx context.Context, pcs ...Policy) error - // ListDomains list all the domains ListDomains(ctx context.Context, pm Page) (DomainsPage, error) - // CheckPolicy check policies in domains database. - CheckPolicy(ctx context.Context, pc Policy) error + roles.Repository +} + +// Below codes should moved out of service, may be can be kept in `cmd//main.go` + +const ( + updatePermission = "update_permission" + enablePermission = "enable_permission" + disablePermission = "disable_permission" + readPermission = "read_permission" + membershipPermission = "membership_permission" + deletePermission = "delete_permission" + manageRolePermission = "manage_role_permission" + addRoleUsersPermission = "add_role_users_permission" + removeRoleUsersPermission = "remove_role_users_permission" + viewRoleUsersPermission = "view_role_users_permission" +) - // DeleteUserPolicies deletes user policies from domains database. - DeleteUserPolicies(ctx context.Context, id string) (err error) +const ( + ClientCreatePermission = "client_create_permission" + ChannelCreatePermission = "channel_create_permission" + GroupCreatePermission = "group_create_permission" +) + +func NewOperationPermissionMap() map[svcutil.Operation]svcutil.Permission { + opPerm := map[svcutil.Operation]svcutil.Permission{ + OpRetrieveDomain: readPermission, + OpUpdateDomain: updatePermission, + OpEnableDomain: enablePermission, + OpDisableDomain: disablePermission, + } + return opPerm +} + +func NewRolesOperationPermissionMap() map[svcutil.Operation]svcutil.Permission { + opPerm := map[svcutil.Operation]svcutil.Permission{ + roles.OpAddRole: manageRolePermission, + roles.OpRemoveRole: manageRolePermission, + roles.OpUpdateRoleName: manageRolePermission, + roles.OpRetrieveRole: manageRolePermission, + roles.OpRetrieveAllRoles: manageRolePermission, + roles.OpRoleAddActions: manageRolePermission, + roles.OpRoleListActions: manageRolePermission, + roles.OpRoleCheckActionsExists: manageRolePermission, + roles.OpRoleRemoveActions: manageRolePermission, + roles.OpRoleRemoveAllActions: manageRolePermission, + roles.OpRoleAddMembers: addRoleUsersPermission, + roles.OpRoleListMembers: viewRoleUsersPermission, + roles.OpRoleCheckMembersExists: viewRoleUsersPermission, + roles.OpRoleRemoveMembers: removeRoleUsersPermission, + roles.OpRoleRemoveAllMembers: manageRolePermission, + } + return opPerm } diff --git a/auth/domains_test.go b/domains/domains_test.go similarity index 78% rename from auth/domains_test.go rename to domains/domains_test.go index 82875bccd5..7e55ff3b2b 100644 --- a/auth/domains_test.go +++ b/domains/domains_test.go @@ -1,12 +1,12 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -package auth_test +package domains_test import ( "testing" - "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/domains" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/stretchr/testify/assert" ) @@ -14,32 +14,32 @@ import ( func TestStatusString(t *testing.T) { cases := []struct { desc string - status auth.Status + status domains.Status expected string }{ { desc: "Enabled", - status: auth.EnabledStatus, + status: domains.EnabledStatus, expected: "enabled", }, { desc: "Disabled", - status: auth.DisabledStatus, + status: domains.DisabledStatus, expected: "disabled", }, { desc: "Freezed", - status: auth.FreezeStatus, + status: domains.FreezeStatus, expected: "freezed", }, { desc: "All", - status: auth.AllStatus, + status: domains.AllStatus, expected: "all", }, { desc: "Unknown", - status: auth.Status(100), + status: domains.Status(100), expected: "unknown", }, } @@ -56,44 +56,44 @@ func TestToStatus(t *testing.T) { cases := []struct { desc string status string - expetcted auth.Status + expetcted domains.Status err error }{ { desc: "Enabled", status: "enabled", - expetcted: auth.EnabledStatus, + expetcted: domains.EnabledStatus, err: nil, }, { desc: "Disabled", status: "disabled", - expetcted: auth.DisabledStatus, + expetcted: domains.DisabledStatus, err: nil, }, { desc: "Freezed", status: "freezed", - expetcted: auth.FreezeStatus, + expetcted: domains.FreezeStatus, err: nil, }, { desc: "All", status: "all", - expetcted: auth.AllStatus, + expetcted: domains.AllStatus, err: nil, }, { desc: "Unknown", status: "unknown", - expetcted: auth.Status(0), + expetcted: domains.Status(0), err: svcerr.ErrInvalidStatus, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - got, err := auth.ToStatus(tc.status) + got, err := domains.ToStatus(tc.status) assert.Equal(t, tc.err, err, "ToStatus() error = %v, expected %v", err, tc.err) assert.Equal(t, tc.expetcted, got, "ToStatus() = %v, expected %v", got, tc.expetcted) }) @@ -104,31 +104,31 @@ func TestStatusMarshalJSON(t *testing.T) { cases := []struct { desc string expected []byte - status auth.Status + status domains.Status err error }{ { desc: "Enabled", expected: []byte(`"enabled"`), - status: auth.EnabledStatus, + status: domains.EnabledStatus, err: nil, }, { desc: "Disabled", expected: []byte(`"disabled"`), - status: auth.DisabledStatus, + status: domains.DisabledStatus, err: nil, }, { desc: "All", expected: []byte(`"all"`), - status: auth.AllStatus, + status: domains.AllStatus, err: nil, }, { desc: "Unknown", expected: []byte(`"unknown"`), - status: auth.Status(100), + status: domains.Status(100), err: nil, }, } @@ -145,31 +145,31 @@ func TestStatusMarshalJSON(t *testing.T) { func TestStatusUnmarshalJSON(t *testing.T) { cases := []struct { desc string - expected auth.Status + expected domains.Status status []byte err error }{ { desc: "Enabled", - expected: auth.EnabledStatus, + expected: domains.EnabledStatus, status: []byte(`"enabled"`), err: nil, }, { desc: "Disabled", - expected: auth.DisabledStatus, + expected: domains.DisabledStatus, status: []byte(`"disabled"`), err: nil, }, { desc: "All", - expected: auth.AllStatus, + expected: domains.AllStatus, status: []byte(`"all"`), err: nil, }, { desc: "Unknown", - expected: auth.Status(0), + expected: domains.Status(0), status: []byte(`"unknown"`), err: svcerr.ErrInvalidStatus, }, @@ -177,7 +177,7 @@ func TestStatusUnmarshalJSON(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - var s auth.Status + var s domains.Status err := s.UnmarshalJSON(tc.status) assert.Equal(t, tc.err, err, "UnmarshalJSON() error = %v, expected %v", err, tc.err) assert.Equal(t, tc.expected, s, "UnmarshalJSON() = %v, expected %v", s, tc.expected) diff --git a/auth/events/doc.go b/domains/events/doc.go similarity index 100% rename from auth/events/doc.go rename to domains/events/doc.go diff --git a/auth/events/events.go b/domains/events/events.go similarity index 52% rename from auth/events/events.go rename to domains/events/events.go index e0fe609a0a..94135ffd79 100644 --- a/auth/events/events.go +++ b/domains/events/events.go @@ -6,38 +6,34 @@ package events import ( "time" - "github.com/absmach/magistrala/auth" + "github.com/absmach/magistrala/domains" "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/policies" ) const ( - domainPrefix = "domain." - domainCreate = domainPrefix + "create" - domainRetrieve = domainPrefix + "retrieve" - domainRetrievePermissions = domainPrefix + "retrieve_permissions" - domainUpdate = domainPrefix + "update" - domainChangeStatus = domainPrefix + "change_status" - domainList = domainPrefix + "list" - domainAssign = domainPrefix + "assign" - domainUnassign = domainPrefix + "unassign" - domainUserList = domainPrefix + "user_list" + domainPrefix = "domain." + domainCreate = domainPrefix + "create" + domainRetrieve = domainPrefix + "retrieve" + domainUpdate = domainPrefix + "update" + domainEnable = domainPrefix + "enable" + domainDisable = domainPrefix + "disable" + domainFreeze = domainPrefix + "freeze" + domainList = domainPrefix + "list" + domainUserDelete = domainPrefix + "user_delete" ) var ( _ events.Event = (*createDomainEvent)(nil) _ events.Event = (*retrieveDomainEvent)(nil) - _ events.Event = (*retrieveDomainPermissionsEvent)(nil) _ events.Event = (*updateDomainEvent)(nil) - _ events.Event = (*changeDomainStatusEvent)(nil) + _ events.Event = (*enableDomainEvent)(nil) + _ events.Event = (*disableDomainEvent)(nil) + _ events.Event = (*freezeDomainEvent)(nil) _ events.Event = (*listDomainsEvent)(nil) - _ events.Event = (*assignUsersEvent)(nil) - _ events.Event = (*unassignUsersEvent)(nil) - _ events.Event = (*listUserDomainsEvent)(nil) ) type createDomainEvent struct { - auth.Domain + domains.Domain } func (cde createDomainEvent) Encode() (map[string]interface{}, error) { @@ -67,7 +63,7 @@ func (cde createDomainEvent) Encode() (map[string]interface{}, error) { } type retrieveDomainEvent struct { - auth.Domain + domains.Domain } func (rde retrieveDomainEvent) Encode() (map[string]interface{}, error) { @@ -98,26 +94,8 @@ func (rde retrieveDomainEvent) Encode() (map[string]interface{}, error) { return val, nil } -type retrieveDomainPermissionsEvent struct { - domainID string - permissions policies.Permissions -} - -func (rpe retrieveDomainPermissionsEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": domainRetrievePermissions, - "domain_id": rpe.domainID, - } - - if rpe.permissions != nil { - val["permissions"] = rpe.permissions - } - - return val, nil -} - type updateDomainEvent struct { - auth.Domain + domains.Domain } func (ude updateDomainEvent) Encode() (map[string]interface{}, error) { @@ -145,25 +123,53 @@ func (ude updateDomainEvent) Encode() (map[string]interface{}, error) { return val, nil } -type changeDomainStatusEvent struct { +type enableDomainEvent struct { + domainID string + updatedAt time.Time + updatedBy string +} + +func (cdse enableDomainEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": domainEnable, + "id": cdse.domainID, + "updated_at": cdse.updatedAt, + "updated_by": cdse.updatedBy, + }, nil +} + +type disableDomainEvent struct { domainID string - status auth.Status updatedAt time.Time updatedBy string } -func (cdse changeDomainStatusEvent) Encode() (map[string]interface{}, error) { +func (cdse disableDomainEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "operation": domainChangeStatus, + "operation": domainDisable, + "id": cdse.domainID, + "updated_at": cdse.updatedAt, + "updated_by": cdse.updatedBy, + }, nil +} + +type freezeDomainEvent struct { + domainID string + updatedAt time.Time + updatedBy string +} + +func (cdse freezeDomainEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": domainFreeze, "id": cdse.domainID, - "status": cdse.status.String(), "updated_at": cdse.updatedAt, "updated_by": cdse.updatedBy, }, nil } type listDomainsEvent struct { - auth.Page + domains.Page total uint64 } @@ -212,85 +218,14 @@ func (lde listDomainsEvent) Encode() (map[string]interface{}, error) { return val, nil } -type assignUsersEvent struct { - userIDs []string - domainID string - relation string -} - -func (ase assignUsersEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": domainAssign, - "user_ids": ase.userIDs, - "domain_id": ase.domainID, - "relation": ase.relation, - } - - return val, nil -} - -type unassignUsersEvent struct { - userID string - domainID string -} - -func (use unassignUsersEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": domainUnassign, - "user_id": use.userID, - "domain_id": use.domainID, - } - - return val, nil -} - -type listUserDomainsEvent struct { - auth.Page +type deleteUserFromDomainsEvent struct { userID string } -func (lde listUserDomainsEvent) Encode() (map[string]interface{}, error) { +func (dude deleteUserFromDomainsEvent) Encode() (map[string]interface{}, error) { val := map[string]interface{}{ - "operation": domainUserList, - "total": lde.Total, - "offset": lde.Offset, - "limit": lde.Limit, - "user_id": lde.userID, - } - - if lde.Name != "" { - val["name"] = lde.Name - } - if lde.Order != "" { - val["order"] = lde.Order + "operation": domainUserDelete, + "user_id": dude.userID, } - if lde.Dir != "" { - val["dir"] = lde.Dir - } - if lde.Metadata != nil { - val["metadata"] = lde.Metadata - } - if lde.Tag != "" { - val["tag"] = lde.Tag - } - if lde.Permission != "" { - val["permission"] = lde.Permission - } - if lde.Status.String() != "" { - val["status"] = lde.Status.String() - } - if lde.ID != "" { - val["id"] = lde.ID - } - if len(lde.IDs) > 0 { - val["ids"] = lde.IDs - } - if lde.Identity != "" { - val["identity"] = lde.Identity - } - if lde.SubjectID != "" { - val["subject_id"] = lde.SubjectID - } - return val, nil } diff --git a/domains/events/streams.go b/domains/events/streams.go new file mode 100644 index 0000000000..6f53b84af7 --- /dev/null +++ b/domains/events/streams.go @@ -0,0 +1,180 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "context" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/events" + "github.com/absmach/magistrala/pkg/events/store" + rmEvents "github.com/absmach/magistrala/pkg/roles/rolemanager/events" +) + +const streamID = "magistrala.domains" + +var _ domains.Service = (*eventStore)(nil) + +type eventStore struct { + events.Publisher + svc domains.Service + rmEvents.RoleManagerEventStore +} + +// NewEventStoreMiddleware returns wrapper around auth service that sends +// events to event store. +func NewEventStoreMiddleware(ctx context.Context, svc domains.Service, url string) (domains.Service, error) { + publisher, err := store.NewPublisher(ctx, url, streamID) + if err != nil { + return nil, err + } + + res := rmEvents.NewRoleManagerEventStore("domains", svc, publisher) + + return &eventStore{ + svc: svc, + Publisher: publisher, + RoleManagerEventStore: res, + }, nil +} + +func (es *eventStore) CreateDomain(ctx context.Context, session authn.Session, domain domains.Domain) (domains.Domain, error) { + domain, err := es.svc.CreateDomain(ctx, session, domain) + if err != nil { + return domain, err + } + + event := createDomainEvent{ + domain, + } + + if err := es.Publish(ctx, event); err != nil { + return domain, err + } + + return domain, nil +} + +func (es *eventStore) RetrieveDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + domain, err := es.svc.RetrieveDomain(ctx, session, id) + if err != nil { + return domain, err + } + + event := retrieveDomainEvent{ + domain, + } + + if err := es.Publish(ctx, event); err != nil { + return domain, err + } + + return domain, nil +} + +func (es *eventStore) UpdateDomain(ctx context.Context, session authn.Session, id string, d domains.DomainReq) (domains.Domain, error) { + domain, err := es.svc.UpdateDomain(ctx, session, id, d) + if err != nil { + return domain, err + } + + event := updateDomainEvent{ + domain, + } + + if err := es.Publish(ctx, event); err != nil { + return domain, err + } + + return domain, nil +} + +func (es *eventStore) EnableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + domain, err := es.svc.EnableDomain(ctx, session, id) + if err != nil { + return domain, err + } + + event := enableDomainEvent{ + domainID: id, + updatedAt: domain.UpdatedAt, + updatedBy: domain.UpdatedBy, + } + + if err := es.Publish(ctx, event); err != nil { + return domain, err + } + + return domain, nil +} + +func (es *eventStore) DisableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + domain, err := es.svc.DisableDomain(ctx, session, id) + if err != nil { + return domain, err + } + + event := disableDomainEvent{ + domainID: id, + updatedAt: domain.UpdatedAt, + updatedBy: domain.UpdatedBy, + } + + if err := es.Publish(ctx, event); err != nil { + return domain, err + } + + return domain, nil +} + +func (es *eventStore) FreezeDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + domain, err := es.svc.FreezeDomain(ctx, session, id) + if err != nil { + return domain, err + } + + event := freezeDomainEvent{ + domainID: id, + updatedAt: domain.UpdatedAt, + updatedBy: domain.UpdatedBy, + } + + if err := es.Publish(ctx, event); err != nil { + return domain, err + } + + return domain, nil +} + +func (es *eventStore) ListDomains(ctx context.Context, session authn.Session, p domains.Page) (domains.DomainsPage, error) { + dp, err := es.svc.ListDomains(ctx, session, p) + if err != nil { + return dp, err + } + + event := listDomainsEvent{ + p, dp.Total, + } + + if err := es.Publish(ctx, event); err != nil { + return dp, err + } + + return dp, nil +} + +func (es *eventStore) DeleteUserFromDomains(ctx context.Context, userID string) error { + if err := es.svc.DeleteUserFromDomains(ctx, userID); err != nil { + return err + } + + event := deleteUserFromDomainsEvent{userID} + + if err := es.Publish(ctx, event); err != nil { + return err + } + + return nil +} diff --git a/domains/middleware/authorization.go b/domains/middleware/authorization.go new file mode 100644 index 0000000000..7e0e92a1a3 --- /dev/null +++ b/domains/middleware/authorization.go @@ -0,0 +1,152 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "context" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/authz" + mgauthz "github.com/absmach/magistrala/pkg/authz" + "github.com/absmach/magistrala/pkg/policies" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" + "github.com/absmach/magistrala/pkg/svcutil" +) + +var _ domains.Service = (*authorizationMiddleware)(nil) + +type authorizationMiddleware struct { + svc domains.Service + authz mgauthz.Authorization + opp svcutil.OperationPerm + rmMW.RoleManagerAuthorizationMiddleware +} + +// AuthorizationMiddleware adds authorization to the clients service. +func AuthorizationMiddleware(entityType string, svc domains.Service, authz mgauthz.Authorization, domainsOpPerm, rolesOpPerm map[svcutil.Operation]svcutil.Permission) (domains.Service, error) { + opp := domains.NewOperationPerm() + if err := opp.AddOperationPermissionMap(domainsOpPerm); err != nil { + return nil, err + } + if err := opp.Validate(); err != nil { + return nil, err + } + + ram, err := rmMW.NewRoleManagerAuthorizationMiddleware(entityType, svc, authz, rolesOpPerm) + if err != nil { + return nil, err + } + return &authorizationMiddleware{ + svc: svc, + authz: authz, + opp: opp, + RoleManagerAuthorizationMiddleware: ram, + }, nil +} + +func (am *authorizationMiddleware) CreateDomain(ctx context.Context, session authn.Session, d domains.Domain) (domains.Domain, error) { + return am.svc.CreateDomain(ctx, session, d) +} + +func (am *authorizationMiddleware) RetrieveDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + if err := am.authorize(ctx, domains.OpRetrieveDomain, authz.PolicyReq{ + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: id, + ObjectType: policies.DomainType, + }); err != nil { + return domains.Domain{}, err + } + return am.svc.RetrieveDomain(ctx, session, id) +} + +func (am *authorizationMiddleware) UpdateDomain(ctx context.Context, session authn.Session, id string, d domains.DomainReq) (domains.Domain, error) { + if err := am.authorize(ctx, domains.OpUpdateDomain, authz.PolicyReq{ + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: id, + ObjectType: policies.DomainType, + }); err != nil { + return domains.Domain{}, err + } + return am.svc.UpdateDomain(ctx, session, id, d) +} + +func (am *authorizationMiddleware) EnableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + if err := am.authorize(ctx, domains.OpEnableDomain, authz.PolicyReq{ + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: id, + ObjectType: policies.DomainType, + }); err != nil { + return domains.Domain{}, err + } + + return am.svc.EnableDomain(ctx, session, id) +} + +func (am *authorizationMiddleware) DisableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + if err := am.authorize(ctx, domains.OpDisableDomain, authz.PolicyReq{ + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: id, + ObjectType: policies.DomainType, + }); err != nil { + return domains.Domain{}, err + } + + return am.svc.DisableDomain(ctx, session, id) +} + +func (am *authorizationMiddleware) FreezeDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + // Only SuperAdmin can freeze the domain + if err := am.authz.Authorize(ctx, authz.PolicyReq{ + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Permission: policies.AdminPermission, + Object: id, + ObjectType: policies.DomainType, + }); err != nil { + return domains.Domain{}, err + } + return am.svc.FreezeDomain(ctx, session, id) +} + +func (am *authorizationMiddleware) ListDomains(ctx context.Context, session authn.Session, page domains.Page) (domains.DomainsPage, error) { + if err := am.authz.Authorize(ctx, authz.PolicyReq{ + Subject: session.UserID, + SubjectType: policies.UserType, + Permission: policies.AdminPermission, + ObjectType: policies.PlatformType, + Object: policies.MagistralaObject, + }); err == nil { + session.SuperAdmin = true + } + + return am.svc.ListDomains(ctx, session, page) +} + +func (am *authorizationMiddleware) DeleteUserFromDomains(ctx context.Context, id string) (err error) { + return am.svc.DeleteUserFromDomains(ctx, id) +} + +func (am *authorizationMiddleware) authorize(ctx context.Context, op svcutil.Operation, authReq authz.PolicyReq) error { + perm, err := am.opp.GetPermission(op) + if err != nil { + return err + } + authReq.Permission = perm.String() + + if err := am.authz.Authorize(ctx, authReq); err != nil { + return err + } + + return nil +} diff --git a/domains/middleware/logging.go b/domains/middleware/logging.go new file mode 100644 index 0000000000..bf114d812b --- /dev/null +++ b/domains/middleware/logging.go @@ -0,0 +1,180 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +//go:build !test + +package middleware + +import ( + "context" + "log/slog" + "time" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/pkg/authn" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" +) + +var _ domains.Service = (*loggingMiddleware)(nil) + +type loggingMiddleware struct { + logger *slog.Logger + svc domains.Service + rmMW.RoleManagerLoggingMiddleware +} + +// LoggingMiddleware adds logging facilities to the core service. +func LoggingMiddleware(svc domains.Service, logger *slog.Logger) domains.Service { + rmlm := rmMW.NewRoleManagerLoggingMiddleware("domains", svc, logger) + return &loggingMiddleware{ + logger: logger, + svc: svc, + RoleManagerLoggingMiddleware: rmlm, + } +} + +func (lm *loggingMiddleware) CreateDomain(ctx context.Context, session authn.Session, d domains.Domain) (do domains.Domain, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", d.ID), + slog.String("name", d.Name), + ), + } + if err != nil { + args := append(args, slog.String("error", err.Error())) + lm.logger.Warn("Create domain failed", args...) + return + } + lm.logger.Info("Create domain completed successfully", args...) + }(time.Now()) + return lm.svc.CreateDomain(ctx, session, d) +} + +func (lm *loggingMiddleware) RetrieveDomain(ctx context.Context, session authn.Session, id string) (do domains.Domain, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", id), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Retrieve domain failed", args...) + return + } + lm.logger.Info("Retrieve domain completed successfully", args...) + }(time.Now()) + return lm.svc.RetrieveDomain(ctx, session, id) +} + +func (lm *loggingMiddleware) UpdateDomain(ctx context.Context, session authn.Session, id string, d domains.DomainReq) (do domains.Domain, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", id), + slog.Any("name", d.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Update domain failed", args...) + return + } + lm.logger.Info("Update domain completed successfully", args...) + }(time.Now()) + return lm.svc.UpdateDomain(ctx, session, id, d) +} + +func (lm *loggingMiddleware) EnableDomain(ctx context.Context, session authn.Session, id string) (do domains.Domain, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", id), + slog.String("name", do.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Enable domain failed", args...) + return + } + lm.logger.Info("Enable domain completed successfully", args...) + }(time.Now()) + return lm.svc.EnableDomain(ctx, session, id) +} + +func (lm *loggingMiddleware) DisableDomain(ctx context.Context, session authn.Session, id string) (do domains.Domain, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", id), + slog.String("name", do.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Disable domain failed", args...) + return + } + lm.logger.Info("Disable domain completed successfully", args...) + }(time.Now()) + return lm.svc.DisableDomain(ctx, session, id) +} + +func (lm *loggingMiddleware) FreezeDomain(ctx context.Context, session authn.Session, id string) (do domains.Domain, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", id), + slog.String("name", do.Name), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Freeze domain failed", args...) + return + } + lm.logger.Info("Freeze domain completed successfully", args...) + }(time.Now()) + return lm.svc.FreezeDomain(ctx, session, id) +} + +func (lm *loggingMiddleware) ListDomains(ctx context.Context, session authn.Session, page domains.Page) (do domains.DomainsPage, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("page", + slog.Uint64("limit", page.Limit), + slog.Uint64("offset", page.Offset), + slog.Uint64("total", page.Total), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("List domains failed", args...) + return + } + lm.logger.Info("List domains completed successfully", args...) + }(time.Now()) + return lm.svc.ListDomains(ctx, session, page) +} +func (lm *loggingMiddleware) DeleteUserFromDomains(ctx context.Context, id string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("id", id), + } + if err != nil { + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Delete entity policies failed to complete successfully", args...) + return + } + lm.logger.Info("Delete entity policies completed successfully", args...) + }(time.Now()) + return lm.svc.DeleteUserFromDomains(ctx, id) +} diff --git a/domains/middleware/metrics.go b/domains/middleware/metrics.go new file mode 100644 index 0000000000..e7378190bc --- /dev/null +++ b/domains/middleware/metrics.go @@ -0,0 +1,101 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +//go:build !test + +package middleware + +import ( + "context" + "time" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/pkg/authn" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" + "github.com/go-kit/kit/metrics" +) + +var _ domains.Service = (*metricsMiddleware)(nil) + +type metricsMiddleware struct { + counter metrics.Counter + latency metrics.Histogram + svc domains.Service + rmMW.RoleManagerMetricsMiddleware +} + +// MetricsMiddleware instruments core service by tracking request count and latency. +func MetricsMiddleware(svc domains.Service, counter metrics.Counter, latency metrics.Histogram) domains.Service { + rmmw := rmMW.NewRoleManagerMetricsMiddleware("domains", svc, counter, latency) + + return &metricsMiddleware{ + counter: counter, + latency: latency, + svc: svc, + RoleManagerMetricsMiddleware: rmmw, + } +} + +func (ms *metricsMiddleware) CreateDomain(ctx context.Context, session authn.Session, d domains.Domain) (domains.Domain, error) { + defer func(begin time.Time) { + ms.counter.With("method", "create_domain").Add(1) + ms.latency.With("method", "create_domain").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.CreateDomain(ctx, session, d) +} + +func (ms *metricsMiddleware) RetrieveDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + defer func(begin time.Time) { + ms.counter.With("method", "retrieve_domain").Add(1) + ms.latency.With("method", "retrieve_domain").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.RetrieveDomain(ctx, session, id) +} + +func (ms *metricsMiddleware) UpdateDomain(ctx context.Context, session authn.Session, id string, d domains.DomainReq) (domains.Domain, error) { + defer func(begin time.Time) { + ms.counter.With("method", "update_domain").Add(1) + ms.latency.With("method", "update_domain").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.UpdateDomain(ctx, session, id, d) +} + +func (ms *metricsMiddleware) EnableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + defer func(begin time.Time) { + ms.counter.With("method", "enable_domain").Add(1) + ms.latency.With("method", "enable_domain").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.EnableDomain(ctx, session, id) +} + +func (ms *metricsMiddleware) DisableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + defer func(begin time.Time) { + ms.counter.With("method", "disable_domain").Add(1) + ms.latency.With("method", "disable_domain").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.DisableDomain(ctx, session, id) +} + +func (ms *metricsMiddleware) FreezeDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + defer func(begin time.Time) { + ms.counter.With("method", "freeze_domain").Add(1) + ms.latency.With("method", "freeze_domain").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.FreezeDomain(ctx, session, id) +} + +func (ms *metricsMiddleware) ListDomains(ctx context.Context, session authn.Session, page domains.Page) (domains.DomainsPage, error) { + defer func(begin time.Time) { + ms.counter.With("method", "list_domains").Add(1) + ms.latency.With("method", "list_domains").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.ListDomains(ctx, session, page) +} + +func (ms *metricsMiddleware) DeleteUserFromDomains(ctx context.Context, id string) error { + defer func(begin time.Time) { + ms.counter.With("method", "delete_user_from_domains").Add(1) + ms.latency.With("method", "delete_user_from_domains").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.DeleteUserFromDomains(ctx, id) +} diff --git a/auth/mocks/domains_client.go b/domains/mocks/domains_client.go similarity index 71% rename from auth/mocks/domains_client.go rename to domains/mocks/domains_client.go index 7950316f88..e664e6b384 100644 --- a/auth/mocks/domains_client.go +++ b/domains/mocks/domains_client.go @@ -11,9 +11,9 @@ import ( grpc "google.golang.org/grpc" - magistrala "github.com/absmach/magistrala" - mock "github.com/stretchr/testify/mock" + + v1 "github.com/absmach/magistrala/internal/grpc/domains/v1" ) // DomainsServiceClient is an autogenerated mock type for the DomainsServiceClient type @@ -30,7 +30,7 @@ func (_m *DomainsServiceClient) EXPECT() *DomainsServiceClient_Expecter { } // DeleteUserFromDomains provides a mock function with given fields: ctx, in, opts -func (_m *DomainsServiceClient) DeleteUserFromDomains(ctx context.Context, in *magistrala.DeleteUserReq, opts ...grpc.CallOption) (*magistrala.DeleteUserRes, error) { +func (_m *DomainsServiceClient) DeleteUserFromDomains(ctx context.Context, in *v1.DeleteUserReq, opts ...grpc.CallOption) (*v1.DeleteUserRes, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -44,20 +44,20 @@ func (_m *DomainsServiceClient) DeleteUserFromDomains(ctx context.Context, in *m panic("no return value specified for DeleteUserFromDomains") } - var r0 *magistrala.DeleteUserRes + var r0 *v1.DeleteUserRes var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.DeleteUserReq, ...grpc.CallOption) (*magistrala.DeleteUserRes, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *v1.DeleteUserReq, ...grpc.CallOption) (*v1.DeleteUserRes, error)); ok { return rf(ctx, in, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.DeleteUserReq, ...grpc.CallOption) *magistrala.DeleteUserRes); ok { + if rf, ok := ret.Get(0).(func(context.Context, *v1.DeleteUserReq, ...grpc.CallOption) *v1.DeleteUserRes); ok { r0 = rf(ctx, in, opts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.DeleteUserRes) + r0 = ret.Get(0).(*v1.DeleteUserRes) } } - if rf, ok := ret.Get(1).(func(context.Context, *magistrala.DeleteUserReq, ...grpc.CallOption) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *v1.DeleteUserReq, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { r1 = ret.Error(1) @@ -73,14 +73,14 @@ type DomainsServiceClient_DeleteUserFromDomains_Call struct { // DeleteUserFromDomains is a helper method to define mock.On call // - ctx context.Context -// - in *magistrala.DeleteUserReq +// - in *v1.DeleteUserReq // - opts ...grpc.CallOption func (_e *DomainsServiceClient_Expecter) DeleteUserFromDomains(ctx interface{}, in interface{}, opts ...interface{}) *DomainsServiceClient_DeleteUserFromDomains_Call { return &DomainsServiceClient_DeleteUserFromDomains_Call{Call: _e.mock.On("DeleteUserFromDomains", append([]interface{}{ctx, in}, opts...)...)} } -func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) Run(run func(ctx context.Context, in *magistrala.DeleteUserReq, opts ...grpc.CallOption)) *DomainsServiceClient_DeleteUserFromDomains_Call { +func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) Run(run func(ctx context.Context, in *v1.DeleteUserReq, opts ...grpc.CallOption)) *DomainsServiceClient_DeleteUserFromDomains_Call { _c.Call.Run(func(args mock.Arguments) { variadicArgs := make([]grpc.CallOption, len(args)-2) for i, a := range args[2:] { @@ -88,17 +88,17 @@ func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) Run(run func(ctx cont variadicArgs[i] = a.(grpc.CallOption) } } - run(args[0].(context.Context), args[1].(*magistrala.DeleteUserReq), variadicArgs...) + run(args[0].(context.Context), args[1].(*v1.DeleteUserReq), variadicArgs...) }) return _c } -func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) Return(_a0 *magistrala.DeleteUserRes, _a1 error) *DomainsServiceClient_DeleteUserFromDomains_Call { +func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) Return(_a0 *v1.DeleteUserRes, _a1 error) *DomainsServiceClient_DeleteUserFromDomains_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) RunAndReturn(run func(context.Context, *magistrala.DeleteUserReq, ...grpc.CallOption) (*magistrala.DeleteUserRes, error)) *DomainsServiceClient_DeleteUserFromDomains_Call { +func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) RunAndReturn(run func(context.Context, *v1.DeleteUserReq, ...grpc.CallOption) (*v1.DeleteUserRes, error)) *DomainsServiceClient_DeleteUserFromDomains_Call { _c.Call.Return(run) return _c } diff --git a/domains/mocks/repository.go b/domains/mocks/repository.go new file mode 100644 index 0000000000..d0d9ec4b47 --- /dev/null +++ b/domains/mocks/repository.go @@ -0,0 +1,654 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + domains "github.com/absmach/magistrala/domains" + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Repository is an autogenerated mock type for the Repository type +type Repository struct { + mock.Mock +} + +// AddRoles provides a mock function with given fields: ctx, rps +func (_m *Repository) AddRoles(ctx context.Context, rps []roles.RoleProvision) ([]roles.Role, error) { + ret := _m.Called(ctx, rps) + + if len(ret) == 0 { + panic("no return value specified for AddRoles") + } + + var r0 []roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) ([]roles.Role, error)); ok { + return rf(ctx, rps) + } + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) []roles.Role); ok { + r0 = rf(ctx, rps) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.Role) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []roles.RoleProvision) error); ok { + r1 = rf(ctx, rps) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, id +func (_m *Repository) Delete(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ListDomains provides a mock function with given fields: ctx, pm +func (_m *Repository) ListDomains(ctx context.Context, pm domains.Page) (domains.DomainsPage, error) { + ret := _m.Called(ctx, pm) + + if len(ret) == 0 { + panic("no return value specified for ListDomains") + } + + var r0 domains.DomainsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domains.Page) (domains.DomainsPage, error)); ok { + return rf(ctx, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, domains.Page) domains.DomainsPage); ok { + r0 = rf(ctx, pm) + } else { + r0 = ret.Get(0).(domains.DomainsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, domains.Page) error); ok { + r1 = rf(ctx, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, members +func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, members string) error { + ret := _m.Called(ctx, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRoles provides a mock function with given fields: ctx, roleIDs +func (_m *Repository) RemoveRoles(ctx context.Context, roleIDs []string) error { + ret := _m.Called(ctx, roleIDs) + + if len(ret) == 0 { + panic("no return value specified for RemoveRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { + r0 = rf(ctx, roleIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAllByIDs provides a mock function with given fields: ctx, pm +func (_m *Repository) RetrieveAllByIDs(ctx context.Context, pm domains.Page) (domains.DomainsPage, error) { + ret := _m.Called(ctx, pm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllByIDs") + } + + var r0 domains.DomainsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domains.Page) (domains.DomainsPage, error)); ok { + return rf(ctx, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, domains.Page) domains.DomainsPage); ok { + r0 = rf(ctx, pm) + } else { + r0 = ret.Get(0).(domains.DomainsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, domains.Page) error); ok { + r1 = rf(ctx, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, entityID, limit, offset +func (_m *Repository) RetrieveAllRoles(ctx context.Context, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByID provides a mock function with given fields: ctx, id +func (_m *Repository) RetrieveByID(ctx context.Context, id string) (domains.Domain, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByID") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (domains.Domain, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) domains.Domain); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveEntitiesRolesActionsMembers provides a mock function with given fields: ctx, entityIDs +func (_m *Repository) RetrieveEntitiesRolesActionsMembers(ctx context.Context, entityIDs []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error) { + ret := _m.Called(ctx, entityIDs) + + if len(ret) == 0 { + panic("no return value specified for RetrieveEntitiesRolesActionsMembers") + } + + var r0 []roles.EntityActionRole + var r1 []roles.EntityMemberRole + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error)); ok { + return rf(ctx, entityIDs) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []roles.EntityActionRole); ok { + r0 = rf(ctx, entityIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.EntityActionRole) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) []roles.EntityMemberRole); ok { + r1 = rf(ctx, entityIDs) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]roles.EntityMemberRole) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, []string) error); ok { + r2 = rf(ctx, entityIDs) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// RetrieveRole provides a mock function with given fields: ctx, roleID +func (_m *Repository) RetrieveRole(ctx context.Context, roleID string) (roles.Role, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (roles.Role, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) roles.Role); ok { + r0 = rf(ctx, roleID) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRoleByEntityIDAndName provides a mock function with given fields: ctx, entityID, roleName +func (_m *Repository) RetrieveRoleByEntityIDAndName(ctx context.Context, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRoleByEntityIDAndName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (roles.Role, error)); ok { + return rf(ctx, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) roles.Role); ok { + r0 = rf(ctx, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleAddActions(ctx context.Context, role roles.Role, actions []string) ([]string, error) { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleAddMembers(ctx context.Context, role roles.Role, members []string) ([]string, error) { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, members) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, roleID, actions +func (_m *Repository) RoleCheckActionsExists(ctx context.Context, roleID string, actions []string) (bool, error) { + ret := _m.Called(ctx, roleID, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, roleID, members +func (_m *Repository) RoleCheckMembersExists(ctx context.Context, roleID string, members []string) (bool, error) { + ret := _m.Called(ctx, roleID, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, members) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, roleID +func (_m *Repository) RoleListActions(ctx context.Context, roleID string) ([]string, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []string); ok { + r0 = rf(ctx, roleID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, roleID, limit, offset +func (_m *Repository) RoleListMembers(ctx context.Context, roleID string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, roleID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, roleID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, roleID, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, roleID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleRemoveActions(ctx context.Context, role roles.Role, actions []string) error { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllActions(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllMembers(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleRemoveMembers(ctx context.Context, role roles.Role, members []string) error { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Save provides a mock function with given fields: ctx, d +func (_m *Repository) Save(ctx context.Context, d domains.Domain) (domains.Domain, error) { + ret := _m.Called(ctx, d) + + if len(ret) == 0 { + panic("no return value specified for Save") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domains.Domain) (domains.Domain, error)); ok { + return rf(ctx, d) + } + if rf, ok := ret.Get(0).(func(context.Context, domains.Domain) domains.Domain); ok { + r0 = rf(ctx, d) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, domains.Domain) error); ok { + r1 = rf(ctx, d) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: ctx, id, userID, d +func (_m *Repository) Update(ctx context.Context, id string, userID string, d domains.DomainReq) (domains.Domain, error) { + ret := _m.Called(ctx, id, userID, d) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, domains.DomainReq) (domains.Domain, error)); ok { + return rf(ctx, id, userID, d) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, domains.DomainReq) domains.Domain); ok { + r0 = rf(ctx, id, userID, d) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, domains.DomainReq) error); ok { + r1 = rf(ctx, id, userID, d) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateRole provides a mock function with given fields: ctx, ro +func (_m *Repository) UpdateRole(ctx context.Context, ro roles.Role) (roles.Role, error) { + ret := _m.Called(ctx, ro) + + if len(ret) == 0 { + panic("no return value specified for UpdateRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) (roles.Role, error)); ok { + return rf(ctx, ro) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) roles.Role); ok { + r0 = rf(ctx, ro) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role) error); ok { + r1 = rf(ctx, ro) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *Repository { + mock := &Repository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/domains/mocks/service.go b/domains/mocks/service.go new file mode 100644 index 0000000000..76d98e7c99 --- /dev/null +++ b/domains/mocks/service.go @@ -0,0 +1,674 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + authn "github.com/absmach/magistrala/pkg/authn" + + domains "github.com/absmach/magistrala/domains" + + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// AddRole provides a mock function with given fields: ctx, session, entityID, roleName, optionalActions, optionalMembers +func (_m *Service) AddRole(ctx context.Context, session authn.Session, entityID string, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName, optionalActions, optionalMembers) + + if len(ret) == 0 { + panic("no return value specified for AddRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateDomain provides a mock function with given fields: ctx, sesssion, d +func (_m *Service) CreateDomain(ctx context.Context, sesssion authn.Session, d domains.Domain) (domains.Domain, error) { + ret := _m.Called(ctx, sesssion, d) + + if len(ret) == 0 { + panic("no return value specified for CreateDomain") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, domains.Domain) (domains.Domain, error)); ok { + return rf(ctx, sesssion, d) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, domains.Domain) domains.Domain); ok { + r0 = rf(ctx, sesssion, d) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, domains.Domain) error); ok { + r1 = rf(ctx, sesssion, d) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteUserFromDomains provides a mock function with given fields: ctx, id +func (_m *Service) DeleteUserFromDomains(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for DeleteUserFromDomains") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DisableDomain provides a mock function with given fields: ctx, sesssion, id +func (_m *Service) DisableDomain(ctx context.Context, sesssion authn.Session, id string) (domains.Domain, error) { + ret := _m.Called(ctx, sesssion, id) + + if len(ret) == 0 { + panic("no return value specified for DisableDomain") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (domains.Domain, error)); ok { + return rf(ctx, sesssion, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) domains.Domain); ok { + r0 = rf(ctx, sesssion, id) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, sesssion, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EnableDomain provides a mock function with given fields: ctx, sesssion, id +func (_m *Service) EnableDomain(ctx context.Context, sesssion authn.Session, id string) (domains.Domain, error) { + ret := _m.Called(ctx, sesssion, id) + + if len(ret) == 0 { + panic("no return value specified for EnableDomain") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (domains.Domain, error)); ok { + return rf(ctx, sesssion, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) domains.Domain); ok { + r0 = rf(ctx, sesssion, id) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, sesssion, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FreezeDomain provides a mock function with given fields: ctx, sesssion, id +func (_m *Service) FreezeDomain(ctx context.Context, sesssion authn.Session, id string) (domains.Domain, error) { + ret := _m.Called(ctx, sesssion, id) + + if len(ret) == 0 { + panic("no return value specified for FreezeDomain") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (domains.Domain, error)); ok { + return rf(ctx, sesssion, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) domains.Domain); ok { + r0 = rf(ctx, sesssion, id) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, sesssion, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAvailableActions provides a mock function with given fields: ctx, session +func (_m *Service) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + ret := _m.Called(ctx, session) + + if len(ret) == 0 { + panic("no return value specified for ListAvailableActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) ([]string, error)); ok { + return rf(ctx, session) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) []string); ok { + r0 = rf(ctx, session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session) error); ok { + r1 = rf(ctx, session) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListDomains provides a mock function with given fields: ctx, sesssion, page +func (_m *Service) ListDomains(ctx context.Context, sesssion authn.Session, page domains.Page) (domains.DomainsPage, error) { + ret := _m.Called(ctx, sesssion, page) + + if len(ret) == 0 { + panic("no return value specified for ListDomains") + } + + var r0 domains.DomainsPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, domains.Page) (domains.DomainsPage, error)); ok { + return rf(ctx, sesssion, page) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, domains.Page) domains.DomainsPage); ok { + r0 = rf(ctx, sesssion, page) + } else { + r0 = ret.Get(0).(domains.DomainsPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, domains.Page) error); ok { + r1 = rf(ctx, sesssion, page) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID +func (_m *Service) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { + ret := _m.Called(ctx, session, memberID) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, memberID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RemoveRole(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RemoveRole") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, session, entityID, limit, offset +func (_m *Service) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, session, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, session, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, session, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveDomain provides a mock function with given fields: ctx, sesssion, id +func (_m *Service) RetrieveDomain(ctx context.Context, sesssion authn.Session, id string) (domains.Domain, error) { + ret := _m.Called(ctx, sesssion, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveDomain") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (domains.Domain, error)); ok { + return rf(ctx, sesssion, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) domains.Domain); ok { + r0 = rf(ctx, sesssion, id) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, sesssion, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RetrieveRole(ctx context.Context, session authn.Session, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleAddActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleAddMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleListActions(ctx context.Context, session authn.Session, entityID string, roleName string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) []string); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, session, entityID, roleName, limit, offset +func (_m *Service) RoleListMembers(ctx context.Context, session authn.Session, entityID string, roleName string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, session, entityID, roleName, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, session, entityID, roleName, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleRemoveActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) error { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) error { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateDomain provides a mock function with given fields: ctx, sesssion, id, d +func (_m *Service) UpdateDomain(ctx context.Context, sesssion authn.Session, id string, d domains.DomainReq) (domains.Domain, error) { + ret := _m.Called(ctx, sesssion, id, d) + + if len(ret) == 0 { + panic("no return value specified for UpdateDomain") + } + + var r0 domains.Domain + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, domains.DomainReq) (domains.Domain, error)); ok { + return rf(ctx, sesssion, id, d) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, domains.DomainReq) domains.Domain); ok { + r0 = rf(ctx, sesssion, id, d) + } else { + r0 = ret.Get(0).(domains.Domain) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, domains.DomainReq) error); ok { + r1 = rf(ctx, sesssion, id, d) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateRoleName provides a mock function with given fields: ctx, session, entityID, oldRoleName, newRoleName +func (_m *Service) UpdateRoleName(ctx context.Context, session authn.Session, entityID string, oldRoleName string, newRoleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, oldRoleName, newRoleName) + + if len(ret) == 0 { + panic("no return value specified for UpdateRoleName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, oldRoleName, newRoleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, string) error); ok { + r1 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/domains/postgres/doc.go b/domains/postgres/doc.go new file mode 100644 index 0000000000..ac5c81ae14 --- /dev/null +++ b/domains/postgres/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package postgres contains Key repository implementations using +// PostgreSQL as the underlying database. +package postgres diff --git a/auth/postgres/domains.go b/domains/postgres/domains.go similarity index 55% rename from auth/postgres/domains.go rename to domains/postgres/domains.go index 40ef9682e9..6c129f1fa9 100644 --- a/auth/postgres/domains.go +++ b/domains/postgres/domains.go @@ -11,61 +11,70 @@ import ( "strings" "time" - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/domains" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" "github.com/absmach/magistrala/pkg/postgres" + rolesPostgres "github.com/absmach/magistrala/pkg/roles/repo/postgres" "github.com/jackc/pgtype" "github.com/jmoiron/sqlx" ) -var _ auth.DomainsRepository = (*domainRepo)(nil) +var _ domains.Repository = (*domainRepo)(nil) + +const ( + rolesTableNamePrefix = "domains" + entityTableName = "domains" + entityIDColumnName = "id" +) type domainRepo struct { db postgres.Database + rolesPostgres.Repository } -// NewDomainRepository instantiates a PostgreSQL +// New instantiates a PostgreSQL // implementation of Domain repository. -func NewDomainRepository(db postgres.Database) auth.DomainsRepository { +func New(db postgres.Database) domains.Repository { + rmsvcRepo := rolesPostgres.NewRepository(db, rolesTableNamePrefix, entityTableName, entityIDColumnName) return &domainRepo{ - db: db, + db: db, + Repository: rmsvcRepo, } } -func (repo domainRepo) Save(ctx context.Context, d auth.Domain) (ad auth.Domain, err error) { +func (repo domainRepo) Save(ctx context.Context, d domains.Domain) (dd domains.Domain, err error) { q := `INSERT INTO domains (id, name, tags, alias, metadata, created_at, updated_at, updated_by, created_by, status) VALUES (:id, :name, :tags, :alias, :metadata, :created_at, :updated_at, :updated_by, :created_by, :status) RETURNING id, name, tags, alias, metadata, created_at, updated_at, updated_by, created_by, status;` dbd, err := toDBDomain(d) if err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrCreateEntity, errors.ErrRollbackTx) + return domains.Domain{}, errors.Wrap(repoerr.ErrCreateEntity, errors.ErrRollbackTx) } row, err := repo.db.NamedQueryContext(ctx, q, dbd) if err != nil { - return auth.Domain{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + return domains.Domain{}, postgres.HandleError(repoerr.ErrCreateEntity, err) } defer row.Close() row.Next() dbd = dbDomain{} if err := row.StructScan(&dbd); err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + return domains.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } domain, err := toDomain(dbd) if err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + return domains.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } return domain, nil } // RetrieveByID retrieves Domain by its unique ID. -func (repo domainRepo) RetrieveByID(ctx context.Context, id string) (auth.Domain, error) { +func (repo domainRepo) RetrieveByID(ctx context.Context, id string) (domains.Domain, error) { q := `SELECT d.id as id, d.name as name, d.tags as tags, d.alias as alias, d.metadata as metadata, d.created_at as created_at, d.updated_at as updated_at, d.updated_by as updated_by, d.created_by as created_by, d.status as status FROM domains d WHERE d.id = :id` @@ -75,64 +84,35 @@ func (repo domainRepo) RetrieveByID(ctx context.Context, id string) (auth.Domain rows, err := repo.db.NamedQueryContext(ctx, q, dbdp) if err != nil { - return auth.Domain{}, postgres.HandleError(repoerr.ErrViewEntity, err) + return domains.Domain{}, postgres.HandleError(repoerr.ErrViewEntity, err) } defer rows.Close() dbd := dbDomain{} if rows.Next() { if err = rows.StructScan(&dbd); err != nil { - return auth.Domain{}, postgres.HandleError(repoerr.ErrViewEntity, err) + return domains.Domain{}, postgres.HandleError(repoerr.ErrViewEntity, err) } domain, err := toDomain(dbd) if err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + return domains.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } return domain, nil } - return auth.Domain{}, repoerr.ErrNotFound -} - -func (repo domainRepo) RetrievePermissions(ctx context.Context, subject, id string) ([]string, error) { - q := `SELECT pc.relation as relation - FROM domains as d - JOIN policies pc - ON pc.object_id = d.id - WHERE d.id = $1 - AND pc.subject_id = $2 - ` - - rows, err := repo.db.QueryxContext(ctx, q, id, subject) - if err != nil { - return []string{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - domains, err := repo.processRows(rows) - if err != nil { - return []string{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - permissions := []string{} - for _, domain := range domains { - if domain.Permission != "" { - permissions = append(permissions, domain.Permission) - } - } - return permissions, nil + return domains.Domain{}, repoerr.ErrNotFound } // RetrieveAllByIDs retrieves for given Domain IDs . -func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth.DomainsPage, error) { +func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm domains.Page) (domains.DomainsPage, error) { var q string if len(pm.IDs) == 0 { - return auth.DomainsPage{}, nil + return domains.DomainsPage{}, nil } query, err := buildPageQuery(pm) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } q = `SELECT d.id as id, d.name as name, d.tags as tags, d.alias as alias, d.metadata as metadata, d.created_at as created_at, d.updated_at as updated_at, d.updated_by as updated_by, d.created_by as created_by, d.status as status @@ -141,18 +121,18 @@ func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth dbPage, err := toDBClientsPage(pm) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() - domains, err := repo.processRows(rows) + doms, err := repo.processRows(rows) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM domains d" @@ -162,60 +142,64 @@ func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - return auth.DomainsPage{ + return domains.DomainsPage{ Total: total, Offset: pm.Offset, Limit: pm.Limit, - Domains: domains, + Domains: doms, }, nil } // ListDomains list domains of user. -func (repo domainRepo) ListDomains(ctx context.Context, pm auth.Page) (auth.DomainsPage, error) { - var q string +func (repo domainRepo) ListDomains(ctx context.Context, pm domains.Page) (domains.DomainsPage, error) { query, err := buildPageQuery(pm) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } - q = `SELECT d.id as id, d.name as name, d.tags as tags, d.alias as alias, d.metadata as metadata, d.created_at as created_at, d.updated_at as updated_at, d.updated_by as updated_by, d.created_by as created_by, d.status as status, pc.relation as relation + q := `SELECT d.id as id, d.name as name, d.tags as tags, d.alias as alias, d.metadata as metadata, d.created_at as created_at, d.updated_at as updated_at, d.updated_by as updated_by, d.created_by as created_by, d.status as status FROM domains as d - JOIN policies pc - ON pc.object_id = d.id` + JOIN domains_roles dr + ON dr.entity_id = d.id + JOIN domains_role_members drm + ON drm.role_id = dr.id + ` - // The service sends the user ID in the pagemeta subject field, which filters domains by joining with the policies table. - // For SuperAdmins, access to domains is granted without the policies filter. - // If the user making the request is a super admin, the service will assign an empty value to the pagemeta subject field. - // In the repository, when the pagemeta subject is empty, the query should be constructed without applying the policies filter. if pm.SubjectID == "" { q = `SELECT d.id as id, d.name as name, d.tags as tags, d.alias as alias, d.metadata as metadata, d.created_at as created_at, d.updated_at as updated_at, d.updated_by as updated_by, d.created_by as created_by, d.status as status FROM domains as d` } - q = fmt.Sprintf("%s %s LIMIT %d OFFSET %d", q, query, pm.Limit, pm.Offset) + q = fmt.Sprintf("%s %s LIMIT :limit OFFSET :offset", q, query) dbPage, err := toDBClientsPage(pm) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() - domains, err := repo.processRows(rows) + doms, err := repo.processRows(rows) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - cq := "SELECT COUNT(*) FROM domains d JOIN policies pc ON pc.object_id = d.id" + cq := `SELECT COUNT(*) + FROM domains as d + JOIN domains_roles dr + ON dr.entity_id = d.id + JOIN domains_role_members drm + ON drm.role_id = dr.id + ` if pm.SubjectID == "" { - cq = "SELECT COUNT(*) FROM domains d" + cq = `SELECT COUNT(*) + FROM domains as d` } if query != "" { cq = fmt.Sprintf(" %s %s", cq, query) @@ -223,23 +207,23 @@ func (repo domainRepo) ListDomains(ctx context.Context, pm auth.Page) (auth.Doma total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + return domains.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - return auth.DomainsPage{ + return domains.DomainsPage{ Total: total, Offset: pm.Offset, Limit: pm.Limit, - Domains: domains, + Domains: doms, }, nil } // Update updates the client name and metadata. -func (repo domainRepo) Update(ctx context.Context, id, userID string, dr auth.DomainReq) (auth.Domain, error) { +func (repo domainRepo) Update(ctx context.Context, id, userID string, dr domains.DomainReq) (domains.Domain, error) { var query []string var upq string var ws string = "AND status = :status" - d := auth.Domain{ID: id} + d := domains.Domain{ID: id} if dr.Name != nil && *dr.Name != "" { query = append(query, "name = :name, ") d.Name = *dr.Name @@ -273,23 +257,23 @@ func (repo domainRepo) Update(ctx context.Context, id, userID string, dr auth.Do dbd, err := toDBDomain(d) if err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + return domains.Domain{}, errors.Wrap(repoerr.ErrUpdateEntity, err) } row, err := repo.db.NamedQueryContext(ctx, q, dbd) if err != nil { - return auth.Domain{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) + return domains.Domain{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) } // defer row.Close() row.Next() dbd = dbDomain{} if err := row.StructScan(&dbd); err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + return domains.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } domain, err := toDomain(dbd) if err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) + return domains.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) } return domain, nil @@ -310,97 +294,8 @@ func (repo domainRepo) Delete(ctx context.Context, id string) error { return nil } -// SavePolicies save policies in domains database. -func (repo domainRepo) SavePolicies(ctx context.Context, pcs ...auth.Policy) error { - q := `INSERT INTO policies (subject_type, subject_id, subject_relation, relation, object_type, object_id) - VALUES (:subject_type, :subject_id, :subject_relation, :relation, :object_type, :object_id) - RETURNING subject_type, subject_id, subject_relation, relation, object_type, object_id;` - - dbpc := toDBPolicies(pcs...) - row, err := repo.db.NamedQueryContext(ctx, q, dbpc) - if err != nil { - return postgres.HandleError(repoerr.ErrCreateEntity, err) - } - defer row.Close() - - return nil -} - -// CheckPolicy check policy in domains database. -func (repo domainRepo) CheckPolicy(ctx context.Context, pc auth.Policy) error { - q := ` - SELECT - subject_type, subject_id, subject_relation, relation, object_type, object_id FROM policies - WHERE - subject_type = :subject_type - AND subject_id = :subject_id - AND subject_relation = :subject_relation - AND relation = :relation - AND object_type = :object_type - AND object_id = :object_id - LIMIT 1 - ` - dbpc := toDBPolicy(pc) - row, err := repo.db.NamedQueryContext(ctx, q, dbpc) - if err != nil { - return postgres.HandleError(repoerr.ErrCreateEntity, err) - } - defer row.Close() - row.Next() - if err := row.StructScan(&dbpc); err != nil { - return errors.Wrap(repoerr.ErrNotFound, err) - } - return nil -} - -// DeletePolicies delete policies from domains database. -func (repo domainRepo) DeletePolicies(ctx context.Context, pcs ...auth.Policy) (err error) { - tx, err := repo.db.BeginTxx(ctx, nil) - if err != nil { - return err - } - defer func() { - if err != nil { - if errRollback := tx.Rollback(); errRollback != nil { - err = errors.Wrap(apiutil.ErrRollbackTx, errRollback) - } - } - }() - - for _, pc := range pcs { - q := ` - DELETE FROM - policies - WHERE - subject_type = :subject_type - AND subject_id = :subject_id - AND subject_relation = :subject_relation - AND object_type = :object_type - AND object_id = :object_id - ;` - - dbpc := toDBPolicy(pc) - row, err := tx.NamedQuery(q, dbpc) - if err != nil { - return postgres.HandleError(repoerr.ErrRemoveEntity, err) - } - defer row.Close() - } - return tx.Commit() -} - -func (repo domainRepo) DeleteUserPolicies(ctx context.Context, id string) (err error) { - q := "DELETE FROM policies WHERE subject_id = $1;" - - if _, err := repo.db.ExecContext(ctx, q, id); err != nil { - return postgres.HandleError(repoerr.ErrRemoveEntity, err) - } - - return nil -} - -func (repo domainRepo) processRows(rows *sqlx.Rows) ([]auth.Domain, error) { - var items []auth.Domain +func (repo domainRepo) processRows(rows *sqlx.Rows) ([]domains.Domain, error) { + var items []domains.Domain for rows.Next() { dbd := dbDomain{} if err := rows.StructScan(&dbd); err != nil { @@ -421,7 +316,7 @@ type dbDomain struct { Metadata []byte `db:"metadata,omitempty"` Tags pgtype.TextArray `db:"tags,omitempty"` Alias *string `db:"alias,omitempty"` - Status auth.Status `db:"status"` + Status domains.Status `db:"status"` Permission string `db:"relation"` CreatedBy string `db:"created_by"` CreatedAt time.Time `db:"created_at"` @@ -429,7 +324,7 @@ type dbDomain struct { UpdatedAt sql.NullTime `db:"updated_at,omitempty"` } -func toDBDomain(d auth.Domain) (dbDomain, error) { +func toDBDomain(d domains.Domain) (dbDomain, error) { data := []byte("{}") if len(d.Metadata) > 0 { b, err := json.Marshal(d.Metadata) @@ -471,11 +366,11 @@ func toDBDomain(d auth.Domain) (dbDomain, error) { }, nil } -func toDomain(d dbDomain) (auth.Domain, error) { - var metadata auth.Metadata +func toDomain(d dbDomain) (domains.Domain, error) { + var metadata domains.Metadata if d.Metadata != nil { if err := json.Unmarshal([]byte(d.Metadata), &metadata); err != nil { - return auth.Domain{}, errors.Wrap(errors.ErrMalformedEntity, err) + return domains.Domain{}, errors.Wrap(errors.ErrMalformedEntity, err) } } var tags []string @@ -495,7 +390,7 @@ func toDomain(d dbDomain) (auth.Domain, error) { updatedAt = d.UpdatedAt.Time } - return auth.Domain{ + return domains.Domain{ ID: d.ID, Name: d.Name, Metadata: metadata, @@ -511,22 +406,22 @@ func toDomain(d dbDomain) (auth.Domain, error) { } type dbDomainsPage struct { - Total uint64 `db:"total"` - Limit uint64 `db:"limit"` - Offset uint64 `db:"offset"` - Order string `db:"order"` - Dir string `db:"dir"` - Name string `db:"name"` - Permission string `db:"permission"` - ID string `db:"id"` - IDs []string `db:"ids"` - Metadata []byte `db:"metadata"` - Tag string `db:"tag"` - Status auth.Status `db:"status"` - SubjectID string `db:"subject_id"` + Total uint64 `db:"total"` + Limit uint64 `db:"limit"` + Offset uint64 `db:"offset"` + Order string `db:"order"` + Dir string `db:"dir"` + Name string `db:"name"` + Permission string `db:"permission"` + ID string `db:"id"` + IDs []string `db:"ids"` + Metadata []byte `db:"metadata"` + Tag string `db:"tag"` + Status domains.Status `db:"status"` + SubjectID string `db:"subject_id"` } -func toDBClientsPage(pm auth.Page) (dbDomainsPage, error) { +func toDBClientsPage(pm domains.Page) (dbDomainsPage, error) { _, data, err := postgres.CreateMetadataQuery("", pm.Metadata) if err != nil { return dbDomainsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) @@ -548,7 +443,7 @@ func toDBClientsPage(pm auth.Page) (dbDomainsPage, error) { }, nil } -func buildPageQuery(pm auth.Page) (string, error) { +func buildPageQuery(pm domains.Page) (string, error) { var query []string var emq string @@ -560,10 +455,10 @@ func buildPageQuery(pm auth.Page) (string, error) { query = append(query, fmt.Sprintf("d.id IN ('%s')", strings.Join(pm.IDs, "','"))) } - if (pm.Status >= auth.EnabledStatus) && (pm.Status < auth.AllStatus) { + if (pm.Status >= domains.EnabledStatus) && (pm.Status < domains.AllStatus) { query = append(query, "d.status = :status") } else { - query = append(query, fmt.Sprintf("d.status < %d", auth.AllStatus)) + query = append(query, fmt.Sprintf("d.status < %d", domains.AllStatus)) } if pm.Name != "" { @@ -571,11 +466,11 @@ func buildPageQuery(pm auth.Page) (string, error) { } if pm.SubjectID != "" { - query = append(query, "pc.subject_id = :subject_id") + query = append(query, "drm.member_id = :subject_id") } if pm.Permission != "" && pm.SubjectID != "" { - query = append(query, "pc.relation = :permission") + query = append(query, "dr.name = :permission") } if pm.Tag != "" { @@ -606,7 +501,7 @@ type dbPolicy struct { ObjectID string `db:"object_id,omitempty"` } -func toDBPolicies(pcs ...auth.Policy) []dbPolicy { +func toDBPolicies(pcs ...domains.Policy) []dbPolicy { var dbpcs []dbPolicy for _, pc := range pcs { dbpcs = append(dbpcs, dbPolicy{ @@ -621,7 +516,7 @@ func toDBPolicies(pcs ...auth.Policy) []dbPolicy { return dbpcs } -func toDBPolicy(pc auth.Policy) dbPolicy { +func toDBPolicy(pc domains.Policy) dbPolicy { return dbPolicy{ SubjectType: pc.SubjectType, SubjectID: pc.SubjectID, diff --git a/domains/postgres/domains_test.go b/domains/postgres/domains_test.go new file mode 100644 index 0000000000..d383a988c8 --- /dev/null +++ b/domains/postgres/domains_test.go @@ -0,0 +1,611 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/domains/postgres" + "github.com/absmach/magistrala/internal/testsutil" + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + inValid = "invalid" +) + +var ( + domainID = testsutil.GenerateUUID(&testing.T{}) + userID = testsutil.GenerateUUID(&testing.T{}) +) + +func TestSave(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.New(database) + + cases := []struct { + desc string + domain domains.Domain + err error + }{ + { + desc: "add new domain with all fields successfully", + domain: domains.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + }, + err: nil, + }, + { + desc: "add the same domain again", + domain: domains.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + }, + err: repoerr.ErrConflict, + }, + { + desc: "add domain with empty ID", + domain: domains.Domain{ + ID: "", + Name: "test1", + Alias: "test1", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + }, + err: nil, + }, + { + desc: "add domain with empty alias", + domain: domains.Domain{ + ID: testsutil.GenerateUUID(&testing.T{}), + Name: "test1", + Alias: "", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + }, + err: repoerr.ErrCreateEntity, + }, + { + desc: "add domain with malformed metadata", + domain: domains.Domain{ + ID: domainID, + Name: "test1", + Alias: "test1", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "key": make(chan int), + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + }, + err: repoerr.ErrCreateEntity, + }, + } + + for _, tc := range cases { + _, err := repo.Save(context.Background(), tc.domain) + { + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } + } +} + +func TestRetrieveByID(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.New(database) + + domain := domains.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + } + + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) + + cases := []struct { + desc string + domainID string + response domains.Domain + err error + }{ + { + desc: "retrieve existing client", + domainID: domain.ID, + response: domain, + err: nil, + }, + { + desc: "retrieve non-existing client", + domainID: inValid, + response: domains.Domain{}, + err: repoerr.ErrNotFound, + }, + { + desc: "retrieve with empty client id", + domainID: "", + response: domains.Domain{}, + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + d, err := repo.RetrieveByID(context.Background(), tc.domainID) + assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) + } +} + +func TestRetrieveAllByIDs(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.New(database) + + items := []domains.Domain{} + for i := 0; i < 10; i++ { + domain := domains.Domain{ + ID: testsutil.GenerateUUID(t), + Name: fmt.Sprintf(`"test%d"`, i), + Alias: fmt.Sprintf(`"test%d"`, i), + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + } + if i%5 == 0 { + domain.Status = domains.DisabledStatus + domain.Tags = []string{"test", "admin"} + domain.Metadata = map[string]interface{}{ + "test1": "test1", + } + } + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("save domain unexpected error: %s", err)) + items = append(items, domain) + } + + cases := []struct { + desc string + pm domains.Page + response domains.DomainsPage + err error + }{ + { + desc: "retrieve by ids successfully", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + }, + response: domains.DomainsPage{ + Total: 2, + Offset: 0, + Limit: 10, + Domains: []domains.Domain{items[1], items[2]}, + }, + err: nil, + }, + { + desc: "retrieve by ids with empty ids", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{}, + }, + response: domains.DomainsPage{ + Total: 0, + Offset: 0, + Limit: 0, + }, + err: nil, + }, + { + desc: "retrieve by ids with invalid ids", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{inValid}, + }, + response: domains.DomainsPage{ + Total: 0, + Offset: 0, + Limit: 10, + }, + err: nil, + }, + { + desc: "retrieve by ids and status", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[0].ID, items[1].ID}, + Status: domains.DisabledStatus, + }, + response: domains.DomainsPage{ + Total: 1, + Offset: 0, + Limit: 10, + Domains: []domains.Domain{items[0]}, + }, + }, + { + desc: "retrieve by ids and status with invalid status", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[0].ID, items[1].ID}, + Status: 5, + }, + response: domains.DomainsPage{ + Total: 2, + Offset: 0, + Limit: 10, + Domains: []domains.Domain{items[0], items[1]}, + }, + }, + { + desc: "retrieve by ids and tags", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[0].ID, items[1].ID}, + Tag: "test", + }, + response: domains.DomainsPage{ + Total: 1, + Offset: 0, + Limit: 10, + Domains: []domains.Domain{items[1]}, + }, + }, + { + desc: " retrieve by ids and metadata", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + Metadata: map[string]interface{}{ + "test": "test", + }, + Status: domains.EnabledStatus, + }, + response: domains.DomainsPage{ + Total: 2, + Offset: 0, + Limit: 10, + Domains: items[1:3], + }, + }, + { + desc: "retrieve by ids and metadata with invalid metadata", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + Metadata: map[string]interface{}{ + "test1": "test1", + }, + Status: domains.EnabledStatus, + }, + response: domains.DomainsPage{ + Total: 0, + Offset: 0, + Limit: 10, + }, + }, + { + desc: "retrieve by ids and malfomed metadata", + pm: domains.Page{ + Offset: 0, + Limit: 10, + IDs: []string{items[1].ID, items[2].ID}, + Metadata: map[string]interface{}{ + "key": make(chan int), + }, + Status: domains.EnabledStatus, + }, + response: domains.DomainsPage{}, + err: repoerr.ErrViewEntity, + }, + { + desc: "retrieve all by ids and id", + pm: domains.Page{ + Offset: 0, + Limit: 10, + ID: items[1].ID, + IDs: []string{items[1].ID, items[2].ID}, + }, + response: domains.DomainsPage{ + Total: 1, + Offset: 0, + Limit: 10, + Domains: []domains.Domain{items[1]}, + }, + }, + { + desc: "retrieve all by ids and id with invalid id", + pm: domains.Page{ + Offset: 0, + Limit: 10, + ID: inValid, + IDs: []string{items[1].ID, items[2].ID}, + }, + response: domains.DomainsPage{ + Total: 0, + Offset: 0, + Limit: 10, + }, + }, + { + desc: "retrieve all by ids and name", + pm: domains.Page{ + Offset: 0, + Limit: 10, + Name: items[1].Name, + IDs: []string{items[1].ID, items[2].ID}, + }, + response: domains.DomainsPage{ + Total: 1, + Offset: 0, + Limit: 10, + Domains: []domains.Domain{items[1]}, + }, + }, + { + desc: "retrieve all by ids with empty page", + pm: domains.Page{}, + response: domains.DomainsPage{}, + }, + } + + for _, tc := range cases { + d, err := repo.RetrieveAllByIDs(context.Background(), tc.pm) + assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) + } +} + +func TestUpdate(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + updatedName := "test1" + updatedMetadata := domains.Metadata{ + "test1": "test1", + } + updatedTags := []string{"test1"} + updatedStatus := domains.DisabledStatus + updatedAlias := "test1" + + repo := postgres.New(database) + + domain := domains.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + } + + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) + + cases := []struct { + desc string + domainID string + d domains.DomainReq + response domains.Domain + err error + }{ + { + desc: "update existing domain name and metadata", + domainID: domain.ID, + d: domains.DomainReq{ + Name: &updatedName, + Metadata: &updatedMetadata, + }, + response: domains.Domain{ + ID: domainID, + Name: "test1", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test1": "test1", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + UpdatedAt: time.Now(), + }, + err: nil, + }, + { + desc: "update existing domain name, metadata, tags, status and alias", + domainID: domain.ID, + d: domains.DomainReq{ + Name: &updatedName, + Metadata: &updatedMetadata, + Tags: &updatedTags, + Status: &updatedStatus, + Alias: &updatedAlias, + }, + response: domains.Domain{ + ID: domainID, + Name: "test1", + Alias: "test1", + Tags: []string{"test1"}, + Metadata: map[string]interface{}{ + "test1": "test1", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.DisabledStatus, + UpdatedAt: time.Now(), + }, + err: nil, + }, + { + desc: "update non-existing domain", + domainID: inValid, + d: domains.DomainReq{ + Name: &updatedName, + Metadata: &updatedMetadata, + }, + response: domains.Domain{}, + err: repoerr.ErrFailedOpDB, + }, + { + desc: "update domain with empty ID", + domainID: "", + d: domains.DomainReq{ + Name: &updatedName, + Metadata: &updatedMetadata, + }, + response: domains.Domain{}, + err: repoerr.ErrFailedOpDB, + }, + { + desc: "update domain with malformed metadata", + domainID: domainID, + d: domains.DomainReq{ + Name: &updatedName, + Metadata: &domains.Metadata{"key": make(chan int)}, + }, + response: domains.Domain{}, + err: repoerr.ErrUpdateEntity, + }, + } + + for _, tc := range cases { + d, err := repo.Update(context.Background(), tc.domainID, userID, tc.d) + d.UpdatedAt = tc.response.UpdatedAt + assert.Equal(t, tc.response, d, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, d)) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestDelete(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM domains") + require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err)) + }) + + repo := postgres.New(database) + + domain := domains.Domain{ + ID: domainID, + Name: "test", + Alias: "test", + Tags: []string{"test"}, + Metadata: map[string]interface{}{ + "test": "test", + }, + CreatedBy: userID, + UpdatedBy: userID, + Status: domains.EnabledStatus, + } + + _, err := repo.Save(context.Background(), domain) + require.Nil(t, err, fmt.Sprintf("failed to save client %s", domain.ID)) + + cases := []struct { + desc string + domainID string + err error + }{ + { + desc: "delete existing domain", + domainID: domain.ID, + err: nil, + }, + { + desc: "delete non-existing domain", + domainID: inValid, + err: repoerr.ErrNotFound, + }, + { + desc: "delete domain with empty ID", + domainID: "", + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + err := repo.Delete(context.Background(), tc.domainID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} diff --git a/domains/postgres/init.go b/domains/postgres/init.go new file mode 100644 index 0000000000..0d0a8d22f6 --- /dev/null +++ b/domains/postgres/init.go @@ -0,0 +1,50 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "github.com/absmach/magistrala/pkg/errors" + + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + rolesPostgres "github.com/absmach/magistrala/pkg/roles/repo/postgres" + _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access + migrate "github.com/rubenv/sql-migrate" +) + +// Migration of Auth service. +func Migration() (*migrate.MemoryMigrationSource, error) { + rolesMigration, err := rolesPostgres.Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName) + if err != nil { + return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err) + } + + domainMigrations := &migrate.MemoryMigrationSource{ + Migrations: []*migrate.Migration{ + { + Id: "domain_1", + Up: []string{ + `CREATE TABLE IF NOT EXISTS domains ( + id VARCHAR(36) PRIMARY KEY, + name VARCHAR(254), + tags TEXT[], + metadata JSONB, + alias VARCHAR(254) NOT NULL UNIQUE, + created_at TIMESTAMP, + updated_at TIMESTAMP, + updated_by VARCHAR(254), + created_by VARCHAR(254), + status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0) + );`, + }, + Down: []string{ + `DROP TABLE IF EXISTS domains`, + }, + }, + }, + } + + domainMigrations.Migrations = append(domainMigrations.Migrations, rolesMigration.Migrations...) + + return domainMigrations, nil +} diff --git a/domains/postgres/setup_test.go b/domains/postgres/setup_test.go new file mode 100644 index 0000000000..89a6b21368 --- /dev/null +++ b/domains/postgres/setup_test.go @@ -0,0 +1,95 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package postgres_test contains tests for PostgreSQL repository +// implementations. +package postgres_test + +import ( + "database/sql" + "fmt" + "log" + "os" + "testing" + "time" + + apostgres "github.com/absmach/magistrala/auth/postgres" + "github.com/absmach/magistrala/pkg/postgres" + pgclient "github.com/absmach/magistrala/pkg/postgres" + "github.com/jmoiron/sqlx" + 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: "16.2-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") + + 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 := pgclient.Config{ + Host: "localhost", + Port: port, + User: "test", + Pass: "test", + Name: "test", + SSLMode: "disable", + SSLCert: "", + SSLKey: "", + SSLRootCert: "", + } + + if db, err = pgclient.Setup(dbConfig, *apostgres.Migration()); err != nil { + log.Fatalf("Could not setup test DB connection: %s", err) + } + + database = postgres.NewDatabase(db, dbConfig, 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/domains/roleactions.go b/domains/roleactions.go new file mode 100644 index 0000000000..c607a5b10a --- /dev/null +++ b/domains/roleactions.go @@ -0,0 +1,124 @@ +package domains + +import "github.com/absmach/magistrala/pkg/roles" + +const ( + // Domain Roles : Actions related to manage the domain. + Update roles.Action = "update" + Enable roles.Action = "enable" + Disable roles.Action = "disable" + Read roles.Action = "read" + Delete roles.Action = "delete" + Membership roles.Action = "membership" + ManageRole roles.Action = "manage_role" + AddRoleUsers roles.Action = "add_role_users" + RemoveRoleUsers roles.Action = "remove_role_users" + ViewRoleUsers roles.Action = "view_role_users" + + // Domain Roles : Actions related to entity creation and entity listing within domain + ClientCreate roles.Action = "client_create" + ChannelCreate roles.Action = "channel_create" + GroupCreate roles.Action = "group_create" + + // Domain Clients Roles: Actions related to clients present within the Domain + ClientUpdate roles.Action = "client_update" + ClientRead roles.Action = "client_read" + ClientDelete roles.Action = "client_delete" + ClientSetParentGroup roles.Action = "client_set_parent_group" + ClientConnectToChannel roles.Action = "client_connect_to_channel" + ClientManageRole roles.Action = "client_manage_role" + ClientAddRoleUsers roles.Action = "client_add_role_users" + ClientRemoveRoleUsers roles.Action = "client_remove_role_users" + ClientViewRoleUsers roles.Action = "client_view_role_users" + + // Domain Channels Roles: Actions related to channels present within the Domain + ChannelUpdate roles.Action = "channel_update" + ChannelRead roles.Action = "channel_read" + ChannelDelete roles.Action = "channel_delete" + ChannelSetParentGroup roles.Action = "channel_set_parent_group" + ChannelConnectToClient roles.Action = "channel_connect_to_client" + ChannelPublish roles.Action = "channel_publish" + ChannelSubscribe roles.Action = "channel_subscribe" + ChannelManageRole roles.Action = "channel_manage_role" + ChannelAddRoleUsers roles.Action = "channel_add_role_users" + ChannelRemoveRoleUsers roles.Action = "channel_remove_role_users" + ChannelViewRoleUsers roles.Action = "channel_view_role_users" + + // Domain Groups Roles: Actions related to Groups present within the Domain + GroupUpdate roles.Action = "group_update" + GroupMembership roles.Action = "group_membership" + GroupRead roles.Action = "group_read" + GroupDelete roles.Action = "group_delete" + GroupSetChild roles.Action = "group_set_child" + GroupSetParent roles.Action = "group_set_parent" + GroupManageRole roles.Action = "group_manage_role" + GroupAddRoleUsers roles.Action = "group_add_role_users" + GroupRemoveRoleUsers roles.Action = "group_remove_role_users" + GroupViewRoleUsers roles.Action = "group_view_role_users" +) + +const ( + BuiltInRoleAdmin = "admin" + BuiltInRoleMembership = "membership" +) + +func AvailableActions() []roles.Action { + return []roles.Action{ + Update, + Enable, + Disable, + Read, + Delete, + Membership, + ManageRole, + AddRoleUsers, + RemoveRoleUsers, + ViewRoleUsers, + ClientCreate, + ChannelCreate, + GroupCreate, + ClientUpdate, + ClientRead, + ClientDelete, + ClientSetParentGroup, + ClientConnectToChannel, + ClientManageRole, + ClientAddRoleUsers, + ClientRemoveRoleUsers, + ClientViewRoleUsers, + ChannelUpdate, + ChannelRead, + ChannelDelete, + ChannelSetParentGroup, + ChannelConnectToClient, + ChannelPublish, + ChannelSubscribe, + ChannelManageRole, + ChannelAddRoleUsers, + ChannelRemoveRoleUsers, + ChannelViewRoleUsers, + GroupUpdate, + GroupMembership, + GroupRead, + GroupDelete, + GroupSetChild, + GroupSetParent, + GroupManageRole, + GroupAddRoleUsers, + GroupRemoveRoleUsers, + GroupViewRoleUsers, + } +} + +func membershipRoleActions() []roles.Action { + return []roles.Action{ + Membership, + } +} + +func BuiltInRoles() map[roles.BuiltInRoleName][]roles.Action { + return map[roles.BuiltInRoleName][]roles.Action{ + BuiltInRoleAdmin: AvailableActions(), + BuiltInRoleMembership: membershipRoleActions(), + } +} diff --git a/domains/service.go b/domains/service.go new file mode 100644 index 0000000000..97956bb008 --- /dev/null +++ b/domains/service.go @@ -0,0 +1,195 @@ +package domains + +import ( + "context" + "time" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/roles" +) + +const defLimit = 100 + +var ( + errCreateDomainPolicy = errors.New("failed to create domain policy") + errRollbackRepo = errors.New("failed to rollback repo") + errRemovePolicyEngine = errors.New("failed to remove from policy engine") +) + +type service struct { + repo Repository + policy policies.Service + idProvider magistrala.IDProvider + roles.ProvisionManageService +} + +var _ Service = (*service)(nil) + +func New(repo Repository, policy policies.Service, idProvider magistrala.IDProvider, sidProvider magistrala.IDProvider) (Service, error) { + + rpms, err := roles.NewProvisionManageService(policies.DomainType, repo, policy, sidProvider, AvailableActions(), BuiltInRoles()) + if err != nil { + return nil, err + } + + return &service{ + repo: repo, + policy: policy, + idProvider: idProvider, + ProvisionManageService: rpms, + }, nil +} + +func (svc service) CreateDomain(ctx context.Context, session authn.Session, d Domain) (do Domain, err error) { + + d.CreatedBy = session.UserID + + domainID, err := svc.idProvider.ID() + if err != nil { + return Domain{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + d.ID = domainID + + if d.Status != DisabledStatus && d.Status != EnabledStatus { + return Domain{}, svcerr.ErrInvalidStatus + } + + d.CreatedAt = time.Now() + + // Domain is created in repo first, because Roles table have foreign key relation with Domain ID + dom, err := svc.repo.Save(ctx, d) + if err != nil { + return Domain{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + defer func() { + if err != nil { + if errRollBack := svc.repo.Delete(ctx, domainID); errRollBack != nil { + err = errors.Wrap(err, errors.Wrap(errRollbackRepo, errRollBack)) + } + } + }() + + newBuiltInRoleMembers := map[roles.BuiltInRoleName][]roles.Member{ + BuiltInRoleAdmin: {roles.Member(session.UserID)}, + BuiltInRoleMembership: {}, + } + + optionalPolicies := []policies.Policy{ + { + Subject: policies.MagistralaObject, + SubjectType: policies.PlatformType, + Relation: "organization", + Object: d.ID, + ObjectType: policies.DomainType, + }, + } + + if _, err := svc.AddNewEntitiesRoles(ctx, domainID, session.UserID, []string{domainID}, optionalPolicies, newBuiltInRoleMembers); err != nil { + return Domain{}, errors.Wrap(errCreateDomainPolicy, err) + } + + return dom, nil +} + +func (svc service) RetrieveDomain(ctx context.Context, session authn.Session, id string) (Domain, error) { + domain, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return Domain{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + return domain, nil +} + +func (svc service) UpdateDomain(ctx context.Context, session authn.Session, id string, d DomainReq) (Domain, error) { + dom, err := svc.repo.Update(ctx, id, session.UserID, d) + if err != nil { + return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return dom, nil +} + +func (svc service) EnableDomain(ctx context.Context, session authn.Session, id string) (Domain, error) { + status := EnabledStatus + dom, err := svc.repo.Update(ctx, id, session.UserID, DomainReq{Status: &status}) + if err != nil { + return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return dom, nil +} + +func (svc service) DisableDomain(ctx context.Context, session authn.Session, id string) (Domain, error) { + status := DisabledStatus + dom, err := svc.repo.Update(ctx, id, session.UserID, DomainReq{Status: &status}) + if err != nil { + return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return dom, nil +} + +// Only SuperAdmin can freeze the domain +func (svc service) FreezeDomain(ctx context.Context, session authn.Session, id string) (Domain, error) { + status := FreezeStatus + dom, err := svc.repo.Update(ctx, id, session.UserID, DomainReq{Status: &status}) + if err != nil { + return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return dom, nil +} + +func (svc service) ListDomains(ctx context.Context, session authn.Session, p Page) (DomainsPage, error) { + p.SubjectID = session.UserID + //ToDo : Check list without below function and confirm and decide to remove or not + if session.SuperAdmin { + p.SubjectID = "" + } + + dp, err := svc.repo.ListDomains(ctx, p) + if err != nil { + return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + return dp, nil +} + +func (svc service) DeleteUserFromDomains(ctx context.Context, id string) (err error) { + domainsPage, err := svc.repo.ListDomains(ctx, Page{SubjectID: id, Limit: defLimit}) + if err != nil { + return err + } + + if domainsPage.Total > defLimit { + for i := defLimit; i < int(domainsPage.Total); i += defLimit { + page := Page{SubjectID: id, Offset: uint64(i), Limit: defLimit} + dp, err := svc.repo.ListDomains(ctx, page) + if err != nil { + return err + } + domainsPage.Domains = append(domainsPage.Domains, dp.Domains...) + } + } + + // if err := svc.RemoveMembersFromAllRoles(ctx, authn.Session{}, []string{id}); err != nil { + // return err + // } + ////////////ToDo////////////// + // Remove user from all roles in all domains + ////////////////////////// + + // for _, domain := range domainsPage.Domains { + // req := policies.Policy{ + // Subject: policies.EncodeDomainUserID(domain.ID, id), + // SubjectType: policies.UserType, + // } + // if err := svc.policies.DeletePolicyFilter(ctx, req); err != nil { + // return err + // } + // } + + // if err := svc.repo.DeleteUserPolicies(ctx, id); err != nil { + // return err + // } + + return nil +} diff --git a/domains/service_test.go b/domains/service_test.go new file mode 100644 index 0000000000..9b782a09e2 --- /dev/null +++ b/domains/service_test.go @@ -0,0 +1,393 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package domains_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/domains/mocks" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" + policiesMocks "github.com/absmach/magistrala/pkg/policies/mocks" + "github.com/absmach/magistrala/pkg/sid" + "github.com/absmach/magistrala/pkg/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const ( + secret = "secret" + email = "test@example.com" + id = "testID" + groupName = "mgx" + description = "Description" + + memberRelation = "member" + authoritiesObj = "authorities" + loginDuration = 30 * time.Minute + refreshDuration = 24 * time.Hour + invalidDuration = 7 * 24 * time.Hour + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" +) + +var ( + errIssueUser = errors.New("failed to issue new login key") + errCreateDomainPolicy = errors.New("failed to create domain policy") + errRetrieve = errors.New("failed to retrieve key data") + ErrExpiry = errors.New("session is expired") + errRollbackPolicy = errors.New("failed to rollback policy") + errAddPolicies = errors.New("failed to add policies") + errPlatform = errors.New("invalid platform id") + inValid = "invalid" + valid = "valid" + domain = domains.Domain{ + ID: validID, + Name: groupName, + Tags: []string{"tag1", "tag2"}, + Alias: "test", + Permission: policies.AdminPermission, + CreatedBy: validID, + UpdatedBy: validID, + } + validSession = authn.Session{} + inValidSession = authn.Session{} +) + +var ( + drepo *mocks.Repository + policyMock *policiesMocks.Service +) + +func newService() domains.Service { + drepo = new(mocks.Repository) + idProvider := uuid.NewMock() + sidProvider := sid.NewMock() + policyMock = new(policiesMocks.Service) + ds, _ := domains.New(drepo, policyMock, idProvider, sidProvider) + return ds +} + +func TestCreateDomain(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + d domains.Domain + session authn.Session + userID string + addPolicyErr error + savePolicyErr error + saveDomainErr error + deleteDomainErr error + deletePoliciesErr error + err error + }{ + { + desc: "create domain successfully", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: validSession, + err: nil, + }, + { + desc: "create domain with invalid session", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: inValidSession, + err: svcerr.ErrAuthentication, + }, + { + desc: "create domain with invalid status", + d: domains.Domain{ + Status: domains.AllStatus, + }, + session: validSession, + err: svcerr.ErrInvalidStatus, + }, + { + desc: "create domain with failed policy request", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: validSession, + addPolicyErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + { + desc: "create domain with failed save policy request", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: validSession, + savePolicyErr: errors.ErrMalformedEntity, + err: errCreateDomainPolicy, + }, + { + desc: "create domain with failed save domain request", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: validSession, + saveDomainErr: errors.ErrMalformedEntity, + err: svcerr.ErrCreateEntity, + }, + { + desc: "create domain with rollback error", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: validSession, + savePolicyErr: errors.ErrMalformedEntity, + deleteDomainErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + { + desc: "create domain with rollback error and failed to delete policies", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: validSession, + savePolicyErr: errors.ErrMalformedEntity, + deleteDomainErr: errors.ErrMalformedEntity, + deletePoliciesErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + { + desc: "create domain with failed to create and failed rollback", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: validSession, + saveDomainErr: errors.ErrMalformedEntity, + deletePoliciesErr: errors.ErrMalformedEntity, + err: errRollbackPolicy, + }, + { + desc: "create domain with failed to create and failed rollback", + d: domains.Domain{ + Status: domains.EnabledStatus, + }, + session: validSession, + saveDomainErr: errors.ErrMalformedEntity, + deleteDomainErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + repoCall := drepo.On("Save", mock.Anything, mock.Anything).Return(domains.Domain{}, tc.saveDomainErr) + _, err := svc.CreateDomain(context.Background(), tc.session, tc.d) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestRetrieveDomain(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + session authn.Session + domainID string + domainRepoErr error + domainRepoErr1 error + checkPolicyErr error + err error + }{ + { + desc: "retrieve domain successfully", + session: validSession, + domainID: validID, + err: nil, + }, + { + desc: "retrieve domain with invalid session", + session: inValidSession, + domainID: validID, + err: svcerr.ErrAuthentication, + }, + { + desc: "retrieve domain with empty domain id", + session: validSession, + domainID: "", + err: svcerr.ErrViewEntity, + domainRepoErr1: repoerr.ErrNotFound, + }, + { + desc: "retrieve non-existing domain", + session: validSession, + domainID: inValid, + domainRepoErr: repoerr.ErrNotFound, + err: svcerr.ErrViewEntity, + domainRepoErr1: repoerr.ErrNotFound, + }, + { + desc: "retrieve domain with failed to retrieve by id", + session: validSession, + domainID: validID, + domainRepoErr1: repoerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + } + + for _, tc := range cases { + repoCall := drepo.On("RetrieveByID", mock.Anything, groupName).Return(domains.Domain{}, tc.domainRepoErr) + repoCall1 := drepo.On("RetrieveByID", mock.Anything, tc.domainID).Return(domains.Domain{}, tc.domainRepoErr1) + _, err := svc.RetrieveDomain(context.Background(), tc.session, tc.domainID) + 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 TestUpdateDomain(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + session authn.Session + domainID string + domReq domains.DomainReq + checkPolicyErr error + retrieveByIDErr error + updateErr error + err error + }{ + { + desc: "update domain successfully", + session: validSession, + domainID: validID, + domReq: domains.DomainReq{ + Name: &valid, + Alias: &valid, + }, + err: nil, + }, + { + desc: "update domain with invalid session", + session: inValidSession, + domainID: validID, + domReq: domains.DomainReq{ + Name: &valid, + Alias: &valid, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "update domain with empty domainID", + session: validSession, + domainID: "", + domReq: domains.DomainReq{ + Name: &valid, + Alias: &valid, + }, + checkPolicyErr: svcerr.ErrAuthorization, + err: svcerr.ErrDomainAuthorization, + }, + { + desc: "update domain with failed to retrieve by id", + session: validSession, + domainID: validID, + domReq: domains.DomainReq{ + Name: &valid, + Alias: &valid, + }, + retrieveByIDErr: repoerr.ErrNotFound, + err: svcerr.ErrNotFound, + }, + { + desc: "update domain with failed to update", + session: validSession, + domainID: validID, + domReq: domains.DomainReq{ + Name: &valid, + Alias: &valid, + }, + updateErr: errors.ErrMalformedEntity, + err: errors.ErrMalformedEntity, + }, + } + + for _, tc := range cases { + repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(domains.Domain{}, tc.retrieveByIDErr) + repoCall2 := drepo.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(domains.Domain{}, tc.updateErr) + _, err := svc.UpdateDomain(context.Background(), tc.session, tc.domainID, tc.domReq) + 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() + } +} + +func TestListDomains(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + session authn.Session + domainID string + authReq domains.Page + listDomainsRes domains.DomainsPage + retreiveByIDErr error + checkPolicyErr error + listDomainErr error + err error + }{ + { + desc: "list domains successfully", + session: validSession, + domainID: validID, + authReq: domains.Page{ + Offset: 0, + Limit: 10, + Permission: policies.AdminPermission, + Status: domains.EnabledStatus, + }, + listDomainsRes: domains.DomainsPage{ + Domains: []domains.Domain{domain}, + }, + err: nil, + }, + { + desc: "list domains with invalid session", + session: inValidSession, + domainID: validID, + authReq: domains.Page{ + Offset: 0, + Limit: 10, + Permission: policies.AdminPermission, + Status: domains.EnabledStatus, + }, + err: svcerr.ErrAuthentication, + }, + { + desc: "list domains with repository error on list domains", + session: validSession, + domainID: validID, + authReq: domains.Page{ + Offset: 0, + Limit: 10, + Permission: policies.AdminPermission, + Status: domains.EnabledStatus, + }, + listDomainErr: errors.ErrMalformedEntity, + err: svcerr.ErrViewEntity, + }, + } + + for _, tc := range cases { + repoCall1 := drepo.On("ListDomains", mock.Anything, mock.Anything).Return(tc.listDomainsRes, tc.listDomainErr) + _, err := svc.ListDomains(context.Background(), tc.session, domains.Page{}) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall1.Unset() + } +} diff --git a/domains/tracing/tracing.go b/domains/tracing/tracing.go new file mode 100644 index 0000000000..ed9f2753b3 --- /dev/null +++ b/domains/tracing/tracing.go @@ -0,0 +1,84 @@ +package tracing + +import ( + "context" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/pkg/authn" + rmTrace "github.com/absmach/magistrala/pkg/roles/rolemanager/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +var _ domains.Service = (*tracingMiddleware)(nil) + +type tracingMiddleware struct { + tracer trace.Tracer + svc domains.Service + rmTrace.RoleManagerTracing +} + +// New returns a new group service with tracing capabilities. +func New(svc domains.Service, tracer trace.Tracer) domains.Service { + return &tracingMiddleware{tracer, svc, rmTrace.NewRoleManagerTracing("domain", svc, tracer)} +} + +func (tm *tracingMiddleware) CreateDomain(ctx context.Context, session authn.Session, d domains.Domain) (domains.Domain, error) { + ctx, span := tm.tracer.Start(ctx, "create_domain", trace.WithAttributes( + attribute.String("name", d.Name), + )) + defer span.End() + return tm.svc.CreateDomain(ctx, session, d) +} + +func (tm *tracingMiddleware) RetrieveDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + ctx, span := tm.tracer.Start(ctx, "view_domain", trace.WithAttributes( + attribute.String("id", id), + )) + defer span.End() + return tm.svc.RetrieveDomain(ctx, session, id) +} + +func (tm *tracingMiddleware) UpdateDomain(ctx context.Context, session authn.Session, id string, d domains.DomainReq) (domains.Domain, error) { + ctx, span := tm.tracer.Start(ctx, "update_domain", trace.WithAttributes( + attribute.String("id", id), + )) + defer span.End() + return tm.svc.UpdateDomain(ctx, session, id, d) +} + +func (tm *tracingMiddleware) EnableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + ctx, span := tm.tracer.Start(ctx, "enable_domain", trace.WithAttributes( + attribute.String("id", id), + )) + defer span.End() + return tm.svc.EnableDomain(ctx, session, id) +} + +func (tm *tracingMiddleware) DisableDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + ctx, span := tm.tracer.Start(ctx, "disable_domain", trace.WithAttributes( + attribute.String("id", id), + )) + defer span.End() + return tm.svc.DisableDomain(ctx, session, id) +} + +func (tm *tracingMiddleware) FreezeDomain(ctx context.Context, session authn.Session, id string) (domains.Domain, error) { + ctx, span := tm.tracer.Start(ctx, "freeze_domain", trace.WithAttributes( + attribute.String("id", id), + )) + defer span.End() + return tm.svc.FreezeDomain(ctx, session, id) +} + +func (tm *tracingMiddleware) ListDomains(ctx context.Context, session authn.Session, p domains.Page) (domains.DomainsPage, error) { + ctx, span := tm.tracer.Start(ctx, "list_domains") + defer span.End() + return tm.svc.ListDomains(ctx, session, p) +} + +func (tm *tracingMiddleware) DeleteUserFromDomains(ctx context.Context, id string) error { + ctx, span := tm.tracer.Start(ctx, "delete_user_from_domains") + defer span.End() + return tm.svc.DeleteUserFromDomains(ctx, id) +} diff --git a/go.mod b/go.mod index c3406acca0..5d40ce7331 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,8 @@ require ( github.com/rubenv/sql-migrate v1.7.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.10.0 + github.com/sqids/sqids-go v0.4.1 + github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 go.opentelemetry.io/otel v1.32.0 diff --git a/go.sum b/go.sum index dd79aa53ed..b27eff4a1b 100644 --- a/go.sum +++ b/go.sum @@ -406,6 +406,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/sqids/sqids-go v0.4.1 h1:eQKYzmAZbLlRwHeHYPF35QhgxwZHLnlmVj9AkIj/rrw= +github.com/sqids/sqids-go v0.4.1/go.mod h1:EMwHuPQgSNFS0A49jESTfIQS+066XQTVhukrzEPScl8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -422,8 +424,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= diff --git a/things/api/grpc/client.go b/groups/api/grpc/client.go similarity index 53% rename from things/api/grpc/client.go rename to groups/api/grpc/client.go index f48ecd6377..e65c5c0cf4 100644 --- a/things/api/grpc/client.go +++ b/groups/api/grpc/client.go @@ -8,10 +8,10 @@ import ( "fmt" "time" - "github.com/absmach/magistrala" + grpcCommonV1 "github.com/absmach/magistrala/internal/grpc/common/v1" + grpcGroupsV1 "github.com/absmach/magistrala/internal/grpc/groups/v1" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/things" "github.com/go-kit/kit/endpoint" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc" @@ -19,62 +19,51 @@ import ( "google.golang.org/grpc/status" ) -const svcName = "magistrala.ThingsService" +const svcName = "groups.v1.GroupsService" -var _ magistrala.ThingsServiceClient = (*grpcClient)(nil) +var _ grpcGroupsV1.GroupsServiceClient = (*grpcClient)(nil) type grpcClient struct { - timeout time.Duration - authorize endpoint.Endpoint + timeout time.Duration + retrieveEntity endpoint.Endpoint } // NewClient returns new gRPC client instance. -func NewClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.ThingsServiceClient { +func NewClient(conn *grpc.ClientConn, timeout time.Duration) grpcGroupsV1.GroupsServiceClient { return &grpcClient{ - authorize: kitgrpc.NewClient( + + retrieveEntity: kitgrpc.NewClient( conn, svcName, - "Authorize", - encodeAuthorizeRequest, - decodeAuthorizeResponse, - magistrala.ThingsAuthzRes{}, + "RetrieveEntity", + encodeRetrieveEntityRequest, + decodeRetrieveEntityResponse, + grpcCommonV1.RetrieveEntityRes{}, ).Endpoint(), timeout: timeout, } } -func (client grpcClient) Authorize(ctx context.Context, req *magistrala.ThingsAuthzReq, _ ...grpc.CallOption) (r *magistrala.ThingsAuthzRes, err error) { +func (client grpcClient) RetrieveEntity(ctx context.Context, req *grpcCommonV1.RetrieveEntityReq, _ ...grpc.CallOption) (r *grpcCommonV1.RetrieveEntityRes, err error) { ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() - res, err := client.authorize(ctx, things.AuthzReq{ - ClientID: req.GetThingId(), - ClientKey: req.GetThingKey(), - ChannelID: req.GetChannelId(), - Permission: req.GetPermission(), - }) + res, err := client.retrieveEntity(ctx, req) if err != nil { - return &magistrala.ThingsAuthzRes{}, decodeError(err) + return &grpcCommonV1.RetrieveEntityRes{}, decodeError(err) } + typedRes := res.(*grpcCommonV1.RetrieveEntityRes) - ar := res.(authorizeRes) - return &magistrala.ThingsAuthzRes{Authorized: ar.authorized, Id: ar.id}, nil + return typedRes, nil } -func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.ThingsAuthzRes) - return authorizeRes{authorized: res.Authorized, id: res.Id}, nil +func encodeRetrieveEntityRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + return grpcReq, nil } -func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(things.AuthzReq) - return &magistrala.ThingsAuthzReq{ - ChannelId: req.ChannelID, - ThingId: req.ClientID, - ThingKey: req.ClientKey, - Permission: req.Permission, - }, nil +func decodeRetrieveEntityResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + return grpcRes, nil } func decodeError(err error) error { diff --git a/groups/api/grpc/doc.go b/groups/api/grpc/doc.go new file mode 100644 index 0000000000..20956ee50b --- /dev/null +++ b/groups/api/grpc/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package grpc contains implementation of Auth service gRPC API. +package grpc diff --git a/groups/api/grpc/endpoint.go b/groups/api/grpc/endpoint.go new file mode 100644 index 0000000000..f49059b322 --- /dev/null +++ b/groups/api/grpc/endpoint.go @@ -0,0 +1,26 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + + groups "github.com/absmach/magistrala/groups/private" + "github.com/go-kit/kit/endpoint" +) + +func retrieveEntityEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + + req := request.(retrieveEntityReq) + group, err := svc.RetrieveById(ctx, req.Id) + + if err != nil { + return retrieveEntityRes{}, err + } + + return retrieveEntityRes{id: group.ID, domain: group.Domain, status: uint8(group.Status)}, nil + + } +} diff --git a/groups/api/grpc/request.go b/groups/api/grpc/request.go new file mode 100644 index 0000000000..4c8286e109 --- /dev/null +++ b/groups/api/grpc/request.go @@ -0,0 +1,8 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +type retrieveEntityReq struct { + Id string +} diff --git a/groups/api/grpc/responses.go b/groups/api/grpc/responses.go new file mode 100644 index 0000000000..6ae8047c53 --- /dev/null +++ b/groups/api/grpc/responses.go @@ -0,0 +1,12 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +type groupBasic struct { + id string + domain string + status uint8 +} + +type retrieveEntityRes groupBasic diff --git a/things/api/grpc/server.go b/groups/api/grpc/server.go similarity index 52% rename from things/api/grpc/server.go rename to groups/api/grpc/server.go index 5dfe4584fe..974ff04c80 100644 --- a/things/api/grpc/server.go +++ b/groups/api/grpc/server.go @@ -6,56 +6,61 @@ package grpc import ( "context" - "github.com/absmach/magistrala" mgauth "github.com/absmach/magistrala/auth" + groups "github.com/absmach/magistrala/groups/private" + grpcCommonV1 "github.com/absmach/magistrala/internal/grpc/common/v1" + grpcGroupsV1 "github.com/absmach/magistrala/internal/grpc/groups/v1" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/things" kitgrpc "github.com/go-kit/kit/transport/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -var _ magistrala.ThingsServiceServer = (*grpcServer)(nil) +var _ grpcGroupsV1.GroupsServiceServer = (*grpcServer)(nil) type grpcServer struct { - magistrala.UnimplementedThingsServiceServer - authorize kitgrpc.Handler + grpcGroupsV1.UnimplementedGroupsServiceServer + retrieveEntity kitgrpc.Handler } // NewServer returns new AuthServiceServer instance. -func NewServer(svc things.Service) magistrala.ThingsServiceServer { +func NewServer(svc groups.Service) grpcGroupsV1.GroupsServiceServer { return &grpcServer{ - authorize: kitgrpc.NewServer( - (authorizeEndpoint(svc)), - decodeAuthorizeRequest, - encodeAuthorizeResponse, + retrieveEntity: kitgrpc.NewServer( + retrieveEntityEndpoint(svc), + decodeRetrieveEntityRequest, + encodeRetrieveEntityResponse, ), } } -func (s *grpcServer) Authorize(ctx context.Context, req *magistrala.ThingsAuthzReq) (*magistrala.ThingsAuthzRes, error) { - _, res, err := s.authorize.ServeGRPC(ctx, req) +func (s *grpcServer) RetrieveEntity(ctx context.Context, req *grpcCommonV1.RetrieveEntityReq) (*grpcCommonV1.RetrieveEntityRes, error) { + _, res, err := s.retrieveEntity.ServeGRPC(ctx, req) if err != nil { return nil, encodeError(err) } - return res.(*magistrala.ThingsAuthzRes), nil + return res.(*grpcCommonV1.RetrieveEntityRes), nil } -func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.ThingsAuthzReq) - return authorizeReq{ - ThingID: req.GetThingId(), - ThingKey: req.GetThingKey(), - ChannelID: req.GetChannelId(), - Permission: req.GetPermission(), +func decodeRetrieveEntityRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { + req := grpcReq.(*grpcCommonV1.RetrieveEntityReq) + return retrieveEntityReq{ + Id: req.GetId(), }, nil } -func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(authorizeRes) - return &magistrala.ThingsAuthzRes{Authorized: res.authorized, Id: res.id}, nil +func encodeRetrieveEntityResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { + res := grpcRes.(retrieveEntityRes) + + return &grpcCommonV1.RetrieveEntityRes{ + Entity: &grpcCommonV1.EntityBasic{ + Id: res.id, + DomainId: res.domain, + Status: uint32(res.status), + }, + }, nil } func encodeError(err error) error { diff --git a/internal/groups/api/decode.go b/groups/api/http/decode.go similarity index 52% rename from internal/groups/api/decode.go rename to groups/api/http/decode.go index c560f5083c..de3d3a68d9 100644 --- a/internal/groups/api/decode.go +++ b/groups/api/http/decode.go @@ -9,164 +9,86 @@ import ( "net/http" "strings" + mggroups "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" - mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/go-chi/chi/v5" ) -func DecodeListGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - pm, err := decodePageMeta(r) - if err != nil { - return nil, err - } - - level, err := apiutil.ReadNumQuery[uint64](r, api.LevelKey, api.DefLevel) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - parentID, err := apiutil.ReadStringQuery(r, api.ParentKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - tree, err := apiutil.ReadBoolQuery(r, api.TreeKey, false) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - dir, err := apiutil.ReadNumQuery[int64](r, api.DirKey, -1) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) +func DecodeGroupCreate(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) } - - memberKind, err := apiutil.ReadStringQuery(r, api.MemberKindKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + var g mggroups.Group + if err := json.NewDecoder(r.Body).Decode(&g); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) } - - permission, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + req := createGroupReq{ + Group: g, } - listPerms, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listGroupsReq{ - tree: tree, - memberKind: memberKind, - memberID: chi.URLParam(r, "memberID"), - Page: mggroups.Page{ - Level: level, - ParentID: parentID, - Permission: permission, - PageMeta: pm, - Direction: dir, - ListPerms: listPerms, - }, - } return req, nil } -func DecodeListParentsRequest(_ context.Context, r *http.Request) (interface{}, error) { +func DecodeListGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { pm, err := decodePageMeta(r) if err != nil { return nil, err } - level, err := apiutil.ReadNumQuery[uint64](r, api.LevelKey, api.DefLevel) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + req := listGroupsReq{ + PageMeta: pm, } + return req, nil +} - tree, err := apiutil.ReadBoolQuery(r, api.TreeKey, false) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - permission, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) +func DecodeGroupUpdate(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) } - - listPerms, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + req := updateGroupReq{ + id: chi.URLParam(r, "groupID"), } - req := listGroupsReq{ - tree: tree, - Page: mggroups.Page{ - Level: level, - ParentID: chi.URLParam(r, "groupID"), - Permission: permission, - PageMeta: pm, - Direction: +1, - ListPerms: listPerms, - }, + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) } return req, nil } -func DecodeListChildrenRequest(_ context.Context, r *http.Request) (interface{}, error) { - pm, err := decodePageMeta(r) - if err != nil { - return nil, err +func DecodeGroupRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := groupReq{ + id: chi.URLParam(r, "groupID"), } + return req, nil +} - level, err := apiutil.ReadNumQuery[uint64](r, api.LevelKey, api.DefLevel) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) +func DecodeChangeGroupStatusRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := changeGroupStatusReq{ + id: chi.URLParam(r, "groupID"), } + return req, nil +} - tree, err := apiutil.ReadBoolQuery(r, api.TreeKey, false) +func decodeRetrieveGroupHierarchy(_ context.Context, r *http.Request) (interface{}, error) { + hm, err := decodeHierarchyPageMeta(r) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - permission, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return nil, err } - listPerms, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listGroupsReq{ - tree: tree, - Page: mggroups.Page{ - Level: level, - ParentID: chi.URLParam(r, "groupID"), - Permission: permission, - PageMeta: pm, - Direction: -1, - ListPerms: listPerms, - }, + req := retrieveGroupHierarchyReq{ + id: chi.URLParam(r, "groupID"), + HierarchyPageMeta: hm, } return req, nil } -func DecodeGroupCreate(_ context.Context, r *http.Request) (interface{}, error) { +func decodeAddParentGroupRequest(_ context.Context, r *http.Request) (interface{}, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) } - var g mggroups.Group - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - req := createGroupReq{ - Group: g, - } - 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.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - req := updateGroupReq{ + req := addParentGroupReq{ id: chi.URLParam(r, "groupID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -175,33 +97,32 @@ func DecodeGroupUpdate(_ context.Context, r *http.Request) (interface{}, error) return req, nil } -func DecodeGroupRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := groupReq{ +func decodeRemoveParentGroupRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := removeParentGroupReq{ id: chi.URLParam(r, "groupID"), } return req, nil } -func DecodeGroupPermsRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := groupPermsReq{ - id: chi.URLParam(r, "groupID"), +func decodeAddChildrenGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) } - return req, nil -} - -func DecodeChangeGroupStatus(_ context.Context, r *http.Request) (interface{}, error) { - req := changeGroupStatusReq{ + req := addChildrenGroupsReq{ id: chi.URLParam(r, "groupID"), } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + } return req, nil } -func DecodeAssignMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { +func decodeRemoveChildrenGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) } - req := assignReq{ - groupID: chi.URLParam(r, "groupID"), + req := removeChildrenGroupsReq{ + id: chi.URLParam(r, "groupID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) @@ -209,36 +130,47 @@ func DecodeAssignMembersRequest(_ context.Context, r *http.Request) (interface{} return req, nil } -func DecodeUnassignMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) +func decodeRemoveAllChildrenGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := removeAllChildrenGroupsReq{ + id: chi.URLParam(r, "groupID"), } - req := unassignReq{ - groupID: chi.URLParam(r, "groupID"), + return req, nil +} + +func decodeListChildrenGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { + pm, err := decodePageMeta(r) + if err != nil { + return nil, err } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + + req := listChildrenGroupsReq{ + id: chi.URLParam(r, "groupID"), + PageMeta: pm, } return req, nil } -func DecodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { - memberKind, err := apiutil.ReadStringQuery(r, api.MemberKindKey, "") +func decodeHierarchyPageMeta(r *http.Request) (mggroups.HierarchyPageMeta, error) { + level, err := apiutil.ReadNumQuery[uint64](r, api.LevelKey, api.DefLevel) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mggroups.HierarchyPageMeta{}, errors.Wrap(apiutil.ErrValidation, err) } - permission, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) + + tree, err := apiutil.ReadBoolQuery(r, api.TreeKey, false) if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) + return mggroups.HierarchyPageMeta{}, errors.Wrap(apiutil.ErrValidation, err) } - req := listMembersReq{ - groupID: chi.URLParam(r, "groupID"), - permission: permission, - memberKind: memberKind, + hierarchyDir, err := apiutil.ReadNumQuery[int64](r, api.DirKey, -1) + if err != nil { + return mggroups.HierarchyPageMeta{}, errors.Wrap(apiutil.ErrValidation, err) } - return req, nil -} + return mggroups.HierarchyPageMeta{ + Level: level, + Direction: hierarchyDir, + Tree: tree, + }, nil +} func decodePageMeta(r *http.Request) (mggroups.PageMeta, error) { s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefGroupStatus) if err != nil { @@ -268,14 +200,23 @@ func decodePageMeta(r *http.Request) (mggroups.PageMeta, error) { if err != nil { return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) } - + permission, err := apiutil.ReadStringQuery(r, api.PermissionKey, api.DefPermission) + if err != nil { + return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) + } + listPerms, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms) + if err != nil { + return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) + } ret := mggroups.PageMeta{ - Offset: offset, - Limit: limit, - Name: name, - ID: id, - Metadata: meta, - Status: st, + Offset: offset, + Limit: limit, + Name: name, + ID: id, + Metadata: meta, + Status: st, + Permission: permission, + ListPerms: listPerms, } return ret, nil } diff --git a/internal/groups/api/decode_test.go b/groups/api/http/decode_test.go similarity index 64% rename from internal/groups/api/decode_test.go rename to groups/api/http/decode_test.go index 2e45e34800..f003120737 100644 --- a/internal/groups/api/decode_test.go +++ b/groups/api/http/decode_test.go @@ -8,13 +8,14 @@ import ( "fmt" "net/http" "net/url" + "reflect" "strings" "testing" + "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/groups" "github.com/stretchr/testify/assert" ) @@ -31,12 +32,9 @@ func TestDecodeListGroupsRequest(t *testing.T) { url: "http://localhost:8080", header: map[string][]string{}, resp: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, + PageMeta: groups.PageMeta{ + Limit: 10, Permission: api.DefPermission, - Direction: -1, }, }, err: nil, @@ -48,24 +46,17 @@ func TestDecodeListGroupsRequest(t *testing.T) { "Authorization": {"Bearer 123"}, }, resp: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Status: groups.EnabledStatus, - Offset: 10, - Limit: 10, - Name: "random", - Metadata: groups.Metadata{ - "test": "test", - }, - }, - Level: 2, - ParentID: "random", - Permission: "random", - Direction: -1, + PageMeta: groups.PageMeta{ + Status: groups.EnabledStatus, + Offset: 10, + Limit: 10, + Name: "random", ListPerms: true, + Permission: "random", + Metadata: groups.Metadata{ + "test": "test", + }, }, - tree: true, - memberKind: "random", }, err: nil, }, @@ -133,7 +124,7 @@ func TestDecodeListGroupsRequest(t *testing.T) { } } -func TestDecodeListParentsRequest(t *testing.T) { +func TestDecodeRetrieveGroupHierarchy(t *testing.T) { cases := []struct { desc string url string @@ -145,14 +136,8 @@ func TestDecodeListParentsRequest(t *testing.T) { desc: "valid request with no parameters", url: "http://localhost:8080", header: map[string][]string{}, - resp: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - Permission: api.DefPermission, - Direction: +1, - }, + resp: retrieveGroupHierarchyReq{ + HierarchyPageMeta: groups.HierarchyPageMeta{}, }, err: nil, }, @@ -162,23 +147,8 @@ func TestDecodeListParentsRequest(t *testing.T) { header: map[string][]string{ "Authorization": {"Bearer 123"}, }, - resp: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Status: groups.EnabledStatus, - Offset: 10, - Limit: 10, - Name: "random", - Metadata: groups.Metadata{ - "test": "test", - }, - }, - Level: 2, - Permission: "random", - Direction: +1, - ListPerms: true, - }, - tree: true, + resp: retrieveGroupHierarchyReq{ + HierarchyPageMeta: groups.HierarchyPageMeta{}, }, err: nil, }, @@ -222,7 +192,7 @@ func TestDecodeListParentsRequest(t *testing.T) { URL: parsedURL, Header: tc.header, } - resp, err := DecodeListParentsRequest(context.Background(), req) + resp, err := decodeRetrieveGroupHierarchy(context.Background(), req) assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) } @@ -241,12 +211,9 @@ func TestDecodeListChildrenRequest(t *testing.T) { url: "http://localhost:8080", header: map[string][]string{}, resp: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, + PageMeta: groups.PageMeta{ + Limit: 10, Permission: api.DefPermission, - Direction: -1, }, }, err: nil, @@ -258,22 +225,17 @@ func TestDecodeListChildrenRequest(t *testing.T) { "Authorization": {"Bearer 123"}, }, resp: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Status: groups.EnabledStatus, - Offset: 10, - Limit: 10, - Name: "random", - Metadata: groups.Metadata{ - "test": "test", - }, + PageMeta: groups.PageMeta{ + Status: groups.EnabledStatus, + Offset: 10, + Limit: 10, + Name: "random", + Metadata: groups.Metadata{ + "test": "test", }, - Level: 2, Permission: "random", - Direction: -1, ListPerms: true, }, - tree: true, }, err: nil, }, @@ -317,64 +279,7 @@ func TestDecodeListChildrenRequest(t *testing.T) { URL: parsedURL, Header: tc.header, } - resp, err := DecodeListChildrenRequest(context.Background(), req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - } -} - -func TestDecodeListMembersRequest(t *testing.T) { - cases := []struct { - desc string - url string - header map[string][]string - resp interface{} - err error - }{ - { - desc: "valid request with no parameters", - url: "http://localhost:8080", - header: map[string][]string{}, - resp: listMembersReq{ - permission: api.DefPermission, - }, - err: nil, - }, - { - desc: "valid request with all parameters", - url: "http://localhost:8080?member_kind=random&permission=random", - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - }, - resp: listMembersReq{ - memberKind: "random", - permission: "random", - }, - err: nil, - }, - { - desc: "valid request with invalid permission", - url: "http://localhost:8080?permission=random&permission=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid member kind", - url: "http://localhost:8080?member_kind=random&member_kind=random", - resp: nil, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - parsedURL, err := url.Parse(tc.url) - assert.NoError(t, err) - - req := &http.Request{ - URL: parsedURL, - Header: tc.header, - } - resp, err := DecodeListMembersRequest(context.Background(), req) + resp, err := decodeListChildrenGroupsRequest(context.Background(), req) assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) } @@ -598,7 +503,7 @@ func TestDecodeGroupRequest(t *testing.T) { } } -func TestDecodeGroupPermsRequest(t *testing.T) { +func TestDecodeChangeGroupStatus(t *testing.T) { cases := []struct { desc string header map[string][]string @@ -610,12 +515,12 @@ func TestDecodeGroupPermsRequest(t *testing.T) { header: map[string][]string{ "Authorization": {"Bearer 123"}, }, - resp: groupPermsReq{}, + resp: changeGroupStatusReq{}, err: nil, }, { desc: "empty token", - resp: groupPermsReq{}, + resp: changeGroupStatusReq{}, err: nil, }, } @@ -624,146 +529,276 @@ func TestDecodeGroupPermsRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", http.NoBody) assert.NoError(t, err) req.Header = tc.header - resp, err := DecodeGroupPermsRequest(context.Background(), req) + resp, err := DecodeChangeGroupStatusRequest(context.Background(), req) assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) } } -func TestDecodeChangeGroupStatus(t *testing.T) { - cases := []struct { - desc string - header map[string][]string - resp interface{} - err error +func TestDecodeChangeGroupStatusRequest(t *testing.T) { + type args struct { + in0 context.Context + r *http.Request + } + tests := []struct { + name string + args args + want interface{} + wantErr bool }{ - { - desc: "valid request", - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - }, - resp: changeGroupStatusReq{}, - err: nil, - }, - { - desc: "empty token", - resp: changeGroupStatusReq{}, - err: nil, - }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := DecodeChangeGroupStatusRequest(tt.args.in0, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("DecodeChangeGroupStatusRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DecodeChangeGroupStatusRequest() = %v, want %v", got, tt.want) + } + }) } +} - for _, tc := range cases { - req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", http.NoBody) - assert.NoError(t, err) - req.Header = tc.header - resp, err := DecodeChangeGroupStatus(context.Background(), req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) +func Test_decodeRetrieveGroupHierarchy(t *testing.T) { + type args struct { + in0 context.Context + r *http.Request + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodeRetrieveGroupHierarchy(tt.args.in0, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodeRetrieveGroupHierarchy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeRetrieveGroupHierarchy() = %v, want %v", got, tt.want) + } + }) } } -func TestDecodeAssignMembersRequest(t *testing.T) { - cases := []struct { - desc string - body string - header map[string][]string - resp interface{} - err error +func Test_decodeAddParentGroupRequest(t *testing.T) { + type args struct { + in0 context.Context + r *http.Request + } + tests := []struct { + name string + args args + want interface{} + wantErr bool }{ - { - desc: "valid request", - body: `{"member_kind": "random", "members": ["random"]}`, - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - "Content-Type": {api.ContentType}, - }, - resp: assignReq{ - MemberKind: "random", - Members: []string{"random"}, - }, - err: nil, - }, - { - desc: "invalid content type", - body: `{"member_kind": "random", "members": ["random"]}`, - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - "Content-Type": {"text/plain"}, - }, - resp: nil, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "invalid request body", - body: `data`, - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - "Content-Type": {api.ContentType}, - }, - resp: nil, - err: errors.ErrMalformedEntity, - }, + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodeAddParentGroupRequest(tt.args.in0, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodeAddParentGroupRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeAddParentGroupRequest() = %v, want %v", got, tt.want) + } + }) } +} - for _, tc := range cases { - req, err := http.NewRequest(http.MethodPost, "http://localhost:8080", strings.NewReader(tc.body)) - assert.NoError(t, err) - req.Header = tc.header - resp, err := DecodeAssignMembersRequest(context.Background(), req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) +func Test_decodeRemoveParentGroupRequest(t *testing.T) { + type args struct { + in0 context.Context + r *http.Request + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodeRemoveParentGroupRequest(tt.args.in0, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodeRemoveParentGroupRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeRemoveParentGroupRequest() = %v, want %v", got, tt.want) + } + }) } } -func TestDecodeUnassignMembersRequest(t *testing.T) { - cases := []struct { - desc string - body string - header map[string][]string - resp interface{} - err error +func Test_decodeAddChildrenGroupsRequest(t *testing.T) { + type args struct { + in0 context.Context + r *http.Request + } + tests := []struct { + name string + args args + want interface{} + wantErr bool }{ - { - desc: "valid request", - body: `{"member_kind": "random", "members": ["random"]}`, - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - "Content-Type": {api.ContentType}, - }, - resp: unassignReq{ - MemberKind: "random", - Members: []string{"random"}, - }, - err: nil, - }, - { - desc: "invalid content type", - body: `{"member_kind": "random", "members": ["random"]}`, - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - "Content-Type": {"text/plain"}, - }, - resp: nil, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "invalid request body", - body: `data`, - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - "Content-Type": {api.ContentType}, - }, - resp: nil, - err: errors.ErrMalformedEntity, - }, + // TODO: Add test cases. } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodeAddChildrenGroupsRequest(tt.args.in0, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodeAddChildrenGroupsRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeAddChildrenGroupsRequest() = %v, want %v", got, tt.want) + } + }) + } +} - for _, tc := range cases { - req, err := http.NewRequest(http.MethodPost, "http://localhost:8080", strings.NewReader(tc.body)) - assert.NoError(t, err) - req.Header = tc.header - resp, err := DecodeUnassignMembersRequest(context.Background(), req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) +func Test_decodeRemoveChildrenGroupsRequest(t *testing.T) { + type args struct { + in0 context.Context + r *http.Request + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodeRemoveChildrenGroupsRequest(tt.args.in0, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodeRemoveChildrenGroupsRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeRemoveChildrenGroupsRequest() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_decodeRemoveAllChildrenGroupsRequest(t *testing.T) { + type args struct { + in0 context.Context + r *http.Request + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodeRemoveAllChildrenGroupsRequest(tt.args.in0, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodeRemoveAllChildrenGroupsRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeRemoveAllChildrenGroupsRequest() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_decodeListChildrenGroupsRequest(t *testing.T) { + type args struct { + in0 context.Context + r *http.Request + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodeListChildrenGroupsRequest(tt.args.in0, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodeListChildrenGroupsRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeListChildrenGroupsRequest() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_decodeHierarchyPageMeta(t *testing.T) { + type args struct { + r *http.Request + } + tests := []struct { + name string + args args + want groups.HierarchyPageMeta + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodeHierarchyPageMeta(tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodeHierarchyPageMeta() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeHierarchyPageMeta() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_decodePageMeta(t *testing.T) { + type args struct { + r *http.Request + } + tests := []struct { + name string + args args + want groups.PageMeta + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := decodePageMeta(tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("decodePageMeta() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodePageMeta() = %v, want %v", got, tt.want) + } + }) } } diff --git a/things/api/doc.go b/groups/api/http/doc.go similarity index 100% rename from things/api/doc.go rename to groups/api/http/doc.go diff --git a/groups/api/http/endpoint_test.go b/groups/api/http/endpoint_test.go new file mode 100644 index 0000000000..7cd03c095f --- /dev/null +++ b/groups/api/http/endpoint_test.go @@ -0,0 +1,4 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api diff --git a/internal/groups/api/endpoints.go b/groups/api/http/endpoints.go similarity index 57% rename from internal/groups/api/endpoints.go rename to groups/api/http/endpoints.go index 7082c3e58c..f802451f22 100644 --- a/internal/groups/api/endpoints.go +++ b/groups/api/http/endpoints.go @@ -6,18 +6,18 @@ package api import ( "context" + "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" "github.com/go-kit/kit/endpoint" ) const groupTypeChannels = "channels" -func CreateGroupEndpoint(svc groups.Service, kind string) endpoint.Endpoint { +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 { @@ -26,10 +26,10 @@ func CreateGroupEndpoint(svc groups.Service, kind string) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return createGroupRes{created: false}, svcerr.ErrAuthorization + return createGroupRes{created: false}, svcerr.ErrAuthentication } - group, err := svc.CreateGroup(ctx, session, kind, req.Group) + group, err := svc.CreateGroup(ctx, session, req.Group) if err != nil { return createGroupRes{created: false}, err } @@ -47,7 +47,7 @@ func ViewGroupEndpoint(svc groups.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return viewGroupRes{}, svcerr.ErrAuthorization + return viewGroupRes{}, svcerr.ErrAuthentication } group, err := svc.ViewGroup(ctx, session, req.id) @@ -59,27 +59,6 @@ func ViewGroupEndpoint(svc groups.Service) endpoint.Endpoint { } } -func ViewGroupPermsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(groupPermsReq) - if err := req.validate(); err != nil { - return viewGroupPermsRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return viewGroupPermsRes{}, svcerr.ErrAuthorization - } - - p, err := svc.ViewGroupPerms(ctx, session, req.id) - if err != nil { - return viewGroupPermsRes{}, err - } - - return viewGroupPermsRes{Permissions: p}, nil - } -} - func UpdateGroupEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(updateGroupReq) @@ -89,7 +68,7 @@ func UpdateGroupEndpoint(svc groups.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return updateGroupRes{}, svcerr.ErrAuthorization + return updateGroupRes{}, svcerr.ErrAuthentication } group := groups.Group{ @@ -117,7 +96,7 @@ func EnableGroupEndpoint(svc groups.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return changeStatusRes{}, svcerr.ErrAuthorization + return changeStatusRes{}, svcerr.ErrAuthentication } group, err := svc.EnableGroup(ctx, session, req.id) @@ -137,7 +116,7 @@ func DisableGroupEndpoint(svc groups.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return changeStatusRes{}, svcerr.ErrAuthorization + return changeStatusRes{}, svcerr.ErrAuthentication } group, err := svc.DisableGroup(ctx, session, req.id) @@ -148,142 +127,208 @@ func DisableGroupEndpoint(svc groups.Service) endpoint.Endpoint { } } -func ListGroupsEndpoint(svc groups.Service, groupType, memberKind string) endpoint.Endpoint { +func ListGroupsEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(listGroupsReq) - if memberKind != "" { - req.memberKind = memberKind - } + if err := req.validate(); err != nil { - if groupType == groupTypeChannels { - return channelPageRes{}, errors.Wrap(apiutil.ErrValidation, err) - } return groupPageRes{}, errors.Wrap(apiutil.ErrValidation, err) } session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - if groupType == groupTypeChannels { - return channelPageRes{}, svcerr.ErrAuthorization - } - return groupPageRes{}, svcerr.ErrAuthorization + return groupPageRes{}, svcerr.ErrAuthentication } - page, err := svc.ListGroups(ctx, session, req.memberKind, req.memberID, req.Page) + page, err := svc.ListGroups(ctx, session, req.PageMeta) if err != nil { - if groupType == groupTypeChannels { - return channelPageRes{}, err - } return groupPageRes{}, err } - if req.tree { - return buildGroupsResponseTree(page), nil + groups := []viewGroupRes{} + for _, g := range page.Groups { + groups = append(groups, toViewGroupRes(g)) } - filterByID := req.Page.ParentID != "" - if groupType == groupTypeChannels { - return buildChannelsResponse(page, filterByID), nil - } - return buildGroupsResponse(page, filterByID), nil + return groupPageRes{pageRes: pageRes{ + Limit: page.Limit, + Offset: page.Offset, + Total: page.Total, + }, + Groups: groups, + }, nil + } } -func ListMembersEndpoint(svc groups.Service, memberKind string) endpoint.Endpoint { +func DeleteGroupEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersReq) - if memberKind != "" { - req.memberKind = memberKind + req := request.(groupReq) + if err := req.validate(); err != nil { + return deleteGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return deleteGroupRes{}, svcerr.ErrAuthentication + } + if err := svc.DeleteGroup(ctx, session, req.id); err != nil { + return deleteGroupRes{}, err + } + return deleteGroupRes{deleted: true}, nil + } +} + +func retrieveGroupHierarchyEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(retrieveGroupHierarchyReq) if err := req.validate(); err != nil { - return listMembersRes{}, errors.Wrap(apiutil.ErrValidation, err) + return retrieveGroupHierarchyRes{}, errors.Wrap(apiutil.ErrValidation, err) } session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return listMembersRes{}, svcerr.ErrAuthorization + return changeStatusRes{}, svcerr.ErrAuthentication } - page, err := svc.ListMembers(ctx, session, req.groupID, req.permission, req.memberKind) + hp, err := svc.RetrieveGroupHierarchy(ctx, session, req.id, req.HierarchyPageMeta) if err != nil { - return listMembersRes{}, err + return retrieveGroupHierarchyRes{}, err } - return listMembersRes{ - pageRes: pageRes{ - Limit: page.Limit, - Offset: page.Offset, - Total: page.Total, - }, - Members: page.Members, - }, nil + groups := []viewGroupRes{} + for _, g := range hp.Groups { + groups = append(groups, toViewGroupRes(g)) + } + return retrieveGroupHierarchyRes{Level: hp.Level, Direction: hp.Direction, Groups: groups}, nil + } } -func AssignMembersEndpoint(svc groups.Service, relation, memberKind string) endpoint.Endpoint { +func addParentGroupEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignReq) - if relation != "" { - req.Relation = relation + req := request.(addParentGroupReq) + if err := req.validate(); err != nil { + return addParentGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) } - if memberKind != "" { - req.MemberKind = memberKind + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return changeStatusRes{}, svcerr.ErrAuthentication + } + + if err := svc.AddParentGroup(ctx, session, req.id, req.ParentID); err != nil { + return addParentGroupRes{}, err } + return addParentGroupRes{}, nil + } +} + +func removeParentGroupEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(removeParentGroupReq) if err := req.validate(); err != nil { - return assignRes{}, errors.Wrap(apiutil.ErrValidation, err) + return removeParentGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) } + session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return assignRes{}, svcerr.ErrAuthorization + return changeStatusRes{}, svcerr.ErrAuthentication } - if err := svc.Assign(ctx, session, req.groupID, req.Relation, req.MemberKind, req.Members...); err != nil { - return assignRes{}, err + if err := svc.RemoveParentGroup(ctx, session, req.id); err != nil { + return removeParentGroupRes{}, err } - return assignRes{assigned: true}, nil + return removeParentGroupRes{}, nil } } -func UnassignMembersEndpoint(svc groups.Service, relation, memberKind string) endpoint.Endpoint { +func addChildrenGroupsEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignReq) - if relation != "" { - req.Relation = relation + req := request.(addChildrenGroupsReq) + if err := req.validate(); err != nil { + return addChildrenGroupsRes{}, errors.Wrap(apiutil.ErrValidation, err) } - if memberKind != "" { - req.MemberKind = memberKind + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return changeStatusRes{}, svcerr.ErrAuthentication } + + if err := svc.AddChildrenGroups(ctx, session, req.id, req.ChildrenIDs); err != nil { + return addChildrenGroupsRes{}, err + } + return addChildrenGroupsRes{}, nil + } +} + +func removeChildrenGroupsEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(removeChildrenGroupsReq) if err := req.validate(); err != nil { - return unassignRes{}, errors.Wrap(apiutil.ErrValidation, err) + return removeChildrenGroupsRes{}, errors.Wrap(apiutil.ErrValidation, err) } + session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return unassignRes{}, svcerr.ErrAuthorization + return changeStatusRes{}, svcerr.ErrAuthentication } - if err := svc.Unassign(ctx, session, req.groupID, req.Relation, req.MemberKind, req.Members...); err != nil { - return unassignRes{}, err + if err := svc.RemoveChildrenGroups(ctx, session, req.id, req.ChildrenIDs); err != nil { + return removeChildrenGroupsRes{}, err } - return unassignRes{unassigned: true}, nil + return removeChildrenGroupsRes{}, nil } } -func DeleteGroupEndpoint(svc groups.Service) endpoint.Endpoint { +func removeAllChildrenGroupsEndpoint(svc groups.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(groupReq) + req := request.(removeAllChildrenGroupsReq) if err := req.validate(); err != nil { - return deleteGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) + return removeAllChildrenGroupsRes{}, errors.Wrap(apiutil.ErrValidation, err) } session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return deleteGroupRes{}, svcerr.ErrAuthorization + return changeStatusRes{}, svcerr.ErrAuthentication } - if err := svc.DeleteGroup(ctx, session, req.id); err != nil { - return deleteGroupRes{}, err + if err := svc.RemoveAllChildrenGroups(ctx, session, req.id); err != nil { + return removeAllChildrenGroupsRes{}, err } - return deleteGroupRes{deleted: true}, nil + return removeAllChildrenGroupsRes{}, nil + } +} + +func listChildrenGroupsEndpoint(svc groups.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listChildrenGroupsReq) + if err := req.validate(); err != nil { + return listChildrenGroupsRes{}, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return changeStatusRes{}, svcerr.ErrAuthentication + } + + gp, err := svc.ListChildrenGroups(ctx, session, req.id, req.PageMeta) + if err != nil { + return listChildrenGroupsRes{}, err + } + viewGroups := []viewGroupRes{} + + for _, group := range gp.Groups { + viewGroups = append(viewGroups, toViewGroupRes(group)) + } + return listChildrenGroupsRes{ + pageRes: pageRes{ + Limit: gp.Limit, + Offset: gp.Offset, + Total: gp.Total, + }, + Groups: viewGroups, + }, nil } } @@ -310,7 +355,6 @@ func buildGroupsResponseTree(page groups.Page) groupPageRes { Limit: page.Limit, Offset: page.Offset, Total: page.Total, - Level: page.Level, }, Groups: []viewGroupRes{}, } @@ -342,7 +386,6 @@ func buildGroupsResponse(gp groups.Page, filterByID bool) groupPageRes { res := groupPageRes{ pageRes: pageRes{ Total: gp.Total, - Level: gp.Level, }, Groups: []viewGroupRes{}, } @@ -359,25 +402,3 @@ func buildGroupsResponse(gp groups.Page, filterByID bool) groupPageRes { return res } - -func buildChannelsResponse(cp groups.Page, filterByID bool) channelPageRes { - res := channelPageRes{ - pageRes: pageRes{ - Total: cp.Total, - Level: cp.Level, - }, - Channels: []viewGroupRes{}, - } - - for _, channel := range cp.Groups { - if filterByID && channel.Level == 0 { - continue - } - view := viewGroupRes{ - Group: channel, - } - res.Channels = append(res.Channels, view) - } - - return res -} diff --git a/groups/api/http/requests.go b/groups/api/http/requests.go new file mode 100644 index 0000000000..ad07254889 --- /dev/null +++ b/groups/api/http/requests.go @@ -0,0 +1,187 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "github.com/absmach/magistrala/groups" + mggroups "github.com/absmach/magistrala/groups" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" +) + +type createGroupReq struct { + mggroups.Group +} + +func (req createGroupReq) validate() error { + if len(req.Name) > api.MaxNameSize || req.Name == "" { + return apiutil.ErrNameSize + } + + return nil +} + +type updateGroupReq struct { + 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.id == "" { + return apiutil.ErrMissingID + } + if len(req.Name) > api.MaxNameSize { + return apiutil.ErrNameSize + } + return nil +} + +type listGroupsReq struct { + mggroups.PageMeta +} + +func (req listGroupsReq) validate() error { + + if req.Limit > api.MaxLimitSize || req.Limit < 1 { + return apiutil.ErrLimitSize + } + + return nil +} + +type groupReq struct { + id string +} + +func (req groupReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + + return nil +} + +type changeGroupStatusReq struct { + id string +} + +func (req changeGroupStatusReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + return nil +} + +type retrieveGroupHierarchyReq struct { + mggroups.HierarchyPageMeta + id string +} + +func (req retrieveGroupHierarchyReq) validate() error { + if req.Level > groups.MaxLevel { + return apiutil.ErrLevel + } + + return nil +} + +type addParentGroupReq struct { + id string + ParentID string `json:"parent_id"` +} + +func (req addParentGroupReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + if err := api.ValidateUUID(req.ParentID); err != nil { + return err + } + if req.id == req.ParentID { + return apiutil.ErrSelfParentingNotAllowed + } + return nil +} + +type removeParentGroupReq struct { + id string +} + +func (req removeParentGroupReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + return nil +} + +type addChildrenGroupsReq struct { + id string + ChildrenIDs []string `json:"children_ids"` +} + +func (req addChildrenGroupsReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + if len(req.ChildrenIDs) == 0 { + return apiutil.ErrMissingChildrenGroupIDs + } + for _, childID := range req.ChildrenIDs { + if err := api.ValidateUUID(childID); err != nil { + return err + } + if req.id == childID { + return apiutil.ErrSelfParentingNotAllowed + } + } + return nil +} + +type removeChildrenGroupsReq struct { + id string + ChildrenIDs []string `json:"children_ids"` +} + +func (req removeChildrenGroupsReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + if len(req.ChildrenIDs) == 0 { + return apiutil.ErrMissingChildrenGroupIDs + } + for _, childID := range req.ChildrenIDs { + if err := api.ValidateUUID(childID); err != nil { + return err + } + } + return nil +} + +type removeAllChildrenGroupsReq struct { + id string +} + +func (req removeAllChildrenGroupsReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + return nil +} + +type listChildrenGroupsReq struct { + id string + mggroups.PageMeta +} + +func (req listChildrenGroupsReq) validate() error { + if req.id == "" { + return apiutil.ErrMissingID + } + if req.Limit > api.MaxLimitSize || req.Limit < 1 { + return apiutil.ErrLimitSize + } + return nil +} diff --git a/groups/api/http/requests_test.go b/groups/api/http/requests_test.go new file mode 100644 index 0000000000..703c0e9ed6 --- /dev/null +++ b/groups/api/http/requests_test.go @@ -0,0 +1,400 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "strings" + "testing" + + "github.com/absmach/magistrala/groups" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/stretchr/testify/assert" +) + +var valid = "valid" + +func TestCreateGroupReqValidation(t *testing.T) { + cases := []struct { + desc string + req createGroupReq + err error + }{ + { + desc: "valid request", + req: createGroupReq{ + Group: groups.Group{ + Name: valid, + }, + }, + err: nil, + }, + { + desc: "long name", + req: createGroupReq{ + Group: groups.Group{ + Name: strings.Repeat("a", api.MaxNameSize+1), + }, + }, + err: apiutil.ErrNameSize, + }, + { + desc: "empty name", + req: createGroupReq{ + Group: groups.Group{}, + }, + err: apiutil.ErrNameSize, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestUpdateGroupReqValidation(t *testing.T) { + cases := []struct { + desc string + req updateGroupReq + err error + }{ + { + desc: "valid request", + req: updateGroupReq{ + id: valid, + Name: valid, + }, + err: nil, + }, + { + desc: "long name", + req: updateGroupReq{ + id: valid, + Name: strings.Repeat("a", api.MaxNameSize+1), + }, + err: apiutil.ErrNameSize, + }, + { + desc: "empty id", + req: updateGroupReq{ + Name: valid, + }, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestListGroupReqValidation(t *testing.T) { + cases := []struct { + desc string + req listGroupsReq + err error + }{ + { + desc: "valid request", + req: listGroupsReq{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + err: nil, + }, + { + desc: "invalid upper level", + req: listGroupsReq{ + PageMeta: groups.PageMeta{ + Limit: 10, + }, + }, + err: apiutil.ErrInvalidLevel, + }, + { + desc: "invalid lower limit", + req: listGroupsReq{ + PageMeta: groups.PageMeta{ + Limit: 0, + }, + }, + err: apiutil.ErrLimitSize, + }, + { + desc: "invalid upper limit", + req: listGroupsReq{ + PageMeta: groups.PageMeta{ + Limit: api.MaxLimitSize + 1, + }, + }, + err: apiutil.ErrLimitSize, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestGroupReqValidation(t *testing.T) { + cases := []struct { + desc string + req groupReq + err error + }{ + { + desc: "valid request", + req: groupReq{ + id: valid, + }, + err: nil, + }, + + { + desc: "empty id", + req: groupReq{}, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func TestChangeGroupStatusReqValidation(t *testing.T) { + cases := []struct { + desc string + req changeGroupStatusReq + err error + }{ + { + desc: "valid request", + req: changeGroupStatusReq{ + id: valid, + }, + err: nil, + }, + { + desc: "empty id", + req: changeGroupStatusReq{}, + err: apiutil.ErrMissingID, + }, + } + + for _, tc := range cases { + err := tc.req.validate() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + } +} + +func Test_createGroupReq_validate(t *testing.T) { + tests := []struct { + name string + req createGroupReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("createGroupReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_updateGroupReq_validate(t *testing.T) { + tests := []struct { + name string + req updateGroupReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("updateGroupReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_listGroupsReq_validate(t *testing.T) { + tests := []struct { + name string + req listGroupsReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("listGroupsReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_groupReq_validate(t *testing.T) { + tests := []struct { + name string + req groupReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("groupReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_changeGroupStatusReq_validate(t *testing.T) { + tests := []struct { + name string + req changeGroupStatusReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("changeGroupStatusReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_retrieveGroupHierarchyReq_validate(t *testing.T) { + tests := []struct { + name string + req retrieveGroupHierarchyReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("retrieveGroupHierarchyReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_addParentGroupReq_validate(t *testing.T) { + tests := []struct { + name string + req addParentGroupReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("addParentGroupReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_removeParentGroupReq_validate(t *testing.T) { + tests := []struct { + name string + req removeParentGroupReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("removeParentGroupReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_addChildrenGroupsReq_validate(t *testing.T) { + tests := []struct { + name string + req addChildrenGroupsReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("addChildrenGroupsReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_removeChildrenGroupsReq_validate(t *testing.T) { + tests := []struct { + name string + req removeChildrenGroupsReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("removeChildrenGroupsReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_removeAllChildrenGroupsReq_validate(t *testing.T) { + tests := []struct { + name string + req removeAllChildrenGroupsReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("removeAllChildrenGroupsReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_listChildrenGroupsReq_validate(t *testing.T) { + tests := []struct { + name string + req listChildrenGroupsReq + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.req.validate(); (err != nil) != tt.wantErr { + t.Errorf("listChildrenGroupsReq.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/groups/api/responses.go b/groups/api/http/responses.go similarity index 54% rename from internal/groups/api/responses.go rename to groups/api/http/responses.go index a2c30795b8..afe8cb8f49 100644 --- a/internal/groups/api/responses.go +++ b/groups/api/http/responses.go @@ -8,7 +8,7 @@ import ( "net/http" "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/groups" ) var ( @@ -17,8 +17,13 @@ var ( _ magistrala.Response = (*changeStatusRes)(nil) _ magistrala.Response = (*viewGroupRes)(nil) _ magistrala.Response = (*updateGroupRes)(nil) - _ magistrala.Response = (*assignRes)(nil) - _ magistrala.Response = (*unassignRes)(nil) + _ magistrala.Response = (*retrieveGroupHierarchyRes)(nil) + _ magistrala.Response = (*addParentGroupRes)(nil) + _ magistrala.Response = (*removeParentGroupRes)(nil) + _ magistrala.Response = (*addChildrenGroupsRes)(nil) + _ magistrala.Response = (*removeChildrenGroupsRes)(nil) + _ magistrala.Response = (*removeAllChildrenGroupsRes)(nil) + _ magistrala.Response = (*listChildrenGroupsRes)(nil) ) type viewGroupRes struct { @@ -37,22 +42,6 @@ func (res viewGroupRes) Empty() bool { return false } -type viewGroupPermsRes struct { - Permissions []string `json:"permissions"` -} - -func (res viewGroupPermsRes) Code() int { - return http.StatusOK -} - -func (res viewGroupPermsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewGroupPermsRes) Empty() bool { - return false -} - type createGroupRes struct { groups.Group `json:",inline"` created bool @@ -89,7 +78,6 @@ type pageRes struct { Limit uint64 `json:"limit,omitempty"` Offset uint64 `json:"offset"` Total uint64 `json:"total"` - Level uint64 `json:"level,omitempty"` } func (res groupPageRes) Code() int { @@ -104,23 +92,6 @@ func (res groupPageRes) Empty() bool { return false } -type channelPageRes struct { - pageRes - Channels []viewGroupRes `json:"channels"` -} - -func (res channelPageRes) Code() int { - return http.StatusOK -} - -func (res channelPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res channelPageRes) Empty() bool { - return false -} - type updateGroupRes struct { groups.Group `json:",inline"` } @@ -153,79 +124,132 @@ func (res changeStatusRes) Empty() bool { return false } -type assignRes struct { - assigned bool +type deleteGroupRes struct { + deleted bool } -func (res assignRes) Code() int { - if res.assigned { - return http.StatusCreated +func (res deleteGroupRes) Code() int { + if res.deleted { + return http.StatusNoContent } return http.StatusBadRequest } -func (res assignRes) Headers() map[string]string { +func (res deleteGroupRes) Headers() map[string]string { return map[string]string{} } -func (res assignRes) Empty() bool { +func (res deleteGroupRes) Empty() bool { return true } -type unassignRes struct { - unassigned bool +type retrieveGroupHierarchyRes struct { + Level uint64 `json:"level"` + Direction int64 `json:"direction"` + Groups []viewGroupRes `json:"groups"` } -func (res unassignRes) Code() int { - if res.unassigned { - return http.StatusCreated - } +func (res retrieveGroupHierarchyRes) Code() int { + return http.StatusOK +} - return http.StatusBadRequest +func (res retrieveGroupHierarchyRes) Headers() map[string]string { + return map[string]string{} +} + +func (res retrieveGroupHierarchyRes) Empty() bool { + return false } -func (res unassignRes) Headers() map[string]string { +type addParentGroupRes struct { +} + +func (res addParentGroupRes) Code() int { + return http.StatusNoContent +} + +func (res addParentGroupRes) Headers() map[string]string { return map[string]string{} } -func (res unassignRes) Empty() bool { +func (res addParentGroupRes) Empty() bool { return true } -type listMembersRes struct { - pageRes - Members []groups.Member `json:"members"` +type removeParentGroupRes struct { } -func (res listMembersRes) Code() int { - return http.StatusOK +func (res removeParentGroupRes) Code() int { + return http.StatusNoContent } -func (res listMembersRes) Headers() map[string]string { +func (res removeParentGroupRes) Headers() map[string]string { return map[string]string{} } -func (res listMembersRes) Empty() bool { - return false +func (res removeParentGroupRes) Empty() bool { + return true } -type deleteGroupRes struct { - deleted bool +type addChildrenGroupsRes struct { } -func (res deleteGroupRes) Code() int { - if res.deleted { - return http.StatusNoContent - } +func (res addChildrenGroupsRes) Code() int { + return http.StatusNoContent +} - return http.StatusBadRequest +func (res addChildrenGroupsRes) Headers() map[string]string { + return map[string]string{} } -func (res deleteGroupRes) Headers() map[string]string { +func (res addChildrenGroupsRes) Empty() bool { + return true +} + +type removeChildrenGroupsRes struct { +} + +func (res removeChildrenGroupsRes) Code() int { + return http.StatusNoContent +} + +func (res removeChildrenGroupsRes) Headers() map[string]string { return map[string]string{} } -func (res deleteGroupRes) Empty() bool { +func (res removeChildrenGroupsRes) Empty() bool { + return true +} + +type removeAllChildrenGroupsRes struct { +} + +func (res removeAllChildrenGroupsRes) Code() int { + return http.StatusNoContent +} + +func (res removeAllChildrenGroupsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res removeAllChildrenGroupsRes) Empty() bool { return true } + +type listChildrenGroupsRes struct { + pageRes + Groups []viewGroupRes `json:"groups"` +} + +func (res listChildrenGroupsRes) Code() int { + return http.StatusOK +} + +func (res listChildrenGroupsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res listChildrenGroupsRes) Empty() bool { + return false +} diff --git a/groups/api/http/transport.go b/groups/api/http/transport.go new file mode 100644 index 0000000000..98b26c6ff6 --- /dev/null +++ b/groups/api/http/transport.go @@ -0,0 +1,139 @@ +package api + +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +import ( + "log/slog" + + "github.com/absmach/magistrala/groups" + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/authn" + roleManagerHttp "github.com/absmach/magistrala/pkg/roles/rolemanager/api" + "github.com/go-chi/chi/v5" + kithttp "github.com/go-kit/kit/transport/http" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +// MakeHandler returns a HTTP handler for Groups API endpoints. +func MakeHandler(svc groups.Service, authn authn.Authentication, mux *chi.Mux, logger *slog.Logger, instanceID string) *chi.Mux { + opts := []kithttp.ServerOption{ + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), + } + d := roleManagerHttp.NewDecoder("groupID") + + mux.Route("/{domainID}/groups", func(r chi.Router) { + r.Use(api.AuthenticateMiddleware(authn, true)) + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + CreateGroupEndpoint(svc), + DecodeGroupCreate, + api.EncodeResponse, + opts..., + ), "create_group").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + ListGroupsEndpoint(svc), + DecodeListGroupsRequest, + api.EncodeResponse, + opts..., + ), "list_groups").ServeHTTP) + r = roleManagerHttp.EntityAvailableActionsRouter(svc, d, r, opts) + + r.Route("/{groupID}", func(r chi.Router) { + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + ViewGroupEndpoint(svc), + DecodeGroupRequest, + api.EncodeResponse, + opts..., + ), "view_group").ServeHTTP) + + r.Put("/", otelhttp.NewHandler(kithttp.NewServer( + UpdateGroupEndpoint(svc), + DecodeGroupUpdate, + api.EncodeResponse, + opts..., + ), "update_group").ServeHTTP) + + r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( + DeleteGroupEndpoint(svc), + DecodeGroupRequest, + api.EncodeResponse, + opts..., + ), "delete_group").ServeHTTP) + + r.Post("/enable", otelhttp.NewHandler(kithttp.NewServer( + EnableGroupEndpoint(svc), + DecodeChangeGroupStatusRequest, + api.EncodeResponse, + opts..., + ), "enable_group").ServeHTTP) + + r.Post("/disable", otelhttp.NewHandler(kithttp.NewServer( + DisableGroupEndpoint(svc), + DecodeChangeGroupStatusRequest, + api.EncodeResponse, + opts..., + ), "disable_group").ServeHTTP) + + r = roleManagerHttp.EntityRoleMangerRouter(svc, d, r, opts) + + r.Get("/hierarchy", otelhttp.NewHandler(kithttp.NewServer( + retrieveGroupHierarchyEndpoint(svc), + decodeRetrieveGroupHierarchy, + api.EncodeResponse, + opts..., + ), "retrieve_group_hierarchy").ServeHTTP) + + r.Route("/parent", func(r chi.Router) { + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + addParentGroupEndpoint(svc), + decodeAddParentGroupRequest, + api.EncodeResponse, + opts..., + ), "add_parent_group").ServeHTTP) + + r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( + removeParentGroupEndpoint(svc), + decodeRemoveParentGroupRequest, + api.EncodeResponse, + opts..., + ), "remove_parent_group").ServeHTTP) + + }) + + r.Route("/children", func(r chi.Router) { + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + addChildrenGroupsEndpoint(svc), + decodeAddChildrenGroupsRequest, + api.EncodeResponse, + opts..., + ), "add_children_groups").ServeHTTP) + + r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( + removeChildrenGroupsEndpoint(svc), + decodeRemoveChildrenGroupsRequest, + api.EncodeResponse, + opts..., + ), "remove_children_groups").ServeHTTP) + + r.Delete("/all", otelhttp.NewHandler(kithttp.NewServer( + removeAllChildrenGroupsEndpoint(svc), + decodeRemoveAllChildrenGroupsRequest, + api.EncodeResponse, + opts..., + ), "remove_all_children_groups").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + listChildrenGroupsEndpoint(svc), + decodeListChildrenGroupsRequest, + api.EncodeResponse, + opts..., + ), "list_children_groups").ServeHTTP) + }) + }) + + }) + + return mux +} diff --git a/pkg/groups/doc.go b/groups/doc.go similarity index 100% rename from pkg/groups/doc.go rename to groups/doc.go diff --git a/pkg/groups/errors.go b/groups/errors.go similarity index 100% rename from pkg/groups/errors.go rename to groups/errors.go diff --git a/internal/groups/events/doc.go b/groups/events/doc.go similarity index 100% rename from internal/groups/events/doc.go rename to groups/events/doc.go diff --git a/internal/groups/events/events.go b/groups/events/events.go similarity index 51% rename from internal/groups/events/events.go rename to groups/events/events.go index eb65fd411a..525a39e364 100644 --- a/internal/groups/events/events.go +++ b/groups/events/events.go @@ -6,27 +6,29 @@ package events import ( "time" + groups "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/pkg/events" - groups "github.com/absmach/magistrala/pkg/groups" ) var ( - groupPrefix = "group." - groupCreate = groupPrefix + "create" - groupUpdate = groupPrefix + "update" - groupChangeStatus = groupPrefix + "change_status" - groupView = groupPrefix + "view" - groupViewPerms = groupPrefix + "view_perms" - groupList = groupPrefix + "list" - groupListMemberships = groupPrefix + "list_by_user" - groupRemove = groupPrefix + "remove" - groupAssign = groupPrefix + "assign" - groupUnassign = groupPrefix + "unassign" + groupPrefix = "group." + groupCreate = groupPrefix + "create" + groupUpdate = groupPrefix + "update" + groupChangeStatus = groupPrefix + "change_status" + groupView = groupPrefix + "view" + groupList = groupPrefix + "list" + groupRemove = groupPrefix + "remove" + groupRetrieveGroupHierarchy = groupPrefix + "retrieve_group_hierarchy" + groupAddParentGroup = groupPrefix + "add_parent_group" + groupRemoveParentGroup = groupPrefix + "remove_parent_group" + groupViewParentGroup = groupPrefix + "view_parent_group" + groupAddChildrenGroups = groupPrefix + "add_children_groups" + groupRemoveChildrenGroups = groupPrefix + "remove_children_groups" + groupRemoveAllChildrenGroups = groupPrefix + "remove_all_children_groups" + groupListChildrenGroups = groupPrefix + "list_children_groups" ) var ( - _ events.Event = (*assignEvent)(nil) - _ events.Event = (*unassignEvent)(nil) _ events.Event = (*createGroupEvent)(nil) _ events.Event = (*updateGroupEvent)(nil) _ events.Event = (*changeStatusGroupEvent)(nil) @@ -34,43 +36,16 @@ var ( _ events.Event = (*deleteGroupEvent)(nil) _ events.Event = (*viewGroupEvent)(nil) _ events.Event = (*listGroupEvent)(nil) - _ events.Event = (*listGroupMembershipEvent)(nil) + _ events.Event = (*addParentGroupEvent)(nil) + _ events.Event = (*removeParentGroupEvent)(nil) + _ events.Event = (*viewParentGroupEvent)(nil) + _ events.Event = (*addChildrenGroupsEvent)(nil) + _ events.Event = (*removeChildrenGroupsEvent)(nil) + _ events.Event = (*removeAllChildrenGroupsEvent)(nil) + _ events.Event = (*listChildrenGroupsEvent)(nil) + _ events.Event = (*retrieveGroupHierarchyEvent)(nil) ) -type assignEvent struct { - memberIDs []string - relation string - memberKind string - groupID string -} - -func (cge assignEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": groupAssign, - "member_ids": cge.memberIDs, - "relation": cge.relation, - "memberKind": cge.memberKind, - "group_id": cge.groupID, - }, nil -} - -type unassignEvent struct { - memberIDs []string - relation string - memberKind string - groupID string -} - -func (cge unassignEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": groupUnassign, - "member_ids": cge.memberIDs, - "relation": cge.relation, - "memberKind": cge.memberKind, - "group_id": cge.groupID, - }, nil -} - type createGroupEvent struct { groups.Group } @@ -202,19 +177,8 @@ func (vge viewGroupEvent) Encode() (map[string]interface{}, error) { return val, nil } -type viewGroupPermsEvent struct { - permissions []string -} - -func (vgpe viewGroupPermsEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": groupViewPerms, - "permissions": vgpe.permissions, - }, nil -} - type listGroupEvent struct { - groups.Page + groups.PageMeta } func (lge listGroupEvent) Encode() (map[string]interface{}, error) { @@ -244,28 +208,132 @@ func (lge listGroupEvent) Encode() (map[string]interface{}, error) { return val, nil } -type listGroupMembershipEvent struct { - groupID string - permission string - memberKind string +type deleteGroupEvent struct { + id string } -func (lgme listGroupMembershipEvent) Encode() (map[string]interface{}, error) { +func (rge deleteGroupEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "operation": groupListMemberships, - "id": lgme.groupID, - "permission": lgme.permission, - "member_kind": lgme.memberKind, + "operation": groupRemove, + "id": rge.id, }, nil } -type deleteGroupEvent struct { +type retrieveGroupHierarchyEvent struct { id string + groups.HierarchyPageMeta } -func (rge deleteGroupEvent) Encode() (map[string]interface{}, error) { +func (vcge retrieveGroupHierarchyEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": groupRetrieveGroupHierarchy, + "id": vcge.id, + "level": vcge.Level, + "direction": vcge.Direction, + "tree": vcge.Tree, + } + return val, nil +} + +type addParentGroupEvent struct { + id string + parentID string +} + +func (apge addParentGroupEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "operation": groupRemove, - "id": rge.id, + "operation": groupAddParentGroup, + "id": apge.id, + "parent_id": apge.parentID, + }, nil +} + +type removeParentGroupEvent struct { + id string +} + +func (rpge removeParentGroupEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": groupRemoveParentGroup, + "id": rpge.id, + }, nil +} + +type viewParentGroupEvent struct { + id string +} + +func (vpge viewParentGroupEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": groupViewParentGroup, + "id": vpge.id, + }, nil +} + +type addChildrenGroupsEvent struct { + id string + childrenIDs []string +} + +func (acge addChildrenGroupsEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": groupAddChildrenGroups, + "id": acge.id, + "childre_ids": acge.childrenIDs, + }, nil +} + +type removeChildrenGroupsEvent struct { + id string + childrenIDs []string +} + +func (rcge removeChildrenGroupsEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": groupRemoveChildrenGroups, + "id": rcge.id, + "children_ids": rcge.childrenIDs, }, nil } + +type removeAllChildrenGroupsEvent struct { + id string +} + +func (racge removeAllChildrenGroupsEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": groupRemoveAllChildrenGroups, + "id": racge.id, + }, nil +} + +type listChildrenGroupsEvent struct { + id string + groups.PageMeta +} + +func (vcge listChildrenGroupsEvent) Encode() (map[string]interface{}, error) { + val := map[string]interface{}{ + "operation": groupListChildrenGroups, + "id": vcge.id, + "total": vcge.Total, + "offset": vcge.Offset, + "limit": vcge.Limit, + } + if vcge.Name != "" { + val["name"] = vcge.Name + } + if vcge.DomainID != "" { + val["domain_id"] = vcge.DomainID + } + if vcge.Tag != "" { + val["tag"] = vcge.Tag + } + if vcge.Metadata != nil { + val["metadata"] = vcge.Metadata + } + if vcge.Status.String() != "" { + val["status"] = vcge.Status.String() + } + return val, nil +} diff --git a/internal/groups/events/streams.go b/groups/events/streams.go similarity index 50% rename from internal/groups/events/streams.go rename to groups/events/streams.go index b473c5e1f2..20c60a5fd6 100644 --- a/internal/groups/events/streams.go +++ b/groups/events/streams.go @@ -6,35 +6,41 @@ package events import ( "context" + "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/store" - "github.com/absmach/magistrala/pkg/groups" + rmEvents "github.com/absmach/magistrala/pkg/roles/rolemanager/events" ) +const streamID = "magistrala.groups" + var _ groups.Service = (*eventStore)(nil) type eventStore struct { events.Publisher svc groups.Service + rmEvents.RoleManagerEventStore } -// NewEventStoreMiddleware returns wrapper around things service that sends +// NewEventStoreMiddleware returns wrapper around clients service that sends // events to event store. -func NewEventStoreMiddleware(ctx context.Context, svc groups.Service, url, streamID string) (groups.Service, error) { +func New(ctx context.Context, svc groups.Service, url string) (groups.Service, error) { publisher, err := store.NewPublisher(ctx, url, streamID) if err != nil { return nil, err } + rmes := rmEvents.NewRoleManagerEventStore("groups", svc, publisher) return &eventStore{ - svc: svc, - Publisher: publisher, + svc: svc, + Publisher: publisher, + RoleManagerEventStore: rmes, }, nil } -func (es eventStore) CreateGroup(ctx context.Context, session authn.Session, kind string, group groups.Group) (groups.Group, error) { - group, err := es.svc.CreateGroup(ctx, session, kind, group) +func (es eventStore) CreateGroup(ctx context.Context, session authn.Session, group groups.Group) (groups.Group, error) { + group, err := es.svc.CreateGroup(ctx, session, group) if err != nil { return group, err } @@ -83,24 +89,8 @@ func (es eventStore) ViewGroup(ctx context.Context, session authn.Session, id st return group, nil } -func (es eventStore) ViewGroupPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - permissions, err := es.svc.ViewGroupPerms(ctx, session, id) - if err != nil { - return permissions, err - } - event := viewGroupPermsEvent{ - permissions, - } - - if err := es.Publish(ctx, event); err != nil { - return permissions, err - } - - return permissions, nil -} - -func (es eventStore) ListGroups(ctx context.Context, session authn.Session, memberKind, memberID string, pm groups.Page) (groups.Page, error) { - gp, err := es.svc.ListGroups(ctx, session, memberKind, memberID, pm) +func (es eventStore) ListGroups(ctx context.Context, session authn.Session, pm groups.PageMeta) (groups.Page, error) { + gp, err := es.svc.ListGroups(ctx, session, pm) if err != nil { return gp, err } @@ -115,24 +105,17 @@ func (es eventStore) ListGroups(ctx context.Context, session authn.Session, memb return gp, nil } -func (es eventStore) ListMembers(ctx context.Context, session authn.Session, groupID, permission, memberKind string) (groups.MembersPage, error) { - mp, err := es.svc.ListMembers(ctx, session, groupID, permission, memberKind) +func (es eventStore) EnableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { + group, err := es.svc.EnableGroup(ctx, session, id) if err != nil { - return mp, err - } - event := listGroupMembershipEvent{ - groupID, permission, memberKind, - } - - if err := es.Publish(ctx, event); err != nil { - return mp, err + return group, err } - return mp, nil + return es.changeStatus(ctx, group) } -func (es eventStore) EnableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - group, err := es.svc.EnableGroup(ctx, session, id) +func (es eventStore) DisableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { + group, err := es.svc.DisableGroup(ctx, session, id) if err != nil { return group, err } @@ -140,73 +123,100 @@ func (es eventStore) EnableGroup(ctx context.Context, session authn.Session, id return es.changeStatus(ctx, group) } -func (es eventStore) Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) error { - if err := es.svc.Assign(ctx, session, groupID, relation, memberKind, memberIDs...); err != nil { - return err - } - - event := assignEvent{ - groupID: groupID, - relation: relation, - memberKind: memberKind, - memberIDs: memberIDs, +func (es eventStore) changeStatus(ctx context.Context, group groups.Group) (groups.Group, error) { + event := changeStatusGroupEvent{ + id: group.ID, + updatedAt: group.UpdatedAt, + updatedBy: group.UpdatedBy, + status: group.Status.String(), } if err := es.Publish(ctx, event); err != nil { - return err + return group, err } - return nil + return group, nil } -func (es eventStore) Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) error { - if err := es.svc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...); err != nil { +func (es eventStore) DeleteGroup(ctx context.Context, session authn.Session, id string) error { + if err := es.svc.DeleteGroup(ctx, session, id); err != nil { + return err + } + if err := es.Publish(ctx, deleteGroupEvent{id}); err != nil { return err } + return nil +} - event := unassignEvent{ - groupID: groupID, - relation: relation, - memberKind: memberKind, - memberIDs: memberIDs, +func (es eventStore) RetrieveGroupHierarchy(ctx context.Context, session authn.Session, id string, hm groups.HierarchyPageMeta) (groups.HierarchyPage, error) { + g, err := es.svc.RetrieveGroupHierarchy(ctx, session, id, hm) + if err != nil { + return g, err } + if err := es.Publish(ctx, retrieveGroupHierarchyEvent{id, hm}); err != nil { + return g, err + } + return g, nil +} - if err := es.Publish(ctx, event); err != nil { +func (es eventStore) AddParentGroup(ctx context.Context, session authn.Session, id, parentID string) error { + if err := es.svc.AddParentGroup(ctx, session, id, parentID); err != nil { return err } - return es.svc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...) + if err := es.Publish(ctx, addParentGroupEvent{id, parentID}); err != nil { + return err + } + return nil } -func (es eventStore) DisableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - group, err := es.svc.DisableGroup(ctx, session, id) - if err != nil { - return group, err +func (es eventStore) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + if err := es.svc.RemoveParentGroup(ctx, session, id); err != nil { + return err } - - return es.changeStatus(ctx, group) + if err := es.Publish(ctx, removeParentGroupEvent{id}); err != nil { + return err + } + return nil } -func (es eventStore) changeStatus(ctx context.Context, group groups.Group) (groups.Group, error) { - event := changeStatusGroupEvent{ - id: group.ID, - updatedAt: group.UpdatedAt, - updatedBy: group.UpdatedBy, - status: group.Status.String(), +func (es eventStore) AddChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + if err := es.svc.AddChildrenGroups(ctx, session, id, childrenGroupIDs); err != nil { + return err + } + if err := es.Publish(ctx, addChildrenGroupsEvent{id, childrenGroupIDs}); err != nil { + return err } + return nil +} - if err := es.Publish(ctx, event); err != nil { - return group, err +func (es eventStore) RemoveChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + if err := es.svc.RemoveChildrenGroups(ctx, session, id, childrenGroupIDs); err != nil { + return err + } + if err := es.Publish(ctx, removeChildrenGroupsEvent{id, childrenGroupIDs}); err != nil { + return err } - return group, nil + return nil } -func (es eventStore) DeleteGroup(ctx context.Context, session authn.Session, id string) error { - if err := es.svc.DeleteGroup(ctx, session, id); err != nil { +func (es eventStore) RemoveAllChildrenGroups(ctx context.Context, session authn.Session, id string) error { + if err := es.svc.RemoveAllChildrenGroups(ctx, session, id); err != nil { return err } - if err := es.Publish(ctx, deleteGroupEvent{id}); err != nil { + if err := es.Publish(ctx, removeAllChildrenGroupsEvent{id}); err != nil { return err } return nil } + +func (es eventStore) ListChildrenGroups(ctx context.Context, session authn.Session, id string, pm groups.PageMeta) (groups.Page, error) { + g, err := es.svc.ListChildrenGroups(ctx, session, id, pm) + if err != nil { + return g, err + } + if err := es.Publish(ctx, listChildrenGroupsEvent{id, pm}); err != nil { + return g, err + } + return g, nil +} diff --git a/groups/groups.go b/groups/groups.go new file mode 100644 index 0000000000..104980715a --- /dev/null +++ b/groups/groups.go @@ -0,0 +1,302 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package groups + +import ( + "context" + "time" + + "github.com/absmach/magistrala/domains" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/roles" + "github.com/absmach/magistrala/pkg/svcutil" +) + +// MaxLevel represents the maximum group hierarchy level. +const ( + MaxLevel = uint64(20) + MaxPathLength = 20 +) + +// Metadata represents arbitrary JSON. +type Metadata map[string]interface{} + +// 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 domain. +type Group struct { + ID string `json:"id"` + Domain string `json:"domain_id,omitempty"` + Parent string `json:"parent_id,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` + Level int `json:"level,omitempty"` + Path string `json:"path,omitempty"` + Children []*Group `json:"children,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Status Status `json:"status"` + Permissions []string `json:"permissions,omitempty"` +} + +type Member struct { + ID string `json:"id"` + Type string `json:"type"` +} + +// Memberships contains page related metadata as well as list of memberships that +// belong to this page. +type MembersPage struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` + Members []Member `json:"members"` +} + +// Page contains page related metadata as well as list +// of Groups that belong to the page. +type Page struct { + PageMeta + Groups []Group +} + +type HierarchyPageMeta struct { + Level uint64 `json:"level"` + Direction int64 `json:"direction"` // ancestors (+1) or descendants (-1) + // - `true` - result is JSON tree representing groups hierarchy, + // - `false` - result is JSON array of groups. + // ToDo: Tree is build in API layer now, not in service layer. This need to be fine tuned. + Tree bool `json:"tree"` +} +type HierarchyPage struct { + HierarchyPageMeta + Groups []Group +} + +// Repository specifies a group persistence API. +// +//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" --unroll-variadic=false +type Repository 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, pm PageMeta) (Page, error) + + // RetrieveByIDs retrieves group by ids and query. + RetrieveByIDs(ctx context.Context, pm PageMeta, ids ...string) (Page, error) + + RetrieveHierarchy(ctx context.Context, id string, hm HierarchyPageMeta) (HierarchyPage, error) + + // ChangeStatus changes groups status to active or inactive + ChangeStatus(ctx context.Context, group Group) (Group, error) + + // AssignParentGroup assigns parent group id to a given group id + AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error + + // UnassignParentGroup unassign parent group id fr given group id + UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error + + UnassignAllChildrenGroup(ctx context.Context, id string) error + + // Delete a group + Delete(ctx context.Context, groupID string) error + + roles.Repository +} + +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" --unroll-variadic=false +type Service interface { + // CreateGroup creates new group. + CreateGroup(ctx context.Context, session authn.Session, g Group) (Group, error) + + // UpdateGroup updates the group identified by the provided ID. + UpdateGroup(ctx context.Context, session authn.Session, g Group) (Group, error) + + // ViewGroup retrieves data about the group identified by ID. + ViewGroup(ctx context.Context, session authn.Session, id string) (Group, error) + + // ListGroups retrieves + ListGroups(ctx context.Context, session authn.Session, pm PageMeta) (Page, error) + + // EnableGroup logically enables the group identified with the provided ID. + EnableGroup(ctx context.Context, session authn.Session, id string) (Group, error) + + // DisableGroup logically disables the group identified with the provided ID. + DisableGroup(ctx context.Context, session authn.Session, id string) (Group, error) + + // DeleteGroup delete the given group id + DeleteGroup(ctx context.Context, session authn.Session, id string) error + + RetrieveGroupHierarchy(ctx context.Context, session authn.Session, id string, hm HierarchyPageMeta) (HierarchyPage, error) + + AddParentGroup(ctx context.Context, session authn.Session, id, parentID string) error + + RemoveParentGroup(ctx context.Context, session authn.Session, id string) error + + AddChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error + + RemoveChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error + + RemoveAllChildrenGroups(ctx context.Context, session authn.Session, id string) error + + ListChildrenGroups(ctx context.Context, session authn.Session, id string, pm PageMeta) (Page, error) + + roles.RoleManager +} + +const ( + OpCreateGroup svcutil.Operation = iota + OpListGroups + OpViewGroup + OpUpdateGroup + OpEnableGroup + OpDisableGroup + OpRetrieveGroupHierarchy + OpAddParentGroup + OpRemoveParentGroup + OpViewParentGroup + OpAddChildrenGroups + OpRemoveChildrenGroups + OpRemoveAllChildrenGroups + OpListChildrenGroups + OpAddChannels + OpRemoveChannels + OpRemoveAllChannels + OpListChannels + OpAddClients + OpRemoveClients + OpRemoveAllClients + OpListClients + OpDeleteGroup +) + +var expectedOperations = []svcutil.Operation{ + OpCreateGroup, + OpListGroups, + OpViewGroup, + OpUpdateGroup, + OpEnableGroup, + OpDisableGroup, + OpRetrieveGroupHierarchy, + OpAddParentGroup, + OpRemoveParentGroup, + OpViewParentGroup, + OpAddChildrenGroups, + OpRemoveChildrenGroups, + OpRemoveAllChildrenGroups, + OpListChildrenGroups, + OpAddChannels, + OpRemoveChannels, + OpRemoveAllChannels, + OpListChannels, + OpAddClients, + OpRemoveClients, + OpRemoveAllClients, + OpListClients, + OpDeleteGroup, +} + +var operationNames = []string{ + "OpCreateGroup", + "OpListGroups", + "OpViewGroup", + "OpUpdateGroup", + "OpEnableGroup", + "OpDisableGroup", + "OpRetrieveGroupHierarchy", + "OpAddParentGroup", + "OpRemoveParentGroup", + "OpViewParentGroup", + "OpAddChildrenGroups", + "OpRemoveChildrenGroups", + "OpRemoveAllChildrenGroups", + "OpListChildrenGroups", + "OpAddChannels", + "OpRemoveChannels", + "OpRemoveAllChannels", + "OpListChannels", + "OpAddClients", + "OpRemoveClients", + "OpRemoveAllClients", + "OpListClients", + "OpDeleteGroup", +} + +func NewOperationPerm() svcutil.OperationPerm { + return svcutil.NewOperationPerm(expectedOperations, operationNames) +} + +// Below codes should moved out of service, may be can be kept in `cmd//main.go` +const ( + updatePermission = "update_permission" + readPermission = "read_permission" + membershipPermission = "membership_permission" + deletePermission = "delete_permission" + setChildPermission = "set_child_permission" + setParentPermission = "set_parent_permission" + manageRolePermission = "manage_role_permission" + addRoleUsersPermission = "add_role_users_permission" + removeRoleUsersPermission = "remove_role_users_permission" + viewRoleUsersPermission = "view_role_users_permission" +) + +func NewOperationPermissionMap() map[svcutil.Operation]svcutil.Permission { + opPerm := map[svcutil.Operation]svcutil.Permission{ + OpCreateGroup: domains.GroupCreatePermission, + OpListGroups: readPermission, + OpViewGroup: readPermission, + OpUpdateGroup: updatePermission, + OpEnableGroup: updatePermission, + OpDisableGroup: updatePermission, + OpRetrieveGroupHierarchy: readPermission, + OpAddParentGroup: setParentPermission, + OpRemoveParentGroup: setParentPermission, + OpViewParentGroup: readPermission, + OpAddChildrenGroups: setChildPermission, + OpRemoveChildrenGroups: setChildPermission, + OpRemoveAllChildrenGroups: setChildPermission, + OpListChildrenGroups: readPermission, + OpAddChannels: "", + OpRemoveChannels: "", + OpRemoveAllChannels: "", + OpListChannels: "", + OpAddClients: "", + OpRemoveClients: "", + OpRemoveAllClients: "", + OpListClients: "", + OpDeleteGroup: deletePermission, + } + return opPerm +} + +func NewRolesOperationPermissionMap() map[svcutil.Operation]svcutil.Permission { + opPerm := map[svcutil.Operation]svcutil.Permission{ + roles.OpAddRole: manageRolePermission, + roles.OpRemoveRole: manageRolePermission, + roles.OpUpdateRoleName: manageRolePermission, + roles.OpRetrieveRole: manageRolePermission, + roles.OpRetrieveAllRoles: manageRolePermission, + roles.OpRoleAddActions: manageRolePermission, + roles.OpRoleListActions: manageRolePermission, + roles.OpRoleCheckActionsExists: manageRolePermission, + roles.OpRoleRemoveActions: manageRolePermission, + roles.OpRoleRemoveAllActions: manageRolePermission, + roles.OpRoleAddMembers: addRoleUsersPermission, + roles.OpRoleListMembers: viewRoleUsersPermission, + roles.OpRoleCheckMembersExists: viewRoleUsersPermission, + roles.OpRoleRemoveMembers: removeRoleUsersPermission, + roles.OpRoleRemoveAllMembers: manageRolePermission, + } + return opPerm +} diff --git a/groups/middleware/authorization.go b/groups/middleware/authorization.go new file mode 100644 index 0000000000..509a983f47 --- /dev/null +++ b/groups/middleware/authorization.go @@ -0,0 +1,314 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "context" + + "github.com/absmach/magistrala/groups" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/authz" + mgauthz "github.com/absmach/magistrala/pkg/authz" + "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/magistrala/pkg/policies" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" + "github.com/absmach/magistrala/pkg/svcutil" +) + +var errParentUnAuthz = errors.New("parent group unauthorized") + +var _ groups.Service = (*authorizationMiddleware)(nil) + +type authorizationMiddleware struct { + svc groups.Service + authz mgauthz.Authorization + opp svcutil.OperationPerm + rmMW.RoleManagerAuthorizationMiddleware +} + +// AuthorizationMiddleware adds authorization to the clients service. +func AuthorizationMiddleware(entityType string, svc groups.Service, authz mgauthz.Authorization, groupsOpPerm, rolesOpPerm map[svcutil.Operation]svcutil.Permission) (groups.Service, error) { + opp := groups.NewOperationPerm() + if err := opp.AddOperationPermissionMap(groupsOpPerm); err != nil { + return nil, err + } + if err := opp.Validate(); err != nil { + return nil, err + } + + ram, err := rmMW.NewRoleManagerAuthorizationMiddleware(entityType, svc, authz, rolesOpPerm) + if err != nil { + return nil, err + } + return &authorizationMiddleware{ + svc: svc, + authz: authz, + opp: opp, + RoleManagerAuthorizationMiddleware: ram, + }, nil +} + +func (am *authorizationMiddleware) CreateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { + + if err := am.authorize(ctx, groups.OpCreateGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Subject: session.DomainUserID, + Object: session.DomainID, + ObjectType: policies.DomainType, + }); err != nil { + return groups.Group{}, errors.Wrap(errParentUnAuthz, err) + } + + if g.Parent != "" { + if err := am.authorize(ctx, groups.OpAddChildrenGroups, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Subject: session.DomainUserID, + Object: g.Parent, + ObjectType: policies.GroupType, + }); err != nil { + return groups.Group{}, errors.Wrap(errParentUnAuthz, err) + } + } + + return am.svc.CreateGroup(ctx, session, g) +} + +func (am *authorizationMiddleware) UpdateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { + if err := am.authorize(ctx, groups.OpUpdateGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Subject: session.DomainUserID, + Object: g.ID, + ObjectType: policies.GroupType, + }); err != nil { + return groups.Group{}, errors.Wrap(errParentUnAuthz, err) + } + + return am.svc.UpdateGroup(ctx, session, g) +} + +func (am *authorizationMiddleware) ViewGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { + if err := am.authorize(ctx, groups.OpViewGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return groups.Group{}, errors.Wrap(errParentUnAuthz, err) + } + + return am.svc.ViewGroup(ctx, session, id) +} + +func (am *authorizationMiddleware) ListGroups(ctx context.Context, session authn.Session, gm groups.PageMeta) (groups.Page, error) { + err := am.checkSuperAdmin(ctx, session.UserID) + switch { + case err == nil: + session.SuperAdmin = true + default: + if err := am.authorize(ctx, groups.OpListGroups, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Subject: session.DomainUserID, + Object: session.DomainID, + ObjectType: policies.DomainType, + }); err != nil { + return groups.Page{}, errors.Wrap(errParentUnAuthz, err) + } + } + + return am.svc.ListGroups(ctx, session, gm) +} + +func (am *authorizationMiddleware) EnableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { + if err := am.authorize(ctx, groups.OpEnableGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return groups.Group{}, err + } + + return am.svc.EnableGroup(ctx, session, id) +} + +func (am *authorizationMiddleware) DisableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { + if err := am.authorize(ctx, groups.OpDisableGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return groups.Group{}, err + } + + return am.svc.DisableGroup(ctx, session, id) +} + +func (am *authorizationMiddleware) DeleteGroup(ctx context.Context, session authn.Session, id string) error { + if err := am.authorize(ctx, groups.OpDeleteGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return err + } + + return am.svc.DeleteGroup(ctx, session, id) +} + +func (am *authorizationMiddleware) RetrieveGroupHierarchy(ctx context.Context, session authn.Session, id string, hm groups.HierarchyPageMeta) (groups.HierarchyPage, error) { + if err := am.authorize(ctx, groups.OpRetrieveGroupHierarchy, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return groups.HierarchyPage{}, err + } + return am.svc.RetrieveGroupHierarchy(ctx, session, id, hm) +} + +func (am *authorizationMiddleware) AddParentGroup(ctx context.Context, session authn.Session, id, parentID string) error { + if err := am.authorize(ctx, groups.OpAddParentGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return err + } + + if err := am.authorize(ctx, groups.OpAddChildrenGroups, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: parentID, + ObjectType: policies.GroupType, + }); err != nil { + return err + } + return am.svc.AddParentGroup(ctx, session, id, parentID) +} + +func (am *authorizationMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + if err := am.authorize(ctx, groups.OpRemoveParentGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return err + } + + return am.svc.RemoveParentGroup(ctx, session, id) +} + +func (am *authorizationMiddleware) AddChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + if err := am.authorize(ctx, groups.OpAddChildrenGroups, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return err + } + + for _, childID := range childrenGroupIDs { + if err := am.authorize(ctx, groups.OpAddParentGroup, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: childID, + ObjectType: policies.GroupType, + }); err != nil { + return err + } + } + + return am.svc.AddChildrenGroups(ctx, session, id, childrenGroupIDs) +} + +func (am *authorizationMiddleware) RemoveChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + if err := am.authorize(ctx, groups.OpRemoveChildrenGroups, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return err + } + + return am.svc.RemoveChildrenGroups(ctx, session, id, childrenGroupIDs) +} + +func (am *authorizationMiddleware) RemoveAllChildrenGroups(ctx context.Context, session authn.Session, id string) error { + if err := am.authorize(ctx, groups.OpRemoveAllChildrenGroups, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return err + } + return am.svc.RemoveAllChildrenGroups(ctx, session, id) +} + +func (am *authorizationMiddleware) ListChildrenGroups(ctx context.Context, session authn.Session, id string, pm groups.PageMeta) (groups.Page, error) { + if err := am.authorize(ctx, groups.OpListChildrenGroups, mgauthz.PolicyReq{ + Domain: session.DomainID, + SubjectType: policies.UserType, + Subject: session.DomainUserID, + Object: id, + ObjectType: policies.GroupType, + }); err != nil { + return groups.Page{}, err + } + + return am.svc.ListChildrenGroups(ctx, session, id, pm) +} + +func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID string) error { + if err := am.authz.Authorize(ctx, authz.PolicyReq{ + SubjectType: policies.UserType, + Subject: adminID, + Permission: policies.AdminPermission, + ObjectType: policies.PlatformType, + Object: policies.MagistralaObject, + }); err != nil { + return err + } + return nil +} + +func (am *authorizationMiddleware) authorize(ctx context.Context, op svcutil.Operation, pr authz.PolicyReq) error { + perm, err := am.opp.GetPermission(op) + if err != nil { + return err + } + pr.Permission = perm.String() + if err := am.authz.Authorize(ctx, pr); err != nil { + return err + } + return nil +} diff --git a/internal/groups/middleware/doc.go b/groups/middleware/doc.go similarity index 100% rename from internal/groups/middleware/doc.go rename to groups/middleware/doc.go diff --git a/internal/groups/middleware/logging.go b/groups/middleware/logging.go similarity index 53% rename from internal/groups/middleware/logging.go rename to groups/middleware/logging.go index f1840efd14..488a7a0cab 100644 --- a/internal/groups/middleware/logging.go +++ b/groups/middleware/logging.go @@ -8,8 +8,9 @@ import ( "log/slog" "time" + "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/groups" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" ) var _ groups.Service = (*loggingMiddleware)(nil) @@ -17,16 +18,17 @@ var _ groups.Service = (*loggingMiddleware)(nil) type loggingMiddleware struct { logger *slog.Logger svc groups.Service + rmMW.RoleManagerLoggingMiddleware } // LoggingMiddleware adds logging facilities to the groups service. func LoggingMiddleware(svc groups.Service, logger *slog.Logger) groups.Service { - return &loggingMiddleware{logger, svc} + return &loggingMiddleware{logger, svc, rmMW.NewRoleManagerLoggingMiddleware("groups", svc, logger)} } -// CreateGroup logs the create_group request. It logs the group name, id and session and the time it took to complete the request. +// CreateGroup logs the create_group request. It logs the group name, id and token and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) CreateGroup(ctx context.Context, session authn.Session, kind string, group groups.Group) (g groups.Group, err error) { +func (lm *loggingMiddleware) CreateGroup(ctx context.Context, session authn.Session, group groups.Group) (g groups.Group, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), @@ -36,13 +38,13 @@ func (lm *loggingMiddleware) CreateGroup(ctx context.Context, session authn.Sess ), } if err != nil { - args = append(args, slog.Any("error", err)) + args = append(args, slog.String("error", err.Error())) lm.logger.Warn("Create group failed", args...) return } lm.logger.Info("Create group completed successfully", args...) }(time.Now()) - return lm.svc.CreateGroup(ctx, session, kind, group) + return lm.svc.CreateGroup(ctx, session, group) } // UpdateGroup logs the update_group request. It logs the group name, id and the time it took to complete the request. @@ -58,7 +60,7 @@ func (lm *loggingMiddleware) UpdateGroup(ctx context.Context, session authn.Sess ), } if err != nil { - args = append(args, slog.Any("error", err)) + args = append(args, slog.String("error", err.Error())) lm.logger.Warn("Update group failed", args...) return } @@ -79,7 +81,7 @@ func (lm *loggingMiddleware) ViewGroup(ctx context.Context, session authn.Sessio ), } if err != nil { - args = append(args, slog.Any("error", err)) + args = append(args, slog.String("error", err.Error())) lm.logger.Warn("View group failed", args...) return } @@ -88,48 +90,26 @@ func (lm *loggingMiddleware) ViewGroup(ctx context.Context, session authn.Sessio return lm.svc.ViewGroup(ctx, session, id) } -// ViewGroupPerms logs the view_group request. It logs the group id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ViewGroupPerms(ctx context.Context, session authn.Session, id string) (p []string, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("group_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View group permissions failed", args...) - return - } - lm.logger.Info("View group permissions completed successfully", args...) - }(time.Now()) - return lm.svc.ViewGroupPerms(ctx, session, id) -} - // ListGroups logs the list_groups request. It logs the page metadata and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) ListGroups(ctx context.Context, session authn.Session, memberKind, memberID string, gp groups.Page) (cg groups.Page, err error) { +func (lm *loggingMiddleware) ListGroups(ctx context.Context, session authn.Session, pm groups.PageMeta) (cg groups.Page, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.Group("member", - slog.String("id", memberID), - slog.String("kind", memberKind), - ), slog.Group("page", - slog.Uint64("limit", gp.Limit), - slog.Uint64("offset", gp.Offset), + slog.Uint64("limit", pm.Limit), + slog.Uint64("offset", pm.Offset), slog.Uint64("total", cg.Total), ), } if err != nil { - args = append(args, slog.Any("error", err)) + args = append(args, slog.String("error", err.Error())) lm.logger.Warn("List groups failed", args...) return } lm.logger.Info("List groups completed successfully", args...) }(time.Now()) - return lm.svc.ListGroups(ctx, session, memberKind, memberID, gp) + return lm.svc.ListGroups(ctx, session, pm) } // EnableGroup logs the enable_group request. It logs the group name, id and the time it took to complete the request. @@ -144,7 +124,7 @@ func (lm *loggingMiddleware) EnableGroup(ctx context.Context, session authn.Sess ), } if err != nil { - args = append(args, slog.Any("error", err)) + args = append(args, slog.String("error", err.Error())) lm.logger.Warn("Enable group failed", args...) return } @@ -165,7 +145,7 @@ func (lm *loggingMiddleware) DisableGroup(ctx context.Context, session authn.Ses ), } if err != nil { - args = append(args, slog.Any("error", err)) + args = append(args, slog.String("error", err.Error())) lm.logger.Warn("Disable group failed", args...) return } @@ -174,78 +154,143 @@ func (lm *loggingMiddleware) DisableGroup(ctx context.Context, session authn.Ses return lm.svc.DisableGroup(ctx, session, id) } -// ListMembers logs the list_members request. It logs the groupID and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ListMembers(ctx context.Context, session authn.Session, groupID, permission, memberKind string) (mp groups.MembersPage, err error) { +func (lm *loggingMiddleware) DeleteGroup(ctx context.Context, session authn.Session, id string) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("group_id", groupID), - slog.String("permission", permission), - slog.String("member_kind", memberKind), + slog.String("group_id", id), } if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List members failed", args...) + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Delete group failed", args...) return } - lm.logger.Info("List members completed successfully", args...) + lm.logger.Info("Delete group completed successfully", args...) }(time.Now()) - return lm.svc.ListMembers(ctx, session, groupID, permission, memberKind) + return lm.svc.DeleteGroup(ctx, session, id) } -func (lm *loggingMiddleware) Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) { +func (lm *loggingMiddleware) RetrieveGroupHierarchy(ctx context.Context, session authn.Session, id string, hm groups.HierarchyPageMeta) (gp groups.HierarchyPage, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("group_id", groupID), - slog.String("relation", relation), - slog.String("member_kind", memberKind), - slog.Any("member_ids", memberIDs), + slog.String("group_id", id), + slog.Group("page", + slog.Uint64("limit", hm.Level), + slog.Int64("offset", hm.Direction), + slog.Bool("total", hm.Tree), + ), } if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Assign member to group failed", args...) + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Retrieve group hierarchy failed", args...) return } - lm.logger.Info("Assign member to group completed successfully", args...) + lm.logger.Info("Retrieve group hierarchy completed successfully", args...) }(time.Now()) + return lm.svc.RetrieveGroupHierarchy(ctx, session, id, hm) +} - return lm.svc.Assign(ctx, session, groupID, relation, memberKind, memberIDs...) +func (lm *loggingMiddleware) AddParentGroup(ctx context.Context, session authn.Session, id, parentID string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", id), + slog.String("parent_group_id", parentID), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Add parent group failed", args...) + return + } + lm.logger.Info("Add parent group completed successfully", args...) + }(time.Now()) + return lm.svc.AddParentGroup(ctx, session, id, parentID) } -func (lm *loggingMiddleware) Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) { +func (lm *loggingMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("group_id", groupID), - slog.String("relation", relation), - slog.String("member_kind", memberKind), - slog.Any("member_ids", memberIDs), + slog.String("group_id", id), } if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Unassign member from group failed", args...) + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Remove parent group failed", args...) return } - lm.logger.Info("Unassign member from group completed successfully", args...) + lm.logger.Info("Remove parent group completed successfully", args...) }(time.Now()) + return lm.svc.RemoveParentGroup(ctx, session, id) +} - return lm.svc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...) +func (lm *loggingMiddleware) AddChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", id), + slog.Any("children_group_ids", childrenGroupIDs), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Add children groups failed", args...) + return + } + lm.logger.Info("Add parent group completed successfully", args...) + }(time.Now()) + return lm.svc.AddChildrenGroups(ctx, session, id, childrenGroupIDs) } -func (lm *loggingMiddleware) DeleteGroup(ctx context.Context, session authn.Session, id string) (err error) { +func (lm *loggingMiddleware) RemoveChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), slog.String("group_id", id), + slog.Any("children_group_ids", childrenGroupIDs), } if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Delete group failed", args...) + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Remove children groups failed", args...) return } - lm.logger.Info("Delete group completed successfully", args...) + lm.logger.Info("Remove parent group completed successfully", args...) }(time.Now()) - return lm.svc.DeleteGroup(ctx, session, id) + return lm.svc.RemoveChildrenGroups(ctx, session, id, childrenGroupIDs) +} + +func (lm *loggingMiddleware) RemoveAllChildrenGroups(ctx context.Context, session authn.Session, id string) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", id), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("Remove all children groups failed", args...) + return + } + lm.logger.Info("Remove all parent group completed successfully", args...) + }(time.Now()) + return lm.svc.RemoveAllChildrenGroups(ctx, session, id) +} + +func (lm *loggingMiddleware) ListChildrenGroups(ctx context.Context, session authn.Session, id string, pm groups.PageMeta) (gp groups.Page, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("group_id", id), + slog.Group("page", + slog.Uint64("limit", pm.Limit), + slog.Uint64("offset", pm.Offset), + slog.Uint64("total", gp.Total), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn("List children groups failed", args...) + return + } + lm.logger.Info("List children groups completed successfully", args...) + }(time.Now()) + return lm.svc.ListChildrenGroups(ctx, session, id, pm) } diff --git a/internal/groups/middleware/metrics.go b/groups/middleware/metrics.go similarity index 52% rename from internal/groups/middleware/metrics.go rename to groups/middleware/metrics.go index 7d6fa13f7f..e5379b7cb4 100644 --- a/internal/groups/middleware/metrics.go +++ b/groups/middleware/metrics.go @@ -7,8 +7,9 @@ import ( "context" "time" + "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/groups" + rmMW "github.com/absmach/magistrala/pkg/roles/rolemanager/middleware" "github.com/go-kit/kit/metrics" ) @@ -18,24 +19,27 @@ type metricsMiddleware struct { counter metrics.Counter latency metrics.Histogram svc groups.Service + rmMW.RoleManagerMetricsMiddleware } // MetricsMiddleware instruments policies service by tracking request count and latency. func MetricsMiddleware(svc groups.Service, counter metrics.Counter, latency metrics.Histogram) groups.Service { + rmm := rmMW.NewRoleManagerMetricsMiddleware("group", svc, counter, latency) return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, + counter: counter, + latency: latency, + svc: svc, + RoleManagerMetricsMiddleware: rmm, } } // CreateGroup instruments CreateGroup method with metrics. -func (ms *metricsMiddleware) CreateGroup(ctx context.Context, session authn.Session, kind string, g groups.Group) (groups.Group, error) { +func (ms *metricsMiddleware) CreateGroup(ctx context.Context, session authn.Session, 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, session, kind, g) + return ms.svc.CreateGroup(ctx, session, g) } // UpdateGroup instruments UpdateGroup method with metrics. @@ -56,22 +60,13 @@ func (ms *metricsMiddleware) ViewGroup(ctx context.Context, session authn.Sessio return ms.svc.ViewGroup(ctx, session, id) } -// ViewGroupPerms instruments ViewGroup method with metrics. -func (ms *metricsMiddleware) ViewGroupPerms(ctx context.Context, session authn.Session, id string) (p []string, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_group_perms").Add(1) - ms.latency.With("method", "view_group_perms").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ViewGroupPerms(ctx, session, id) -} - // ListGroups instruments ListGroups method with metrics. -func (ms *metricsMiddleware) ListGroups(ctx context.Context, session authn.Session, memberKind, memberID string, gp groups.Page) (cg groups.Page, err error) { +func (ms *metricsMiddleware) ListGroups(ctx context.Context, session authn.Session, pm groups.PageMeta) (cg groups.Page, 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, session, memberKind, memberID, gp) + return ms.svc.ListGroups(ctx, session, pm) } // EnableGroup instruments EnableGroup method with metrics. @@ -92,39 +87,66 @@ func (ms *metricsMiddleware) DisableGroup(ctx context.Context, session authn.Ses return ms.svc.DisableGroup(ctx, session, id) } -// ListMembers instruments ListMembers method with metrics. -func (ms *metricsMiddleware) ListMembers(ctx context.Context, session authn.Session, groupID, permission, memberKind string) (mp groups.MembersPage, err error) { +func (ms *metricsMiddleware) DeleteGroup(ctx context.Context, session authn.Session, id string) (err error) { + defer func(begin time.Time) { + ms.counter.With("method", "delete_group").Add(1) + ms.latency.With("method", "delete_group").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.DeleteGroup(ctx, session, id) +} + +func (ms *metricsMiddleware) RetrieveGroupHierarchy(ctx context.Context, session authn.Session, id string, hm groups.HierarchyPageMeta) (groups.HierarchyPage, error) { + defer func(begin time.Time) { + ms.counter.With("method", "list_parent_groups").Add(1) + ms.latency.With("method", "list_parent_groups").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.RetrieveGroupHierarchy(ctx, session, id, hm) +} + +func (ms *metricsMiddleware) AddParentGroup(ctx context.Context, session authn.Session, id, parentID string) 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()) + ms.counter.With("method", "add_parent_group").Add(1) + ms.latency.With("method", "add_parent_group").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.ListMembers(ctx, session, groupID, permission, memberKind) + return ms.svc.AddParentGroup(ctx, session, id, parentID) } -// Assign instruments Assign method with metrics. -func (ms *metricsMiddleware) Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) { +func (ms *metricsMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { defer func(begin time.Time) { - ms.counter.With("method", "assign").Add(1) - ms.latency.With("method", "assign").Observe(time.Since(begin).Seconds()) + ms.counter.With("method", "remove_parent_group").Add(1) + ms.latency.With("method", "remove_parent_group").Observe(time.Since(begin).Seconds()) }(time.Now()) + return ms.svc.RemoveParentGroup(ctx, session, id) +} - return ms.svc.Assign(ctx, session, groupID, relation, memberKind, memberIDs...) +func (ms *metricsMiddleware) AddChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + defer func(begin time.Time) { + ms.counter.With("method", "add_children_groups").Add(1) + ms.latency.With("method", "add_children_groups").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.AddChildrenGroups(ctx, session, id, childrenGroupIDs) } -// Unassign instruments Unassign method with metrics. -func (ms *metricsMiddleware) Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) { +func (ms *metricsMiddleware) RemoveChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { defer func(begin time.Time) { - ms.counter.With("method", "unassign").Add(1) - ms.latency.With("method", "unassign").Observe(time.Since(begin).Seconds()) + ms.counter.With("method", "remove_children_groups").Add(1) + ms.latency.With("method", "remove_children_groups").Observe(time.Since(begin).Seconds()) }(time.Now()) + return ms.svc.RemoveChildrenGroups(ctx, session, id, childrenGroupIDs) +} - return ms.svc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...) +func (ms *metricsMiddleware) RemoveAllChildrenGroups(ctx context.Context, session authn.Session, id string) error { + defer func(begin time.Time) { + ms.counter.With("method", "remove_all_children_groups").Add(1) + ms.latency.With("method", "remove_all_children_groups").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.RemoveAllChildrenGroups(ctx, session, id) } -func (ms *metricsMiddleware) DeleteGroup(ctx context.Context, session authn.Session, id string) (err error) { +func (ms *metricsMiddleware) ListChildrenGroups(ctx context.Context, session authn.Session, id string, pm groups.PageMeta) (groups.Page, error) { defer func(begin time.Time) { - ms.counter.With("method", "delete_group").Add(1) - ms.latency.With("method", "delete_group").Observe(time.Since(begin).Seconds()) + ms.counter.With("method", "list_children_groups").Add(1) + ms.latency.With("method", "list_children_groups").Observe(time.Since(begin).Seconds()) }(time.Now()) - return ms.svc.DeleteGroup(ctx, session, id) + return ms.svc.ListChildrenGroups(ctx, session, id, pm) } diff --git a/things/mocks/doc.go b/groups/mocks/doc.go similarity index 100% rename from things/mocks/doc.go rename to groups/mocks/doc.go diff --git a/groups/mocks/groups_client.go b/groups/mocks/groups_client.go new file mode 100644 index 0000000000..fb92cf927b --- /dev/null +++ b/groups/mocks/groups_client.go @@ -0,0 +1,118 @@ +// Copyright (c) Abstract Machines + +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" + + v1 "github.com/absmach/magistrala/internal/grpc/common/v1" +) + +// GroupsServiceClient is an autogenerated mock type for the GroupsServiceClient type +type GroupsServiceClient struct { + mock.Mock +} + +type GroupsServiceClient_Expecter struct { + mock *mock.Mock +} + +func (_m *GroupsServiceClient) EXPECT() *GroupsServiceClient_Expecter { + return &GroupsServiceClient_Expecter{mock: &_m.Mock} +} + +// RetrieveEntity provides a mock function with given fields: ctx, in, opts +func (_m *GroupsServiceClient) RetrieveEntity(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption) (*v1.RetrieveEntityRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for RetrieveEntity") + } + + var r0 *v1.RetrieveEntityRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v1.RetrieveEntityReq, ...grpc.CallOption) (*v1.RetrieveEntityRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *v1.RetrieveEntityReq, ...grpc.CallOption) *v1.RetrieveEntityRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1.RetrieveEntityRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v1.RetrieveEntityReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GroupsServiceClient_RetrieveEntity_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveEntity' +type GroupsServiceClient_RetrieveEntity_Call struct { + *mock.Call +} + +// RetrieveEntity is a helper method to define mock.On call +// - ctx context.Context +// - in *v1.RetrieveEntityReq +// - opts ...grpc.CallOption +func (_e *GroupsServiceClient_Expecter) RetrieveEntity(ctx interface{}, in interface{}, opts ...interface{}) *GroupsServiceClient_RetrieveEntity_Call { + return &GroupsServiceClient_RetrieveEntity_Call{Call: _e.mock.On("RetrieveEntity", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *GroupsServiceClient_RetrieveEntity_Call) Run(run func(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption)) *GroupsServiceClient_RetrieveEntity_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*v1.RetrieveEntityReq), variadicArgs...) + }) + return _c +} + +func (_c *GroupsServiceClient_RetrieveEntity_Call) Return(_a0 *v1.RetrieveEntityRes, _a1 error) *GroupsServiceClient_RetrieveEntity_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GroupsServiceClient_RetrieveEntity_Call) RunAndReturn(run func(context.Context, *v1.RetrieveEntityReq, ...grpc.CallOption) (*v1.RetrieveEntityRes, error)) *GroupsServiceClient_RetrieveEntity_Call { + _c.Call.Return(run) + return _c +} + +// NewGroupsServiceClient creates a new instance of GroupsServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewGroupsServiceClient(t interface { + mock.TestingT + Cleanup(func()) +}) *GroupsServiceClient { + mock := &GroupsServiceClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/groups/mocks/repository.go b/groups/mocks/repository.go new file mode 100644 index 0000000000..a4951ad168 --- /dev/null +++ b/groups/mocks/repository.go @@ -0,0 +1,764 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + groups "github.com/absmach/magistrala/groups" + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Repository is an autogenerated mock type for the Repository type +type Repository struct { + mock.Mock +} + +// AddRoles provides a mock function with given fields: ctx, rps +func (_m *Repository) AddRoles(ctx context.Context, rps []roles.RoleProvision) ([]roles.Role, error) { + ret := _m.Called(ctx, rps) + + if len(ret) == 0 { + panic("no return value specified for AddRoles") + } + + var r0 []roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) ([]roles.Role, error)); ok { + return rf(ctx, rps) + } + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) []roles.Role); ok { + r0 = rf(ctx, rps) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.Role) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []roles.RoleProvision) error); ok { + r1 = rf(ctx, rps) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AssignParentGroup provides a mock function with given fields: ctx, parentGroupID, groupIDs +func (_m *Repository) AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { + ret := _m.Called(ctx, parentGroupID, groupIDs) + + if len(ret) == 0 { + panic("no return value specified for AssignParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { + r0 = rf(ctx, parentGroupID, groupIDs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ChangeStatus provides a mock function with given fields: ctx, group +func (_m *Repository) ChangeStatus(ctx context.Context, group groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, group) + + if len(ret) == 0 { + panic("no return value specified for ChangeStatus") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { + return rf(ctx, group) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { + r0 = rf(ctx, group) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { + r1 = rf(ctx, group) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, groupID +func (_m *Repository) Delete(ctx context.Context, groupID string) error { + ret := _m.Called(ctx, groupID) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, groupID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, members +func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, members string) error { + ret := _m.Called(ctx, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRoles provides a mock function with given fields: ctx, roleIDs +func (_m *Repository) RemoveRoles(ctx context.Context, roleIDs []string) error { + ret := _m.Called(ctx, roleIDs) + + if len(ret) == 0 { + panic("no return value specified for RemoveRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { + r0 = rf(ctx, roleIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAll provides a mock function with given fields: ctx, pm +func (_m *Repository) RetrieveAll(ctx context.Context, pm groups.PageMeta) (groups.Page, error) { + ret := _m.Called(ctx, pm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAll") + } + + var r0 groups.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.PageMeta) (groups.Page, error)); ok { + return rf(ctx, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.PageMeta) groups.Page); ok { + r0 = rf(ctx, pm) + } else { + r0 = ret.Get(0).(groups.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.PageMeta) error); ok { + r1 = rf(ctx, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, entityID, limit, offset +func (_m *Repository) RetrieveAllRoles(ctx context.Context, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByID provides a mock function with given fields: ctx, id +func (_m *Repository) RetrieveByID(ctx context.Context, id string) (groups.Group, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByID") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (groups.Group, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) groups.Group); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveByIDs provides a mock function with given fields: ctx, pm, ids +func (_m *Repository) RetrieveByIDs(ctx context.Context, pm groups.PageMeta, ids ...string) (groups.Page, error) { + ret := _m.Called(ctx, pm, ids) + + if len(ret) == 0 { + panic("no return value specified for RetrieveByIDs") + } + + var r0 groups.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.PageMeta, ...string) (groups.Page, error)); ok { + return rf(ctx, pm, ids...) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.PageMeta, ...string) groups.Page); ok { + r0 = rf(ctx, pm, ids...) + } else { + r0 = ret.Get(0).(groups.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.PageMeta, ...string) error); ok { + r1 = rf(ctx, pm, ids...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveEntitiesRolesActionsMembers provides a mock function with given fields: ctx, entityIDs +func (_m *Repository) RetrieveEntitiesRolesActionsMembers(ctx context.Context, entityIDs []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error) { + ret := _m.Called(ctx, entityIDs) + + if len(ret) == 0 { + panic("no return value specified for RetrieveEntitiesRolesActionsMembers") + } + + var r0 []roles.EntityActionRole + var r1 []roles.EntityMemberRole + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error)); ok { + return rf(ctx, entityIDs) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []roles.EntityActionRole); ok { + r0 = rf(ctx, entityIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.EntityActionRole) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) []roles.EntityMemberRole); ok { + r1 = rf(ctx, entityIDs) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]roles.EntityMemberRole) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, []string) error); ok { + r2 = rf(ctx, entityIDs) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// RetrieveHierarchy provides a mock function with given fields: ctx, id, hm +func (_m *Repository) RetrieveHierarchy(ctx context.Context, id string, hm groups.HierarchyPageMeta) (groups.HierarchyPage, error) { + ret := _m.Called(ctx, id, hm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveHierarchy") + } + + var r0 groups.HierarchyPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, groups.HierarchyPageMeta) (groups.HierarchyPage, error)); ok { + return rf(ctx, id, hm) + } + if rf, ok := ret.Get(0).(func(context.Context, string, groups.HierarchyPageMeta) groups.HierarchyPage); ok { + r0 = rf(ctx, id, hm) + } else { + r0 = ret.Get(0).(groups.HierarchyPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, groups.HierarchyPageMeta) error); ok { + r1 = rf(ctx, id, hm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRole provides a mock function with given fields: ctx, roleID +func (_m *Repository) RetrieveRole(ctx context.Context, roleID string) (roles.Role, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (roles.Role, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) roles.Role); ok { + r0 = rf(ctx, roleID) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRoleByEntityIDAndName provides a mock function with given fields: ctx, entityID, roleName +func (_m *Repository) RetrieveRoleByEntityIDAndName(ctx context.Context, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRoleByEntityIDAndName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (roles.Role, error)); ok { + return rf(ctx, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) roles.Role); ok { + r0 = rf(ctx, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleAddActions(ctx context.Context, role roles.Role, actions []string) ([]string, error) { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleAddMembers(ctx context.Context, role roles.Role, members []string) ([]string, error) { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, members) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, roleID, actions +func (_m *Repository) RoleCheckActionsExists(ctx context.Context, roleID string, actions []string) (bool, error) { + ret := _m.Called(ctx, roleID, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, roleID, members +func (_m *Repository) RoleCheckMembersExists(ctx context.Context, roleID string, members []string) (bool, error) { + ret := _m.Called(ctx, roleID, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, members) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, roleID +func (_m *Repository) RoleListActions(ctx context.Context, roleID string) ([]string, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []string); ok { + r0 = rf(ctx, roleID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, roleID, limit, offset +func (_m *Repository) RoleListMembers(ctx context.Context, roleID string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, roleID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, roleID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, roleID, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, roleID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleRemoveActions(ctx context.Context, role roles.Role, actions []string) error { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllActions(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllMembers(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleRemoveMembers(ctx context.Context, role roles.Role, members []string) error { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Save provides a mock function with given fields: ctx, g +func (_m *Repository) Save(ctx context.Context, g groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, g) + + if len(ret) == 0 { + panic("no return value specified for Save") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { + return rf(ctx, g) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { + r0 = rf(ctx, g) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { + r1 = rf(ctx, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnassignAllChildrenGroup provides a mock function with given fields: ctx, id +func (_m *Repository) UnassignAllChildrenGroup(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for UnassignAllChildrenGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnassignParentGroup provides a mock function with given fields: ctx, parentGroupID, groupIDs +func (_m *Repository) UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { + ret := _m.Called(ctx, parentGroupID, groupIDs) + + if len(ret) == 0 { + panic("no return value specified for UnassignParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { + r0 = rf(ctx, parentGroupID, groupIDs...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: ctx, g +func (_m *Repository) Update(ctx context.Context, g groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, g) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { + return rf(ctx, g) + } + if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { + r0 = rf(ctx, g) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { + r1 = rf(ctx, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateRole provides a mock function with given fields: ctx, ro +func (_m *Repository) UpdateRole(ctx context.Context, ro roles.Role) (roles.Role, error) { + ret := _m.Called(ctx, ro) + + if len(ret) == 0 { + panic("no return value specified for UpdateRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) (roles.Role, error)); ok { + return rf(ctx, ro) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) roles.Role); ok { + r0 = rf(ctx, ro) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role) error); ok { + r1 = rf(ctx, ro) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *Repository { + mock := &Repository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/groups/mocks/service.go b/groups/mocks/service.go new file mode 100644 index 0000000000..9f679ebffc --- /dev/null +++ b/groups/mocks/service.go @@ -0,0 +1,792 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + authn "github.com/absmach/magistrala/pkg/authn" + + groups "github.com/absmach/magistrala/groups" + + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// AddChildrenGroups provides a mock function with given fields: ctx, session, id, childrenGroupIDs +func (_m *Service) AddChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + ret := _m.Called(ctx, session, id, childrenGroupIDs) + + if len(ret) == 0 { + panic("no return value specified for AddChildrenGroups") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, []string) error); ok { + r0 = rf(ctx, session, id, childrenGroupIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AddParentGroup provides a mock function with given fields: ctx, session, id, parentID +func (_m *Service) AddParentGroup(ctx context.Context, session authn.Session, id string, parentID string) error { + ret := _m.Called(ctx, session, id, parentID) + + if len(ret) == 0 { + panic("no return value specified for AddParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, id, parentID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AddRole provides a mock function with given fields: ctx, session, entityID, roleName, optionalActions, optionalMembers +func (_m *Service) AddRole(ctx context.Context, session authn.Session, entityID string, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName, optionalActions, optionalMembers) + + if len(ret) == 0 { + panic("no return value specified for AddRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateGroup provides a mock function with given fields: ctx, session, g +func (_m *Service) CreateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, session, g) + + if len(ret) == 0 { + panic("no return value specified for CreateGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, groups.Group) (groups.Group, error)); ok { + return rf(ctx, session, g) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, groups.Group) groups.Group); ok { + r0 = rf(ctx, session, g) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, groups.Group) error); ok { + r1 = rf(ctx, session, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteGroup provides a mock function with given fields: ctx, session, id +func (_m *Service) DeleteGroup(ctx context.Context, session authn.Session, id string) error { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for DeleteGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DisableGroup provides a mock function with given fields: ctx, session, id +func (_m *Service) DisableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for DisableGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (groups.Group, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) groups.Group); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EnableGroup provides a mock function with given fields: ctx, session, id +func (_m *Service) EnableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for EnableGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (groups.Group, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) groups.Group); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAvailableActions provides a mock function with given fields: ctx, session +func (_m *Service) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + ret := _m.Called(ctx, session) + + if len(ret) == 0 { + panic("no return value specified for ListAvailableActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) ([]string, error)); ok { + return rf(ctx, session) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) []string); ok { + r0 = rf(ctx, session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session) error); ok { + r1 = rf(ctx, session) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListChildrenGroups provides a mock function with given fields: ctx, session, id, pm +func (_m *Service) ListChildrenGroups(ctx context.Context, session authn.Session, id string, pm groups.PageMeta) (groups.Page, error) { + ret := _m.Called(ctx, session, id, pm) + + if len(ret) == 0 { + panic("no return value specified for ListChildrenGroups") + } + + var r0 groups.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, groups.PageMeta) (groups.Page, error)); ok { + return rf(ctx, session, id, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, groups.PageMeta) groups.Page); ok { + r0 = rf(ctx, session, id, pm) + } else { + r0 = ret.Get(0).(groups.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, groups.PageMeta) error); ok { + r1 = rf(ctx, session, id, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListGroups provides a mock function with given fields: ctx, session, pm +func (_m *Service) ListGroups(ctx context.Context, session authn.Session, pm groups.PageMeta) (groups.Page, error) { + ret := _m.Called(ctx, session, pm) + + if len(ret) == 0 { + panic("no return value specified for ListGroups") + } + + var r0 groups.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, groups.PageMeta) (groups.Page, error)); ok { + return rf(ctx, session, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, groups.PageMeta) groups.Page); ok { + r0 = rf(ctx, session, pm) + } else { + r0 = ret.Get(0).(groups.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, groups.PageMeta) error); ok { + r1 = rf(ctx, session, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveAllChildrenGroups provides a mock function with given fields: ctx, session, id +func (_m *Service) RemoveAllChildrenGroups(ctx context.Context, session authn.Session, id string) error { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for RemoveAllChildrenGroups") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveChildrenGroups provides a mock function with given fields: ctx, session, id, childrenGroupIDs +func (_m *Service) RemoveChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + ret := _m.Called(ctx, session, id, childrenGroupIDs) + + if len(ret) == 0 { + panic("no return value specified for RemoveChildrenGroups") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, []string) error); ok { + r0 = rf(ctx, session, id, childrenGroupIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID +func (_m *Service) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { + ret := _m.Called(ctx, session, memberID) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, memberID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveParentGroup provides a mock function with given fields: ctx, session, id +func (_m *Service) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for RemoveParentGroup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RemoveRole(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RemoveRole") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, session, entityID, limit, offset +func (_m *Service) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, session, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, session, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, session, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveGroupHierarchy provides a mock function with given fields: ctx, session, id, hm +func (_m *Service) RetrieveGroupHierarchy(ctx context.Context, session authn.Session, id string, hm groups.HierarchyPageMeta) (groups.HierarchyPage, error) { + ret := _m.Called(ctx, session, id, hm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveGroupHierarchy") + } + + var r0 groups.HierarchyPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, groups.HierarchyPageMeta) (groups.HierarchyPage, error)); ok { + return rf(ctx, session, id, hm) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, groups.HierarchyPageMeta) groups.HierarchyPage); ok { + r0 = rf(ctx, session, id, hm) + } else { + r0 = ret.Get(0).(groups.HierarchyPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, groups.HierarchyPageMeta) error); ok { + r1 = rf(ctx, session, id, hm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RetrieveRole(ctx context.Context, session authn.Session, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleAddActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleAddMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleListActions(ctx context.Context, session authn.Session, entityID string, roleName string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) []string); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, session, entityID, roleName, limit, offset +func (_m *Service) RoleListMembers(ctx context.Context, session authn.Session, entityID string, roleName string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, session, entityID, roleName, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, session, entityID, roleName, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *Service) RoleRemoveActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) error { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *Service) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *Service) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) error { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateGroup provides a mock function with given fields: ctx, session, g +func (_m *Service) UpdateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { + ret := _m.Called(ctx, session, g) + + if len(ret) == 0 { + panic("no return value specified for UpdateGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, groups.Group) (groups.Group, error)); ok { + return rf(ctx, session, g) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, groups.Group) groups.Group); ok { + r0 = rf(ctx, session, g) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, groups.Group) error); ok { + r1 = rf(ctx, session, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateRoleName provides a mock function with given fields: ctx, session, entityID, oldRoleName, newRoleName +func (_m *Service) UpdateRoleName(ctx context.Context, session authn.Session, entityID string, oldRoleName string, newRoleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, oldRoleName, newRoleName) + + if len(ret) == 0 { + panic("no return value specified for UpdateRoleName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, oldRoleName, newRoleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, string) error); ok { + r1 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ViewGroup provides a mock function with given fields: ctx, session, id +func (_m *Service) ViewGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { + ret := _m.Called(ctx, session, id) + + if len(ret) == 0 { + panic("no return value specified for ViewGroup") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (groups.Group, error)); ok { + return rf(ctx, session, id) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) groups.Group); ok { + r0 = rf(ctx, session, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { + r1 = rf(ctx, session, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/groups/page.go b/groups/page.go new file mode 100644 index 0000000000..6226244a18 --- /dev/null +++ b/groups/page.go @@ -0,0 +1,21 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package groups + +// PageMeta contains page metadata that helps navigation. +type PageMeta struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + Level int `json:"level,omitempty"` + Path string `json:"path,omitempty"` + DomainID string `json:"domain_id,omitempty"` + Tag string `json:"tag,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` + Status Status `json:"status,omitempty"` + Permission string + ListPerms bool +} diff --git a/internal/groups/postgres/doc.go b/groups/postgres/doc.go similarity index 100% rename from internal/groups/postgres/doc.go rename to groups/postgres/doc.go diff --git a/internal/groups/postgres/groups.go b/groups/postgres/groups.go similarity index 51% rename from internal/groups/postgres/groups.go rename to groups/postgres/groups.go index 15d9b3973d..b5ec6a4f0b 100644 --- a/internal/groups/postgres/groups.go +++ b/groups/postgres/groups.go @@ -11,32 +11,49 @@ import ( "strings" "time" + mggroups "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/groups" - mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/postgres" + rolesPostgres "github.com/absmach/magistrala/pkg/roles/repo/postgres" "github.com/jmoiron/sqlx" ) var _ mggroups.Repository = (*groupRepository)(nil) +const ( + rolesTableNamePrefix = "groups" + entityTableName = "groups" + entityIDColumnName = "id" +) + +var ( + errParentGroupID = errors.New("parent group id is empty") + errParentGroupPath = errors.New("parent group path is empty") + errParentSuffix = errors.New("parent group path doesn't have parent id suffix") +) + type groupRepository struct { db postgres.Database + rolesPostgres.Repository } // New instantiates a PostgreSQL implementation of group // repository. func New(db postgres.Database) mggroups.Repository { + roleRepo := rolesPostgres.NewRepository(db, rolesTableNamePrefix, entityTableName, entityIDColumnName) + return &groupRepository{ - db: db, + db: db, + Repository: roleRepo, } } func (repo groupRepository) Save(ctx context.Context, g mggroups.Group) (mggroups.Group, error) { - q := `INSERT INTO groups (name, description, id, domain_id, parent_id, metadata, created_at, status) - VALUES (:name, :description, :id, :domain_id, :parent_id, :metadata, :created_at, :status) - RETURNING id, name, description, domain_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, status;` + q, err := repo.getInsertQuery(ctx, g) + if err != nil { + return mggroups.Group{}, errors.Wrap(repoerr.ErrCreateEntity, err) + } dbg, err := toDBGroup(g) if err != nil { return mggroups.Group{}, err @@ -145,24 +162,18 @@ func (repo groupRepository) RetrieveByID(ctx context.Context, id string) (mggrou return toGroup(dbg) } -func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) (mggroups.Page, error) { +func (repo groupRepository) RetrieveAll(ctx context.Context, pm mggroups.PageMeta) (mggroups.Page, error) { var q string - query := buildQuery(gm) + query := buildQuery(pm) - if gm.ParentID != "" { - q = buildHierachy(gm) - } - if gm.ParentID == "" { - q = `SELECT DISTINCT g.id, g.domain_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, - g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g` - } - q = fmt.Sprintf("%s %s ORDER BY g.created_at LIMIT :limit OFFSET :offset;", q, query) + q = fmt.Sprintf(`SELECT DISTINCT g.id, g.domain_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, + g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g %s ORDER BY g.created_at LIMIT :limit OFFSET :offset;`, query) - dbPage, err := toDBGroupPage(gm) + dbPageMeta, err := toDBGroupPageMeta(pm) if err != nil { return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) + rows, err := repo.db.NamedQueryContext(ctx, q, dbPageMeta) if err != nil { return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } @@ -173,44 +184,39 @@ func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) ( return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - cq := "SELECT COUNT(*) FROM groups g" - if query != "" { - cq = fmt.Sprintf(" %s %s", cq, query) - } + cq := fmt.Sprintf(` SELECT COUNT(*) AS total_count + FROM ( + SELECT DISTINCT g.id, g.domain_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, + g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g %s + ) AS subquery; + `, query) - total, err := postgres.Total(ctx, repo.db, cq, dbPage) + total, err := postgres.Total(ctx, repo.db, cq, dbPageMeta) if err != nil { return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - page := gm - page.Groups = items + page := mggroups.Page{PageMeta: pm} page.Total = total - + page.Groups = items return page, nil } -func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, ids ...string) (mggroups.Page, error) { +func (repo groupRepository) RetrieveByIDs(ctx context.Context, pm mggroups.PageMeta, ids ...string) (mggroups.Page, error) { var q string - if (len(ids) == 0) && (gm.PageMeta.DomainID == "") { - return mggroups.Page{PageMeta: mggroups.PageMeta{Offset: gm.Offset, Limit: gm.Limit}}, nil + if (len(ids) == 0) && (pm.DomainID == "") { + return mggroups.Page{PageMeta: mggroups.PageMeta{Offset: pm.Offset, Limit: pm.Limit}}, nil } - query := buildQuery(gm, ids...) + query := buildQuery(pm, ids...) - if gm.ParentID != "" { - q = buildHierachy(gm) - } - if gm.ParentID == "" { - q = `SELECT DISTINCT g.id, g.domain_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, - g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g` - } - q = fmt.Sprintf("%s %s ORDER BY g.created_at LIMIT :limit OFFSET :offset;", q, query) + q = fmt.Sprintf(`SELECT DISTINCT g.id, g.domain_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, + g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g %s ORDER BY g.created_at LIMIT :limit OFFSET :offset;`, query) - dbPage, err := toDBGroupPage(gm) + dbPageMeta, err := toDBGroupPageMeta(pm) if err != nil { return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) + rows, err := repo.db.NamedQueryContext(ctx, q, dbPageMeta) if err != nil { return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } @@ -221,69 +227,274 @@ func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - cq := "SELECT COUNT(*) FROM groups g" - if query != "" { - cq = fmt.Sprintf(" %s %s", cq, query) - } + cq := fmt.Sprintf(` SELECT COUNT(*) AS total_count + FROM ( + SELECT DISTINCT g.id, g.domain_id, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description, + g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g %s + ) AS subquery; + `, query) - total, err := postgres.Total(ctx, repo.db, cq, dbPage) + total, err := postgres.Total(ctx, repo.db, cq, dbPageMeta) if err != nil { return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } - page := gm - page.Groups = items + page := mggroups.Page{PageMeta: pm} page.Total = total - + page.Groups = items return page, nil } -func (repo groupRepository) AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { +func (repo groupRepository) RetrieveHierarchy(ctx context.Context, id string, hm mggroups.HierarchyPageMeta) (mggroups.HierarchyPage, error) { + query := "" + switch { + // ancestors + case hm.Direction >= 0: + query = ` + SELECT + g.id, + COALESCE(g.parent_id, '') AS parent_id, + g.domain_id, + g.name, + g.description, + g.metadata, + g.created_at, + g.updated_at, + g.updated_by, + g.status, + g.path, + nlevel(g.path) AS level + FROM + groups g + WHERE + g.path @> (SELECT path FROM groups WHERE id = :id LIMIT 1); + ` + // descendants + case hm.Direction < 0: + fallthrough + default: + query = ` + SELECT + g.id, + COALESCE(g.parent_id, '') AS parent_id, + g.domain_id, + g.name, + g.description, + g.metadata, + g.created_at, + g.updated_at, + g.updated_by, + g.status, + g.path, + nlevel(g.path) AS level + FROM + groups g + WHERE + g.path <@ (SELECT path FROM groups WHERE id = :id LIMIT 1); + ` + } + parameters := map[string]interface{}{ + "id": id, + "level": hm.Level, + } + rows, err := repo.db.NamedQueryContext(ctx, query, parameters) + if err != nil { + return mggroups.HierarchyPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + } + defer rows.Close() + + items, err := repo.processRows(rows) + if err != nil { + return mggroups.HierarchyPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) + } + + return mggroups.HierarchyPage{HierarchyPageMeta: hm, Groups: items}, nil +} + +func (repo groupRepository) AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) (err error) { if len(groupIDs) == 0 { return nil } - var updateColumns []string - for _, groupID := range groupIDs { - updateColumns = append(updateColumns, fmt.Sprintf("('%s', '%s') ", groupID, parentGroupID)) + + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) } - uc := strings.Join(updateColumns, ",") - query := fmt.Sprintf(` - UPDATE groups AS g SET - parent_id = u.parent_group_id - FROM (VALUES - %s - ) AS u(id, parent_group_id) - WHERE g.id = u.id; - `, uc) - - row, err := repo.db.QueryContext(ctx, query) + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(err, errRollback) + } + } + }() + + pq := `SELECT id, path FROM groups WHERE id = $1 LIMIT 1;` + rows, err := tx.Queryx(pq, parentGroupID) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + defer rows.Close() + + pGroups, err := repo.processRows(rows) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + if len(pGroups) == 0 { + return repoerr.ErrUpdateEntity + } + pGroup := pGroups[0] + + if pGroup.ID == "" { + return errors.Wrap(repoerr.ErrViewEntity, errParentGroupID) + } + if pGroup.Path == "" { + return errors.Wrap(repoerr.ErrViewEntity, errParentGroupPath) + } + if !strings.HasSuffix(pGroup.Path, pGroup.ID) { + return errors.Wrap(repoerr.ErrViewEntity, errParentSuffix) + } + sPaths := strings.Split(pGroup.Path, ".") // 021b9f24-5337-469b-abfa-586f5813dd41.bd4a1fea-6303-4dca-9628-301cd1165a8c.c7e8f389-11e9-4849-a474-e186012ddf38 + for _, sPath := range sPaths { + for _, cgid := range groupIDs { + if sPath == cgid { + return errors.Wrap(repoerr.ErrUpdateEntity, fmt.Errorf("cyclic parent, group %s is parent of requested group %s", cgid, parentGroupID)) + } + } + } + + query := ` UPDATE groups + SET parent_id = :parent_id + WHERE id = ANY(:children_group_ids) + RETURNING id, path;` + + params := map[string]interface{}{ + "parent_id": pGroup.ID, + "children_group_ids": groupIDs, + } + + crows, err := tx.NamedQuery(query, params) if err != nil { return postgres.HandleError(repoerr.ErrUpdateEntity, err) } - defer row.Close() + defer crows.Close() + cgroups, err := repo.processRows(crows) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + + childrenPaths := []string{} + for _, cg := range cgroups { + spath := strings.Split(cg.Path, ".") + if len(spath) > 0 { + childrenPaths = append(childrenPaths, cg.Path) + } + } + + query = `UPDATE groups + SET path = text2ltree(COALESCE($1, '') || '.' || ltree2text(path)) + WHERE path <@ ANY($2::ltree[]);` + + if _, err := tx.Exec(query, pGroup.Path, childrenPaths); err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + if err := tx.Commit(); err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } return nil } -func (repo groupRepository) UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { +// ToDo: Query need to change to ANY +// ToDo: If parent is changed, then path of all children need to be updated https://patshaughnessy.net/2017/12/14/manipulating-trees-using-sql-and-the-postgres-ltree-extension +func (repo groupRepository) UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) (err error) { if len(groupIDs) == 0 { return nil } - var updateColumns []string - for _, groupID := range groupIDs { - updateColumns = append(updateColumns, fmt.Sprintf("('%s', '%s') ", groupID, parentGroupID)) + + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(err, errRollback) + } + } + }() + pq := `SELECT id, path FROM groups WHERE id = $1 LIMIT 1;` + rows, err := tx.Queryx(pq, parentGroupID) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + defer rows.Close() + + pGroups, err := repo.processRows(rows) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + if len(pGroups) == 0 { + return repoerr.ErrUpdateEntity + } + pGroup := pGroups[0] + + if pGroup.ID == "" { + return errors.Wrap(repoerr.ErrViewEntity, errParentGroupID) + } + if pGroup.Path == "" { + return errors.Wrap(repoerr.ErrViewEntity, errParentGroupPath) } - uc := strings.Join(updateColumns, ",") - query := fmt.Sprintf(` + + query := `UPDATE groups + SET parent_id = NULL + WHERE id = ANY(:children_group_ids) AND parent_id = :parent_id + RETURNING id, path;` + + parameters := map[string]interface{}{ + "parent_id": pGroup.ID, + "children_group_ids": groupIDs, + } + crows, err := tx.NamedQuery(query, parameters) + if err != nil { + return postgres.HandleError(repoerr.ErrUpdateEntity, err) + } + defer crows.Close() + cgroups, err := repo.processRows(crows) + if err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + + childrenPaths := []string{} + for _, cg := range cgroups { + spath := strings.Split(cg.Path, ".") + if len(spath) > 0 { + childrenPaths = append(childrenPaths, cg.Path) + } + } + + query = `UPDATE groups + SET path = text2ltree(replace(ltree2text(path), $1 || '.', '')) + WHERE path <@ ANY($2::ltree[]);` + + if _, err := tx.Exec(query, pGroup.Path, childrenPaths); err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + + if err := tx.Commit(); err != nil { + return errors.Wrap(repoerr.ErrUpdateEntity, err) + } + return nil +} + +func (repo groupRepository) UnassignAllChildrenGroup(ctx context.Context, id string) error { + + query := ` UPDATE groups AS g SET parent_id = NULL - FROM (VALUES - %s - ) AS u(id, parent_group_id) - WHERE g.id = u.id ; - `, uc) + WHERE g.parent = :parent_id ; + ` - row, err := repo.db.QueryContext(ctx, query) + row, err := repo.db.NamedQueryContext(ctx, query, dbGroup{ParentID: &id}) if err != nil { return postgres.HandleError(repoerr.ErrUpdateEntity, err) } @@ -305,27 +516,7 @@ func (repo groupRepository) Delete(ctx context.Context, groupID string) error { return nil } -func buildHierachy(gm mggroups.Page) string { - query := "" - switch { - case gm.Direction >= 0: // ancestors - query = `WITH RECURSIVE groups_cte as ( - SELECT id, COALESCE(parent_id, '') AS parent_id, domain_id, name, description, metadata, created_at, updated_at, updated_by, status, 0 as level from groups WHERE id = :parent_id - UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.domain_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.updated_by, 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, domain_id, name, description, metadata, created_at, updated_at, updated_by, status, 0 as level, CONCAT('', '', id) as path from groups WHERE id = :parent_id - UNION SELECT x.id, COALESCE(x.parent_id, '') AS parent_id, x.domain_id, x.name, x.description, x.metadata, x.created_at, x.updated_at, x.updated_by, 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 mggroups.Page, ids ...string) string { +func buildQuery(gm mggroups.PageMeta, ids ...string) string { queries := []string{} if len(ids) > 0 { @@ -334,7 +525,7 @@ func buildQuery(gm mggroups.Page, ids ...string) string { if gm.Name != "" { queries = append(queries, "g.name ILIKE '%' || :name || '%'") } - if gm.PageMeta.ID != "" { + if gm.ID != "" { queries = append(queries, "g.id ILIKE '%' || :id || '%'") } if gm.Status != mggroups.AllStatus { @@ -405,7 +596,7 @@ func toDBGroup(g mggroups.Group) (dbGroup, error) { } func toGroup(g dbGroup) (mggroups.Group, error) { - var metadata groups.Metadata + var metadata mggroups.Metadata if g.Metadata != nil { if err := json.Unmarshal(g.Metadata, &metadata); err != nil { return mggroups.Group{}, errors.Wrap(repoerr.ErrMalformedEntity, err) @@ -440,36 +631,29 @@ func toGroup(g dbGroup) (mggroups.Group, error) { }, nil } -func toDBGroupPage(pm mggroups.Page) (dbGroupPage, error) { - level := mggroups.MaxLevel - if pm.Level < mggroups.MaxLevel { - level = pm.Level - } +func toDBGroupPageMeta(pm mggroups.PageMeta) (dbGroupPageMeta, error) { data := []byte("{}") if len(pm.Metadata) > 0 { b, err := json.Marshal(pm.Metadata) if err != nil { - return dbGroupPage{}, errors.Wrap(errors.ErrMalformedEntity, err) + return dbGroupPageMeta{}, errors.Wrap(errors.ErrMalformedEntity, err) } data = b } - return dbGroupPage{ + return dbGroupPageMeta{ ID: pm.ID, Name: pm.Name, Metadata: data, - Path: pm.Path, - Level: level, Total: pm.Total, Offset: pm.Offset, Limit: pm.Limit, - ParentID: pm.ParentID, DomainID: pm.DomainID, Status: pm.Status, }, nil } -type dbGroupPage struct { - ClientID string `db:"client_id"` +// ToDo: check and remove field "Level" after new auth stabilize +type dbGroupPageMeta struct { ID string `db:"id"` Name string `db:"name"` ParentID string `db:"parent_id"` @@ -500,3 +684,24 @@ func (repo groupRepository) processRows(rows *sqlx.Rows) ([]mggroups.Group, erro } return items, nil } + +func (repo groupRepository) getInsertQuery(c context.Context, g mggroups.Group) (string, error) { + switch { + case g.Parent != "": + parent, err := repo.RetrieveByID(c, g.Parent) + if err != nil { + return "", err + } + path := parent.Path + "." + g.ID + if len(strings.Split(path, ".")) > mggroups.MaxPathLength { + return "", fmt.Errorf("reached max nested depth") + } + return fmt.Sprintf(`INSERT INTO groups (name, description, id, domain_id, parent_id, metadata, created_at, status, path) + VALUES (:name, :description, :id, :domain_id, :parent_id, :metadata, :created_at, :status, '%s') + RETURNING id, name, description, domain_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, status, path, nlevel(path) as level;`, path), nil + default: + return `INSERT INTO groups (name, description, id, domain_id, metadata, created_at, status, path) + VALUES (:name, :description, :id, :domain_id, :metadata, :created_at, :status, :id) + RETURNING id, name, description, domain_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, status, path, nlevel(path) as level;`, nil + } +} diff --git a/internal/groups/postgres/groups_test.go b/groups/postgres/groups_test.go similarity index 82% rename from internal/groups/postgres/groups_test.go rename to groups/postgres/groups_test.go index 7bbbee20d4..2c553111d4 100644 --- a/internal/groups/postgres/groups_test.go +++ b/groups/postgres/groups_test.go @@ -11,11 +11,11 @@ import ( "time" "github.com/0x6flab/namegenerator" - "github.com/absmach/magistrala/internal/groups/postgres" + "github.com/absmach/magistrala/groups" + "github.com/absmach/magistrala/groups/postgres" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" - mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,14 +23,14 @@ import ( var ( namegen = namegenerator.NewGenerator() invalidID = strings.Repeat("a", 37) - validGroup = mggroups.Group{ + validGroup = groups.Group{ ID: testsutil.GenerateUUID(&testing.T{}), Domain: testsutil.GenerateUUID(&testing.T{}), Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, } ) @@ -44,7 +44,7 @@ func TestSave(t *testing.T) { cases := []struct { desc string - group mggroups.Group + group groups.Group err error }{ { @@ -59,72 +59,72 @@ func TestSave(t *testing.T) { }, { desc: "add group with invalid ID", - group: mggroups.Group{ + group: groups.Group{ ID: invalidID, Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, }, err: repoerr.ErrMalformedEntity, }, { desc: "add group with invalid domain", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), Domain: invalidID, Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, }, err: repoerr.ErrMalformedEntity, }, { desc: "add group with invalid parent", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), Parent: invalidID, Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, }, err: repoerr.ErrMalformedEntity, }, { desc: "add group with invalid name", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Name: strings.Repeat("a", 1025), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, }, err: repoerr.ErrMalformedEntity, }, { desc: "add group with invalid description", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), Description: strings.Repeat("a", 1025), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, }, err: repoerr.ErrMalformedEntity, }, { desc: "add group with invalid metadata", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Name: namegen.Generate(), @@ -133,31 +133,31 @@ func TestSave(t *testing.T) { "key": make(chan int), }, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, }, err: repoerr.ErrMalformedEntity, }, { desc: "add group with empty domain", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, }, err: repoerr.ErrMalformedEntity, }, { desc: "add group with empty name", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, }, err: repoerr.ErrMalformedEntity, }, @@ -187,12 +187,12 @@ func TestUpdate(t *testing.T) { cases := []struct { desc string - group mggroups.Group + group groups.Group err error }{ { desc: "update group successfully", - group: mggroups.Group{ + group: groups.Group{ ID: group.ID, Name: namegen.Generate(), Description: strings.Repeat("a", 64), @@ -204,7 +204,7 @@ func TestUpdate(t *testing.T) { }, { desc: "update group name", - group: mggroups.Group{ + group: groups.Group{ ID: group.ID, Name: namegen.Generate(), UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), @@ -214,7 +214,7 @@ func TestUpdate(t *testing.T) { }, { desc: "update group description", - group: mggroups.Group{ + group: groups.Group{ ID: group.ID, Description: strings.Repeat("a", 64), UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), @@ -224,7 +224,7 @@ func TestUpdate(t *testing.T) { }, { desc: "update group metadata", - group: mggroups.Group{ + group: groups.Group{ ID: group.ID, Metadata: map[string]interface{}{"key": "value"}, UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), @@ -234,7 +234,7 @@ func TestUpdate(t *testing.T) { }, { desc: "update group with invalid ID", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), Name: namegen.Generate(), Description: strings.Repeat("a", 64), @@ -246,7 +246,7 @@ func TestUpdate(t *testing.T) { }, { desc: "update group with empty ID", - group: mggroups.Group{ + group: groups.Group{ Name: namegen.Generate(), Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"key": "value"}, @@ -283,14 +283,14 @@ func TestChangeStatus(t *testing.T) { cases := []struct { desc string - group mggroups.Group + group groups.Group err error }{ { desc: "change status group successfully", - group: mggroups.Group{ + group: groups.Group{ ID: group.ID, - Status: mggroups.DisabledStatus, + Status: groups.DisabledStatus, UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), UpdatedBy: testsutil.GenerateUUID(t), }, @@ -298,9 +298,9 @@ func TestChangeStatus(t *testing.T) { }, { desc: "change status group with invalid ID", - group: mggroups.Group{ + group: groups.Group{ ID: testsutil.GenerateUUID(t), - Status: mggroups.DisabledStatus, + Status: groups.DisabledStatus, UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), UpdatedBy: testsutil.GenerateUUID(t), }, @@ -308,8 +308,8 @@ func TestChangeStatus(t *testing.T) { }, { desc: "change status group with empty ID", - group: mggroups.Group{ - Status: mggroups.DisabledStatus, + group: groups.Group{ + Status: groups.DisabledStatus, UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), UpdatedBy: testsutil.GenerateUUID(t), }, @@ -344,7 +344,7 @@ func TestRetrieveByID(t *testing.T) { cases := []struct { desc string id string - group mggroups.Group + group groups.Group err error }{ { @@ -356,13 +356,13 @@ func TestRetrieveByID(t *testing.T) { { desc: "retrieve group by id with invalid ID", id: invalidID, - group: mggroups.Group{}, + group: groups.Group{}, err: repoerr.ErrNotFound, }, { desc: "retrieve group by id with empty ID", id: "", - group: mggroups.Group{}, + group: groups.Group{}, err: repoerr.ErrNotFound, }, } @@ -387,11 +387,11 @@ func TestRetrieveAll(t *testing.T) { repo := postgres.New(database) num := 200 - var items []mggroups.Group + var items []groups.Group parentID := "" for i := 0; i < num; i++ { name := namegen.Generate() - group := mggroups.Group{ + group := groups.Group{ ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Parent: parentID, @@ -399,7 +399,7 @@ func TestRetrieveAll(t *testing.T) { Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"name": name}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, } _, err := repo.Save(context.Background(), group) require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) @@ -409,20 +409,20 @@ func TestRetrieveAll(t *testing.T) { cases := []struct { desc string - page mggroups.Page - response mggroups.Page + page groups.Page + response groups.Page err error }{ { desc: "retrieve groups successfully", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 0, Limit: 10, @@ -433,14 +433,14 @@ func TestRetrieveAll(t *testing.T) { }, { desc: "retrieve groups with offset", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 10, Limit: 10, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 10, Limit: 10, @@ -451,14 +451,14 @@ func TestRetrieveAll(t *testing.T) { }, { desc: "retrieve groups with limit", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 50, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 0, Limit: 50, @@ -469,14 +469,14 @@ func TestRetrieveAll(t *testing.T) { }, { desc: "retrieve groups with offset and limit", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 50, Limit: 50, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 50, Limit: 50, @@ -487,32 +487,32 @@ func TestRetrieveAll(t *testing.T) { }, { desc: "retrieve groups with offset out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 1000, Limit: 50, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 1000, Limit: 50, }, - Groups: []mggroups.Group(nil), + Groups: []groups.Group(nil), }, err: nil, }, { desc: "retrieve groups with offset and limit out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 170, Limit: 50, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 170, Limit: 50, @@ -523,14 +523,14 @@ func TestRetrieveAll(t *testing.T) { }, { desc: "retrieve groups with limit out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 1000, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 0, Limit: 1000, @@ -541,78 +541,78 @@ func TestRetrieveAll(t *testing.T) { }, { desc: "retrieve groups with empty page", - page: mggroups.Page{}, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{}, + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 0, Limit: 0, }, - Groups: []mggroups.Group(nil), + Groups: []groups.Group(nil), }, err: nil, }, { desc: "retrieve groups with name", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, Name: items[0].Name, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 1, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group{items[0]}, + Groups: []groups.Group{items[0]}, }, err: nil, }, { desc: "retrieve groups with domain", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, DomainID: items[0].Domain, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 1, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group{items[0]}, + Groups: []groups.Group{items[0]}, }, err: nil, }, { desc: "retrieve groups with metadata", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, Metadata: items[0].Metadata, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 1, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group{items[0]}, + Groups: []groups.Group{items[0]}, }, err: nil, }, { desc: "retrieve groups with invalid metadata", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, Metadata: map[string]interface{}{ @@ -620,28 +620,26 @@ func TestRetrieveAll(t *testing.T) { }, }, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 0, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group(nil), + Groups: []groups.Group(nil), }, err: errors.ErrMalformedEntity, }, { desc: "retrieve parent groups", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: uint64(num), }, - ParentID: items[5].ID, - Direction: 1, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 0, Limit: uint64(num), @@ -652,16 +650,14 @@ func TestRetrieveAll(t *testing.T) { }, { desc: "retrieve children groups", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: uint64(num), }, - ParentID: items[150].ID, - Direction: -1, }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: uint64(num), Offset: 0, Limit: uint64(num), @@ -673,7 +669,7 @@ func TestRetrieveAll(t *testing.T) { } for _, tc := range cases { - switch groups, err := repo.RetrieveAll(context.Background(), tc.page); { + switch groups, err := repo.RetrieveAll(context.Background(), tc.page.PageMeta); { case err == nil: assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response.Total, groups.Total, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Total, groups.Total)) @@ -699,11 +695,11 @@ func TestRetrieveByIDs(t *testing.T) { repo := postgres.New(database) num := 200 - var items []mggroups.Group + var items []groups.Group parentID := "" for i := 0; i < num; i++ { name := namegen.Generate() - group := mggroups.Group{ + group := groups.Group{ ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Parent: parentID, @@ -711,7 +707,7 @@ func TestRetrieveByIDs(t *testing.T) { Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"name": name}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, } _, err := repo.Save(context.Background(), group) require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) @@ -721,22 +717,22 @@ func TestRetrieveByIDs(t *testing.T) { cases := []struct { desc string - page mggroups.Page + page groups.Page ids []string - response mggroups.Page + response groups.Page err error }{ { desc: "retrieve groups successfully", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, }, }, ids: getIDs(items[0:3]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 3, Offset: 0, Limit: 10, @@ -747,53 +743,53 @@ func TestRetrieveByIDs(t *testing.T) { }, { desc: "retrieve groups with empty ids", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, }, }, ids: []string{}, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, }, - Groups: []mggroups.Group(nil), + Groups: []groups.Group(nil), }, err: nil, }, { desc: "retrieve groups with empty ids but with domain", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, DomainID: items[0].Domain, }, }, ids: []string{}, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 1, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group{items[0]}, + Groups: []groups.Group{items[0]}, }, err: nil, }, { desc: "retrieve groups with offset", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 10, Limit: 10, }, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 20, Offset: 10, Limit: 10, @@ -804,34 +800,34 @@ func TestRetrieveByIDs(t *testing.T) { }, { desc: "retrieve groups with offset out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 1000, Limit: 50, }, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 20, Offset: 1000, Limit: 50, }, - Groups: []mggroups.Group(nil), + Groups: []groups.Group(nil), }, err: nil, }, { desc: "retrieve groups with offset and limit out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 15, Limit: 10, }, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 20, Offset: 15, Limit: 10, @@ -842,15 +838,15 @@ func TestRetrieveByIDs(t *testing.T) { }, { desc: "retrieve groups with limit out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 1000, }, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 20, Offset: 0, Limit: 1000, @@ -861,68 +857,68 @@ func TestRetrieveByIDs(t *testing.T) { }, { desc: "retrieve groups with name", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, Name: items[0].Name, }, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 1, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group{items[0]}, + Groups: []groups.Group{items[0]}, }, err: nil, }, { desc: "retrieve groups with domain", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, DomainID: items[0].Domain, }, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 1, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group{items[0]}, + Groups: []groups.Group{items[0]}, }, err: nil, }, { desc: "retrieve groups with metadata", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, Metadata: items[0].Metadata, }, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 1, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group{items[0]}, + Groups: []groups.Group{items[0]}, }, err: nil, }, { desc: "retrieve groups with invalid metadata", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: 10, Metadata: map[string]interface{}{ @@ -931,29 +927,27 @@ func TestRetrieveByIDs(t *testing.T) { }, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 0, Offset: 0, Limit: 10, }, - Groups: []mggroups.Group(nil), + Groups: []groups.Group(nil), }, err: errors.ErrMalformedEntity, }, { desc: "retrieve parent groups", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: uint64(num), }, - ParentID: items[5].ID, - Direction: 1, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 20, Offset: 0, Limit: uint64(num), @@ -964,17 +958,15 @@ func TestRetrieveByIDs(t *testing.T) { }, { desc: "retrieve children groups", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + page: groups.Page{ + PageMeta: groups.PageMeta{ Offset: 0, Limit: uint64(num), }, - ParentID: items[15].ID, - Direction: -1, }, ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ + response: groups.Page{ + PageMeta: groups.PageMeta{ Total: 20, Offset: 0, Limit: uint64(num), @@ -986,7 +978,7 @@ func TestRetrieveByIDs(t *testing.T) { } for _, tc := range cases { - switch groups, err := repo.RetrieveByIDs(context.Background(), tc.page, tc.ids...); { + switch groups, err := repo.RetrieveByIDs(context.Background(), tc.page.PageMeta, tc.ids...); { case err == nil: assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.response.Total, groups.Total, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Total, groups.Total)) @@ -1056,11 +1048,11 @@ func TestAssignParentGroup(t *testing.T) { num := 10 - var items []mggroups.Group + var items []groups.Group parentID := "" for i := 0; i < num; i++ { name := namegen.Generate() - group := mggroups.Group{ + group := groups.Group{ ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Parent: parentID, @@ -1068,7 +1060,7 @@ func TestAssignParentGroup(t *testing.T) { Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"name": name}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, } _, err := repo.Save(context.Background(), group) require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) @@ -1134,11 +1126,11 @@ func TestUnassignParentGroup(t *testing.T) { num := 10 - var items []mggroups.Group + var items []groups.Group parentID := "" for i := 0; i < num; i++ { name := namegen.Generate() - group := mggroups.Group{ + group := groups.Group{ ID: testsutil.GenerateUUID(t), Domain: testsutil.GenerateUUID(t), Parent: parentID, @@ -1146,7 +1138,7 @@ func TestUnassignParentGroup(t *testing.T) { Description: strings.Repeat("a", 64), Metadata: map[string]interface{}{"name": name}, CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, + Status: groups.EnabledStatus, } _, err := repo.Save(context.Background(), group) require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) @@ -1202,7 +1194,7 @@ func TestUnassignParentGroup(t *testing.T) { } } -func getIDs(groups []mggroups.Group) []string { +func getIDs(groups []groups.Group) []string { var ids []string for _, group := range groups { ids = append(ids, group.ID) diff --git a/groups/postgres/init.go b/groups/postgres/init.go new file mode 100644 index 0000000000..f8793099a0 --- /dev/null +++ b/groups/postgres/init.go @@ -0,0 +1,63 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + rolesPostgres "github.com/absmach/magistrala/pkg/roles/repo/postgres" + _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access + migrate "github.com/rubenv/sql-migrate" +) + +func Migration() (*migrate.MemoryMigrationSource, error) { + rolesMigration, err := rolesPostgres.Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName) + if err != nil { + return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err) + } + + groupsMigration := &migrate.MemoryMigrationSource{ + Migrations: []*migrate.Migration{ + { + Id: "groups_01", + Up: []string{ + `CREATE TABLE IF NOT EXISTS groups ( + id VARCHAR(36) PRIMARY KEY, + parent_id VARCHAR(36), + domain_id VARCHAR(36) NOT NULL, + name VARCHAR(1024) NOT NULL, + description VARCHAR(1024), + metadata JSONB, + created_at TIMESTAMP, + updated_at TIMESTAMP, + updated_by VARCHAR(254), + status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), + UNIQUE (domain_id, name), + FOREIGN KEY (parent_id) REFERENCES groups (id) ON DELETE SET NULL, + CHECK (id != parent_id) + )`, + }, + Down: []string{ + `DROP TABLE IF EXISTS groups`, + }, + }, + { + Id: "groups_02", + Up: []string{ + `CREATE EXTENSION IF NOT EXISTS LTREE`, + `ALTER TABLE groups ADD COLUMN path LTREE`, + `CREATE INDEX path_gist_idx ON groups USING GIST (path);`, + }, + Down: []string{ + `DROP TABLE IF EXISTS groups`, + `DROP EXTENSION IF EXISTS LTREE`, + }, + }, + }, + } + + groupsMigration.Migrations = append(groupsMigration.Migrations, rolesMigration.Migrations...) + + return groupsMigration, nil +} diff --git a/internal/groups/postgres/setup_test.go b/groups/postgres/setup_test.go similarity index 90% rename from internal/groups/postgres/setup_test.go rename to groups/postgres/setup_test.go index a809a2b488..716255df8f 100644 --- a/internal/groups/postgres/setup_test.go +++ b/groups/postgres/setup_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - gpostgres "github.com/absmach/magistrala/internal/groups/postgres" + gpostgres "github.com/absmach/magistrala/groups/postgres" "github.com/absmach/magistrala/pkg/postgres" pgclient "github.com/absmach/magistrala/pkg/postgres" "github.com/jmoiron/sqlx" @@ -76,7 +76,12 @@ func TestMain(m *testing.M) { SSLRootCert: "", } - if db, err = pgclient.Setup(dbConfig, *gpostgres.Migration()); err != nil { + mig, err := gpostgres.Migration() + if err != nil { + log.Fatalf("Could not get groups migration : %s", err) + + } + if db, err = pgclient.Setup(dbConfig, *mig); err != nil { log.Fatalf("Could not setup test DB connection: %s", err) } diff --git a/groups/private/mocks/service.go b/groups/private/mocks/service.go new file mode 100644 index 0000000000..5d8a440ba4 --- /dev/null +++ b/groups/private/mocks/service.go @@ -0,0 +1,59 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + groups "github.com/absmach/magistrala/groups" + mock "github.com/stretchr/testify/mock" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// RetrieveById provides a mock function with given fields: ctx, id +func (_m *Service) RetrieveById(ctx context.Context, id string) (groups.Group, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for RetrieveById") + } + + var r0 groups.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (groups.Group, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) groups.Group); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(groups.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/groups/private/service.go b/groups/private/service.go new file mode 100644 index 0000000000..65ecafbd61 --- /dev/null +++ b/groups/private/service.go @@ -0,0 +1,26 @@ +package private + +import ( + "context" + + "github.com/absmach/magistrala/groups" +) + +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" +type Service interface { + RetrieveById(ctx context.Context, id string) (groups.Group, error) +} + +var _ Service = (*service)(nil) + +func New(repo groups.Repository) Service { + return service{repo} +} + +type service struct { + repo groups.Repository +} + +func (svc service) RetrieveById(ctx context.Context, ids string) (groups.Group, error) { + return svc.repo.RetrieveByID(ctx, ids) +} diff --git a/groups/roleactions.go b/groups/roleactions.go new file mode 100644 index 0000000000..0ca7cee809 --- /dev/null +++ b/groups/roleactions.go @@ -0,0 +1,171 @@ +package groups + +import "github.com/absmach/magistrala/pkg/roles" + +const ( + Update = "update" + Read = "read" + Membership = "membership" + Delete = "delete" + SetChild = "set_child" + SetParent = "set_parent" + + ManageRole = "manage_role" + AddRoleUsers = "add_role_users" + RemoveRoleUsers = "remove_role_users" + ViewRoleUsers = "view_role_users" + + ClientCreate = "client_create" + ChannelCreate = "channel_create" + SubgroupCreate = "subgroup_create" + SubgroupClientCreate = "subgroup_client_create" + SubgroupChannelCreate = "subgroup_channel_create" + + ClientUpdate = "client_update" + ClientRead = "client_read" + ClientDelete = "client_delete" + ClientSetParentGroup = "client_set_parent_group" + ClientConnectToChannel = "client_connect_to_channel" + + ClientManageRole = "client_manage_role" + ClientAddRoleUsers = "client_add_role_users" + ClientRemoveRoleUsers = "client_remove_role_users" + ClientViewRoleUsers = "client_view_role_users" + + ChannelUpdate = "channel_update" + ChannelRead = "channel_read" + ChannelDelete = "channel_delete" + ChannelSetParentGroup = "channel_set_parent_group" + ChannelConnectToClient = "channel_connect_to_client" + ChannelPublish = "channel_publish" + ChannelSubscribe = "channel_subscribe" + + ChannelManageRole = "channel_manage_role" + ChannelAddRoleUsers = "channel_add_role_users" + ChannelRemoveRoleUsers = "channel_remove_role_users" + ChannelViewRoleUsers = "channel_view_role_users" + + SubgroupUpdate = "subgroup_update" + SubgroupRead = "subgroup_read" + SubgroupMembership = "subgroup_membership" + SubgroupDelete = "subgroup_delete" + SubgroupSetChild = "subgroup_set_child" + SubgroupSetParent = "subgroup_set_parent" + + SubgroupManageRole = "subgroup_manage_role" + SubgroupAddRoleUsers = "subgroup_add_role_users" + SubgroupRemoveRoleUsers = "subgroup_remove_role_users" + SubgroupViewRoleUsers = "subgroup_view_role_users" + + SubgroupClientUpdate = "subgroup_client_update" + SubgroupClientRead = "subgroup_client_read" + SubgroupClientDelete = "subgroup_client_delete" + SubgroupClientSetParentGroup = "subgroup_client_set_parent_group" + SubgroupClientConnectToChannel = "subgroup_client_connect_to_channel" + + SubgroupClientManageRole = "subgroup_client_manage_role" + SubgroupClientAddRoleUsers = "subgroup_client_add_role_users" + SubgroupClientRemoveRoleUsers = "subgroup_client_remove_role_users" + SubgroupClientViewRoleUsers = "subgroup_client_view_role_users" + + SubgroupChannelUpdate = "subgroup_channel_update" + SubgroupChannelRead = "subgroup_channel_read" + SubgroupChannelDelete = "subgroup_channel_delete" + SubgroupChannelSetParentGroup = "subgroup_channel_set_parent_group" + SubgroupChannelConnectToClient = "subgroup_channel_connect_to_client" + SubgroupChannelPublish = "subgroup_channel_publish" + SubgroupChannelSubscribe = "subgroup_channel_subscribe" + + SubgroupChannelManageRole = "subgroup_channel_manage_role" + SubgroupChannelAddRoleUsers = "subgroup_channel_add_role_users" + SubgroupChannelRemoveRoleUsers = "subgroup_channel_remove_role_users" + SubgroupChannelViewRoleUsers = "subgroup_channel_view_role_users" +) + +const ( + BuiltInRoleAdmin = "admin" + BuiltInRoleMembership = "membership" +) + +func AvailableActions() []roles.Action { + return []roles.Action{ + Update, + Read, + Membership, + Delete, + SetChild, + SetParent, + ManageRole, + AddRoleUsers, + RemoveRoleUsers, + ViewRoleUsers, + ClientCreate, + ChannelCreate, + SubgroupCreate, + SubgroupClientCreate, + SubgroupChannelCreate, + ClientUpdate, + ClientRead, + ClientDelete, + ClientSetParentGroup, + ClientConnectToChannel, + ClientManageRole, + ClientAddRoleUsers, + ClientRemoveRoleUsers, + ClientViewRoleUsers, + ChannelUpdate, + ChannelRead, + ChannelDelete, + ChannelSetParentGroup, + ChannelConnectToClient, + ChannelPublish, + ChannelSubscribe, + ChannelManageRole, + ChannelAddRoleUsers, + ChannelRemoveRoleUsers, + ChannelViewRoleUsers, + SubgroupUpdate, + SubgroupRead, + SubgroupMembership, + SubgroupDelete, + SubgroupSetChild, + SubgroupSetParent, + SubgroupManageRole, + SubgroupAddRoleUsers, + SubgroupRemoveRoleUsers, + SubgroupViewRoleUsers, + SubgroupClientUpdate, + SubgroupClientRead, + SubgroupClientDelete, + SubgroupClientSetParentGroup, + SubgroupClientConnectToChannel, + SubgroupClientManageRole, + SubgroupClientAddRoleUsers, + SubgroupClientRemoveRoleUsers, + SubgroupClientViewRoleUsers, + SubgroupChannelUpdate, + SubgroupChannelRead, + SubgroupChannelDelete, + SubgroupChannelSetParentGroup, + SubgroupChannelConnectToClient, + SubgroupChannelPublish, + SubgroupChannelSubscribe, + SubgroupChannelManageRole, + SubgroupChannelAddRoleUsers, + SubgroupChannelRemoveRoleUsers, + SubgroupChannelViewRoleUsers, + } +} + +func membershipRoleActions() []roles.Action { + return []roles.Action{ + Membership, + } +} + +func BuiltInRoles() map[roles.BuiltInRoleName][]roles.Action { + return map[roles.BuiltInRoleName][]roles.Action{ + BuiltInRoleAdmin: AvailableActions(), + BuiltInRoleMembership: membershipRoleActions(), + } +} diff --git a/groups/service.go b/groups/service.go new file mode 100644 index 0000000000..dad9b1222e --- /dev/null +++ b/groups/service.go @@ -0,0 +1,550 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package groups + +import ( + "context" + "fmt" + "time" + + "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/roles" + "golang.org/x/sync/errgroup" +) + +var errGroupIDs = errors.New("invalid group ids") + +type service struct { + repo Repository + policy policies.Service + idProvider magistrala.IDProvider + channels grpcChannelsV1.ChannelsServiceClient + clients grpcClientsV1.ClientsServiceClient + + roles.ProvisionManageService +} + +// NewService returns a new groups service implementation. +func NewService(repo Repository, policy policies.Service, idp magistrala.IDProvider, channels grpcChannelsV1.ChannelsServiceClient, clients grpcClientsV1.ClientsServiceClient, sidProvider magistrala.IDProvider) (Service, error) { + rpms, err := roles.NewProvisionManageService(policies.GroupType, repo, policy, sidProvider, AvailableActions(), BuiltInRoles()) + if err != nil { + return service{}, err + } + return service{ + repo: repo, + policy: policy, + idProvider: idp, + channels: channels, + clients: clients, + ProvisionManageService: rpms, + }, nil +} + +func (svc service) CreateGroup(ctx context.Context, session mgauthn.Session, g Group) (gr Group, retErr error) { + groupID, err := svc.idProvider.ID() + if err != nil { + return Group{}, err + } + if g.Status != EnabledStatus && g.Status != DisabledStatus { + return Group{}, svcerr.ErrInvalidStatus + } + + g.ID = groupID + g.CreatedAt = time.Now() + g.Domain = session.DomainID + + saved, err := svc.repo.Save(ctx, g) + if err != nil { + return Group{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + + defer func() { + if retErr != nil { + if errRollback := svc.repo.Delete(ctx, saved.ID); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + + oprs := []policies.Policy{} + + oprs = append(oprs, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.DomainType, + Subject: session.DomainID, + Relation: policies.DomainRelation, + ObjectType: policies.GroupType, + Object: saved.ID, + }) + if saved.Parent != "" { + oprs = append(oprs, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: saved.Parent, + Relation: policies.ParentGroupRelation, + ObjectType: policies.GroupType, + Object: saved.ID, + }) + } + newBuiltInRoleMembers := map[roles.BuiltInRoleName][]roles.Member{ + BuiltInRoleAdmin: {roles.Member(session.UserID)}, + BuiltInRoleMembership: {}, + } + if _, err := svc.AddNewEntitiesRoles(ctx, session.DomainID, session.UserID, []string{saved.ID}, oprs, newBuiltInRoleMembers); err != nil { + return Group{}, errors.Wrap(svcerr.ErrAddPolicies, err) + } + + return saved, nil +} + +func (svc service) ViewGroup(ctx context.Context, session mgauthn.Session, id string) (Group, error) { + group, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return Group{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + + return group, nil +} + +func (svc service) ViewGroupPerms(ctx context.Context, session mgauthn.Session, id string) ([]string, error) { + return svc.listUserGroupPermission(ctx, session.DomainUserID, id) +} + +func (svc service) ListGroups(ctx context.Context, session mgauthn.Session, gm PageMeta) (Page, error) { + var ids []string + var err error + + switch session.SuperAdmin { + case true: + gm.DomainID = session.DomainID + default: + ids, err = svc.listAllGroupsOfUserID(ctx, session.DomainUserID, gm.Permission) + if err != nil { + return Page{}, err + } + } + + gp, err := svc.repo.RetrieveByIDs(ctx, gm, ids...) + if err != nil { + return Page{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + + if gm.ListPerms && len(gp.Groups) > 0 { + g, ctx := errgroup.WithContext(ctx) + + for i := range gp.Groups { + // Copying loop variable "i" to avoid "loop variable captured by func literal" + iter := i + g.Go(func() error { + return svc.retrievePermissions(ctx, session.DomainUserID, &gp.Groups[iter]) + }) + } + + if err := g.Wait(); err != nil { + return Page{}, err + } + } + return gp, nil +} + +// Experimental functions used for async calling of svc.listUserClientPermission. This might be helpful during listing of large number of entities. +func (svc service) retrievePermissions(ctx context.Context, userID string, group *Group) error { + permissions, err := svc.listUserGroupPermission(ctx, userID, group.ID) + if err != nil { + return err + } + group.Permissions = permissions + return nil +} + +func (svc service) listUserGroupPermission(ctx context.Context, userID, groupID string) ([]string, error) { + permissions, err := svc.policy.ListPermissions(ctx, policies.Policy{ + SubjectType: policies.UserType, + Subject: userID, + Object: groupID, + ObjectType: policies.GroupType, + }, []string{}) + if err != nil { + return []string{}, err + } + if len(permissions) == 0 { + return []string{}, svcerr.ErrAuthorization + } + return permissions, nil +} + +func (svc service) UpdateGroup(ctx context.Context, session mgauthn.Session, g Group) (Group, error) { + g.UpdatedAt = time.Now() + g.UpdatedBy = session.UserID + + return svc.repo.Update(ctx, g) +} + +func (svc service) EnableGroup(ctx context.Context, session mgauthn.Session, id string) (Group, error) { + group := Group{ + ID: id, + Status: EnabledStatus, + UpdatedAt: time.Now(), + } + group, err := svc.changeGroupStatus(ctx, session, group) + if err != nil { + return Group{}, err + } + return group, nil +} + +func (svc service) DisableGroup(ctx context.Context, session mgauthn.Session, id string) (Group, error) { + group := Group{ + ID: id, + Status: DisabledStatus, + UpdatedAt: time.Now(), + } + group, err := svc.changeGroupStatus(ctx, session, group) + if err != nil { + return Group{}, err + } + return group, nil +} + +func (svc service) RetrieveGroupHierarchy(ctx context.Context, session mgauthn.Session, id string, hm HierarchyPageMeta) (HierarchyPage, error) { + hp, err := svc.repo.RetrieveHierarchy(ctx, id, hm) + if err != nil { + return HierarchyPage{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + hids := svc.getGroupIDs(hp.Groups) + ids, err := svc.filterAllowedGroupIDsOfUserID(ctx, session.DomainUserID, "read_permission", hids) + if err != nil { + return HierarchyPage{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + hp.Groups = svc.allowedGroups(hp.Groups, ids) + return hp, nil +} + +func (svc service) allowedGroups(gps []Group, ids []string) []Group { + aIDs := make(map[string]struct{}, len(ids)) + + for _, id := range ids { + aIDs[id] = struct{}{} + } + + aGroups := []Group{} + for _, g := range gps { + ag := g + if _, ok := aIDs[g.ID]; !ok { + ag = Group{ID: "xxxx-xxxx-xxxx-xxxx", Level: g.Level} + } + aGroups = append(aGroups, ag) + } + return aGroups +} + +func (svc service) getGroupIDs(gps []Group) []string { + hids := []string{} + for _, g := range gps { + hids = append(hids, g.ID) + if len(g.Children) > 0 { + children := make([]Group, len(g.Children)) + for i, child := range g.Children { + children[i] = *child + } + cids := svc.getGroupIDs(children) + hids = append(hids, cids...) + } + } + return hids +} + +func (svc service) AddParentGroup(ctx context.Context, session mgauthn.Session, id, parentID string) (retErr error) { + group, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + + var pols []policies.Policy + if group.Parent != "" { + return errors.Wrap(svcerr.ErrConflict, fmt.Errorf("%s group already have parent", group.ID)) + } + pols = append(pols, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: parentID, + Relation: policies.ParentGroupRelation, + ObjectType: policies.GroupType, + Object: group.ID, + }) + + if err := svc.policy.AddPolicies(ctx, pols); err != nil { + return errors.Wrap(svcerr.ErrAddPolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.DeletePolicies(ctx, pols); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + + if err := svc.repo.AssignParentGroup(ctx, parentID, group.ID); err != nil { + return err + } + return nil +} + +func (svc service) RemoveParentGroup(ctx context.Context, session mgauthn.Session, id string) (retErr error) { + group, err := svc.repo.RetrieveByID(ctx, id) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + + if group.Parent != "" { + + var pols []policies.Policy + pols = append(pols, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: group.Parent, + Relation: policies.ParentGroupRelation, + ObjectType: policies.GroupType, + Object: group.ID, + }) + + if err := svc.policy.DeletePolicies(ctx, pols); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.AddPolicies(ctx, pols); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + + return svc.repo.UnassignParentGroup(ctx, group.Parent, group.ID) + } + + return nil +} + +func (svc service) AddChildrenGroups(ctx context.Context, session mgauthn.Session, parentGroupID string, childrenGroupIDs []string) (retErr error) { + childrenGroupsPage, err := svc.repo.RetrieveByIDs(ctx, PageMeta{Limit: 1<<63 - 1}, childrenGroupIDs...) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + if len(childrenGroupsPage.Groups) == 0 { + return errGroupIDs + } + + for _, childGroup := range childrenGroupsPage.Groups { + if childGroup.Parent != "" { + return errors.Wrap(svcerr.ErrConflict, fmt.Errorf("%s group already have parent", childGroup.ID)) + } + } + + var pols []policies.Policy + for _, childGroup := range childrenGroupsPage.Groups { + pols = append(pols, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: parentGroupID, + Relation: policies.ParentGroupRelation, + ObjectType: policies.GroupType, + Object: childGroup.ID, + }) + } + + if err := svc.policy.AddPolicies(ctx, pols); err != nil { + return errors.Wrap(svcerr.ErrAddPolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.DeletePolicies(ctx, pols); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + + return svc.repo.AssignParentGroup(ctx, parentGroupID, childrenGroupIDs...) +} + +func (svc service) RemoveChildrenGroups(ctx context.Context, session mgauthn.Session, parentGroupID string, childrenGroupIDs []string) (retErr error) { + childrenGroupsPage, err := svc.repo.RetrieveByIDs(ctx, PageMeta{Limit: 1<<63 - 1}, childrenGroupIDs...) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + if len(childrenGroupsPage.Groups) == 0 { + return errGroupIDs + } + + var pols []policies.Policy + + for _, group := range childrenGroupsPage.Groups { + if group.Parent != "" && group.Parent != parentGroupID { + return errors.Wrap(svcerr.ErrConflict, fmt.Errorf("%s group doesn't have same parent", group.ID)) + } + pols = append(pols, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: parentGroupID, + Relation: policies.ParentGroupRelation, + ObjectType: policies.GroupType, + Object: group.ID, + }) + } + + if err := svc.policy.DeletePolicies(ctx, pols); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + defer func() { + if retErr != nil { + if errRollback := svc.policy.AddPolicies(ctx, pols); errRollback != nil { + retErr = errors.Wrap(retErr, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) + } + } + }() + + return svc.repo.UnassignParentGroup(ctx, parentGroupID, childrenGroupIDs...) +} + +func (svc service) RemoveAllChildrenGroups(ctx context.Context, session mgauthn.Session, id string) error { + pol := policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: id, + Relation: policies.ParentGroupRelation, + ObjectType: policies.GroupType, + } + + if err := svc.policy.DeletePolicyFilter(ctx, pol); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + return svc.repo.UnassignAllChildrenGroup(ctx, id) +} + +func (svc service) ListChildrenGroups(ctx context.Context, session mgauthn.Session, id string, pm PageMeta) (Page, error) { + cids, err := svc.policy.ListAllObjects(ctx, policies.Policy{ + SubjectType: policies.GroupType, + Subject: id, + Permission: policies.ParentGroupRelation, + ObjectType: policies.GroupType, + }) + if err != nil { + return Page{}, err + } + + ids, err := svc.filterAllowedGroupIDsOfUserID(ctx, session.DomainUserID, pm.Permission, cids.Policies) + if err != nil { + return Page{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + gp, err := svc.repo.RetrieveByIDs(ctx, pm, ids...) + if err != nil { + return Page{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + return gp, nil +} + +func (svc service) DeleteGroup(ctx context.Context, session mgauthn.Session, id string) error { + if _, err := svc.channels.UnsetParentGroupFromChannels(ctx, &grpcChannelsV1.UnsetParentGroupFromChannelsReq{ParentGroupId: id}); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + if _, err := svc.clients.UnsetParentGroupFromClient(ctx, &grpcClientsV1.UnsetParentGroupFromClientReq{ParentGroupId: id}); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + g, err := svc.repo.ChangeStatus(ctx, Group{ID: id, Status: DeletedStatus}) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + filterDeletePolicies := []policies.Policy{ + { + SubjectType: policies.GroupType, + Subject: id, + }, + { + ObjectType: policies.GroupType, + Object: id, + }, + } + deletePolicies := []policies.Policy{ + { + SubjectType: policies.DomainType, + Subject: session.DomainID, + Relation: policies.DomainRelation, + ObjectType: policies.GroupType, + Object: id, + }, + } + if g.Parent != "" { + deletePolicies = append(deletePolicies, policies.Policy{ + Domain: session.DomainID, + SubjectType: policies.GroupType, + Subject: g.Parent, + Relation: policies.ParentGroupRelation, + ObjectType: policies.GroupType, + Object: id, + }) + } + if err := svc.RemoveEntitiesRoles(ctx, session.DomainID, session.DomainUserID, []string{id}, filterDeletePolicies, deletePolicies); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + if err := svc.repo.Delete(ctx, id); err != nil { + return err + } + + return nil +} + +func (svc service) filterAllowedGroupIDsOfUserID(ctx context.Context, userID, permission string, groupIDs []string) ([]string, error) { + var ids []string + allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, permission) + if err != nil { + return []string{}, err + } + + for _, gid := range groupIDs { + for _, id := range allowedIDs { + if id == gid { + ids = append(ids, id) + } + } + } + return ids, nil +} + +func (svc service) listAllGroupsOfUserID(ctx context.Context, userID, permission string) ([]string, error) { + allowedIDs, err := svc.policy.ListAllObjects(ctx, policies.Policy{ + SubjectType: policies.UserType, + Subject: userID, + Permission: permission, + ObjectType: policies.GroupType, + }) + if err != nil { + return []string{}, err + } + return allowedIDs.Policies, nil +} + +func (svc service) changeGroupStatus(ctx context.Context, session mgauthn.Session, group Group) (Group, error) { + dbGroup, err := svc.repo.RetrieveByID(ctx, group.ID) + if err != nil { + return Group{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + if dbGroup.Status == group.Status { + return Group{}, errors.ErrStatusAlreadyAssigned + } + + group.UpdatedBy = session.UserID + return svc.repo.ChangeStatus(ctx, group) +} diff --git a/groups/service_test.go b/groups/service_test.go new file mode 100644 index 0000000000..365aa8f767 --- /dev/null +++ b/groups/service_test.go @@ -0,0 +1,4 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package groups_test diff --git a/internal/groups/status.go b/groups/status.go similarity index 90% rename from internal/groups/status.go rename to groups/status.go index d967dbc0b4..a33ed29f0b 100644 --- a/internal/groups/status.go +++ b/groups/status.go @@ -14,6 +14,8 @@ const ( EnabledStatus Status = iota // DisabledStatus represents disabled Group. DisabledStatus + // DeletedStatus + DeletedStatus // AllStatus is used for querying purposes to list groups irrespective // of their status - both active and inactive. It is never stored in the @@ -26,6 +28,7 @@ const ( const ( Disabled = "disabled" Enabled = "enabled" + Deleted = "deleted" All = "all" Unknown = "unknown" ) @@ -37,6 +40,8 @@ func (s Status) String() string { return Disabled case EnabledStatus: return Enabled + case DeletedStatus: + return Deleted case AllStatus: return All default: @@ -51,6 +56,8 @@ func ToStatus(status string) (Status, error) { return DisabledStatus, nil case Enabled: return EnabledStatus, nil + case Deleted: + return DeletedStatus, nil case All: return AllStatus, nil } diff --git a/internal/groups/status_test.go b/groups/status_test.go similarity index 90% rename from internal/groups/status_test.go rename to groups/status_test.go index a715ee392d..1e3a627e98 100644 --- a/internal/groups/status_test.go +++ b/groups/status_test.go @@ -6,7 +6,7 @@ package groups_test import ( "testing" - "github.com/absmach/magistrala/internal/groups" + "github.com/absmach/magistrala/groups" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/stretchr/testify/assert" ) @@ -19,6 +19,7 @@ func TestStatus_String(t *testing.T) { }{ {"Enabled", groups.EnabledStatus, "enabled"}, {"Disabled", groups.DisabledStatus, "disabled"}, + {"Deleted", groups.DeletedStatus, "deleted"}, {"All", groups.AllStatus, "all"}, {"Unknown", groups.Status(100), "unknown"}, } @@ -38,6 +39,7 @@ func TestToStatus(t *testing.T) { }{ {"Enabled", "enabled", groups.EnabledStatus, nil}, {"Disabled", "disabled", groups.DisabledStatus, nil}, + {"Deleted", "deleted", groups.DeletedStatus, nil}, {"All", "all", groups.AllStatus, nil}, {"Unknown", "unknown", groups.Status(0), svcerr.ErrInvalidStatus}, } diff --git a/internal/groups/tracing/doc.go b/groups/tracing/doc.go similarity index 100% rename from internal/groups/tracing/doc.go rename to groups/tracing/doc.go diff --git a/groups/tracing/tracing.go b/groups/tracing/tracing.go new file mode 100644 index 0000000000..929919108b --- /dev/null +++ b/groups/tracing/tracing.go @@ -0,0 +1,167 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + "fmt" + + "github.com/absmach/magistrala/groups" + "github.com/absmach/magistrala/pkg/authn" + rmTrace "github.com/absmach/magistrala/pkg/roles/rolemanager/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +var _ groups.Service = (*tracingMiddleware)(nil) + +type tracingMiddleware struct { + tracer trace.Tracer + svc groups.Service + rmTrace.RoleManagerTracing +} + +// New returns a new group service with tracing capabilities. +func New(svc groups.Service, tracer trace.Tracer) groups.Service { + return &tracingMiddleware{tracer, svc, rmTrace.NewRoleManagerTracing("group", svc, tracer)} +} + +// CreateGroup traces the "CreateGroup" operation of the wrapped groups.Service. +func (tm *tracingMiddleware) CreateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { + ctx, span := tm.tracer.Start(ctx, "svc_create_group") + defer span.End() + + return tm.svc.CreateGroup(ctx, session, g) +} + +// ViewGroup traces the "ViewGroup" operation of the wrapped groups.Service. +func (tm *tracingMiddleware) ViewGroup(ctx context.Context, session authn.Session, 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.svc.ViewGroup(ctx, session, id) +} + +// ListGroups traces the "ListGroups" operation of the wrapped groups.Service. +func (tm *tracingMiddleware) ListGroups(ctx context.Context, session authn.Session, pm groups.PageMeta) (groups.Page, error) { + attr := []attribute.KeyValue{ + attribute.String("name", pm.Name), + attribute.String("tag", pm.Tag), + attribute.String("status", pm.Status.String()), + attribute.Int64("offset", int64(pm.Offset)), + attribute.Int64("limit", int64(pm.Limit)), + } + for k, v := range pm.Metadata { + attr = append(attr, attribute.String(k, fmt.Sprintf("%v", v))) + } + ctx, span := tm.tracer.Start(ctx, "svc_list_groups", trace.WithAttributes(attr...)) + defer span.End() + + return tm.svc.ListGroups(ctx, session, pm) +} + +// UpdateGroup traces the "UpdateGroup" operation of the wrapped groups.Service. +func (tm *tracingMiddleware) UpdateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { + ctx, span := tm.tracer.Start(ctx, "svc_update_group") + defer span.End() + + return tm.svc.UpdateGroup(ctx, session, g) +} + +// EnableGroup traces the "EnableGroup" operation of the wrapped groups.Service. +func (tm *tracingMiddleware) EnableGroup(ctx context.Context, session authn.Session, 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.svc.EnableGroup(ctx, session, id) +} + +// DisableGroup traces the "DisableGroup" operation of the wrapped groups.Service. +func (tm *tracingMiddleware) DisableGroup(ctx context.Context, session authn.Session, 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.svc.DisableGroup(ctx, session, id) +} + +func (tm *tracingMiddleware) RetrieveGroupHierarchy(ctx context.Context, session authn.Session, id string, hm groups.HierarchyPageMeta) (groups.HierarchyPage, error) { + ctx, span := tm.tracer.Start(ctx, "svc_list_group_hierarchy", + trace.WithAttributes( + attribute.String("id", id), + attribute.Int64("level", int64(hm.Level)), + attribute.Int64("direction", hm.Direction), + attribute.Bool("tree", hm.Tree), + )) + defer span.End() + + return tm.svc.RetrieveGroupHierarchy(ctx, session, id, hm) +} + +func (tm *tracingMiddleware) AddParentGroup(ctx context.Context, session authn.Session, id, parentID string) error { + ctx, span := tm.tracer.Start(ctx, "svc_add_parent_group", + trace.WithAttributes( + attribute.String("id", id), + attribute.String("parent_id", parentID), + )) + defer span.End() + return tm.svc.AddParentGroup(ctx, session, id, parentID) +} + +func (tm *tracingMiddleware) RemoveParentGroup(ctx context.Context, session authn.Session, id string) error { + ctx, span := tm.tracer.Start(ctx, "svc_remove_parent_group", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + return tm.svc.RemoveParentGroup(ctx, session, id) +} + +func (tm *tracingMiddleware) AddChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + ctx, span := tm.tracer.Start(ctx, "svc_add_children_groups", + trace.WithAttributes( + attribute.String("id", id), + attribute.StringSlice("children_group_ids", childrenGroupIDs), + )) + + defer span.End() + return tm.svc.AddChildrenGroups(ctx, session, id, childrenGroupIDs) +} + +func (tm *tracingMiddleware) RemoveChildrenGroups(ctx context.Context, session authn.Session, id string, childrenGroupIDs []string) error { + ctx, span := tm.tracer.Start(ctx, "svc_remove_children_groups", + trace.WithAttributes( + attribute.String("id", id), + attribute.StringSlice("children_group_ids", childrenGroupIDs), + )) + defer span.End() + return tm.svc.RemoveChildrenGroups(ctx, session, id, childrenGroupIDs) +} + +func (tm *tracingMiddleware) RemoveAllChildrenGroups(ctx context.Context, session authn.Session, id string) error { + ctx, span := tm.tracer.Start(ctx, "svc_remove_all_children_groups", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + return tm.svc.RemoveAllChildrenGroups(ctx, session, id) +} + +func (tm *tracingMiddleware) ListChildrenGroups(ctx context.Context, session authn.Session, id string, pm groups.PageMeta) (groups.Page, error) { + attr := []attribute.KeyValue{ + attribute.String("id", id), + attribute.String("name", pm.Name), + attribute.String("tag", pm.Tag), + attribute.String("status", pm.Status.String()), + attribute.Int64("offset", int64(pm.Offset)), + attribute.Int64("limit", int64(pm.Limit)), + } + for k, v := range pm.Metadata { + attr = append(attr, attribute.String(k, fmt.Sprintf("%v", v))) + } + ctx, span := tm.tracer.Start(ctx, "svc_list_children_groups", trace.WithAttributes(attr...)) + defer span.End() + return tm.svc.ListChildrenGroups(ctx, session, id, pm) +} + +// DeleteGroup traces the "DeleteGroup" operation of the wrapped groups.Service. +func (tm *tracingMiddleware) DeleteGroup(ctx context.Context, session authn.Session, id string) error { + ctx, span := tm.tracer.Start(ctx, "svc_delete_group", trace.WithAttributes(attribute.String("id", id))) + defer span.End() + + return tm.svc.DeleteGroup(ctx, session, id) +} diff --git a/http/README.md b/http/README.md index 5aeaa75180..21a2b8ca73 100644 --- a/http/README.md +++ b/http/README.md @@ -6,29 +6,29 @@ HTTP adapter provides an HTTP API for sending messages through the platform. The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| -------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------- | -| MG_HTTP_ADAPTER_LOG_LEVEL | Log level for the HTTP Adapter (debug, info, warn, error) | info | -| MG_HTTP_ADAPTER_HOST | Service HTTP host | "" | -| MG_HTTP_ADAPTER_PORT | Service HTTP port | 80 | -| MG_HTTP_ADAPTER_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| MG_HTTP_ADAPTER_SERVER_KEY | Path to the PEM encoded server key file | "" | -| MG_THINGS_AUTH_GRPC_URL | Things service Auth gRPC URL | | -| MG_THINGS_AUTH_GRPC_TIMEOUT | Things service Auth gRPC request timeout in seconds | 1s | -| MG_THINGS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded things service Auth gRPC client certificate file | "" | -| MG_THINGS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded things service Auth gRPC client key file | "" | -| MG_THINGS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded things server Auth gRPC server trusted CA certificate file | "" | -| MG_MESSAGE_BROKER_URL | Message broker instance URL | | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_HTTP_ADAPTER_INSTANCE_ID | Service instance ID | "" | +| Variable | Description | Default | +| --------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | +| MG_HTTP_ADAPTER_LOG_LEVEL | Log level for the HTTP Adapter (debug, info, warn, error) | info | +| MG_HTTP_ADAPTER_HOST | Service HTTP host | "" | +| MG_HTTP_ADAPTER_PORT | Service HTTP port | 80 | +| MG_HTTP_ADAPTER_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| MG_HTTP_ADAPTER_SERVER_KEY | Path to the PEM encoded server key file | "" | +| MG_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| MG_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| MG_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | +| MG_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | +| MG_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | +| MG_MESSAGE_BROKER_URL | Message broker instance URL | | +| MG_JAEGER_URL | Jaeger server URL | | +| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_HTTP_ADAPTER_INSTANCE_ID | Service instance ID | "" | ## Deployment The service itself is distributed as Docker container. Check the [`http-adapter`](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yml) service section in docker-compose file to see how service is deployed. -Running this service outside of container requires working instance of the message broker service, things service and Jaeger server. +Running this service outside of container requires working instance of the message broker service, clients service and Jaeger server. To start the service outside of the container, execute the following shell script: ```bash @@ -49,11 +49,11 @@ MG_HTTP_ADAPTER_HOST=localhost \ MG_HTTP_ADAPTER_PORT=80 \ MG_HTTP_ADAPTER_SERVER_CERT="" \ MG_HTTP_ADAPTER_SERVER_KEY="" \ -MG_THINGS_AUTH_GRPC_URL=localhost:7000 \ -MG_THINGS_AUTH_GRPC_TIMEOUT=1s \ -MG_THINGS_AUTH_GRPC_CLIENT_CERT="" \ -MG_THINGS_AUTH_GRPC_CLIENT_KEY="" \ -MG_THINGS_AUTH_GRPC_SERVER_CERTS="" \ +MG_CLIENTS_AUTH_GRPC_URL=localhost:7000 \ +MG_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ +MG_CLIENTS_AUTH_GRPC_CLIENT_CERT="" \ +MG_CLIENTS_AUTH_GRPC_CLIENT_KEY="" \ +MG_CLIENTS_AUTH_GRPC_SERVER_CERTS="" \ MG_MESSAGE_BROKER_URL=nats://localhost:4222 \ MG_JAEGER_URL=http://localhost:14268/api/traces \ MG_JAEGER_TRACE_RATIO=1.0 \ @@ -64,8 +64,8 @@ $GOBIN/magistrala-http Setting `MG_HTTP_ADAPTER_SERVER_CERT` and `MG_HTTP_ADAPTER_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key. -Setting `MG_THINGS_AUTH_GRPC_CLIENT_CERT` and `MG_THINGS_AUTH_GRPC_CLIENT_KEY` will enable TLS against the things service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_THINGS_AUTH_GRPC_SERVER_CERTS` will enable TLS against the things service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. +Setting `MG_CLIENTS_AUTH_GRPC_CLIENT_CERT` and `MG_CLIENTS_AUTH_GRPC_CLIENT_KEY` will enable TLS against the clients service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_CLIENTS_AUTH_GRPC_SERVER_CERTS` will enable TLS against the clients service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. ## Usage -HTTP Authorization request header contains the credentials to authenticate a Thing. The authorization header can be a plain Thing key or a Thing key encoded as a password for Basic Authentication. In case the Basic Authentication schema is used, the username is ignored. For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=http.yml). +HTTP Authorization request header contains the credentials to authenticate a Client. The authorization header can be a plain Client key or a Client key encoded as a password for Basic Authentication. In case the Basic Authentication schema is used, the username is ignored. For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=http.yml). diff --git a/http/api/endpoint_test.go b/http/api/endpoint_test.go index b41f223f38..231120eb17 100644 --- a/http/api/endpoint_test.go +++ b/http/api/endpoint_test.go @@ -11,13 +11,17 @@ import ( "strings" "testing" - "github.com/absmach/magistrala" + chmocks "github.com/absmach/magistrala/channels/mocks" + climocks "github.com/absmach/magistrala/clients/mocks" server "github.com/absmach/magistrala/http" "github.com/absmach/magistrala/http/api" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" + authnMocks "github.com/absmach/magistrala/pkg/authn/mocks" pubsub "github.com/absmach/magistrala/pkg/messaging/mocks" - thmocks "github.com/absmach/magistrala/things/mocks" "github.com/absmach/mgate" proxy "github.com/absmach/mgate/pkg/http" "github.com/absmach/mgate/pkg/session" @@ -30,9 +34,9 @@ const ( invalidValue = "invalid" ) -func newService(things magistrala.ThingsServiceClient) (session.Handler, *pubsub.PubSub) { +func newService(authn mgauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) (session.Handler, *pubsub.PubSub) { pub := new(pubsub.PubSub) - return server.NewHandler(pub, mglog.NewMock(), things), pub + return server.NewHandler(pub, authn, clients, channels, mglog.NewMock()), pub } func newTargetHTTPServer() *httptest.Server { @@ -69,7 +73,7 @@ func (tr testRequest) make() (*http.Response, error) { } if tr.token != "" { - req.Header.Set("Authorization", apiutil.ThingPrefix+tr.token) + req.Header.Set("Authorization", apiutil.ClientPrefix+tr.token) } if tr.basicAuth && tr.token != "" { req.SetBasicAuth("", tr.token) @@ -81,17 +85,19 @@ func (tr testRequest) make() (*http.Response, error) { } func TestPublish(t *testing.T) { - things := new(thmocks.ThingsServiceClient) + clients := new(climocks.ClientsServiceClient) + authn := new(authnMocks.Authentication) + channels := new(chmocks.ChannelsServiceClient) chanID := "1" ctSenmlJSON := "application/senml+json" ctSenmlCBOR := "application/senml+cbor" ctJSON := "application/json" - thingKey := "thing_key" + clientKey := "client_key" invalidKey := invalidValue msg := `[{"n":"current","t":-1,"v":1.6}]` msgJSON := `{"field1":"val1","field2":"val2"}` msgCBOR := `81A3616E6763757272656E746174206176FB3FF999999999999A` - svc, pub := newService(things) + svc, pub := newService(authn, clients, channels) target := newTargetHTTPServer() defer target.Close() ts, err := newProxyHTPPServer(svc, target) @@ -99,9 +105,6 @@ func TestPublish(t *testing.T) { defer ts.Close() - things.On("Authorize", mock.Anything, &magistrala.ThingsAuthzReq{ThingKey: thingKey, ChannelId: chanID, Permission: "publish"}).Return(&magistrala.ThingsAuthzRes{Authorized: true, Id: ""}, nil) - things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.ThingsAuthzRes{Authorized: false, Id: ""}, nil) - cases := map[string]struct { chanID string msg string @@ -114,21 +117,21 @@ func TestPublish(t *testing.T) { chanID: chanID, msg: msg, contentType: ctSenmlJSON, - key: thingKey, + key: clientKey, status: http.StatusAccepted, }, "publish message with application/senml+cbor content-type": { chanID: chanID, msg: msgCBOR, contentType: ctSenmlCBOR, - key: thingKey, + key: clientKey, status: http.StatusAccepted, }, "publish message with application/json content-type": { chanID: chanID, msg: msgJSON, contentType: ctJSON, - key: thingKey, + key: clientKey, status: http.StatusAccepted, }, "publish message with empty key": { @@ -142,7 +145,7 @@ func TestPublish(t *testing.T) { chanID: chanID, msg: msg, contentType: ctSenmlJSON, - key: thingKey, + key: clientKey, basicAuth: true, status: http.StatusAccepted, }, @@ -165,14 +168,14 @@ func TestPublish(t *testing.T) { chanID: chanID, msg: msg, contentType: "", - key: thingKey, + key: clientKey, status: http.StatusUnsupportedMediaType, }, "publish message to invalid channel": { chanID: "", msg: msg, contentType: ctSenmlJSON, - key: thingKey, + key: clientKey, status: http.StatusBadRequest, }, } diff --git a/http/api/transport.go b/http/api/transport.go index 52ed24208a..7796cf884a 100644 --- a/http/api/transport.go +++ b/http/api/transport.go @@ -64,7 +64,7 @@ func decodeRequest(_ context.Context, r *http.Request) (interface{}, error) { case ok: req.token = pass case !ok: - req.token = apiutil.ExtractThingKey(r) + req.token = apiutil.ExtractClientSecret(r) } payload, err := io.ReadAll(r.Body) diff --git a/http/handler.go b/http/handler.go index f81059c521..be26f666b1 100644 --- a/http/handler.go +++ b/http/handler.go @@ -13,8 +13,11 @@ import ( "strings" "time" - "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" @@ -25,12 +28,20 @@ import ( var _ session.Handler = (*handler)(nil) -const protocol = "http" +type ctxKey string + +const ( + protocol = "http" + clientIDCtxKey ctxKey = "client_id" + clientTypeCtxKey ctxKey = "client_type" +) // Log message formats. const ( - logInfoConnected = "connected with thing_key %s" - logInfoPublished = "published with client_id %s to the topic %s" + logInfoConnected = "connected with client_key %s" + logInfoPublished = "published with client_type %s client_id %s to the topic %s" + logInfoFailedAuthNToken = "failed to authenticate token for topic %s with error %s" + logInfoFailedAuthNClient = "failed to authenticate client key %s for topic %s with error %s" ) // Error wrappers for MQTT errors. @@ -49,16 +60,20 @@ var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?] // Event implements events.Event interface. type handler struct { publisher messaging.Publisher - things magistrala.ThingsServiceClient + clients grpcClientsV1.ClientsServiceClient + channels grpcChannelsV1.ChannelsServiceClient + authn mgauthn.Authentication logger *slog.Logger } // NewHandler creates new Handler entity. -func NewHandler(publisher messaging.Publisher, logger *slog.Logger, thingsClient magistrala.ThingsServiceClient) session.Handler { +func NewHandler(publisher messaging.Publisher, authn mgauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient, logger *slog.Logger) session.Handler { return &handler{ - logger: logger, publisher: publisher, - things: thingsClient, + authn: authn, + clients: clients, + channels: channels, + logger: logger, } } @@ -74,8 +89,8 @@ func (h *handler) AuthConnect(ctx context.Context) error { switch { case string(s.Password) == "": return mgate.NewHTTPProxyError(http.StatusBadRequest, errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerKey)) - case strings.HasPrefix(string(s.Password), apiutil.ThingPrefix): - tok = strings.TrimPrefix(string(s.Password), apiutil.ThingPrefix) + case strings.HasPrefix(string(s.Password), apiutil.ClientPrefix): + tok = strings.TrimPrefix(string(s.Password), apiutil.ClientPrefix) default: tok = string(s.Password) } @@ -109,21 +124,38 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e if !ok { return errors.Wrap(errFailedPublish, errClientNotInitialized) } - h.logger.Info(fmt.Sprintf(logInfoPublished, s.ID, *topic)) - // Topics are in the format: - // channels//messages//.../ct/ - channelParts := channelRegExp.FindStringSubmatch(*topic) - if len(channelParts) < 2 { - return mgate.NewHTTPProxyError(http.StatusBadRequest, errors.Wrap(errFailedPublish, errMalformedTopic)) + var clientID, clientType string + switch { + case strings.HasPrefix(string(s.Password), "Client"): + secret := strings.TrimPrefix(string(s.Password), apiutil.ClientPrefix) + authnRes, err := h.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ClientSecret: secret}) + if err != nil { + h.logger.Info(fmt.Sprintf(logInfoFailedAuthNClient, secret, *topic, err)) + return mgate.NewHTTPProxyError(http.StatusUnauthorized, svcerr.ErrAuthentication) + } + if !authnRes.Authenticated { + h.logger.Info(fmt.Sprintf(logInfoFailedAuthNClient, secret, *topic, svcerr.ErrAuthentication)) + return mgate.NewHTTPProxyError(http.StatusUnauthorized, svcerr.ErrAuthentication) + } + clientType = policies.ClientType + clientID = authnRes.GetId() + case strings.HasPrefix(string(s.Password), apiutil.BearerPrefix): + token := strings.TrimPrefix(string(s.Password), apiutil.BearerPrefix) + authnSession, err := h.authn.Authenticate(ctx, token) + if err != nil { + h.logger.Info(fmt.Sprintf(logInfoFailedAuthNToken, *topic, err)) + return mgate.NewHTTPProxyError(http.StatusUnauthorized, svcerr.ErrAuthentication) + } + clientType = policies.UserType + clientID = authnSession.DomainUserID + default: + return mgate.NewHTTPProxyError(http.StatusUnauthorized, svcerr.ErrAuthentication) } - chanID := channelParts[1] - subtopic := channelParts[2] - - subtopic, err := parseSubtopic(subtopic) + chanID, subtopic, err := parseTopic(*topic) if err != nil { - return mgate.NewHTTPProxyError(http.StatusBadRequest, errors.Wrap(errFailedParseSubtopic, err)) + return mgate.NewHTTPProxyError(http.StatusBadRequest, err) } msg := messaging.Message{ @@ -133,33 +165,31 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e Payload: *payload, Created: time.Now().UnixNano(), } - var tok string - switch { - case string(s.Password) == "": - return errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerKey) - case strings.HasPrefix(string(s.Password), apiutil.ThingPrefix): - tok = strings.TrimPrefix(string(s.Password), apiutil.ThingPrefix) - default: - tok = string(s.Password) - } - ar := &magistrala.ThingsAuthzReq{ - ThingKey: tok, + + ar := &grpcChannelsV1.AuthzReq{ + ClientId: clientID, + ClientType: clientType, ChannelId: msg.Channel, - Permission: policies.PublishPermission, + Type: uint32(connections.Publish), } - res, err := h.things.Authorize(ctx, ar) + res, err := h.channels.Authorize(ctx, ar) if err != nil { return mgate.NewHTTPProxyError(http.StatusBadRequest, err) } if !res.GetAuthorized() { return mgate.NewHTTPProxyError(http.StatusUnauthorized, svcerr.ErrAuthorization) } - msg.Publisher = res.GetId() + + if clientType == policies.ClientType { + msg.Publisher = clientID + } if err := h.publisher.Publish(ctx, msg.Channel, &msg); err != nil { return errors.Wrap(errFailedPublishToMsgBroker, err) } + h.logger.Info(fmt.Sprintf(logInfoPublished, clientType, clientID, *topic)) + return nil } @@ -178,6 +208,25 @@ func (h *handler) Disconnect(ctx context.Context) error { return nil } +func parseTopic(topic string) (string, string, error) { + // Topics are in the format: + // channels//messages//.../ct/ + channelParts := channelRegExp.FindStringSubmatch(topic) + if len(channelParts) < 2 { + return "", "", errors.Wrap(errFailedPublish, errMalformedTopic) + } + + chanID := channelParts[1] + subtopic := channelParts[2] + + subtopic, err := parseSubtopic(subtopic) + if err != nil { + return "", "", errors.Wrap(errFailedParseSubtopic, err) + } + + return chanID, subtopic, nil +} + func parseSubtopic(subtopic string) (string, error) { if subtopic == "" { return subtopic, nil diff --git a/internal/api/common.go b/internal/api/common.go index 7c61ed26e9..61ccf3c272 100644 --- a/internal/api/common.go +++ b/internal/api/common.go @@ -11,11 +11,11 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/certs" - "github.com/absmach/magistrala/internal/groups" + "github.com/absmach/magistrala/clients" + "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/things" "github.com/absmach/magistrala/users" "github.com/gofrs/uuid/v5" ) @@ -50,7 +50,7 @@ const ( EmailKey = "email" SharedByKey = "shared_by" TokenKey = "token" - DefPermission = "view" + DefPermission = "read_permission" DefTotal = uint64(100) DefOffset = 0 DefOrder = "updated_at" @@ -58,7 +58,7 @@ const ( DefLimit = 10 DefLevel = 0 DefStatus = "enabled" - DefClientStatus = things.Enabled + DefClientStatus = clients.Enabled DefUserStatus = users.Enabled DefGroupStatus = groups.Enabled DefListPerms = false @@ -71,6 +71,7 @@ const ( // MaxNameSize limits name size to prevent making them too complex. MaxLimitSize = 100 MaxNameSize = 1024 + MaxIDSize = 36 NameOrder = "name" IDOrder = "id" AscDir = "asc" @@ -184,7 +185,9 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { case errors.Contains(err, svcerr.ErrCreateEntity), errors.Contains(err, svcerr.ErrUpdateEntity), errors.Contains(err, svcerr.ErrRemoveEntity), - errors.Contains(err, svcerr.ErrEnableClient): + errors.Contains(err, svcerr.ErrEnableClient), + errors.Contains(err, svcerr.ErrEnableUser), + errors.Contains(err, svcerr.ErrDisableUser): err = unwrap(err) w.WriteHeader(http.StatusUnprocessableEntity) diff --git a/internal/groups/api/endpoint_test.go b/internal/groups/api/endpoint_test.go deleted file mode 100644 index 4a69f2fcdb..0000000000 --- a/internal/groups/api/endpoint_test.go +++ /dev/null @@ -1,1195 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "fmt" - "net/http" - "testing" - "time" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/groups/mocks" - "github.com/absmach/magistrala/pkg/policies" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - validGroupResp = groups.Group{ - ID: testsutil.GenerateUUID(&testing.T{}), - Name: valid, - Description: valid, - Domain: testsutil.GenerateUUID(&testing.T{}), - Parent: testsutil.GenerateUUID(&testing.T{}), - Metadata: groups.Metadata{ - "name": "test", - }, - Children: []*groups.Group{}, - CreatedAt: time.Now().Add(-1 * time.Second), - UpdatedAt: time.Now(), - UpdatedBy: testsutil.GenerateUUID(&testing.T{}), - Status: groups.EnabledStatus, - } - validID = testsutil.GenerateUUID(&testing.T{}) -) - -func TestCreateGroupEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - kind string - session interface{} - req createGroupReq - svcResp groups.Group - svcErr error - resp createGroupRes - err error - }{ - { - desc: "successfully with groups kind", - kind: policies.NewGroupKind, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: createGroupReq{ - Group: groups.Group{ - Name: valid, - }, - }, - svcResp: validGroupResp, - svcErr: nil, - resp: createGroupRes{created: true, Group: validGroupResp}, - err: nil, - }, - { - desc: "successfully with channels kind", - kind: policies.NewChannelKind, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: createGroupReq{ - Group: groups.Group{ - Name: valid, - }, - }, - svcResp: validGroupResp, - svcErr: nil, - resp: createGroupRes{created: true, Group: validGroupResp}, - err: nil, - }, - { - desc: "unsuccessfully with invalid session", - kind: policies.NewGroupKind, - session: nil, - req: createGroupReq{ - Group: groups.Group{ - Name: valid, - }, - }, - resp: createGroupRes{created: false}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid request", - kind: policies.NewGroupKind, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: createGroupReq{ - Group: groups.Group{}, - }, - resp: createGroupRes{created: false}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - kind: policies.NewGroupKind, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: createGroupReq{ - Group: groups.Group{ - Name: valid, - }, - }, - svcResp: groups.Group{}, - svcErr: svcerr.ErrAuthorization, - resp: createGroupRes{created: false}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - svcCall := svc.On("CreateGroup", ctx, tc.session, tc.kind, tc.req.Group).Return(tc.svcResp, tc.svcErr) - resp, err := CreateGroupEndpoint(svc, tc.kind)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(createGroupRes) - switch err { - case nil: - assert.Equal(t, response.Code(), http.StatusCreated) - assert.Equal(t, response.Headers()["Location"], fmt.Sprintf("/groups/%s", response.ID)) - default: - assert.Equal(t, response.Code(), http.StatusOK) - assert.Empty(t, response.Headers()) - } - assert.False(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestViewGroupEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - req groupReq - session interface{} - svcResp groups.Group - svcErr error - resp viewGroupRes - err error - }{ - { - desc: "successfully", - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: groupReq{ - id: testsutil.GenerateUUID(t), - }, - svcResp: validGroupResp, - svcErr: nil, - resp: viewGroupRes{Group: validGroupResp}, - err: nil, - }, - { - desc: "unsuccessfully with invalid session", - req: groupReq{ - id: testsutil.GenerateUUID(t), - }, - svcResp: groups.Group{}, - svcErr: nil, - resp: viewGroupRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid request", - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: groupReq{}, - svcResp: groups.Group{}, - svcErr: nil, - resp: viewGroupRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: groupReq{ - id: testsutil.GenerateUUID(t), - }, - svcResp: groups.Group{}, - svcErr: svcerr.ErrAuthorization, - resp: viewGroupRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - svcCall := svc.On("ViewGroup", ctx, tc.session, tc.req.id).Return(tc.svcResp, tc.svcErr) - resp, err := ViewGroupEndpoint(svc)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(viewGroupRes) - assert.Equal(t, response.Code(), http.StatusOK) - assert.Empty(t, response.Headers()) - assert.False(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestViewGroupPermsEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - req groupPermsReq - session interface{} - svcResp []string - svcErr error - resp viewGroupPermsRes - err error - }{ - { - desc: "successfully", - req: groupPermsReq{ - id: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: []string{ - valid, - }, - svcErr: nil, - resp: viewGroupPermsRes{Permissions: []string{valid}}, - err: nil, - }, - { - desc: "unsuccessfully with invalid session", - req: groupPermsReq{ - id: testsutil.GenerateUUID(t), - }, - resp: viewGroupPermsRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid request", - req: groupPermsReq{}, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - resp: viewGroupPermsRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: groupPermsReq{ - id: testsutil.GenerateUUID(t), - }, - svcResp: []string{}, - svcErr: svcerr.ErrAuthorization, - resp: viewGroupPermsRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - svcCall := svc.On("ViewGroupPerms", ctx, tc.session, tc.req.id).Return(tc.svcResp, tc.svcErr) - resp, err := ViewGroupPermsEndpoint(svc)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(viewGroupPermsRes) - assert.Equal(t, response.Code(), http.StatusOK) - assert.Empty(t, response.Headers()) - assert.False(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestEnableGroupEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - req changeGroupStatusReq - session interface{} - svcResp groups.Group - svcErr error - resp changeStatusRes - err error - }{ - { - desc: "successfully", - req: changeGroupStatusReq{ - id: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: validGroupResp, - svcErr: nil, - resp: changeStatusRes{Group: validGroupResp}, - err: nil, - }, - { - desc: "unsuccessfully with invalid session", - req: changeGroupStatusReq{ - id: testsutil.GenerateUUID(t), - }, - resp: changeStatusRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid request", - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: changeGroupStatusReq{}, - resp: changeStatusRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - req: changeGroupStatusReq{ - id: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Group{}, - svcErr: svcerr.ErrAuthorization, - resp: changeStatusRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - svcCall := svc.On("EnableGroup", ctx, tc.session, tc.req.id).Return(tc.svcResp, tc.svcErr) - resp, err := EnableGroupEndpoint(svc)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(changeStatusRes) - assert.Equal(t, response.Code(), http.StatusOK) - assert.Empty(t, response.Headers()) - assert.False(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestDisableGroupEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - req changeGroupStatusReq - session interface{} - svcResp groups.Group - svcErr error - resp changeStatusRes - err error - }{ - { - desc: "successfully", - req: changeGroupStatusReq{ - id: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: validGroupResp, - svcErr: nil, - resp: changeStatusRes{Group: validGroupResp}, - err: nil, - }, - { - desc: "unsuccessfully with invalid session", - req: changeGroupStatusReq{ - id: testsutil.GenerateUUID(t), - }, - resp: changeStatusRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid request", - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: changeGroupStatusReq{}, - resp: changeStatusRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - req: changeGroupStatusReq{ - id: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Group{}, - svcErr: svcerr.ErrAuthorization, - resp: changeStatusRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - svcCall := svc.On("DisableGroup", ctx, tc.session, tc.req.id).Return(tc.svcResp, tc.svcErr) - resp, err := DisableGroupEndpoint(svc)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(changeStatusRes) - assert.Equal(t, response.Code(), http.StatusOK) - assert.Empty(t, response.Headers()) - assert.False(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestDeleteGroupEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - req groupReq - session interface{} - svcErr error - resp deleteGroupRes - err error - }{ - { - desc: "successfully", - req: groupReq{ - id: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: nil, - resp: deleteGroupRes{deleted: true}, - err: nil, - }, - { - desc: "unsuccessfully with invalid session", - req: groupReq{ - id: testsutil.GenerateUUID(t), - }, - resp: deleteGroupRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid request", - req: groupReq{}, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - resp: deleteGroupRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - req: groupReq{ - id: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: svcerr.ErrAuthorization, - resp: deleteGroupRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - svcCall := svc.On("DeleteGroup", ctx, tc.session, tc.req.id).Return(tc.svcErr) - resp, err := DeleteGroupEndpoint(svc)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(deleteGroupRes) - switch err { - case nil: - assert.Equal(t, response.Code(), http.StatusNoContent) - default: - assert.Equal(t, response.Code(), http.StatusBadRequest) - } - assert.Empty(t, response.Headers()) - assert.True(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestUpdateGroupEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - req updateGroupReq - session interface{} - svcResp groups.Group - svcErr error - resp updateGroupRes - err error - }{ - { - desc: "successfully", - req: updateGroupReq{ - id: testsutil.GenerateUUID(t), - Name: valid, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: validGroupResp, - svcErr: nil, - resp: updateGroupRes{Group: validGroupResp}, - err: nil, - }, - { - desc: "unsuccessfully with invalid session", - req: updateGroupReq{ - id: testsutil.GenerateUUID(t), - Name: valid, - }, - resp: updateGroupRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid request", - req: updateGroupReq{}, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - resp: updateGroupRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - req: updateGroupReq{ - id: testsutil.GenerateUUID(t), - Name: valid, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Group{}, - svcErr: svcerr.ErrAuthorization, - resp: updateGroupRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - group := groups.Group{ - ID: tc.req.id, - Name: tc.req.Name, - Description: tc.req.Description, - Metadata: tc.req.Metadata, - } - svcCall := svc.On("UpdateGroup", ctx, tc.session, group).Return(tc.svcResp, tc.svcErr) - resp, err := UpdateGroupEndpoint(svc)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(updateGroupRes) - assert.Equal(t, response.Code(), http.StatusOK) - assert.Empty(t, response.Headers()) - assert.False(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestListGroupsEndpoint(t *testing.T) { - svc := new(mocks.Service) - childGroup := groups.Group{ - ID: testsutil.GenerateUUID(t), - Name: valid, - Description: valid, - Domain: testsutil.GenerateUUID(t), - Parent: validGroupResp.ID, - Metadata: groups.Metadata{ - "name": "test", - }, - Level: -1, - Children: []*groups.Group{}, - CreatedAt: time.Now().Add(-1 * time.Second), - UpdatedAt: time.Now(), - UpdatedBy: testsutil.GenerateUUID(t), - Status: groups.EnabledStatus, - } - parentGroup := groups.Group{ - ID: testsutil.GenerateUUID(t), - Name: valid, - Description: valid, - Domain: testsutil.GenerateUUID(t), - Metadata: groups.Metadata{ - "name": "test", - }, - Level: 1, - Children: []*groups.Group{}, - CreatedAt: time.Now().Add(-1 * time.Second), - UpdatedAt: time.Now(), - UpdatedBy: testsutil.GenerateUUID(t), - Status: groups.EnabledStatus, - } - - validGroupResp.Children = append(validGroupResp.Children, &childGroup) - parentGroup.Children = append(parentGroup.Children, &validGroupResp) - - cases := []struct { - desc string - memberKind string - req listGroupsReq - session interface{} - svcResp groups.Page - svcErr error - resp groupPageRes - err error - }{ - { - desc: "successfully", - memberKind: policies.ThingsKind, - req: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - memberKind: policies.ThingsKind, - memberID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Page{ - Groups: []groups.Group{validGroupResp}, - }, - svcErr: nil, - resp: groupPageRes{ - Groups: []viewGroupRes{ - { - Group: validGroupResp, - }, - }, - }, - err: nil, - }, - { - desc: "successfully with empty member kind", - req: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - memberKind: policies.ThingsKind, - memberID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Page{ - Groups: []groups.Group{validGroupResp}, - }, - svcErr: nil, - resp: groupPageRes{ - Groups: []viewGroupRes{ - { - Group: validGroupResp, - }, - }, - }, - err: nil, - }, - { - desc: "successfully with tree", - memberKind: policies.ThingsKind, - req: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - tree: true, - memberKind: policies.ThingsKind, - memberID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Page{ - Groups: []groups.Group{validGroupResp, childGroup}, - }, - svcErr: nil, - resp: groupPageRes{ - Groups: []viewGroupRes{ - { - Group: validGroupResp, - }, - }, - }, - err: nil, - }, - { - desc: "list children groups successfully without tree", - memberKind: policies.UsersKind, - req: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - ParentID: validGroupResp.ID, - Direction: -1, - }, - tree: false, - memberKind: policies.UsersKind, - memberID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Page{ - Groups: []groups.Group{validGroupResp, childGroup}, - }, - svcErr: nil, - resp: groupPageRes{ - Groups: []viewGroupRes{ - { - Group: childGroup, - }, - }, - }, - err: nil, - }, - { - desc: "list parent group successfully without tree", - memberKind: policies.UsersKind, - req: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - ParentID: validGroupResp.ID, - Direction: 1, - }, - tree: false, - memberKind: policies.UsersKind, - memberID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Page{ - Groups: []groups.Group{parentGroup, validGroupResp}, - }, - svcErr: nil, - resp: groupPageRes{ - Groups: []viewGroupRes{ - { - Group: parentGroup, - }, - }, - }, - err: nil, - }, - { - desc: "unsuccessfully with invalid request", - memberKind: policies.ThingsKind, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - req: listGroupsReq{}, - resp: groupPageRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - memberKind: policies.ThingsKind, - req: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - memberKind: policies.ThingsKind, - memberID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.Page{}, - svcErr: svcerr.ErrAuthorization, - resp: groupPageRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid session", - memberKind: policies.ThingsKind, - req: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - memberKind: policies.ThingsKind, - memberID: testsutil.GenerateUUID(t), - }, - resp: groupPageRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with empty member kind", - req: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - memberKind: "", - memberID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - resp: groupPageRes{}, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - if tc.memberKind != "" { - tc.req.memberKind = tc.memberKind - } - svcCall := svc.On("ListGroups", ctx, tc.session, tc.req.memberKind, tc.req.memberID, tc.req.Page).Return(tc.svcResp, tc.svcErr) - resp, err := ListGroupsEndpoint(svc, mock.Anything, tc.memberKind)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(groupPageRes) - assert.Equal(t, response.Code(), http.StatusOK) - assert.Empty(t, response.Headers()) - assert.False(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestListMembersEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - memberKind string - req listMembersReq - session interface{} - svcResp groups.MembersPage - svcErr error - resp listMembersRes - err error - }{ - { - desc: "successfully", - memberKind: policies.ThingsKind, - req: listMembersReq{ - memberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.MembersPage{ - Members: []groups.Member{ - { - ID: valid, - Type: valid, - }, - }, - }, - svcErr: nil, - resp: listMembersRes{ - Members: []groups.Member{ - { - ID: valid, - Type: valid, - }, - }, - }, - err: nil, - }, - { - desc: "successfully with empty member kind", - req: listMembersReq{ - memberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.MembersPage{ - Members: []groups.Member{ - { - ID: valid, - Type: valid, - }, - }, - }, - svcErr: nil, - resp: listMembersRes{ - Members: []groups.Member{ - { - ID: valid, - Type: valid, - }, - }, - }, - err: nil, - }, - { - desc: "unsuccessfully with invalid request", - memberKind: policies.ThingsKind, - req: listMembersReq{}, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - resp: listMembersRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - memberKind: policies.ThingsKind, - req: listMembersReq{ - memberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcResp: groups.MembersPage{}, - svcErr: svcerr.ErrAuthorization, - resp: listMembersRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid session", - memberKind: policies.ThingsKind, - req: listMembersReq{ - memberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - }, - resp: listMembersRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - if tc.memberKind != "" { - tc.req.memberKind = tc.memberKind - } - svcCall := svc.On("ListMembers", ctx, tc.session, tc.req.groupID, tc.req.permission, tc.req.memberKind).Return(tc.svcResp, tc.svcErr) - resp, err := ListMembersEndpoint(svc, tc.memberKind)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(listMembersRes) - assert.Equal(t, response.Code(), http.StatusOK) - assert.Empty(t, response.Headers()) - assert.False(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestAssignMembersEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - relation string - session interface{} - memberKind string - req assignReq - svcErr error - resp assignRes - err error - }{ - { - desc: "successfully", - relation: policies.ContributorRelation, - memberKind: policies.ThingsKind, - req: assignReq{ - MemberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: nil, - resp: assignRes{assigned: true}, - err: nil, - }, - { - desc: "successfully with empty member kind", - relation: policies.ContributorRelation, - req: assignReq{ - groupID: testsutil.GenerateUUID(t), - MemberKind: policies.ThingsKind, - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: nil, - resp: assignRes{assigned: true}, - err: nil, - }, - { - desc: "successfully with empty relation", - memberKind: policies.ThingsKind, - req: assignReq{ - MemberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: nil, - resp: assignRes{assigned: true}, - err: nil, - }, - { - desc: "unsuccessfully with invalid request", - relation: policies.ContributorRelation, - memberKind: policies.ThingsKind, - req: assignReq{}, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - resp: assignRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - relation: policies.ContributorRelation, - memberKind: policies.ThingsKind, - req: assignReq{ - MemberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: svcerr.ErrAuthorization, - resp: assignRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid session", - relation: policies.ContributorRelation, - memberKind: policies.ThingsKind, - req: assignReq{ - MemberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - resp: assignRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - if tc.memberKind != "" { - tc.req.MemberKind = tc.memberKind - } - if tc.relation != "" { - tc.req.Relation = tc.relation - } - svcCall := svc.On("Assign", ctx, tc.session, tc.req.groupID, tc.req.Relation, tc.req.MemberKind, tc.req.Members).Return(tc.svcErr) - resp, err := AssignMembersEndpoint(svc, tc.relation, tc.memberKind)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(assignRes) - switch err { - case nil: - assert.Equal(t, response.Code(), http.StatusCreated) - default: - assert.Equal(t, response.Code(), http.StatusBadRequest) - } - assert.Empty(t, response.Headers()) - assert.True(t, response.Empty()) - svcCall.Unset() - }) - } -} - -func TestUnassignMembersEndpoint(t *testing.T) { - svc := new(mocks.Service) - cases := []struct { - desc string - relation string - memberKind string - req unassignReq - session interface{} - svcErr error - resp unassignRes - err error - }{ - { - desc: "successfully", - relation: policies.ContributorRelation, - memberKind: policies.ThingsKind, - req: unassignReq{ - MemberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: nil, - resp: unassignRes{unassigned: true}, - err: nil, - }, - { - desc: "successfully with empty member kind", - relation: policies.ContributorRelation, - req: unassignReq{ - groupID: testsutil.GenerateUUID(t), - MemberKind: policies.ThingsKind, - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: nil, - resp: unassignRes{unassigned: true}, - err: nil, - }, - { - desc: "successfully with empty relation", - memberKind: policies.ThingsKind, - req: unassignReq{ - MemberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - svcErr: nil, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - resp: unassignRes{unassigned: true}, - err: nil, - }, - { - desc: "unsuccessfully with invalid request", - relation: policies.ContributorRelation, - memberKind: policies.ThingsKind, - req: unassignReq{}, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - resp: unassignRes{}, - err: apiutil.ErrValidation, - }, - { - desc: "unsuccessfully with repo error", - relation: policies.ContributorRelation, - memberKind: policies.ThingsKind, - req: unassignReq{ - MemberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - session: mgauthn.Session{DomainUserID: validID + "_" + validID, UserID: validID, DomainID: validID}, - svcErr: svcerr.ErrAuthorization, - resp: unassignRes{}, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with invalid session", - relation: policies.ContributorRelation, - memberKind: policies.ThingsKind, - req: unassignReq{ - MemberKind: policies.ThingsKind, - groupID: testsutil.GenerateUUID(t), - Members: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - resp: unassignRes{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - ctx := context.WithValue(context.Background(), api.SessionKey, tc.session) - if tc.memberKind != "" { - tc.req.MemberKind = tc.memberKind - } - if tc.relation != "" { - tc.req.Relation = tc.relation - } - svcCall := svc.On("Unassign", ctx, tc.session, tc.req.groupID, tc.req.Relation, tc.req.MemberKind, tc.req.Members).Return(tc.svcErr) - resp, err := UnassignMembersEndpoint(svc, tc.relation, tc.memberKind)(ctx, tc.req) - assert.Equal(t, tc.resp, resp, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.resp, resp)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - response := resp.(unassignRes) - switch err { - case nil: - assert.Equal(t, response.Code(), http.StatusCreated) - default: - assert.Equal(t, response.Code(), http.StatusBadRequest) - } - assert.Empty(t, response.Headers()) - assert.True(t, response.Empty()) - svcCall.Unset() - }) - } -} diff --git a/internal/groups/api/requests.go b/internal/groups/api/requests.go deleted file mode 100644 index 7144ef2361..0000000000 --- a/internal/groups/api/requests.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - mggroups "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/policies" -) - -type createGroupReq struct { - mggroups.Group -} - -func (req createGroupReq) validate() error { - if len(req.Name) > api.MaxNameSize || req.Name == "" { - return apiutil.ErrNameSize - } - - return nil -} - -type updateGroupReq struct { - 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.id == "" { - return apiutil.ErrMissingID - } - if len(req.Name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - return nil -} - -type listGroupsReq struct { - mggroups.Page - memberKind string - memberID 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.memberKind == "" { - return apiutil.ErrMissingMemberKind - } - if req.memberKind == policies.ThingsKind && req.memberID == "" { - return apiutil.ErrMissingID - } - if req.Level > mggroups.MaxLevel { - return apiutil.ErrInvalidLevel - } - if req.Limit > api.MaxLimitSize || req.Limit < 1 { - return apiutil.ErrLimitSize - } - - return nil -} - -type groupReq struct { - id string -} - -func (req groupReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type groupPermsReq struct { - id string -} - -func (req groupPermsReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type changeGroupStatusReq struct { - id string -} - -func (req changeGroupStatusReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - return nil -} - -type assignReq struct { - groupID string - Relation string `json:"relation,omitempty"` - MemberKind string `json:"member_kind,omitempty"` - Members []string `json:"members"` -} - -func (req assignReq) validate() error { - if req.MemberKind == "" { - return apiutil.ErrMissingMemberKind - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.Members) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type unassignReq struct { - groupID string - Relation string `json:"relation,omitempty"` - MemberKind string `json:"member_kind,omitempty"` - Members []string `json:"members"` -} - -func (req unassignReq) validate() error { - if req.MemberKind == "" { - return apiutil.ErrMissingMemberKind - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.Members) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type listMembersReq struct { - groupID string - permission string - memberKind string -} - -func (req listMembersReq) validate() error { - if req.memberKind == "" { - return apiutil.ErrMissingMemberKind - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - return nil -} diff --git a/internal/groups/api/requests_test.go b/internal/groups/api/requests_test.go deleted file mode 100644 index ed9fa15ac5..0000000000 --- a/internal/groups/api/requests_test.go +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "strings" - "testing" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/policies" - "github.com/stretchr/testify/assert" -) - -var valid = "valid" - -func TestCreateGroupReqValidation(t *testing.T) { - cases := []struct { - desc string - req createGroupReq - err error - }{ - { - desc: "valid request", - req: createGroupReq{ - Group: groups.Group{ - Name: valid, - }, - }, - err: nil, - }, - { - desc: "long name", - req: createGroupReq{ - Group: groups.Group{ - Name: strings.Repeat("a", api.MaxNameSize+1), - }, - }, - err: apiutil.ErrNameSize, - }, - { - desc: "empty name", - req: createGroupReq{ - Group: groups.Group{}, - }, - err: apiutil.ErrNameSize, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestUpdateGroupReqValidation(t *testing.T) { - cases := []struct { - desc string - req updateGroupReq - err error - }{ - { - desc: "valid request", - req: updateGroupReq{ - id: valid, - Name: valid, - }, - err: nil, - }, - { - desc: "long name", - req: updateGroupReq{ - id: valid, - Name: strings.Repeat("a", api.MaxNameSize+1), - }, - err: apiutil.ErrNameSize, - }, - { - desc: "empty id", - req: updateGroupReq{ - Name: valid, - }, - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestListGroupReqValidation(t *testing.T) { - cases := []struct { - desc string - req listGroupsReq - err error - }{ - { - desc: "valid request", - req: listGroupsReq{ - memberKind: policies.ThingsKind, - memberID: valid, - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - }, - err: nil, - }, - { - desc: "empty memberkind", - req: listGroupsReq{ - memberID: valid, - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - }, - err: apiutil.ErrMissingMemberKind, - }, - { - desc: "empty member id", - req: listGroupsReq{ - memberKind: policies.ThingsKind, - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - }, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "invalid upper level", - req: listGroupsReq{ - memberKind: policies.ThingsKind, - memberID: valid, - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - Level: groups.MaxLevel + 1, - }, - }, - err: apiutil.ErrInvalidLevel, - }, - { - desc: "invalid lower limit", - req: listGroupsReq{ - memberKind: policies.ThingsKind, - memberID: valid, - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 0, - }, - }, - }, - err: apiutil.ErrLimitSize, - }, - { - desc: "invalid upper limit", - req: listGroupsReq{ - memberKind: policies.ThingsKind, - memberID: valid, - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: api.MaxLimitSize + 1, - }, - }, - }, - err: apiutil.ErrLimitSize, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestGroupReqValidation(t *testing.T) { - cases := []struct { - desc string - req groupReq - err error - }{ - { - desc: "valid request", - req: groupReq{ - id: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: groupReq{}, - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestGroupPermsReqValidation(t *testing.T) { - cases := []struct { - desc string - req groupPermsReq - err error - }{ - { - desc: "valid request", - req: groupPermsReq{ - id: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: groupPermsReq{}, - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestChangeGroupStatusReqValidation(t *testing.T) { - cases := []struct { - desc string - req changeGroupStatusReq - err error - }{ - { - desc: "valid request", - req: changeGroupStatusReq{ - id: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: changeGroupStatusReq{}, - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestAssignReqValidation(t *testing.T) { - cases := []struct { - desc string - req assignReq - err error - }{ - { - desc: "valid request", - req: assignReq{ - groupID: valid, - Relation: policies.ContributorRelation, - MemberKind: policies.ThingsKind, - Members: []string{valid}, - }, - err: nil, - }, - { - desc: "empty member kind", - req: assignReq{ - groupID: valid, - Relation: policies.ContributorRelation, - Members: []string{valid}, - }, - err: apiutil.ErrMissingMemberKind, - }, - { - desc: "empty groupID", - req: assignReq{ - Relation: policies.ContributorRelation, - MemberKind: policies.ThingsKind, - Members: []string{valid}, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty Members", - req: assignReq{ - groupID: valid, - Relation: policies.ContributorRelation, - MemberKind: policies.ThingsKind, - }, - err: apiutil.ErrEmptyList, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestUnAssignReqValidation(t *testing.T) { - cases := []struct { - desc string - req unassignReq - err error - }{ - { - desc: "valid request", - req: unassignReq{ - groupID: valid, - Relation: policies.ContributorRelation, - MemberKind: policies.ThingsKind, - Members: []string{valid}, - }, - err: nil, - }, - { - desc: "empty member kind", - req: unassignReq{ - groupID: valid, - Relation: policies.ContributorRelation, - Members: []string{valid}, - }, - err: apiutil.ErrMissingMemberKind, - }, - { - desc: "empty groupID", - req: unassignReq{ - Relation: policies.ContributorRelation, - MemberKind: policies.ThingsKind, - Members: []string{valid}, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty Members", - req: unassignReq{ - groupID: valid, - Relation: policies.ContributorRelation, - MemberKind: policies.ThingsKind, - }, - err: apiutil.ErrEmptyList, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestListMembersReqValidation(t *testing.T) { - cases := []struct { - desc string - req listMembersReq - err error - }{ - { - desc: "valid request", - req: listMembersReq{ - groupID: valid, - permission: policies.ViewPermission, - memberKind: policies.ThingsKind, - }, - err: nil, - }, - { - desc: "empty member kind", - req: listMembersReq{ - groupID: valid, - permission: policies.ViewPermission, - }, - err: apiutil.ErrMissingMemberKind, - }, - { - desc: "empty groupID", - req: listMembersReq{ - permission: policies.ViewPermission, - memberKind: policies.ThingsKind, - }, - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} diff --git a/internal/groups/middleware/authorization.go b/internal/groups/middleware/authorization.go deleted file mode 100644 index d6a2e0acff..0000000000 --- a/internal/groups/middleware/authorization.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/authz" - mgauthz "github.com/absmach/magistrala/pkg/authz" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/policies" -) - -var _ groups.Service = (*authorizationMiddleware)(nil) - -type authorizationMiddleware struct { - svc groups.Service - authz mgauthz.Authorization -} - -// AuthorizationMiddleware adds authorization to the clients service. -func AuthorizationMiddleware(svc groups.Service, authz mgauthz.Authorization) groups.Service { - return &authorizationMiddleware{ - svc: svc, - authz: authz, - } -} - -func (am *authorizationMiddleware) CreateGroup(ctx context.Context, session authn.Session, kind string, g groups.Group) (groups.Group, error) { - if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.CreatePermission, policies.DomainType, session.DomainID); err != nil { - return groups.Group{}, err - } - if g.Parent != "" { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.GroupType, g.Parent); err != nil { - return groups.Group{}, err - } - } - - return am.svc.CreateGroup(ctx, session, kind, g) -} - -func (am *authorizationMiddleware) UpdateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.GroupType, g.ID); err != nil { - return groups.Group{}, err - } - - return am.svc.UpdateGroup(ctx, session, g) -} - -func (am *authorizationMiddleware) ViewGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.ViewPermission, policies.GroupType, id); err != nil { - return groups.Group{}, err - } - - return am.svc.ViewGroup(ctx, session, id) -} - -func (am *authorizationMiddleware) ViewGroupPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - return am.svc.ViewGroupPerms(ctx, session, id) -} - -func (am *authorizationMiddleware) ListGroups(ctx context.Context, session authn.Session, memberKind, memberID string, gm groups.Page) (groups.Page, error) { - switch memberKind { - case policies.ThingsKind: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.ViewPermission, policies.ThingType, memberID); err != nil { - return groups.Page{}, err - } - case policies.GroupsKind: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, gm.Permission, policies.GroupType, memberID); err != nil { - return groups.Page{}, err - } - case policies.ChannelsKind: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.ViewPermission, policies.GroupType, memberID); err != nil { - return groups.Page{}, err - } - case policies.UsersKind: - switch { - case memberID != "" && session.UserID != memberID: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.AdminPermission, policies.DomainType, session.DomainID); err != nil { - return groups.Page{}, err - } - default: - err := am.checkSuperAdmin(ctx, session.UserID) - switch { - case err == nil: - session.SuperAdmin = true - default: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID); err != nil { - return groups.Page{}, err - } - } - } - default: - return groups.Page{}, svcerr.ErrAuthorization - } - - return am.svc.ListGroups(ctx, session, memberKind, memberID, gm) -} - -func (am *authorizationMiddleware) ListMembers(ctx context.Context, session authn.Session, groupID, permission, memberKind string) (groups.MembersPage, error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.ViewPermission, policies.GroupType, groupID); err != nil { - return groups.MembersPage{}, err - } - - return am.svc.ListMembers(ctx, session, groupID, permission, memberKind) -} - -func (am *authorizationMiddleware) EnableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.GroupType, id); err != nil { - return groups.Group{}, err - } - - return am.svc.EnableGroup(ctx, session, id) -} - -func (am *authorizationMiddleware) DisableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.GroupType, id); err != nil { - return groups.Group{}, err - } - - return am.svc.DisableGroup(ctx, session, id) -} - -func (am *authorizationMiddleware) DeleteGroup(ctx context.Context, session authn.Session, id string) error { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.GroupType, id); err != nil { - return err - } - - return am.svc.DeleteGroup(ctx, session, id) -} - -func (am *authorizationMiddleware) Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.GroupType, groupID); err != nil { - return err - } - - return am.svc.Assign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -func (am *authorizationMiddleware) Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.GroupType, groupID); err != nil { - return err - } - - return am.svc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID string) error { - if err := am.authz.Authorize(ctx, authz.PolicyReq{ - SubjectType: policies.UserType, - Subject: adminID, - Permission: policies.AdminPermission, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }); err != nil { - return err - } - return nil -} - -func (am *authorizationMiddleware) authorize(ctx context.Context, domain, subjType, subjKind, subj, perm, objType, obj string) error { - req := authz.PolicyReq{ - Domain: domain, - SubjectType: subjType, - SubjectKind: subjKind, - Subject: subj, - Permission: perm, - ObjectType: objType, - Object: obj, - } - if err := am.authz.Authorize(ctx, req); err != nil { - return err - } - - return nil -} diff --git a/internal/groups/postgres/init.go b/internal/groups/postgres/init.go deleted file mode 100644 index 0b799c46cb..0000000000 --- a/internal/groups/postgres/init.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access - migrate "github.com/rubenv/sql-migrate" -) - -func Migration() *migrate.MemoryMigrationSource { - return &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "groups_01", - Up: []string{ - `CREATE TABLE IF NOT EXISTS groups ( - id VARCHAR(36) PRIMARY KEY, - parent_id VARCHAR(36), - domain_id VARCHAR(36) NOT NULL, - name VARCHAR(1024) NOT NULL, - description VARCHAR(1024), - metadata JSONB, - created_at TIMESTAMP, - updated_at TIMESTAMP, - updated_by VARCHAR(254), - status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), - UNIQUE (domain_id, name), - FOREIGN KEY (parent_id) REFERENCES groups (id) ON DELETE SET NULL - )`, - }, - Down: []string{ - `DROP TABLE IF EXISTS groups`, - }, - }, - }, - } -} diff --git a/internal/groups/service.go b/internal/groups/service.go deleted file mode 100644 index 807a91772b..0000000000 --- a/internal/groups/service.go +++ /dev/null @@ -1,586 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package groups - -import ( - "context" - "fmt" - "time" - - "github.com/absmach/magistrala" - mgauth "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/policies" - "golang.org/x/sync/errgroup" -) - -var ( - errMemberKind = errors.New("invalid member kind") - errGroupIDs = errors.New("invalid group ids") -) - -type service struct { - groups groups.Repository - policies policies.Service - idProvider magistrala.IDProvider -} - -// NewService returns a new Clients service implementation. -func NewService(g groups.Repository, idp magistrala.IDProvider, policyService policies.Service) groups.Service { - return service{ - groups: g, - idProvider: idp, - policies: policyService, - } -} - -func (svc service) CreateGroup(ctx context.Context, session authn.Session, kind string, g groups.Group) (gr groups.Group, err error) { - groupID, err := svc.idProvider.ID() - if err != nil { - return groups.Group{}, err - } - if g.Status != groups.EnabledStatus && g.Status != groups.DisabledStatus { - return groups.Group{}, svcerr.ErrInvalidStatus - } - - g.ID = groupID - g.CreatedAt = time.Now() - g.Domain = session.DomainID - - policyList, err := svc.addGroupPolicy(ctx, session.DomainUserID, session.DomainID, g.ID, g.Parent, kind) - if err != nil { - return groups.Group{}, err - } - - defer func() { - if err != nil { - if errRollback := svc.policies.DeletePolicies(ctx, policyList); errRollback != nil { - err = errors.Wrap(errors.Wrap(errors.ErrRollbackTx, errRollback), err) - } - } - }() - - saved, err := svc.groups.Save(ctx, g) - if err != nil { - return groups.Group{}, errors.Wrap(svcerr.ErrCreateEntity, err) - } - - return saved, nil -} - -func (svc service) ViewGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - group, err := svc.groups.RetrieveByID(ctx, id) - if err != nil { - return groups.Group{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - return group, nil -} - -func (svc service) ViewGroupPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - return svc.listUserGroupPermission(ctx, session.DomainUserID, id) -} - -func (svc service) ListGroups(ctx context.Context, session authn.Session, memberKind, memberID string, gm groups.Page) (groups.Page, error) { - var ids []string - var err error - - switch memberKind { - case policies.ThingsKind: - cids, err := svc.policies.ListAllSubjects(ctx, policies.Policy{ - SubjectType: policies.GroupType, - Permission: policies.GroupRelation, - ObjectType: policies.ThingType, - Object: memberID, - }) - if err != nil { - return groups.Page{}, err - } - ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, session.DomainUserID, gm.Permission, cids.Policies) - if err != nil { - return groups.Page{}, err - } - case policies.GroupsKind: - gids, err := svc.policies.ListAllObjects(ctx, policies.Policy{ - SubjectType: policies.GroupType, - Subject: memberID, - Permission: policies.ParentGroupRelation, - ObjectType: policies.GroupType, - }) - if err != nil { - return groups.Page{}, err - } - ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, session.DomainUserID, gm.Permission, gids.Policies) - if err != nil { - return groups.Page{}, err - } - case policies.ChannelsKind: - gids, err := svc.policies.ListAllSubjects(ctx, policies.Policy{ - SubjectType: policies.GroupType, - Permission: policies.ParentGroupRelation, - ObjectType: policies.GroupType, - Object: memberID, - }) - if err != nil { - return groups.Page{}, err - } - - ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, session.DomainUserID, gm.Permission, gids.Policies) - if err != nil { - return groups.Page{}, err - } - case policies.UsersKind: - switch { - case memberID != "" && session.UserID != memberID: - gids, err := svc.policies.ListAllObjects(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: mgauth.EncodeDomainUserID(session.DomainID, memberID), - Permission: gm.Permission, - ObjectType: policies.GroupType, - }) - if err != nil { - return groups.Page{}, err - } - ids, err = svc.filterAllowedGroupIDsOfUserID(ctx, session.DomainUserID, gm.Permission, gids.Policies) - if err != nil { - return groups.Page{}, err - } - default: - switch session.SuperAdmin { - case true: - gm.PageMeta.DomainID = session.DomainID - default: - ids, err = svc.listAllGroupsOfUserID(ctx, session.DomainUserID, gm.Permission) - if err != nil { - return groups.Page{}, err - } - } - } - default: - return groups.Page{}, errMemberKind - } - gp, err := svc.groups.RetrieveByIDs(ctx, gm, ids...) - if err != nil { - return groups.Page{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - if gm.ListPerms && len(gp.Groups) > 0 { - g, ctx := errgroup.WithContext(ctx) - - for i := range gp.Groups { - // Copying loop variable "i" to avoid "loop variable captured by func literal" - iter := i - g.Go(func() error { - return svc.retrievePermissions(ctx, session.DomainUserID, &gp.Groups[iter]) - }) - } - - if err := g.Wait(); err != nil { - return groups.Page{}, err - } - } - return gp, nil -} - -// Experimental functions used for async calling of svc.listUserThingPermission. This might be helpful during listing of large number of entities. -func (svc service) retrievePermissions(ctx context.Context, userID string, group *groups.Group) error { - permissions, err := svc.listUserGroupPermission(ctx, userID, group.ID) - if err != nil { - return err - } - group.Permissions = permissions - return nil -} - -func (svc service) listUserGroupPermission(ctx context.Context, userID, groupID string) ([]string, error) { - permissions, err := svc.policies.ListPermissions(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Object: groupID, - ObjectType: policies.GroupType, - }, []string{}) - if err != nil { - return []string{}, err - } - if len(permissions) == 0 { - return []string{}, svcerr.ErrAuthorization - } - return permissions, nil -} - -// IMPROVEMENT NOTE: remove this function and all its related auxiliary function, ListMembers are moved to respective service. -func (svc service) ListMembers(ctx context.Context, session authn.Session, groupID, permission, memberKind string) (groups.MembersPage, error) { - switch memberKind { - case policies.ThingsKind: - tids, err := svc.policies.ListAllObjects(ctx, policies.Policy{ - SubjectType: policies.GroupType, - Subject: groupID, - Relation: policies.GroupRelation, - ObjectType: policies.ThingType, - }) - if err != nil { - return groups.MembersPage{}, err - } - - members := []groups.Member{} - - for _, id := range tids.Policies { - members = append(members, groups.Member{ - ID: id, - Type: policies.ThingType, - }) - } - return groups.MembersPage{ - Total: uint64(len(members)), - Offset: 0, - Limit: uint64(len(members)), - Members: members, - }, nil - case policies.UsersKind: - uids, err := svc.policies.ListAllSubjects(ctx, policies.Policy{ - SubjectType: policies.UserType, - Permission: permission, - Object: groupID, - ObjectType: policies.GroupType, - }) - if err != nil { - return groups.MembersPage{}, err - } - - members := []groups.Member{} - - for _, id := range uids.Policies { - members = append(members, groups.Member{ - ID: id, - Type: policies.UserType, - }) - } - return groups.MembersPage{ - Total: uint64(len(members)), - Offset: 0, - Limit: uint64(len(members)), - Members: members, - }, nil - default: - return groups.MembersPage{}, errMemberKind - } -} - -func (svc service) UpdateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { - g.UpdatedAt = time.Now() - g.UpdatedBy = session.UserID - - return svc.groups.Update(ctx, g) -} - -func (svc service) EnableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - group := groups.Group{ - ID: id, - Status: groups.EnabledStatus, - UpdatedAt: time.Now(), - } - group, err := svc.changeGroupStatus(ctx, session, group) - if err != nil { - return groups.Group{}, err - } - return group, nil -} - -func (svc service) DisableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - group := groups.Group{ - ID: id, - Status: groups.DisabledStatus, - UpdatedAt: time.Now(), - } - group, err := svc.changeGroupStatus(ctx, session, group) - if err != nil { - return groups.Group{}, err - } - return group, nil -} - -func (svc service) Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) error { - policyList := []policies.Policy{} - switch memberKind { - case policies.ThingsKind: - for _, memberID := range memberIDs { - policyList = append(policyList, policies.Policy{ - Domain: session.DomainID, - SubjectType: policies.GroupType, - SubjectKind: policies.ChannelsKind, - Subject: groupID, - Relation: relation, - ObjectType: policies.ThingType, - Object: memberID, - }) - } - case policies.ChannelsKind: - for _, memberID := range memberIDs { - policyList = append(policyList, policies.Policy{ - Domain: session.DomainID, - SubjectType: policies.GroupType, - Subject: memberID, - Relation: relation, - ObjectType: policies.GroupType, - Object: groupID, - }) - } - case policies.GroupsKind: - return svc.assignParentGroup(ctx, session.DomainID, groupID, memberIDs) - - case policies.UsersKind: - for _, memberID := range memberIDs { - policyList = append(policyList, policies.Policy{ - Domain: session.DomainID, - SubjectType: policies.UserType, - Subject: mgauth.EncodeDomainUserID(session.DomainID, memberID), - Relation: relation, - ObjectType: policies.GroupType, - Object: groupID, - }) - } - default: - return errMemberKind - } - - if err := svc.policies.AddPolicies(ctx, policyList); err != nil { - return errors.Wrap(svcerr.ErrAddPolicies, err) - } - - return nil -} - -func (svc service) assignParentGroup(ctx context.Context, domain, parentGroupID string, groupIDs []string) (err error) { - groupsPage, err := svc.groups.RetrieveByIDs(ctx, groups.Page{PageMeta: groups.PageMeta{Limit: 1<<63 - 1}}, groupIDs...) - if err != nil { - return errors.Wrap(svcerr.ErrViewEntity, err) - } - if len(groupsPage.Groups) == 0 { - return errGroupIDs - } - - policyList := []policies.Policy{} - for _, group := range groupsPage.Groups { - if group.Parent != "" { - return errors.Wrap(svcerr.ErrConflict, fmt.Errorf("%s group already have parent", group.ID)) - } - policyList = append(policyList, policies.Policy{ - Domain: domain, - SubjectType: policies.GroupType, - Subject: parentGroupID, - Relation: policies.ParentGroupRelation, - ObjectType: policies.GroupType, - Object: group.ID, - }) - } - - if err := svc.policies.AddPolicies(ctx, policyList); err != nil { - return errors.Wrap(svcerr.ErrAddPolicies, err) - } - defer func() { - if err != nil { - if errRollback := svc.policies.DeletePolicies(ctx, policyList); errRollback != nil { - err = errors.Wrap(err, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) - } - } - }() - - return svc.groups.AssignParentGroup(ctx, parentGroupID, groupIDs...) -} - -func (svc service) unassignParentGroup(ctx context.Context, domain, parentGroupID string, groupIDs []string) (err error) { - groupsPage, err := svc.groups.RetrieveByIDs(ctx, groups.Page{PageMeta: groups.PageMeta{Limit: 1<<63 - 1}}, groupIDs...) - if err != nil { - return errors.Wrap(svcerr.ErrViewEntity, err) - } - if len(groupsPage.Groups) == 0 { - return errGroupIDs - } - - policyList := []policies.Policy{} - for _, group := range groupsPage.Groups { - if group.Parent != "" && group.Parent != parentGroupID { - return errors.Wrap(svcerr.ErrConflict, fmt.Errorf("%s group doesn't have same parent", group.ID)) - } - policyList = append(policyList, policies.Policy{ - Domain: domain, - SubjectType: policies.GroupType, - Subject: parentGroupID, - Relation: policies.ParentGroupRelation, - ObjectType: policies.GroupType, - Object: group.ID, - }) - } - - if err := svc.policies.DeletePolicies(ctx, policyList); err != nil { - return errors.Wrap(svcerr.ErrDeletePolicies, err) - } - defer func() { - if err != nil { - if errRollback := svc.policies.AddPolicies(ctx, policyList); errRollback != nil { - err = errors.Wrap(err, errors.Wrap(apiutil.ErrRollbackTx, errRollback)) - } - } - }() - - return svc.groups.UnassignParentGroup(ctx, parentGroupID, groupIDs...) -} - -func (svc service) Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) error { - policyList := []policies.Policy{} - switch memberKind { - case policies.ThingsKind: - for _, memberID := range memberIDs { - policyList = append(policyList, policies.Policy{ - Domain: session.DomainID, - SubjectType: policies.GroupType, - SubjectKind: policies.ChannelsKind, - Subject: groupID, - Relation: relation, - ObjectType: policies.ThingType, - Object: memberID, - }) - } - case policies.ChannelsKind: - for _, memberID := range memberIDs { - policyList = append(policyList, policies.Policy{ - Domain: session.DomainID, - SubjectType: policies.GroupType, - Subject: memberID, - Relation: relation, - ObjectType: policies.GroupType, - Object: groupID, - }) - } - case policies.GroupsKind: - return svc.unassignParentGroup(ctx, session.DomainID, groupID, memberIDs) - case policies.UsersKind: - for _, memberID := range memberIDs { - policyList = append(policyList, policies.Policy{ - Domain: session.DomainID, - SubjectType: policies.UserType, - Subject: mgauth.EncodeDomainUserID(session.DomainID, memberID), - Relation: relation, - ObjectType: policies.GroupType, - Object: groupID, - }) - } - default: - return errMemberKind - } - - if err := svc.policies.DeletePolicies(ctx, policyList); err != nil { - return errors.Wrap(svcerr.ErrDeletePolicies, err) - } - return nil -} - -func (svc service) DeleteGroup(ctx context.Context, session authn.Session, id string) error { - req := policies.Policy{ - SubjectType: policies.GroupType, - Subject: id, - } - if err := svc.policies.DeletePolicyFilter(ctx, req); err != nil { - return errors.Wrap(svcerr.ErrDeletePolicies, err) - } - - req = policies.Policy{ - Object: id, - ObjectType: policies.GroupType, - } - - if err := svc.policies.DeletePolicyFilter(ctx, req); err != nil { - return errors.Wrap(svcerr.ErrDeletePolicies, err) - } - - if err := svc.groups.Delete(ctx, id); err != nil { - return err - } - - return nil -} - -func (svc service) filterAllowedGroupIDsOfUserID(ctx context.Context, userID, permission string, groupIDs []string) ([]string, error) { - var ids []string - allowedIDs, err := svc.listAllGroupsOfUserID(ctx, userID, permission) - if err != nil { - return []string{}, err - } - - for _, gid := range groupIDs { - for _, id := range allowedIDs { - if id == gid { - ids = append(ids, id) - } - } - } - return ids, nil -} - -func (svc service) listAllGroupsOfUserID(ctx context.Context, userID, permission string) ([]string, error) { - allowedIDs, err := svc.policies.ListAllObjects(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Permission: permission, - ObjectType: policies.GroupType, - }) - if err != nil { - return []string{}, err - } - return allowedIDs.Policies, nil -} - -func (svc service) changeGroupStatus(ctx context.Context, session authn.Session, group groups.Group) (groups.Group, error) { - dbGroup, err := svc.groups.RetrieveByID(ctx, group.ID) - if err != nil { - return groups.Group{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - if dbGroup.Status == group.Status { - return groups.Group{}, errors.ErrStatusAlreadyAssigned - } - - group.UpdatedBy = session.UserID - return svc.groups.ChangeStatus(ctx, group) -} - -func (svc service) addGroupPolicy(ctx context.Context, userID, domainID, id, parentID, kind string) ([]policies.Policy, error) { - policyList := []policies.Policy{} - policyList = append(policyList, policies.Policy{ - Domain: domainID, - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.AdministratorRelation, - ObjectKind: kind, - ObjectType: policies.GroupType, - Object: id, - }) - policyList = append(policyList, policies.Policy{ - Domain: domainID, - SubjectType: policies.DomainType, - Subject: domainID, - Relation: policies.DomainRelation, - ObjectType: policies.GroupType, - Object: id, - }) - if parentID != "" { - policyList = append(policyList, policies.Policy{ - Domain: domainID, - SubjectType: policies.GroupType, - Subject: parentID, - Relation: policies.ParentGroupRelation, - ObjectKind: kind, - ObjectType: policies.GroupType, - Object: id, - }) - } - if err := svc.policies.AddPolicies(ctx, policyList); err != nil { - return policyList, errors.Wrap(svcerr.ErrAddPolicies, err) - } - - return []policies.Policy{}, nil -} diff --git a/internal/groups/service_test.go b/internal/groups/service_test.go deleted file mode 100644 index 799a03f91f..0000000000 --- a/internal/groups/service_test.go +++ /dev/null @@ -1,1460 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package groups_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/0x6flab/namegenerator" - mgauth "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/internal/groups" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/authn" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mggroups "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/groups/mocks" - policysvc "github.com/absmach/magistrala/pkg/policies" - policymocks "github.com/absmach/magistrala/pkg/policies/mocks" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - idProvider = uuid.New() - namegen = namegenerator.NewGenerator() - validGroup = mggroups.Group{ - Name: namegen.Generate(), - Description: namegen.Generate(), - Metadata: map[string]interface{}{ - "key": "value", - }, - Status: mggroups.EnabledStatus, - } - allowedIDs = []string{ - testsutil.GenerateUUID(&testing.T{}), - testsutil.GenerateUUID(&testing.T{}), - testsutil.GenerateUUID(&testing.T{}), - } - validID = testsutil.GenerateUUID(&testing.T{}) -) - -func TestCreateGroup(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - session authn.Session - kind string - group mggroups.Group - repoResp mggroups.Group - repoErr error - addPolErr error - deletePolErr error - err error - }{ - { - desc: "successfully", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - kind: policysvc.NewGroupKind, - group: validGroup, - repoResp: mggroups.Group{ - ID: testsutil.GenerateUUID(t), - CreatedAt: time.Now(), - Domain: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "with invalid status", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - kind: policysvc.NewGroupKind, - group: mggroups.Group{ - Name: namegen.Generate(), - Description: namegen.Generate(), - Status: mggroups.Status(100), - }, - err: svcerr.ErrInvalidStatus, - }, - { - desc: "successfully with parent", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - kind: policysvc.NewGroupKind, - group: mggroups.Group{ - Name: namegen.Generate(), - Description: namegen.Generate(), - Status: mggroups.EnabledStatus, - Parent: testsutil.GenerateUUID(t), - }, - repoResp: mggroups.Group{ - ID: testsutil.GenerateUUID(t), - CreatedAt: time.Now(), - Domain: testsutil.GenerateUUID(t), - Parent: testsutil.GenerateUUID(t), - }, - }, - { - desc: "with repo error", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - kind: policysvc.NewGroupKind, - group: validGroup, - repoResp: mggroups.Group{}, - repoErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "with failed to add policies", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - kind: policysvc.NewGroupKind, - group: validGroup, - repoResp: mggroups.Group{ - ID: testsutil.GenerateUUID(t), - }, - addPolErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "with failed to delete policies response", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - kind: policysvc.NewGroupKind, - group: mggroups.Group{ - Name: namegen.Generate(), - Description: namegen.Generate(), - Status: mggroups.EnabledStatus, - Parent: testsutil.GenerateUUID(t), - }, - repoErr: errors.ErrMalformedEntity, - deletePolErr: svcerr.ErrAuthorization, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := repo.On("Save", context.Background(), mock.Anything).Return(tc.repoResp, tc.repoErr) - policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPolErr) - policyCall1 := policies.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePolErr) - got, err := svc.CreateGroup(context.Background(), tc.session, tc.kind, tc.group) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - if err == nil { - assert.NotEmpty(t, got.ID) - assert.NotEmpty(t, got.CreatedAt) - assert.NotEmpty(t, got.Domain) - assert.WithinDuration(t, time.Now(), got.CreatedAt, 2*time.Second) - ok := repoCall.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) - } - repoCall.Unset() - policyCall.Unset() - policyCall1.Unset() - }) - } -} - -func TestViewGroup(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - id string - repoResp mggroups.Group - repoErr error - err error - }{ - { - desc: "successfully", - id: testsutil.GenerateUUID(t), - repoResp: validGroup, - }, - { - desc: "with repo error", - id: testsutil.GenerateUUID(t), - repoErr: repoerr.ErrNotFound, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := repo.On("RetrieveByID", context.Background(), tc.id).Return(tc.repoResp, tc.repoErr) - got, err := svc.ViewGroup(context.Background(), mgauthn.Session{}, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - if err == nil { - assert.Equal(t, tc.repoResp, got) - ok := repo.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - } - repoCall.Unset() - }) - } -} - -func TestViewGroupPerms(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - session authn.Session - id string - listResp policysvc.Permissions - listErr error - err error - }{ - { - desc: "successfully", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - listResp: []string{ - policysvc.ViewPermission, - policysvc.EditPermission, - }, - }, - { - desc: "with failed to list permissions", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - listErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "with empty permissions", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - listResp: []string{}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - policyCall := policies.On("ListPermissions", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: validID, - Object: tc.id, - ObjectType: policysvc.GroupType, - }, []string{}).Return(tc.listResp, tc.listErr) - got, err := svc.ViewGroupPerms(context.Background(), tc.session, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - if err == nil { - assert.ElementsMatch(t, tc.listResp, got) - } - policyCall.Unset() - }) - } -} - -func TestUpdateGroup(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - session authn.Session - group mggroups.Group - repoResp mggroups.Group - repoErr error - err error - }{ - { - desc: "successfully", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - group: mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - }, - repoResp: validGroup, - }, - { - desc: " with repo error", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - group: mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - }, - repoErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := repo.On("Update", context.Background(), mock.Anything).Return(tc.repoResp, tc.repoErr) - got, err := svc.UpdateGroup(context.Background(), tc.session, tc.group) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - if err == nil { - assert.Equal(t, tc.repoResp, got) - ok := repo.AssertCalled(t, "Update", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) - } - repoCall.Unset() - }) - } -} - -func TestEnableGroup(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - session authn.Session - id string - retrieveResp mggroups.Group - retrieveErr error - changeResp mggroups.Group - changeErr error - err error - }{ - { - desc: "successfully", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - retrieveResp: mggroups.Group{ - Status: mggroups.DisabledStatus, - }, - changeResp: validGroup, - }, - { - desc: "with enabled group", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - retrieveResp: mggroups.Group{ - Status: mggroups.EnabledStatus, - }, - err: errors.ErrStatusAlreadyAssigned, - }, - { - desc: "with retrieve error", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - retrieveResp: mggroups.Group{}, - retrieveErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := repo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveResp, tc.retrieveErr) - repoCall1 := repo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeResp, tc.changeErr) - got, err := svc.EnableGroup(context.Background(), tc.session, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - if err == nil { - assert.Equal(t, tc.changeResp, got) - ok := repo.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - }) - } -} - -func TestDisableGroup(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - session authn.Session - id string - retrieveResp mggroups.Group - retrieveErr error - changeResp mggroups.Group - changeErr error - err error - }{ - { - desc: "successfully", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - retrieveResp: mggroups.Group{ - Status: mggroups.EnabledStatus, - }, - changeResp: validGroup, - }, - { - desc: "with enabled group", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - retrieveResp: mggroups.Group{ - Status: mggroups.DisabledStatus, - }, - err: errors.ErrStatusAlreadyAssigned, - }, - { - desc: "with retrieve error", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - id: testsutil.GenerateUUID(t), - retrieveResp: mggroups.Group{}, - retrieveErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := repo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveResp, tc.retrieveErr) - repoCall1 := repo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeResp, tc.changeErr) - got, err := svc.DisableGroup(context.Background(), tc.session, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - if err == nil { - assert.Equal(t, tc.changeResp, got) - ok := repo.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - }) - } -} - -func TestListMembers(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - groupID string - permission string - memberKind string - listSubjectResp policysvc.PolicyPage - listSubjectErr error - listObjectResp policysvc.PolicyPage - listObjectErr error - err error - }{ - { - desc: "successfully with things kind", - groupID: testsutil.GenerateUUID(t), - memberKind: policysvc.ThingsKind, - listObjectResp: policysvc.PolicyPage{ - Policies: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - }, - { - desc: "successfully with users kind", - groupID: testsutil.GenerateUUID(t), - memberKind: policysvc.UsersKind, - permission: policysvc.ViewPermission, - listSubjectResp: policysvc.PolicyPage{ - Policies: []string{ - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - testsutil.GenerateUUID(t), - }, - }, - }, - { - desc: "with invalid kind", - groupID: testsutil.GenerateUUID(t), - memberKind: policysvc.GroupsKind, - permission: policysvc.ViewPermission, - err: errors.New("invalid member kind"), - }, - { - desc: "failed to list objects with things kind", - groupID: testsutil.GenerateUUID(t), - memberKind: policysvc.ThingsKind, - listObjectResp: policysvc.PolicyPage{}, - listObjectErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "failed to list subjects with users kind", - groupID: testsutil.GenerateUUID(t), - memberKind: policysvc.UsersKind, - permission: policysvc.ViewPermission, - listSubjectResp: policysvc.PolicyPage{}, - listSubjectErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - policyCall := policies.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.GroupType, - Subject: tc.groupID, - Relation: policysvc.GroupRelation, - ObjectType: policysvc.ThingType, - }).Return(tc.listObjectResp, tc.listObjectErr) - policyCall1 := policies.On("ListAllSubjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: tc.permission, - Object: tc.groupID, - ObjectType: policysvc.GroupType, - }).Return(tc.listSubjectResp, tc.listSubjectErr) - got, err := svc.ListMembers(context.Background(), mgauthn.Session{}, tc.groupID, tc.permission, tc.memberKind) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - if err == nil { - assert.NotEmpty(t, got) - } - policyCall.Unset() - policyCall1.Unset() - }) - } -} - -func TestListGroups(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - session authn.Session - memberKind string - memberID string - page mggroups.Page - listSubjectResp policysvc.PolicyPage - listSubjectErr error - listObjectResp policysvc.PolicyPage - listObjectErr error - listObjectFilterResp policysvc.PolicyPage - listObjectFilterErr error - repoResp mggroups.Page - repoErr error - listPermResp policysvc.Permissions - listPermErr error - err error - }{ - { - desc: "successfully with things kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.ThingsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listSubjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{Policies: allowedIDs}, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - listPermResp: []string{ - policysvc.ViewPermission, - policysvc.EditPermission, - }, - }, - { - desc: "successfully with groups kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.GroupsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listObjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{Policies: allowedIDs}, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - listPermResp: []string{ - policysvc.ViewPermission, - policysvc.EditPermission, - }, - }, - { - desc: "successfully with channels kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.ChannelsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listSubjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{Policies: allowedIDs}, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - listPermResp: []string{ - policysvc.ViewPermission, - policysvc.EditPermission, - }, - }, - { - desc: "successfully with users kind non admin", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.UsersKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listObjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{Policies: allowedIDs}, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - listPermResp: []string{ - policysvc.ViewPermission, - policysvc.EditPermission, - }, - }, - { - desc: "successfully with users kind admin", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberKind: policysvc.UsersKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listObjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{Policies: allowedIDs}, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - listPermResp: []string{ - policysvc.ViewPermission, - policysvc.EditPermission, - }, - }, - { - desc: "unsuccessfully with things kind due to failed to list subjects", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.ThingsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listSubjectResp: policysvc.PolicyPage{}, - listSubjectErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with things kind due to failed to list filtered objects", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.ThingsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listSubjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{}, - listObjectFilterErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with groups kind due to failed to list subjects", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.GroupsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listObjectResp: policysvc.PolicyPage{}, - listObjectErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with groups kind due to failed to list filtered objects", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.GroupsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listObjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{}, - listObjectFilterErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with channels kind due to failed to list subjects", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.ChannelsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listSubjectResp: policysvc.PolicyPage{}, - listSubjectErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with channels kind due to failed to list filtered objects", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.ChannelsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listSubjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{}, - listObjectFilterErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with users kind due to failed to list subjects", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.UsersKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listObjectResp: policysvc.PolicyPage{}, - listObjectErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with users kind due to failed to list filtered objects", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.UsersKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listObjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{}, - listObjectFilterErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "successfully with users kind admin", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberKind: policysvc.UsersKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listObjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{Policies: allowedIDs}, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - listPermResp: []string{ - policysvc.ViewPermission, - policysvc.EditPermission, - }, - }, - { - desc: "unsuccessfully with invalid kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: "invalid", - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - err: errors.New("invalid member kind"), - }, - { - desc: "unsuccessfully with things kind due to repo error", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.ThingsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listSubjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{Policies: allowedIDs}, - repoResp: mggroups.Page{}, - repoErr: repoerr.ErrViewEntity, - err: repoerr.ErrViewEntity, - }, - { - desc: "unsuccessfully with things kind due to failed to list permissions", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - memberID: testsutil.GenerateUUID(t), - memberKind: policysvc.ThingsKind, - page: mggroups.Page{ - Permission: policysvc.ViewPermission, - ListPerms: true, - }, - listSubjectResp: policysvc.PolicyPage{Policies: allowedIDs}, - listObjectFilterResp: policysvc.PolicyPage{Policies: allowedIDs}, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - listPermResp: []string{}, - listPermErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - policyCall := &mock.Call{} - policyCall1 := &mock.Call{} - switch tc.memberKind { - case policysvc.ThingsKind: - policyCall = policies.On("ListAllSubjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.GroupType, - Permission: policysvc.GroupRelation, - ObjectType: policysvc.ThingType, - Object: tc.memberID, - }).Return(tc.listSubjectResp, tc.listSubjectErr) - policyCall1 = policies.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: validID, - Permission: tc.page.Permission, - ObjectType: policysvc.GroupType, - }).Return(tc.listObjectFilterResp, tc.listObjectFilterErr) - case policysvc.GroupsKind: - policyCall = policies.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.GroupType, - Subject: tc.memberID, - Permission: policysvc.ParentGroupRelation, - ObjectType: policysvc.GroupType, - }).Return(tc.listObjectResp, tc.listObjectErr) - policyCall1 = policies.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: validID, - Permission: tc.page.Permission, - ObjectType: policysvc.GroupType, - }).Return(tc.listObjectFilterResp, tc.listObjectFilterErr) - case policysvc.ChannelsKind: - policyCall = policies.On("ListAllSubjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.GroupType, - Permission: policysvc.ParentGroupRelation, - ObjectType: policysvc.GroupType, - Object: tc.memberID, - }).Return(tc.listSubjectResp, tc.listSubjectErr) - policyCall1 = policies.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: validID, - Permission: tc.page.Permission, - ObjectType: policysvc.GroupType, - }).Return(tc.listObjectFilterResp, tc.listObjectFilterErr) - case policysvc.UsersKind: - policyCall = policies.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: mgauth.EncodeDomainUserID(validID, tc.memberID), - Permission: tc.page.Permission, - ObjectType: policysvc.GroupType, - }).Return(tc.listObjectResp, tc.listObjectErr) - policyCall1 = policies.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: validID, - Permission: tc.page.Permission, - ObjectType: policysvc.GroupType, - }).Return(tc.listObjectFilterResp, tc.listObjectFilterErr) - } - repoCall := repo.On("RetrieveByIDs", context.Background(), mock.Anything, mock.Anything).Return(tc.repoResp, tc.repoErr) - policyCall2 := policies.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(tc.listPermResp, tc.listPermErr) - got, err := svc.ListGroups(context.Background(), tc.session, tc.memberKind, tc.memberID, tc.page) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - if err == nil { - assert.NotEmpty(t, got) - } - repoCall.Unset() - switch tc.memberKind { - case policysvc.ThingsKind, policysvc.GroupsKind, policysvc.ChannelsKind, policysvc.UsersKind: - policyCall.Unset() - policyCall1.Unset() - policyCall2.Unset() - } - }) - } -} - -func TestAssign(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - session authn.Session - groupID string - relation string - memberKind string - memberIDs []string - addPoliciesErr error - repoResp mggroups.Page - repoErr error - addParentPoliciesErr error - deleteParentPoliciesErr error - repoParentGroupErr error - err error - }{ - { - desc: "successfully with things kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.ThingsKind, - memberIDs: allowedIDs, - err: nil, - }, - { - desc: "successfully with channels kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.ChannelsKind, - memberIDs: allowedIDs, - err: nil, - }, - { - desc: "successfully with groups kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - repoParentGroupErr: nil, - }, - { - desc: "successfully with users kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.UsersKind, - memberIDs: allowedIDs, - err: nil, - }, - { - desc: "unsuccessfully with groups kind due to repo err", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{}, - repoErr: repoerr.ErrViewEntity, - err: repoerr.ErrViewEntity, - }, - { - desc: "unsuccessfully with groups kind due to empty page", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{}, - }, - err: errors.New("invalid group ids"), - }, - { - desc: "unsuccessfully with groups kind due to non empty parent", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - { - ID: testsutil.GenerateUUID(t), - Parent: testsutil.GenerateUUID(t), - }, - }, - }, - err: repoerr.ErrConflict, - }, - { - desc: "unsuccessfully with groups kind due to failed to add policies", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - addPoliciesErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with groups kind due to failed to assign parent", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - repoParentGroupErr: repoerr.ErrConflict, - err: repoerr.ErrConflict, - }, - { - desc: "unsuccessfully with groups kind due to failed to assign parent and delete policies", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - deleteParentPoliciesErr: svcerr.ErrAuthorization, - repoParentGroupErr: repoerr.ErrConflict, - err: apiutil.ErrRollbackTx, - }, - { - desc: "unsuccessfully with invalid kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: "invalid", - memberIDs: allowedIDs, - err: errors.New("invalid member kind"), - }, - { - desc: "unsuccessfully with failed to add policies", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.ThingsKind, - memberIDs: allowedIDs, - addPoliciesErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - retrieveByIDsCall := &mock.Call{} - deletePoliciesCall := &mock.Call{} - assignParentCall := &mock.Call{} - policyList := []policysvc.Policy{} - switch tc.memberKind { - case policysvc.ThingsKind: - for _, memberID := range tc.memberIDs { - policyList = append(policyList, policysvc.Policy{ - Domain: validID, - SubjectType: policysvc.GroupType, - SubjectKind: policysvc.ChannelsKind, - Subject: tc.groupID, - Relation: tc.relation, - ObjectType: policysvc.ThingType, - Object: memberID, - }) - } - case policysvc.GroupsKind: - retrieveByIDsCall = repo.On("RetrieveByIDs", context.Background(), mggroups.Page{PageMeta: mggroups.PageMeta{Limit: 1<<63 - 1}}, mock.Anything).Return(tc.repoResp, tc.repoErr) - for _, group := range tc.repoResp.Groups { - policyList = append(policyList, policysvc.Policy{ - Domain: validID, - SubjectType: policysvc.GroupType, - Subject: tc.groupID, - Relation: policysvc.ParentGroupRelation, - ObjectType: policysvc.GroupType, - Object: group.ID, - }) - } - deletePoliciesCall = policies.On("DeletePolicies", context.Background(), policyList).Return(tc.deleteParentPoliciesErr) - assignParentCall = repo.On("AssignParentGroup", context.Background(), tc.groupID, tc.memberIDs).Return(tc.repoParentGroupErr) - case policysvc.ChannelsKind: - for _, memberID := range tc.memberIDs { - policyList = append(policyList, policysvc.Policy{ - Domain: validID, - SubjectType: policysvc.GroupType, - Subject: memberID, - Relation: tc.relation, - ObjectType: policysvc.GroupType, - Object: tc.groupID, - }) - } - case policysvc.UsersKind: - for _, memberID := range tc.memberIDs { - policyList = append(policyList, policysvc.Policy{ - Domain: validID, - SubjectType: policysvc.UserType, - Subject: mgauth.EncodeDomainUserID(validID, memberID), - Relation: tc.relation, - ObjectType: policysvc.GroupType, - Object: tc.groupID, - }) - } - } - policyCall := policies.On("AddPolicies", context.Background(), policyList).Return(tc.addPoliciesErr) - err := svc.Assign(context.Background(), tc.session, tc.groupID, tc.relation, tc.memberKind, tc.memberIDs...) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - policyCall.Unset() - if tc.memberKind == policysvc.GroupsKind { - retrieveByIDsCall.Unset() - deletePoliciesCall.Unset() - assignParentCall.Unset() - } - }) - } -} - -func TestUnassign(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - session authn.Session - groupID string - relation string - memberKind string - memberIDs []string - deletePoliciesErr error - repoResp mggroups.Page - repoErr error - addParentPoliciesErr error - deleteParentPoliciesErr error - repoParentGroupErr error - err error - }{ - { - desc: "successfully with things kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.ThingsKind, - memberIDs: allowedIDs, - err: nil, - }, - { - desc: "successfully with channels kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.ChannelsKind, - memberIDs: allowedIDs, - err: nil, - }, - { - desc: "successfully with groups kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - repoParentGroupErr: nil, - }, - { - desc: "successfully with users kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.UsersKind, - memberIDs: allowedIDs, - err: nil, - }, - { - desc: "unsuccessfully with groups kind due to repo err", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{}, - repoErr: repoerr.ErrViewEntity, - err: repoerr.ErrViewEntity, - }, - { - desc: "unsuccessfully with groups kind due to empty page", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{}, - }, - err: errors.New("invalid group ids"), - }, - { - desc: "unsuccessfully with groups kind due to non empty parent", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - { - ID: testsutil.GenerateUUID(t), - Parent: testsutil.GenerateUUID(t), - }, - }, - }, - err: repoerr.ErrConflict, - }, - { - desc: "unsuccessfully with groups kind due to failed to add policies", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - deletePoliciesErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with groups kind due to failed to unassign parent", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - repoParentGroupErr: repoerr.ErrConflict, - err: repoerr.ErrConflict, - }, - { - desc: "unsuccessfully with groups kind due to failed to unassign parent and add policies", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.GroupsKind, - memberIDs: allowedIDs, - repoResp: mggroups.Page{ - Groups: []mggroups.Group{ - validGroup, - validGroup, - validGroup, - }, - }, - repoParentGroupErr: repoerr.ErrConflict, - addParentPoliciesErr: svcerr.ErrAuthorization, - err: repoerr.ErrConflict, - }, - { - desc: "unsuccessfully with invalid kind", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: "invalid", - memberIDs: allowedIDs, - err: errors.New("invalid member kind"), - }, - { - desc: "unsuccessfully with failed to add policies", - session: authn.Session{UserID: validID, DomainID: validID, DomainUserID: validID}, - groupID: testsutil.GenerateUUID(t), - relation: policysvc.ContributorRelation, - memberKind: policysvc.ThingsKind, - memberIDs: allowedIDs, - deletePoliciesErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - retrieveByIDsCall := &mock.Call{} - addPoliciesCall := &mock.Call{} - assignParentCall := &mock.Call{} - policyList := []policysvc.Policy{} - switch tc.memberKind { - case policysvc.ThingsKind: - for _, memberID := range tc.memberIDs { - policyList = append(policyList, policysvc.Policy{ - Domain: validID, - SubjectType: policysvc.GroupType, - SubjectKind: policysvc.ChannelsKind, - Subject: tc.groupID, - Relation: tc.relation, - ObjectType: policysvc.ThingType, - Object: memberID, - }) - } - case policysvc.GroupsKind: - retrieveByIDsCall = repo.On("RetrieveByIDs", context.Background(), mggroups.Page{PageMeta: mggroups.PageMeta{Limit: 1<<63 - 1}}, mock.Anything).Return(tc.repoResp, tc.repoErr) - for _, group := range tc.repoResp.Groups { - policyList = append(policyList, policysvc.Policy{ - Domain: validID, - SubjectType: policysvc.GroupType, - Subject: tc.groupID, - Relation: policysvc.ParentGroupRelation, - ObjectType: policysvc.GroupType, - Object: group.ID, - }) - } - addPoliciesCall = policies.On("AddPolicies", context.Background(), policyList).Return(tc.addParentPoliciesErr) - assignParentCall = repo.On("UnassignParentGroup", context.Background(), tc.groupID, tc.memberIDs).Return(tc.repoParentGroupErr) - case policysvc.ChannelsKind: - for _, memberID := range tc.memberIDs { - policyList = append(policyList, policysvc.Policy{ - Domain: validID, - SubjectType: policysvc.GroupType, - Subject: memberID, - Relation: tc.relation, - ObjectType: policysvc.GroupType, - Object: tc.groupID, - }) - } - case policysvc.UsersKind: - for _, memberID := range tc.memberIDs { - policyList = append(policyList, policysvc.Policy{ - Domain: validID, - SubjectType: policysvc.UserType, - Subject: mgauth.EncodeDomainUserID(validID, memberID), - Relation: tc.relation, - ObjectType: policysvc.GroupType, - Object: tc.groupID, - }) - } - } - policyCall := policies.On("DeletePolicies", context.Background(), policyList).Return(tc.deletePoliciesErr) - err := svc.Unassign(context.Background(), tc.session, tc.groupID, tc.relation, tc.memberKind, tc.memberIDs...) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - policyCall.Unset() - if tc.memberKind == policysvc.GroupsKind { - retrieveByIDsCall.Unset() - addPoliciesCall.Unset() - assignParentCall.Unset() - } - }) - } -} - -func TestDeleteGroup(t *testing.T) { - repo := new(mocks.Repository) - policies := new(policymocks.Service) - svc := groups.NewService(repo, idProvider, policies) - - cases := []struct { - desc string - groupID string - deleteSubjectPoliciesErr error - deleteObjectPoliciesErr error - repoErr error - err error - }{ - { - desc: "successfully", - groupID: testsutil.GenerateUUID(t), - err: nil, - }, - { - desc: "unsuccessfully with failed to remove subject policies", - groupID: testsutil.GenerateUUID(t), - deleteSubjectPoliciesErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with failed to remove object policies", - groupID: testsutil.GenerateUUID(t), - deleteObjectPoliciesErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "unsuccessfully with repo err", - groupID: testsutil.GenerateUUID(t), - repoErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - policyCall := policies.On("DeletePolicyFilter", context.Background(), policysvc.Policy{ - SubjectType: policysvc.GroupType, - Subject: tc.groupID, - }).Return(tc.deleteSubjectPoliciesErr) - policyCall2 := policies.On("DeletePolicyFilter", context.Background(), policysvc.Policy{ - ObjectType: policysvc.GroupType, - Object: tc.groupID, - }).Return(tc.deleteObjectPoliciesErr) - repoCall := repo.On("Delete", context.Background(), tc.groupID).Return(tc.repoErr) - err := svc.DeleteGroup(context.Background(), mgauthn.Session{}, tc.groupID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v to contain %v", err, tc.err)) - policyCall.Unset() - policyCall2.Unset() - repoCall.Unset() - }) - } -} diff --git a/internal/groups/tracing/tracing.go b/internal/groups/tracing/tracing.go deleted file mode 100644 index 190188668f..0000000000 --- a/internal/groups/tracing/tracing.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/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 -} - -// New returns a new group service with tracing capabilities. -func New(gsvc groups.Service, tracer trace.Tracer) groups.Service { - return &tracingMiddleware{tracer, gsvc} -} - -// CreateGroup traces the "CreateGroup" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) CreateGroup(ctx context.Context, session authn.Session, kind string, g groups.Group) (groups.Group, error) { - ctx, span := tm.tracer.Start(ctx, "svc_create_group") - defer span.End() - - return tm.gsvc.CreateGroup(ctx, session, kind, g) -} - -// ViewGroup traces the "ViewGroup" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) ViewGroup(ctx context.Context, session authn.Session, 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, session, id) -} - -// ViewGroupPerms traces the "ViewGroupPerms" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) ViewGroupPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - ctx, span := tm.tracer.Start(ctx, "svc_view_group", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return tm.gsvc.ViewGroupPerms(ctx, session, id) -} - -// ListGroups traces the "ListGroups" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) ListGroups(ctx context.Context, session authn.Session, memberKind, memberID string, gm groups.Page) (groups.Page, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_groups") - defer span.End() - - return tm.gsvc.ListGroups(ctx, session, memberKind, memberID, gm) -} - -// ListMembers traces the "ListMembers" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) ListMembers(ctx context.Context, session authn.Session, groupID, permission, memberKind string) (groups.MembersPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_members", trace.WithAttributes(attribute.String("groupID", groupID))) - defer span.End() - - return tm.gsvc.ListMembers(ctx, session, groupID, permission, memberKind) -} - -// UpdateGroup traces the "UpdateGroup" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) UpdateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_group") - defer span.End() - - return tm.gsvc.UpdateGroup(ctx, session, g) -} - -// EnableGroup traces the "EnableGroup" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) EnableGroup(ctx context.Context, session authn.Session, 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, session, id) -} - -// DisableGroup traces the "DisableGroup" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) DisableGroup(ctx context.Context, session authn.Session, 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, session, id) -} - -// Assign traces the "Assign" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) error { - ctx, span := tm.tracer.Start(ctx, "svc_assign", trace.WithAttributes(attribute.String("id", groupID))) - defer span.End() - - return tm.gsvc.Assign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -// Unassign traces the "Unassign" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) error { - ctx, span := tm.tracer.Start(ctx, "svc_unassign", trace.WithAttributes(attribute.String("id", groupID))) - defer span.End() - - return tm.gsvc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -// DeleteGroup traces the "DeleteGroup" operation of the wrapped groups.Service. -func (tm *tracingMiddleware) DeleteGroup(ctx context.Context, session authn.Session, id string) error { - ctx, span := tm.tracer.Start(ctx, "svc_delete_group", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return tm.gsvc.DeleteGroup(ctx, session, id) -} diff --git a/internal/grpc/auth/v1/auth.pb.go b/internal/grpc/auth/v1/auth.pb.go new file mode 100644 index 0000000000..cf85fdf79a --- /dev/null +++ b/internal/grpc/auth/v1/auth.pb.go @@ -0,0 +1,454 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: auth/v1/auth.proto + +package v1 + +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 AuthNReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` +} + +func (x *AuthNReq) Reset() { + *x = AuthNReq{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_v1_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthNReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthNReq) ProtoMessage() {} + +func (x *AuthNReq) ProtoReflect() protoreflect.Message { + mi := &file_auth_v1_auth_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 AuthNReq.ProtoReflect.Descriptor instead. +func (*AuthNReq) Descriptor() ([]byte, []int) { + return file_auth_v1_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *AuthNReq) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +type AuthNRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // IMPROVEMENT NOTE: change name from "id" to "subject" , sub in jwt = user id + domain id // + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // user id + DomainId string `protobuf:"bytes,3,opt,name=domain_id,json=domainId,proto3" json:"domain_id,omitempty"` // domain id +} + +func (x *AuthNRes) Reset() { + *x = AuthNRes{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_v1_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthNRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthNRes) ProtoMessage() {} + +func (x *AuthNRes) ProtoReflect() protoreflect.Message { + mi := &file_auth_v1_auth_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 AuthNRes.ProtoReflect.Descriptor instead. +func (*AuthNRes) Descriptor() ([]byte, []int) { + return file_auth_v1_auth_proto_rawDescGZIP(), []int{1} +} + +func (x *AuthNRes) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AuthNRes) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *AuthNRes) GetDomainId() string { + if x != nil { + return x.DomainId + } + return "" +} + +type AuthZReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Domain string `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"` // Domain + SubjectType string `protobuf:"bytes,2,opt,name=subject_type,json=subjectType,proto3" json:"subject_type,omitempty"` // Client or User + SubjectKind string `protobuf:"bytes,3,opt,name=subject_kind,json=subjectKind,proto3" json:"subject_kind,omitempty"` // ID or Token + SubjectRelation string `protobuf:"bytes,4,opt,name=subject_relation,json=subjectRelation,proto3" json:"subject_relation,omitempty"` // Subject relation + Subject string `protobuf:"bytes,5,opt,name=subject,proto3" json:"subject,omitempty"` // Subject value (id or token, depending on kind) + Relation string `protobuf:"bytes,6,opt,name=relation,proto3" json:"relation,omitempty"` // Relation to filter + Permission string `protobuf:"bytes,7,opt,name=permission,proto3" json:"permission,omitempty"` // Action + Object string `protobuf:"bytes,8,opt,name=object,proto3" json:"object,omitempty"` // Object ID + ObjectType string `protobuf:"bytes,9,opt,name=object_type,json=objectType,proto3" json:"object_type,omitempty"` // Client, User, Group +} + +func (x *AuthZReq) Reset() { + *x = AuthZReq{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_v1_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthZReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthZReq) ProtoMessage() {} + +func (x *AuthZReq) ProtoReflect() protoreflect.Message { + mi := &file_auth_v1_auth_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 AuthZReq.ProtoReflect.Descriptor instead. +func (*AuthZReq) Descriptor() ([]byte, []int) { + return file_auth_v1_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *AuthZReq) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +func (x *AuthZReq) GetSubjectType() string { + if x != nil { + return x.SubjectType + } + return "" +} + +func (x *AuthZReq) GetSubjectKind() string { + if x != nil { + return x.SubjectKind + } + return "" +} + +func (x *AuthZReq) GetSubjectRelation() string { + if x != nil { + return x.SubjectRelation + } + return "" +} + +func (x *AuthZReq) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *AuthZReq) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *AuthZReq) GetPermission() string { + if x != nil { + return x.Permission + } + return "" +} + +func (x *AuthZReq) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *AuthZReq) GetObjectType() string { + if x != nil { + return x.ObjectType + } + return "" +} + +type AuthZRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *AuthZRes) Reset() { + *x = AuthZRes{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_v1_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthZRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthZRes) ProtoMessage() {} + +func (x *AuthZRes) ProtoReflect() protoreflect.Message { + mi := &file_auth_v1_auth_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 AuthZRes.ProtoReflect.Descriptor instead. +func (*AuthZRes) Descriptor() ([]byte, []int) { + return file_auth_v1_auth_proto_rawDescGZIP(), []int{3} +} + +func (x *AuthZRes) GetAuthorized() bool { + if x != nil { + return x.Authorized + } + return false +} + +func (x *AuthZRes) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +var File_auth_v1_auth_proto protoreflect.FileDescriptor + +var file_auth_v1_auth_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x22, 0x20, 0x0a, + 0x08, 0x41, 0x75, 0x74, 0x68, 0x4e, 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, 0x22, + 0x50, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x4e, 0x52, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, + 0x64, 0x22, 0xa2, 0x02, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x5a, 0x52, 0x65, 0x71, 0x12, 0x16, + 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x10, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, + 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x5a, 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, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x32, 0x7a, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x33, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x11, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x5a, 0x52, 0x65, + 0x71, 0x1a, 0x11, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, + 0x5a, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x11, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x4e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x4e, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x35, + 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x62, 0x73, + 0x6d, 0x61, 0x63, 0x68, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x61, 0x75, + 0x74, 0x68, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_auth_v1_auth_proto_rawDescOnce sync.Once + file_auth_v1_auth_proto_rawDescData = file_auth_v1_auth_proto_rawDesc +) + +func file_auth_v1_auth_proto_rawDescGZIP() []byte { + file_auth_v1_auth_proto_rawDescOnce.Do(func() { + file_auth_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_auth_v1_auth_proto_rawDescData) + }) + return file_auth_v1_auth_proto_rawDescData +} + +var file_auth_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_auth_v1_auth_proto_goTypes = []any{ + (*AuthNReq)(nil), // 0: auth.v1.AuthNReq + (*AuthNRes)(nil), // 1: auth.v1.AuthNRes + (*AuthZReq)(nil), // 2: auth.v1.AuthZReq + (*AuthZRes)(nil), // 3: auth.v1.AuthZRes +} +var file_auth_v1_auth_proto_depIdxs = []int32{ + 2, // 0: auth.v1.AuthService.Authorize:input_type -> auth.v1.AuthZReq + 0, // 1: auth.v1.AuthService.Authenticate:input_type -> auth.v1.AuthNReq + 3, // 2: auth.v1.AuthService.Authorize:output_type -> auth.v1.AuthZRes + 1, // 3: auth.v1.AuthService.Authenticate:output_type -> auth.v1.AuthNRes + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] 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_auth_v1_auth_proto_init() } +func file_auth_v1_auth_proto_init() { + if File_auth_v1_auth_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_auth_v1_auth_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*AuthNReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_auth_v1_auth_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*AuthNRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_auth_v1_auth_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*AuthZReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_auth_v1_auth_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*AuthZRes); 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_auth_v1_auth_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_auth_v1_auth_proto_goTypes, + DependencyIndexes: file_auth_v1_auth_proto_depIdxs, + MessageInfos: file_auth_v1_auth_proto_msgTypes, + }.Build() + File_auth_v1_auth_proto = out.File + file_auth_v1_auth_proto_rawDesc = nil + file_auth_v1_auth_proto_goTypes = nil + file_auth_v1_auth_proto_depIdxs = nil +} diff --git a/internal/grpc/auth/v1/auth_grpc.pb.go b/internal/grpc/auth/v1/auth_grpc.pb.go new file mode 100644 index 0000000000..c1ff612fe1 --- /dev/null +++ b/internal/grpc/auth/v1/auth_grpc.pb.go @@ -0,0 +1,157 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.27.1 +// source: auth/v1/auth.proto + +package v1 + +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.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + AuthService_Authorize_FullMethodName = "/auth.v1.AuthService/Authorize" + AuthService_Authenticate_FullMethodName = "/auth.v1.AuthService/Authenticate" +) + +// 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. +// +// AuthService is a service that provides authentication and authorization +// functionalities for magistrala services. +type AuthServiceClient interface { + Authorize(ctx context.Context, in *AuthZReq, opts ...grpc.CallOption) (*AuthZRes, error) + Authenticate(ctx context.Context, in *AuthNReq, opts ...grpc.CallOption) (*AuthNRes, error) +} + +type authServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient { + return &authServiceClient{cc} +} + +func (c *authServiceClient) Authorize(ctx context.Context, in *AuthZReq, opts ...grpc.CallOption) (*AuthZRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthZRes) + err := c.cc.Invoke(ctx, AuthService_Authorize_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) Authenticate(ctx context.Context, in *AuthNReq, opts ...grpc.CallOption) (*AuthNRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthNRes) + err := c.cc.Invoke(ctx, AuthService_Authenticate_FullMethodName, in, out, cOpts...) + 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 +// +// AuthService is a service that provides authentication and authorization +// functionalities for magistrala services. +type AuthServiceServer interface { + Authorize(context.Context, *AuthZReq) (*AuthZRes, error) + Authenticate(context.Context, *AuthNReq) (*AuthNRes, error) + mustEmbedUnimplementedAuthServiceServer() +} + +// UnimplementedAuthServiceServer must be embedded to have forward compatible implementations. +type UnimplementedAuthServiceServer struct { +} + +func (UnimplementedAuthServiceServer) Authorize(context.Context, *AuthZReq) (*AuthZRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method Authorize not implemented") +} +func (UnimplementedAuthServiceServer) Authenticate(context.Context, *AuthNReq) (*AuthNRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method Authenticate 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(AuthZReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).Authorize(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthService_Authorize_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).Authorize(ctx, req.(*AuthZReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_Authenticate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthNReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).Authenticate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthService_Authenticate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).Authenticate(ctx, req.(*AuthNReq)) + } + 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: "auth.v1.AuthService", + HandlerType: (*AuthServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Authorize", + Handler: _AuthService_Authorize_Handler, + }, + { + MethodName: "Authenticate", + Handler: _AuthService_Authenticate_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "auth/v1/auth.proto", +} diff --git a/internal/grpc/channels/v1/channels.pb.go b/internal/grpc/channels/v1/channels.pb.go new file mode 100644 index 0000000000..999eb4592e --- /dev/null +++ b/internal/grpc/channels/v1/channels.pb.go @@ -0,0 +1,511 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: channels/v1/channels.proto + +package v1 + +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 RemoveClientConnectionsReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (x *RemoveClientConnectionsReq) Reset() { + *x = RemoveClientConnectionsReq{} + if protoimpl.UnsafeEnabled { + mi := &file_channels_v1_channels_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveClientConnectionsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveClientConnectionsReq) ProtoMessage() {} + +func (x *RemoveClientConnectionsReq) ProtoReflect() protoreflect.Message { + mi := &file_channels_v1_channels_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 RemoveClientConnectionsReq.ProtoReflect.Descriptor instead. +func (*RemoveClientConnectionsReq) Descriptor() ([]byte, []int) { + return file_channels_v1_channels_proto_rawDescGZIP(), []int{0} +} + +func (x *RemoveClientConnectionsReq) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +type RemoveClientConnectionsRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RemoveClientConnectionsRes) Reset() { + *x = RemoveClientConnectionsRes{} + if protoimpl.UnsafeEnabled { + mi := &file_channels_v1_channels_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveClientConnectionsRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveClientConnectionsRes) ProtoMessage() {} + +func (x *RemoveClientConnectionsRes) ProtoReflect() protoreflect.Message { + mi := &file_channels_v1_channels_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 RemoveClientConnectionsRes.ProtoReflect.Descriptor instead. +func (*RemoveClientConnectionsRes) Descriptor() ([]byte, []int) { + return file_channels_v1_channels_proto_rawDescGZIP(), []int{1} +} + +type UnsetParentGroupFromChannelsReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ParentGroupId string `protobuf:"bytes,1,opt,name=parent_group_id,json=parentGroupId,proto3" json:"parent_group_id,omitempty"` +} + +func (x *UnsetParentGroupFromChannelsReq) Reset() { + *x = UnsetParentGroupFromChannelsReq{} + if protoimpl.UnsafeEnabled { + mi := &file_channels_v1_channels_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnsetParentGroupFromChannelsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnsetParentGroupFromChannelsReq) ProtoMessage() {} + +func (x *UnsetParentGroupFromChannelsReq) ProtoReflect() protoreflect.Message { + mi := &file_channels_v1_channels_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 UnsetParentGroupFromChannelsReq.ProtoReflect.Descriptor instead. +func (*UnsetParentGroupFromChannelsReq) Descriptor() ([]byte, []int) { + return file_channels_v1_channels_proto_rawDescGZIP(), []int{2} +} + +func (x *UnsetParentGroupFromChannelsReq) GetParentGroupId() string { + if x != nil { + return x.ParentGroupId + } + return "" +} + +type UnsetParentGroupFromChannelsRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UnsetParentGroupFromChannelsRes) Reset() { + *x = UnsetParentGroupFromChannelsRes{} + if protoimpl.UnsafeEnabled { + mi := &file_channels_v1_channels_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnsetParentGroupFromChannelsRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnsetParentGroupFromChannelsRes) ProtoMessage() {} + +func (x *UnsetParentGroupFromChannelsRes) ProtoReflect() protoreflect.Message { + mi := &file_channels_v1_channels_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 UnsetParentGroupFromChannelsRes.ProtoReflect.Descriptor instead. +func (*UnsetParentGroupFromChannelsRes) Descriptor() ([]byte, []int) { + return file_channels_v1_channels_proto_rawDescGZIP(), []int{3} +} + +type AuthzReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DomainId string `protobuf:"bytes,1,opt,name=domain_id,json=domainId,proto3" json:"domain_id,omitempty"` + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + ClientType string `protobuf:"bytes,3,opt,name=client_type,json=clientType,proto3" json:"client_type,omitempty"` + ChannelId string `protobuf:"bytes,4,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + Type uint32 `protobuf:"varint,5,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *AuthzReq) Reset() { + *x = AuthzReq{} + if protoimpl.UnsafeEnabled { + mi := &file_channels_v1_channels_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthzReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthzReq) ProtoMessage() {} + +func (x *AuthzReq) ProtoReflect() protoreflect.Message { + mi := &file_channels_v1_channels_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 AuthzReq.ProtoReflect.Descriptor instead. +func (*AuthzReq) Descriptor() ([]byte, []int) { + return file_channels_v1_channels_proto_rawDescGZIP(), []int{4} +} + +func (x *AuthzReq) GetDomainId() string { + if x != nil { + return x.DomainId + } + return "" +} + +func (x *AuthzReq) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +func (x *AuthzReq) GetClientType() string { + if x != nil { + return x.ClientType + } + return "" +} + +func (x *AuthzReq) GetChannelId() string { + if x != nil { + return x.ChannelId + } + return "" +} + +func (x *AuthzReq) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +type AuthzRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` +} + +func (x *AuthzRes) Reset() { + *x = AuthzRes{} + if protoimpl.UnsafeEnabled { + mi := &file_channels_v1_channels_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthzRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthzRes) ProtoMessage() {} + +func (x *AuthzRes) ProtoReflect() protoreflect.Message { + mi := &file_channels_v1_channels_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 AuthzRes.ProtoReflect.Descriptor instead. +func (*AuthzRes) Descriptor() ([]byte, []int) { + return file_channels_v1_channels_proto_rawDescGZIP(), []int{5} +} + +func (x *AuthzRes) GetAuthorized() bool { + if x != nil { + return x.Authorized + } + return false +} + +var File_channels_v1_channels_proto protoreflect.FileDescriptor + +var file_channels_v1_channels_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x76, 0x31, 0x22, 0x39, 0x0a, 0x1a, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x49, 0x64, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x22, 0x49, 0x0a, 0x1f, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x21, 0x0a, + 0x1f, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x22, 0x98, 0x01, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, 0x71, 0x12, 0x1b, 0x0a, + 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x08, 0x41, + 0x75, 0x74, 0x68, 0x7a, 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, 0xbb, 0x02, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x15, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, 0x71, 0x1a, + 0x15, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, + 0x74, 0x68, 0x7a, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x6d, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x27, 0x2e, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x7c, 0x0a, 0x1c, 0x55, 0x6e, 0x73, 0x65, 0x74, + 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x2c, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x62, 0x73, 0x6d, 0x61, 0x63, 0x68, 0x2f, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x67, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x2f, 0x76, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_channels_v1_channels_proto_rawDescOnce sync.Once + file_channels_v1_channels_proto_rawDescData = file_channels_v1_channels_proto_rawDesc +) + +func file_channels_v1_channels_proto_rawDescGZIP() []byte { + file_channels_v1_channels_proto_rawDescOnce.Do(func() { + file_channels_v1_channels_proto_rawDescData = protoimpl.X.CompressGZIP(file_channels_v1_channels_proto_rawDescData) + }) + return file_channels_v1_channels_proto_rawDescData +} + +var file_channels_v1_channels_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_channels_v1_channels_proto_goTypes = []any{ + (*RemoveClientConnectionsReq)(nil), // 0: channels.v1.RemoveClientConnectionsReq + (*RemoveClientConnectionsRes)(nil), // 1: channels.v1.RemoveClientConnectionsRes + (*UnsetParentGroupFromChannelsReq)(nil), // 2: channels.v1.UnsetParentGroupFromChannelsReq + (*UnsetParentGroupFromChannelsRes)(nil), // 3: channels.v1.UnsetParentGroupFromChannelsRes + (*AuthzReq)(nil), // 4: channels.v1.AuthzReq + (*AuthzRes)(nil), // 5: channels.v1.AuthzRes +} +var file_channels_v1_channels_proto_depIdxs = []int32{ + 4, // 0: channels.v1.ChannelsService.Authorize:input_type -> channels.v1.AuthzReq + 0, // 1: channels.v1.ChannelsService.RemoveClientConnections:input_type -> channels.v1.RemoveClientConnectionsReq + 2, // 2: channels.v1.ChannelsService.UnsetParentGroupFromChannels:input_type -> channels.v1.UnsetParentGroupFromChannelsReq + 5, // 3: channels.v1.ChannelsService.Authorize:output_type -> channels.v1.AuthzRes + 1, // 4: channels.v1.ChannelsService.RemoveClientConnections:output_type -> channels.v1.RemoveClientConnectionsRes + 3, // 5: channels.v1.ChannelsService.UnsetParentGroupFromChannels:output_type -> channels.v1.UnsetParentGroupFromChannelsRes + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] 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_channels_v1_channels_proto_init() } +func file_channels_v1_channels_proto_init() { + if File_channels_v1_channels_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_channels_v1_channels_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*RemoveClientConnectionsReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_channels_v1_channels_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*RemoveClientConnectionsRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_channels_v1_channels_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*UnsetParentGroupFromChannelsReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_channels_v1_channels_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*UnsetParentGroupFromChannelsRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_channels_v1_channels_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*AuthzReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_channels_v1_channels_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*AuthzRes); 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_channels_v1_channels_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_channels_v1_channels_proto_goTypes, + DependencyIndexes: file_channels_v1_channels_proto_depIdxs, + MessageInfos: file_channels_v1_channels_proto_msgTypes, + }.Build() + File_channels_v1_channels_proto = out.File + file_channels_v1_channels_proto_rawDesc = nil + file_channels_v1_channels_proto_goTypes = nil + file_channels_v1_channels_proto_depIdxs = nil +} diff --git a/internal/grpc/channels/v1/channels_grpc.pb.go b/internal/grpc/channels/v1/channels_grpc.pb.go new file mode 100644 index 0000000000..3082ccea81 --- /dev/null +++ b/internal/grpc/channels/v1/channels_grpc.pb.go @@ -0,0 +1,189 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.27.1 +// source: channels/v1/channels.proto + +package v1 + +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.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + ChannelsService_Authorize_FullMethodName = "/channels.v1.ChannelsService/Authorize" + ChannelsService_RemoveClientConnections_FullMethodName = "/channels.v1.ChannelsService/RemoveClientConnections" + ChannelsService_UnsetParentGroupFromChannels_FullMethodName = "/channels.v1.ChannelsService/UnsetParentGroupFromChannels" +) + +// ChannelsServiceClient is the client API for ChannelsService 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 ChannelsServiceClient interface { + Authorize(ctx context.Context, in *AuthzReq, opts ...grpc.CallOption) (*AuthzRes, error) + RemoveClientConnections(ctx context.Context, in *RemoveClientConnectionsReq, opts ...grpc.CallOption) (*RemoveClientConnectionsRes, error) + UnsetParentGroupFromChannels(ctx context.Context, in *UnsetParentGroupFromChannelsReq, opts ...grpc.CallOption) (*UnsetParentGroupFromChannelsRes, error) +} + +type channelsServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewChannelsServiceClient(cc grpc.ClientConnInterface) ChannelsServiceClient { + return &channelsServiceClient{cc} +} + +func (c *channelsServiceClient) Authorize(ctx context.Context, in *AuthzReq, opts ...grpc.CallOption) (*AuthzRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthzRes) + err := c.cc.Invoke(ctx, ChannelsService_Authorize_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *channelsServiceClient) RemoveClientConnections(ctx context.Context, in *RemoveClientConnectionsReq, opts ...grpc.CallOption) (*RemoveClientConnectionsRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RemoveClientConnectionsRes) + err := c.cc.Invoke(ctx, ChannelsService_RemoveClientConnections_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *channelsServiceClient) UnsetParentGroupFromChannels(ctx context.Context, in *UnsetParentGroupFromChannelsReq, opts ...grpc.CallOption) (*UnsetParentGroupFromChannelsRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UnsetParentGroupFromChannelsRes) + err := c.cc.Invoke(ctx, ChannelsService_UnsetParentGroupFromChannels_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ChannelsServiceServer is the server API for ChannelsService service. +// All implementations must embed UnimplementedChannelsServiceServer +// for forward compatibility +type ChannelsServiceServer interface { + Authorize(context.Context, *AuthzReq) (*AuthzRes, error) + RemoveClientConnections(context.Context, *RemoveClientConnectionsReq) (*RemoveClientConnectionsRes, error) + UnsetParentGroupFromChannels(context.Context, *UnsetParentGroupFromChannelsReq) (*UnsetParentGroupFromChannelsRes, error) + mustEmbedUnimplementedChannelsServiceServer() +} + +// UnimplementedChannelsServiceServer must be embedded to have forward compatible implementations. +type UnimplementedChannelsServiceServer struct { +} + +func (UnimplementedChannelsServiceServer) Authorize(context.Context, *AuthzReq) (*AuthzRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method Authorize not implemented") +} +func (UnimplementedChannelsServiceServer) RemoveClientConnections(context.Context, *RemoveClientConnectionsReq) (*RemoveClientConnectionsRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveClientConnections not implemented") +} +func (UnimplementedChannelsServiceServer) UnsetParentGroupFromChannels(context.Context, *UnsetParentGroupFromChannelsReq) (*UnsetParentGroupFromChannelsRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnsetParentGroupFromChannels not implemented") +} +func (UnimplementedChannelsServiceServer) mustEmbedUnimplementedChannelsServiceServer() {} + +// UnsafeChannelsServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ChannelsServiceServer will +// result in compilation errors. +type UnsafeChannelsServiceServer interface { + mustEmbedUnimplementedChannelsServiceServer() +} + +func RegisterChannelsServiceServer(s grpc.ServiceRegistrar, srv ChannelsServiceServer) { + s.RegisterService(&ChannelsService_ServiceDesc, srv) +} + +func _ChannelsService_Authorize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthzReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelsServiceServer).Authorize(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChannelsService_Authorize_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelsServiceServer).Authorize(ctx, req.(*AuthzReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChannelsService_RemoveClientConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveClientConnectionsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelsServiceServer).RemoveClientConnections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChannelsService_RemoveClientConnections_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelsServiceServer).RemoveClientConnections(ctx, req.(*RemoveClientConnectionsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChannelsService_UnsetParentGroupFromChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UnsetParentGroupFromChannelsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelsServiceServer).UnsetParentGroupFromChannels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChannelsService_UnsetParentGroupFromChannels_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelsServiceServer).UnsetParentGroupFromChannels(ctx, req.(*UnsetParentGroupFromChannelsReq)) + } + return interceptor(ctx, in, info, handler) +} + +// ChannelsService_ServiceDesc is the grpc.ServiceDesc for ChannelsService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ChannelsService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "channels.v1.ChannelsService", + HandlerType: (*ChannelsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Authorize", + Handler: _ChannelsService_Authorize_Handler, + }, + { + MethodName: "RemoveClientConnections", + Handler: _ChannelsService_RemoveClientConnections_Handler, + }, + { + MethodName: "UnsetParentGroupFromChannels", + Handler: _ChannelsService_UnsetParentGroupFromChannels_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "channels/v1/channels.proto", +} diff --git a/internal/grpc/clients/v1/clients.pb.go b/internal/grpc/clients/v1/clients.pb.go new file mode 100644 index 0000000000..989bbd06ff --- /dev/null +++ b/internal/grpc/clients/v1/clients.pb.go @@ -0,0 +1,530 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: clients/v1/clients.proto + +package v1 + +import ( + v1 "github.com/absmach/magistrala/internal/grpc/common/v1" + 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 AuthnReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + ClientSecret string `protobuf:"bytes,2,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"` +} + +func (x *AuthnReq) Reset() { + *x = AuthnReq{} + if protoimpl.UnsafeEnabled { + mi := &file_clients_v1_clients_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthnReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthnReq) ProtoMessage() {} + +func (x *AuthnReq) ProtoReflect() protoreflect.Message { + mi := &file_clients_v1_clients_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 AuthnReq.ProtoReflect.Descriptor instead. +func (*AuthnReq) Descriptor() ([]byte, []int) { + return file_clients_v1_clients_proto_rawDescGZIP(), []int{0} +} + +func (x *AuthnReq) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +func (x *AuthnReq) GetClientSecret() string { + if x != nil { + return x.ClientSecret + } + return "" +} + +type AuthnRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Authenticated bool `protobuf:"varint,1,opt,name=authenticated,proto3" json:"authenticated,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *AuthnRes) Reset() { + *x = AuthnRes{} + if protoimpl.UnsafeEnabled { + mi := &file_clients_v1_clients_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthnRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthnRes) ProtoMessage() {} + +func (x *AuthnRes) ProtoReflect() protoreflect.Message { + mi := &file_clients_v1_clients_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 AuthnRes.ProtoReflect.Descriptor instead. +func (*AuthnRes) Descriptor() ([]byte, []int) { + return file_clients_v1_clients_proto_rawDescGZIP(), []int{1} +} + +func (x *AuthnRes) GetAuthenticated() bool { + if x != nil { + return x.Authenticated + } + return false +} + +func (x *AuthnRes) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type RemoveChannelConnectionsReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (x *RemoveChannelConnectionsReq) Reset() { + *x = RemoveChannelConnectionsReq{} + if protoimpl.UnsafeEnabled { + mi := &file_clients_v1_clients_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveChannelConnectionsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveChannelConnectionsReq) ProtoMessage() {} + +func (x *RemoveChannelConnectionsReq) ProtoReflect() protoreflect.Message { + mi := &file_clients_v1_clients_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 RemoveChannelConnectionsReq.ProtoReflect.Descriptor instead. +func (*RemoveChannelConnectionsReq) Descriptor() ([]byte, []int) { + return file_clients_v1_clients_proto_rawDescGZIP(), []int{2} +} + +func (x *RemoveChannelConnectionsReq) GetChannelId() string { + if x != nil { + return x.ChannelId + } + return "" +} + +type RemoveChannelConnectionsRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RemoveChannelConnectionsRes) Reset() { + *x = RemoveChannelConnectionsRes{} + if protoimpl.UnsafeEnabled { + mi := &file_clients_v1_clients_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveChannelConnectionsRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveChannelConnectionsRes) ProtoMessage() {} + +func (x *RemoveChannelConnectionsRes) ProtoReflect() protoreflect.Message { + mi := &file_clients_v1_clients_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 RemoveChannelConnectionsRes.ProtoReflect.Descriptor instead. +func (*RemoveChannelConnectionsRes) Descriptor() ([]byte, []int) { + return file_clients_v1_clients_proto_rawDescGZIP(), []int{3} +} + +type UnsetParentGroupFromClientReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ParentGroupId string `protobuf:"bytes,1,opt,name=parent_group_id,json=parentGroupId,proto3" json:"parent_group_id,omitempty"` +} + +func (x *UnsetParentGroupFromClientReq) Reset() { + *x = UnsetParentGroupFromClientReq{} + if protoimpl.UnsafeEnabled { + mi := &file_clients_v1_clients_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnsetParentGroupFromClientReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnsetParentGroupFromClientReq) ProtoMessage() {} + +func (x *UnsetParentGroupFromClientReq) ProtoReflect() protoreflect.Message { + mi := &file_clients_v1_clients_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 UnsetParentGroupFromClientReq.ProtoReflect.Descriptor instead. +func (*UnsetParentGroupFromClientReq) Descriptor() ([]byte, []int) { + return file_clients_v1_clients_proto_rawDescGZIP(), []int{4} +} + +func (x *UnsetParentGroupFromClientReq) GetParentGroupId() string { + if x != nil { + return x.ParentGroupId + } + return "" +} + +type UnsetParentGroupFromClientRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UnsetParentGroupFromClientRes) Reset() { + *x = UnsetParentGroupFromClientRes{} + if protoimpl.UnsafeEnabled { + mi := &file_clients_v1_clients_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnsetParentGroupFromClientRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnsetParentGroupFromClientRes) ProtoMessage() {} + +func (x *UnsetParentGroupFromClientRes) ProtoReflect() protoreflect.Message { + mi := &file_clients_v1_clients_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 UnsetParentGroupFromClientRes.ProtoReflect.Descriptor instead. +func (*UnsetParentGroupFromClientRes) Descriptor() ([]byte, []int) { + return file_clients_v1_clients_proto_rawDescGZIP(), []int{5} +} + +var File_clients_v1_clients_proto protoreflect.FileDescriptor + +var file_clients_v1_clients_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, + 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4c, + 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x40, 0x0a, 0x08, + 0x41, 0x75, 0x74, 0x68, 0x6e, 0x52, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x3c, + 0x0a, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x22, 0x1d, 0x0a, 0x1b, + 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x22, 0x47, 0x0a, 0x1d, 0x55, + 0x6e, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x46, + 0x72, 0x6f, 0x6d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x26, 0x0a, 0x0f, + 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x49, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x32, 0x83, 0x05, 0x0a, 0x0e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x14, + 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, + 0x6e, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, + 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x10, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, + 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0e, + 0x41, 0x64, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x11, + 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x1f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x1a, 0x1f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x6e, 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x27, 0x2e, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x1a, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x50, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x12, 0x29, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x29, + 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, + 0x74, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x46, 0x72, 0x6f, 0x6d, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x38, 0x5a, 0x36, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x62, 0x73, 0x6d, 0x61, 0x63, + 0x68, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x73, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_clients_v1_clients_proto_rawDescOnce sync.Once + file_clients_v1_clients_proto_rawDescData = file_clients_v1_clients_proto_rawDesc +) + +func file_clients_v1_clients_proto_rawDescGZIP() []byte { + file_clients_v1_clients_proto_rawDescOnce.Do(func() { + file_clients_v1_clients_proto_rawDescData = protoimpl.X.CompressGZIP(file_clients_v1_clients_proto_rawDescData) + }) + return file_clients_v1_clients_proto_rawDescData +} + +var file_clients_v1_clients_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_clients_v1_clients_proto_goTypes = []any{ + (*AuthnReq)(nil), // 0: clients.v1.AuthnReq + (*AuthnRes)(nil), // 1: clients.v1.AuthnRes + (*RemoveChannelConnectionsReq)(nil), // 2: clients.v1.RemoveChannelConnectionsReq + (*RemoveChannelConnectionsRes)(nil), // 3: clients.v1.RemoveChannelConnectionsRes + (*UnsetParentGroupFromClientReq)(nil), // 4: clients.v1.UnsetParentGroupFromClientReq + (*UnsetParentGroupFromClientRes)(nil), // 5: clients.v1.UnsetParentGroupFromClientRes + (*v1.RetrieveEntityReq)(nil), // 6: common.v1.RetrieveEntityReq + (*v1.RetrieveEntitiesReq)(nil), // 7: common.v1.RetrieveEntitiesReq + (*v1.AddConnectionsReq)(nil), // 8: common.v1.AddConnectionsReq + (*v1.RemoveConnectionsReq)(nil), // 9: common.v1.RemoveConnectionsReq + (*v1.RetrieveEntityRes)(nil), // 10: common.v1.RetrieveEntityRes + (*v1.RetrieveEntitiesRes)(nil), // 11: common.v1.RetrieveEntitiesRes + (*v1.AddConnectionsRes)(nil), // 12: common.v1.AddConnectionsRes + (*v1.RemoveConnectionsRes)(nil), // 13: common.v1.RemoveConnectionsRes +} +var file_clients_v1_clients_proto_depIdxs = []int32{ + 0, // 0: clients.v1.ClientsService.Authenticate:input_type -> clients.v1.AuthnReq + 6, // 1: clients.v1.ClientsService.RetrieveEntity:input_type -> common.v1.RetrieveEntityReq + 7, // 2: clients.v1.ClientsService.RetrieveEntities:input_type -> common.v1.RetrieveEntitiesReq + 8, // 3: clients.v1.ClientsService.AddConnections:input_type -> common.v1.AddConnectionsReq + 9, // 4: clients.v1.ClientsService.RemoveConnections:input_type -> common.v1.RemoveConnectionsReq + 2, // 5: clients.v1.ClientsService.RemoveChannelConnections:input_type -> clients.v1.RemoveChannelConnectionsReq + 4, // 6: clients.v1.ClientsService.UnsetParentGroupFromClient:input_type -> clients.v1.UnsetParentGroupFromClientReq + 1, // 7: clients.v1.ClientsService.Authenticate:output_type -> clients.v1.AuthnRes + 10, // 8: clients.v1.ClientsService.RetrieveEntity:output_type -> common.v1.RetrieveEntityRes + 11, // 9: clients.v1.ClientsService.RetrieveEntities:output_type -> common.v1.RetrieveEntitiesRes + 12, // 10: clients.v1.ClientsService.AddConnections:output_type -> common.v1.AddConnectionsRes + 13, // 11: clients.v1.ClientsService.RemoveConnections:output_type -> common.v1.RemoveConnectionsRes + 3, // 12: clients.v1.ClientsService.RemoveChannelConnections:output_type -> clients.v1.RemoveChannelConnectionsRes + 5, // 13: clients.v1.ClientsService.UnsetParentGroupFromClient:output_type -> clients.v1.UnsetParentGroupFromClientRes + 7, // [7:14] is the sub-list for method output_type + 0, // [0:7] 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_v1_clients_proto_init() } +func file_clients_v1_clients_proto_init() { + if File_clients_v1_clients_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_clients_v1_clients_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*AuthnReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clients_v1_clients_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*AuthnRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clients_v1_clients_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*RemoveChannelConnectionsReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clients_v1_clients_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*RemoveChannelConnectionsRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clients_v1_clients_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*UnsetParentGroupFromClientReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clients_v1_clients_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*UnsetParentGroupFromClientRes); 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_v1_clients_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_clients_v1_clients_proto_goTypes, + DependencyIndexes: file_clients_v1_clients_proto_depIdxs, + MessageInfos: file_clients_v1_clients_proto_msgTypes, + }.Build() + File_clients_v1_clients_proto = out.File + file_clients_v1_clients_proto_rawDesc = nil + file_clients_v1_clients_proto_goTypes = nil + file_clients_v1_clients_proto_depIdxs = nil +} diff --git a/internal/grpc/clients/v1/clients_grpc.pb.go b/internal/grpc/clients/v1/clients_grpc.pb.go new file mode 100644 index 0000000000..628e9d66a6 --- /dev/null +++ b/internal/grpc/clients/v1/clients_grpc.pb.go @@ -0,0 +1,350 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.27.1 +// source: clients/v1/clients.proto + +package v1 + +import ( + context "context" + v1 "github.com/absmach/magistrala/internal/grpc/common/v1" + 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.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + ClientsService_Authenticate_FullMethodName = "/clients.v1.ClientsService/Authenticate" + ClientsService_RetrieveEntity_FullMethodName = "/clients.v1.ClientsService/RetrieveEntity" + ClientsService_RetrieveEntities_FullMethodName = "/clients.v1.ClientsService/RetrieveEntities" + ClientsService_AddConnections_FullMethodName = "/clients.v1.ClientsService/AddConnections" + ClientsService_RemoveConnections_FullMethodName = "/clients.v1.ClientsService/RemoveConnections" + ClientsService_RemoveChannelConnections_FullMethodName = "/clients.v1.ClientsService/RemoveChannelConnections" + ClientsService_UnsetParentGroupFromClient_FullMethodName = "/clients.v1.ClientsService/UnsetParentGroupFromClient" +) + +// ClientsServiceClient is the client API for ClientsService 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. +// +// ClientsService is a service that provides clients authorization functionalities +// for magistrala services. +type ClientsServiceClient interface { + // Authorize checks if the client is authorized to perform + Authenticate(ctx context.Context, in *AuthnReq, opts ...grpc.CallOption) (*AuthnRes, error) + RetrieveEntity(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption) (*v1.RetrieveEntityRes, error) + RetrieveEntities(ctx context.Context, in *v1.RetrieveEntitiesReq, opts ...grpc.CallOption) (*v1.RetrieveEntitiesRes, error) + AddConnections(ctx context.Context, in *v1.AddConnectionsReq, opts ...grpc.CallOption) (*v1.AddConnectionsRes, error) + RemoveConnections(ctx context.Context, in *v1.RemoveConnectionsReq, opts ...grpc.CallOption) (*v1.RemoveConnectionsRes, error) + RemoveChannelConnections(ctx context.Context, in *RemoveChannelConnectionsReq, opts ...grpc.CallOption) (*RemoveChannelConnectionsRes, error) + UnsetParentGroupFromClient(ctx context.Context, in *UnsetParentGroupFromClientReq, opts ...grpc.CallOption) (*UnsetParentGroupFromClientRes, error) +} + +type clientsServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewClientsServiceClient(cc grpc.ClientConnInterface) ClientsServiceClient { + return &clientsServiceClient{cc} +} + +func (c *clientsServiceClient) Authenticate(ctx context.Context, in *AuthnReq, opts ...grpc.CallOption) (*AuthnRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthnRes) + err := c.cc.Invoke(ctx, ClientsService_Authenticate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *clientsServiceClient) RetrieveEntity(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption) (*v1.RetrieveEntityRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(v1.RetrieveEntityRes) + err := c.cc.Invoke(ctx, ClientsService_RetrieveEntity_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *clientsServiceClient) RetrieveEntities(ctx context.Context, in *v1.RetrieveEntitiesReq, opts ...grpc.CallOption) (*v1.RetrieveEntitiesRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(v1.RetrieveEntitiesRes) + err := c.cc.Invoke(ctx, ClientsService_RetrieveEntities_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *clientsServiceClient) AddConnections(ctx context.Context, in *v1.AddConnectionsReq, opts ...grpc.CallOption) (*v1.AddConnectionsRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(v1.AddConnectionsRes) + err := c.cc.Invoke(ctx, ClientsService_AddConnections_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *clientsServiceClient) RemoveConnections(ctx context.Context, in *v1.RemoveConnectionsReq, opts ...grpc.CallOption) (*v1.RemoveConnectionsRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(v1.RemoveConnectionsRes) + err := c.cc.Invoke(ctx, ClientsService_RemoveConnections_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *clientsServiceClient) RemoveChannelConnections(ctx context.Context, in *RemoveChannelConnectionsReq, opts ...grpc.CallOption) (*RemoveChannelConnectionsRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RemoveChannelConnectionsRes) + err := c.cc.Invoke(ctx, ClientsService_RemoveChannelConnections_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *clientsServiceClient) UnsetParentGroupFromClient(ctx context.Context, in *UnsetParentGroupFromClientReq, opts ...grpc.CallOption) (*UnsetParentGroupFromClientRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UnsetParentGroupFromClientRes) + err := c.cc.Invoke(ctx, ClientsService_UnsetParentGroupFromClient_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ClientsServiceServer is the server API for ClientsService service. +// All implementations must embed UnimplementedClientsServiceServer +// for forward compatibility +// +// ClientsService is a service that provides clients authorization functionalities +// for magistrala services. +type ClientsServiceServer interface { + // Authorize checks if the client is authorized to perform + Authenticate(context.Context, *AuthnReq) (*AuthnRes, error) + RetrieveEntity(context.Context, *v1.RetrieveEntityReq) (*v1.RetrieveEntityRes, error) + RetrieveEntities(context.Context, *v1.RetrieveEntitiesReq) (*v1.RetrieveEntitiesRes, error) + AddConnections(context.Context, *v1.AddConnectionsReq) (*v1.AddConnectionsRes, error) + RemoveConnections(context.Context, *v1.RemoveConnectionsReq) (*v1.RemoveConnectionsRes, error) + RemoveChannelConnections(context.Context, *RemoveChannelConnectionsReq) (*RemoveChannelConnectionsRes, error) + UnsetParentGroupFromClient(context.Context, *UnsetParentGroupFromClientReq) (*UnsetParentGroupFromClientRes, error) + mustEmbedUnimplementedClientsServiceServer() +} + +// UnimplementedClientsServiceServer must be embedded to have forward compatible implementations. +type UnimplementedClientsServiceServer struct { +} + +func (UnimplementedClientsServiceServer) Authenticate(context.Context, *AuthnReq) (*AuthnRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method Authenticate not implemented") +} +func (UnimplementedClientsServiceServer) RetrieveEntity(context.Context, *v1.RetrieveEntityReq) (*v1.RetrieveEntityRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method RetrieveEntity not implemented") +} +func (UnimplementedClientsServiceServer) RetrieveEntities(context.Context, *v1.RetrieveEntitiesReq) (*v1.RetrieveEntitiesRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method RetrieveEntities not implemented") +} +func (UnimplementedClientsServiceServer) AddConnections(context.Context, *v1.AddConnectionsReq) (*v1.AddConnectionsRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddConnections not implemented") +} +func (UnimplementedClientsServiceServer) RemoveConnections(context.Context, *v1.RemoveConnectionsReq) (*v1.RemoveConnectionsRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveConnections not implemented") +} +func (UnimplementedClientsServiceServer) RemoveChannelConnections(context.Context, *RemoveChannelConnectionsReq) (*RemoveChannelConnectionsRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveChannelConnections not implemented") +} +func (UnimplementedClientsServiceServer) UnsetParentGroupFromClient(context.Context, *UnsetParentGroupFromClientReq) (*UnsetParentGroupFromClientRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnsetParentGroupFromClient not implemented") +} +func (UnimplementedClientsServiceServer) mustEmbedUnimplementedClientsServiceServer() {} + +// UnsafeClientsServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ClientsServiceServer will +// result in compilation errors. +type UnsafeClientsServiceServer interface { + mustEmbedUnimplementedClientsServiceServer() +} + +func RegisterClientsServiceServer(s grpc.ServiceRegistrar, srv ClientsServiceServer) { + s.RegisterService(&ClientsService_ServiceDesc, srv) +} + +func _ClientsService_Authenticate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthnReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClientsServiceServer).Authenticate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ClientsService_Authenticate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClientsServiceServer).Authenticate(ctx, req.(*AuthnReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ClientsService_RetrieveEntity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(v1.RetrieveEntityReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClientsServiceServer).RetrieveEntity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ClientsService_RetrieveEntity_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClientsServiceServer).RetrieveEntity(ctx, req.(*v1.RetrieveEntityReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ClientsService_RetrieveEntities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(v1.RetrieveEntitiesReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClientsServiceServer).RetrieveEntities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ClientsService_RetrieveEntities_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClientsServiceServer).RetrieveEntities(ctx, req.(*v1.RetrieveEntitiesReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ClientsService_AddConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(v1.AddConnectionsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClientsServiceServer).AddConnections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ClientsService_AddConnections_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClientsServiceServer).AddConnections(ctx, req.(*v1.AddConnectionsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ClientsService_RemoveConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(v1.RemoveConnectionsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClientsServiceServer).RemoveConnections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ClientsService_RemoveConnections_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClientsServiceServer).RemoveConnections(ctx, req.(*v1.RemoveConnectionsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ClientsService_RemoveChannelConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveChannelConnectionsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClientsServiceServer).RemoveChannelConnections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ClientsService_RemoveChannelConnections_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClientsServiceServer).RemoveChannelConnections(ctx, req.(*RemoveChannelConnectionsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ClientsService_UnsetParentGroupFromClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UnsetParentGroupFromClientReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClientsServiceServer).UnsetParentGroupFromClient(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ClientsService_UnsetParentGroupFromClient_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClientsServiceServer).UnsetParentGroupFromClient(ctx, req.(*UnsetParentGroupFromClientReq)) + } + return interceptor(ctx, in, info, handler) +} + +// ClientsService_ServiceDesc is the grpc.ServiceDesc for ClientsService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ClientsService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "clients.v1.ClientsService", + HandlerType: (*ClientsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Authenticate", + Handler: _ClientsService_Authenticate_Handler, + }, + { + MethodName: "RetrieveEntity", + Handler: _ClientsService_RetrieveEntity_Handler, + }, + { + MethodName: "RetrieveEntities", + Handler: _ClientsService_RetrieveEntities_Handler, + }, + { + MethodName: "AddConnections", + Handler: _ClientsService_AddConnections_Handler, + }, + { + MethodName: "RemoveConnections", + Handler: _ClientsService_RemoveConnections_Handler, + }, + { + MethodName: "RemoveChannelConnections", + Handler: _ClientsService_RemoveChannelConnections_Handler, + }, + { + MethodName: "UnsetParentGroupFromClient", + Handler: _ClientsService_UnsetParentGroupFromClient_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "clients/v1/clients.proto", +} diff --git a/internal/grpc/common/v1/common.pb.go b/internal/grpc/common/v1/common.pb.go new file mode 100644 index 0000000000..19c1633551 --- /dev/null +++ b/internal/grpc/common/v1/common.pb.go @@ -0,0 +1,800 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: common/v1/common.proto + +package v1 + +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 RetrieveEntitiesReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` +} + +func (x *RetrieveEntitiesReq) Reset() { + *x = RetrieveEntitiesReq{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveEntitiesReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveEntitiesReq) ProtoMessage() {} + +func (x *RetrieveEntitiesReq) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 RetrieveEntitiesReq.ProtoReflect.Descriptor instead. +func (*RetrieveEntitiesReq) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{0} +} + +func (x *RetrieveEntitiesReq) GetIds() []string { + if x != nil { + return x.Ids + } + return nil +} + +type RetrieveEntitiesRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total uint64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + Limit uint64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` + Entities []*EntityBasic `protobuf:"bytes,4,rep,name=entities,proto3" json:"entities,omitempty"` +} + +func (x *RetrieveEntitiesRes) Reset() { + *x = RetrieveEntitiesRes{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveEntitiesRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveEntitiesRes) ProtoMessage() {} + +func (x *RetrieveEntitiesRes) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 RetrieveEntitiesRes.ProtoReflect.Descriptor instead. +func (*RetrieveEntitiesRes) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{1} +} + +func (x *RetrieveEntitiesRes) GetTotal() uint64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *RetrieveEntitiesRes) GetLimit() uint64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *RetrieveEntitiesRes) GetOffset() uint64 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *RetrieveEntitiesRes) GetEntities() []*EntityBasic { + if x != nil { + return x.Entities + } + return nil +} + +type RetrieveEntityReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *RetrieveEntityReq) Reset() { + *x = RetrieveEntityReq{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveEntityReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveEntityReq) ProtoMessage() {} + +func (x *RetrieveEntityReq) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 RetrieveEntityReq.ProtoReflect.Descriptor instead. +func (*RetrieveEntityReq) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{2} +} + +func (x *RetrieveEntityReq) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type RetrieveEntityRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entity *EntityBasic `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` +} + +func (x *RetrieveEntityRes) Reset() { + *x = RetrieveEntityRes{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveEntityRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveEntityRes) ProtoMessage() {} + +func (x *RetrieveEntityRes) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 RetrieveEntityRes.ProtoReflect.Descriptor instead. +func (*RetrieveEntityRes) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{3} +} + +func (x *RetrieveEntityRes) GetEntity() *EntityBasic { + if x != nil { + return x.Entity + } + return nil +} + +type EntityBasic struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + DomainId string `protobuf:"bytes,2,opt,name=domain_id,json=domainId,proto3" json:"domain_id,omitempty"` + Status uint32 `protobuf:"varint,3,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *EntityBasic) Reset() { + *x = EntityBasic{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EntityBasic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EntityBasic) ProtoMessage() {} + +func (x *EntityBasic) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 EntityBasic.ProtoReflect.Descriptor instead. +func (*EntityBasic) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{4} +} + +func (x *EntityBasic) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *EntityBasic) GetDomainId() string { + if x != nil { + return x.DomainId + } + return "" +} + +func (x *EntityBasic) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +type AddConnectionsReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Connections []*Connection `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections,omitempty"` +} + +func (x *AddConnectionsReq) Reset() { + *x = AddConnectionsReq{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddConnectionsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddConnectionsReq) ProtoMessage() {} + +func (x *AddConnectionsReq) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 AddConnectionsReq.ProtoReflect.Descriptor instead. +func (*AddConnectionsReq) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{5} +} + +func (x *AddConnectionsReq) GetConnections() []*Connection { + if x != nil { + return x.Connections + } + return nil +} + +type AddConnectionsRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` +} + +func (x *AddConnectionsRes) Reset() { + *x = AddConnectionsRes{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddConnectionsRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddConnectionsRes) ProtoMessage() {} + +func (x *AddConnectionsRes) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 AddConnectionsRes.ProtoReflect.Descriptor instead. +func (*AddConnectionsRes) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{6} +} + +func (x *AddConnectionsRes) GetOk() bool { + if x != nil { + return x.Ok + } + return false +} + +type RemoveConnectionsReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Connections []*Connection `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections,omitempty"` +} + +func (x *RemoveConnectionsReq) Reset() { + *x = RemoveConnectionsReq{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveConnectionsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveConnectionsReq) ProtoMessage() {} + +func (x *RemoveConnectionsReq) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 RemoveConnectionsReq.ProtoReflect.Descriptor instead. +func (*RemoveConnectionsReq) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{7} +} + +func (x *RemoveConnectionsReq) GetConnections() []*Connection { + if x != nil { + return x.Connections + } + return nil +} + +type RemoveConnectionsRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` +} + +func (x *RemoveConnectionsRes) Reset() { + *x = RemoveConnectionsRes{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveConnectionsRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveConnectionsRes) ProtoMessage() {} + +func (x *RemoveConnectionsRes) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 RemoveConnectionsRes.ProtoReflect.Descriptor instead. +func (*RemoveConnectionsRes) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{8} +} + +func (x *RemoveConnectionsRes) GetOk() bool { + if x != nil { + return x.Ok + } + return false +} + +type Connection struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + DomainId string `protobuf:"bytes,3,opt,name=domain_id,json=domainId,proto3" json:"domain_id,omitempty"` + Type uint32 `protobuf:"varint,4,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *Connection) Reset() { + *x = Connection{} + if protoimpl.UnsafeEnabled { + mi := &file_common_v1_common_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Connection) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Connection) ProtoMessage() {} + +func (x *Connection) ProtoReflect() protoreflect.Message { + mi := &file_common_v1_common_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 Connection.ProtoReflect.Descriptor instead. +func (*Connection) Descriptor() ([]byte, []int) { + return file_common_v1_common_proto_rawDescGZIP(), []int{9} +} + +func (x *Connection) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +func (x *Connection) GetChannelId() string { + if x != nil { + return x.ChannelId + } + return "" +} + +func (x *Connection) GetDomainId() string { + if x != nil { + return x.DomainId + } + return "" +} + +func (x *Connection) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +var File_common_v1_common_proto protoreflect.FileDescriptor + +var file_common_v1_common_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x22, 0x27, 0x0a, 0x13, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x8d, 0x01, 0x0a, + 0x13, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x61, 0x73, + 0x69, 0x63, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x23, 0x0a, 0x11, + 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x22, 0x43, 0x0a, 0x11, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x61, 0x73, 0x69, 0x63, 0x52, 0x06, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x52, 0x0a, 0x0b, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x42, 0x61, 0x73, 0x69, 0x63, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4c, 0x0a, 0x11, 0x41, 0x64, + 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, + 0x37, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x12, 0x0e, 0x0a, + 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x4f, 0x0a, + 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x37, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x26, + 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x79, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, + 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x61, 0x62, 0x73, 0x6d, 0x61, 0x63, 0x68, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x6c, 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, + 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_common_v1_common_proto_rawDescOnce sync.Once + file_common_v1_common_proto_rawDescData = file_common_v1_common_proto_rawDesc +) + +func file_common_v1_common_proto_rawDescGZIP() []byte { + file_common_v1_common_proto_rawDescOnce.Do(func() { + file_common_v1_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_v1_common_proto_rawDescData) + }) + return file_common_v1_common_proto_rawDescData +} + +var file_common_v1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_common_v1_common_proto_goTypes = []any{ + (*RetrieveEntitiesReq)(nil), // 0: common.v1.RetrieveEntitiesReq + (*RetrieveEntitiesRes)(nil), // 1: common.v1.RetrieveEntitiesRes + (*RetrieveEntityReq)(nil), // 2: common.v1.RetrieveEntityReq + (*RetrieveEntityRes)(nil), // 3: common.v1.RetrieveEntityRes + (*EntityBasic)(nil), // 4: common.v1.EntityBasic + (*AddConnectionsReq)(nil), // 5: common.v1.AddConnectionsReq + (*AddConnectionsRes)(nil), // 6: common.v1.AddConnectionsRes + (*RemoveConnectionsReq)(nil), // 7: common.v1.RemoveConnectionsReq + (*RemoveConnectionsRes)(nil), // 8: common.v1.RemoveConnectionsRes + (*Connection)(nil), // 9: common.v1.Connection +} +var file_common_v1_common_proto_depIdxs = []int32{ + 4, // 0: common.v1.RetrieveEntitiesRes.entities:type_name -> common.v1.EntityBasic + 4, // 1: common.v1.RetrieveEntityRes.entity:type_name -> common.v1.EntityBasic + 9, // 2: common.v1.AddConnectionsReq.connections:type_name -> common.v1.Connection + 9, // 3: common.v1.RemoveConnectionsReq.connections:type_name -> common.v1.Connection + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_common_v1_common_proto_init() } +func file_common_v1_common_proto_init() { + if File_common_v1_common_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_common_v1_common_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*RetrieveEntitiesReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*RetrieveEntitiesRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*RetrieveEntityReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*RetrieveEntityRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*EntityBasic); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*AddConnectionsReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*AddConnectionsRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*RemoveConnectionsReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*RemoveConnectionsRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_v1_common_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*Connection); 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_common_v1_common_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_common_v1_common_proto_goTypes, + DependencyIndexes: file_common_v1_common_proto_depIdxs, + MessageInfos: file_common_v1_common_proto_msgTypes, + }.Build() + File_common_v1_common_proto = out.File + file_common_v1_common_proto_rawDesc = nil + file_common_v1_common_proto_goTypes = nil + file_common_v1_common_proto_depIdxs = nil +} diff --git a/internal/grpc/domains/v1/domains.pb.go b/internal/grpc/domains/v1/domains.pb.go new file mode 100644 index 0000000000..c62a2d0d49 --- /dev/null +++ b/internal/grpc/domains/v1/domains.pb.go @@ -0,0 +1,219 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: domains/v1/domains.proto + +package v1 + +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 DeleteUserRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` +} + +func (x *DeleteUserRes) Reset() { + *x = DeleteUserRes{} + if protoimpl.UnsafeEnabled { + mi := &file_domains_v1_domains_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteUserRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserRes) ProtoMessage() {} + +func (x *DeleteUserRes) ProtoReflect() protoreflect.Message { + mi := &file_domains_v1_domains_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 DeleteUserRes.ProtoReflect.Descriptor instead. +func (*DeleteUserRes) Descriptor() ([]byte, []int) { + return file_domains_v1_domains_proto_rawDescGZIP(), []int{0} +} + +func (x *DeleteUserRes) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +type DeleteUserReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteUserReq) Reset() { + *x = DeleteUserReq{} + if protoimpl.UnsafeEnabled { + mi := &file_domains_v1_domains_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserReq) ProtoMessage() {} + +func (x *DeleteUserReq) ProtoReflect() protoreflect.Message { + mi := &file_domains_v1_domains_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 DeleteUserReq.ProtoReflect.Descriptor instead. +func (*DeleteUserReq) Descriptor() ([]byte, []int) { + return file_domains_v1_domains_proto_rawDescGZIP(), []int{1} +} + +func (x *DeleteUserReq) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +var File_domains_v1_domains_proto protoreflect.FileDescriptor + +var file_domains_v1_domains_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x22, 0x29, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x55, 0x73, 0x65, 0x72, 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, 0x1f, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x32, 0x61, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, + 0x65, 0x72, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x19, 0x2e, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x62, 0x73, 0x6d, 0x61, 0x63, 0x68, 0x2f, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_domains_v1_domains_proto_rawDescOnce sync.Once + file_domains_v1_domains_proto_rawDescData = file_domains_v1_domains_proto_rawDesc +) + +func file_domains_v1_domains_proto_rawDescGZIP() []byte { + file_domains_v1_domains_proto_rawDescOnce.Do(func() { + file_domains_v1_domains_proto_rawDescData = protoimpl.X.CompressGZIP(file_domains_v1_domains_proto_rawDescData) + }) + return file_domains_v1_domains_proto_rawDescData +} + +var file_domains_v1_domains_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_domains_v1_domains_proto_goTypes = []any{ + (*DeleteUserRes)(nil), // 0: domains.v1.DeleteUserRes + (*DeleteUserReq)(nil), // 1: domains.v1.DeleteUserReq +} +var file_domains_v1_domains_proto_depIdxs = []int32{ + 1, // 0: domains.v1.DomainsService.DeleteUserFromDomains:input_type -> domains.v1.DeleteUserReq + 0, // 1: domains.v1.DomainsService.DeleteUserFromDomains:output_type -> domains.v1.DeleteUserRes + 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_domains_v1_domains_proto_init() } +func file_domains_v1_domains_proto_init() { + if File_domains_v1_domains_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_domains_v1_domains_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*DeleteUserRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_domains_v1_domains_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*DeleteUserReq); 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_domains_v1_domains_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_domains_v1_domains_proto_goTypes, + DependencyIndexes: file_domains_v1_domains_proto_depIdxs, + MessageInfos: file_domains_v1_domains_proto_msgTypes, + }.Build() + File_domains_v1_domains_proto = out.File + file_domains_v1_domains_proto_rawDesc = nil + file_domains_v1_domains_proto_goTypes = nil + file_domains_v1_domains_proto_depIdxs = nil +} diff --git a/internal/grpc/domains/v1/domains_grpc.pb.go b/internal/grpc/domains/v1/domains_grpc.pb.go new file mode 100644 index 0000000000..9c9b30d02a --- /dev/null +++ b/internal/grpc/domains/v1/domains_grpc.pb.go @@ -0,0 +1,119 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.27.1 +// source: domains/v1/domains.proto + +package v1 + +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.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + DomainsService_DeleteUserFromDomains_FullMethodName = "/domains.v1.DomainsService/DeleteUserFromDomains" +) + +// DomainsServiceClient is the client API for DomainsService 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. +// +// DomainsService is a service that provides access to domains +// functionalities for magistrala services. +type DomainsServiceClient interface { + DeleteUserFromDomains(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*DeleteUserRes, error) +} + +type domainsServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDomainsServiceClient(cc grpc.ClientConnInterface) DomainsServiceClient { + return &domainsServiceClient{cc} +} + +func (c *domainsServiceClient) DeleteUserFromDomains(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*DeleteUserRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteUserRes) + err := c.cc.Invoke(ctx, DomainsService_DeleteUserFromDomains_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DomainsServiceServer is the server API for DomainsService service. +// All implementations must embed UnimplementedDomainsServiceServer +// for forward compatibility +// +// DomainsService is a service that provides access to domains +// functionalities for magistrala services. +type DomainsServiceServer interface { + DeleteUserFromDomains(context.Context, *DeleteUserReq) (*DeleteUserRes, error) + mustEmbedUnimplementedDomainsServiceServer() +} + +// UnimplementedDomainsServiceServer must be embedded to have forward compatible implementations. +type UnimplementedDomainsServiceServer struct { +} + +func (UnimplementedDomainsServiceServer) DeleteUserFromDomains(context.Context, *DeleteUserReq) (*DeleteUserRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteUserFromDomains not implemented") +} +func (UnimplementedDomainsServiceServer) mustEmbedUnimplementedDomainsServiceServer() {} + +// UnsafeDomainsServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DomainsServiceServer will +// result in compilation errors. +type UnsafeDomainsServiceServer interface { + mustEmbedUnimplementedDomainsServiceServer() +} + +func RegisterDomainsServiceServer(s grpc.ServiceRegistrar, srv DomainsServiceServer) { + s.RegisterService(&DomainsService_ServiceDesc, srv) +} + +func _DomainsService_DeleteUserFromDomains_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DomainsServiceServer).DeleteUserFromDomains(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DomainsService_DeleteUserFromDomains_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DomainsServiceServer).DeleteUserFromDomains(ctx, req.(*DeleteUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +// DomainsService_ServiceDesc is the grpc.ServiceDesc for DomainsService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DomainsService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "domains.v1.DomainsService", + HandlerType: (*DomainsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "DeleteUserFromDomains", + Handler: _DomainsService_DeleteUserFromDomains_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "domains/v1/domains.proto", +} diff --git a/internal/grpc/groups/v1/groups.pb.go b/internal/grpc/groups/v1/groups.pb.go new file mode 100644 index 0000000000..26e0c5a934 --- /dev/null +++ b/internal/grpc/groups/v1/groups.pb.go @@ -0,0 +1,81 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: groups/v1/groups.proto + +package v1 + +import ( + v1 "github.com/absmach/magistrala/internal/grpc/common/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +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) +) + +var File_groups_v1_groups_proto protoreflect.FileDescriptor + +var file_groups_v1_groups_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x2e, 0x76, 0x31, 0x1a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x5f, 0x0a, 0x0d, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a, 0x0e, + 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1c, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, + 0x65, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, + 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x37, 0x5a, 0x35, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x62, 0x73, 0x6d, 0x61, + 0x63, 0x68, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x73, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_groups_v1_groups_proto_goTypes = []any{ + (*v1.RetrieveEntityReq)(nil), // 0: common.v1.RetrieveEntityReq + (*v1.RetrieveEntityRes)(nil), // 1: common.v1.RetrieveEntityRes +} +var file_groups_v1_groups_proto_depIdxs = []int32{ + 0, // 0: groups.v1.GroupsService.RetrieveEntity:input_type -> common.v1.RetrieveEntityReq + 1, // 1: groups.v1.GroupsService.RetrieveEntity:output_type -> common.v1.RetrieveEntityRes + 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_groups_v1_groups_proto_init() } +func file_groups_v1_groups_proto_init() { + if File_groups_v1_groups_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_groups_v1_groups_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_groups_v1_groups_proto_goTypes, + DependencyIndexes: file_groups_v1_groups_proto_depIdxs, + }.Build() + File_groups_v1_groups_proto = out.File + file_groups_v1_groups_proto_rawDesc = nil + file_groups_v1_groups_proto_goTypes = nil + file_groups_v1_groups_proto_depIdxs = nil +} diff --git a/internal/grpc/groups/v1/groups_grpc.pb.go b/internal/grpc/groups/v1/groups_grpc.pb.go new file mode 100644 index 0000000000..f4422b8d79 --- /dev/null +++ b/internal/grpc/groups/v1/groups_grpc.pb.go @@ -0,0 +1,120 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.27.1 +// source: groups/v1/groups.proto + +package v1 + +import ( + context "context" + v1 "github.com/absmach/magistrala/internal/grpc/common/v1" + 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.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + GroupsService_RetrieveEntity_FullMethodName = "/groups.v1.GroupsService/RetrieveEntity" +) + +// GroupsServiceClient is the client API for GroupsService 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. +// +// GroupssService is a service that provides groups functionalities +// for magistrala services. +type GroupsServiceClient interface { + RetrieveEntity(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption) (*v1.RetrieveEntityRes, error) +} + +type groupsServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewGroupsServiceClient(cc grpc.ClientConnInterface) GroupsServiceClient { + return &groupsServiceClient{cc} +} + +func (c *groupsServiceClient) RetrieveEntity(ctx context.Context, in *v1.RetrieveEntityReq, opts ...grpc.CallOption) (*v1.RetrieveEntityRes, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(v1.RetrieveEntityRes) + err := c.cc.Invoke(ctx, GroupsService_RetrieveEntity_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GroupsServiceServer is the server API for GroupsService service. +// All implementations must embed UnimplementedGroupsServiceServer +// for forward compatibility +// +// GroupssService is a service that provides groups functionalities +// for magistrala services. +type GroupsServiceServer interface { + RetrieveEntity(context.Context, *v1.RetrieveEntityReq) (*v1.RetrieveEntityRes, error) + mustEmbedUnimplementedGroupsServiceServer() +} + +// UnimplementedGroupsServiceServer must be embedded to have forward compatible implementations. +type UnimplementedGroupsServiceServer struct { +} + +func (UnimplementedGroupsServiceServer) RetrieveEntity(context.Context, *v1.RetrieveEntityReq) (*v1.RetrieveEntityRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method RetrieveEntity not implemented") +} +func (UnimplementedGroupsServiceServer) mustEmbedUnimplementedGroupsServiceServer() {} + +// UnsafeGroupsServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GroupsServiceServer will +// result in compilation errors. +type UnsafeGroupsServiceServer interface { + mustEmbedUnimplementedGroupsServiceServer() +} + +func RegisterGroupsServiceServer(s grpc.ServiceRegistrar, srv GroupsServiceServer) { + s.RegisterService(&GroupsService_ServiceDesc, srv) +} + +func _GroupsService_RetrieveEntity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(v1.RetrieveEntityReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupsServiceServer).RetrieveEntity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GroupsService_RetrieveEntity_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupsServiceServer).RetrieveEntity(ctx, req.(*v1.RetrieveEntityReq)) + } + return interceptor(ctx, in, info, handler) +} + +// GroupsService_ServiceDesc is the grpc.ServiceDesc for GroupsService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GroupsService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "groups.v1.GroupsService", + HandlerType: (*GroupsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RetrieveEntity", + Handler: _GroupsService_RetrieveEntity_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "groups/v1/groups.proto", +} diff --git a/internal/grpc/token/v1/token.pb.go b/internal/grpc/token/v1/token.pb.go new file mode 100644 index 0000000000..03175dda4c --- /dev/null +++ b/internal/grpc/token/v1/token.pb.go @@ -0,0 +1,320 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: token/v1/token.proto + +package v1 + +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 IssueReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *IssueReq) Reset() { + *x = IssueReq{} + if protoimpl.UnsafeEnabled { + mi := &file_token_v1_token_proto_msgTypes[0] + 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_token_v1_token_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 IssueReq.ProtoReflect.Descriptor instead. +func (*IssueReq) Descriptor() ([]byte, []int) { + return file_token_v1_token_proto_rawDescGZIP(), []int{0} +} + +func (x *IssueReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *IssueReq) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +type RefreshReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` +} + +func (x *RefreshReq) Reset() { + *x = RefreshReq{} + if protoimpl.UnsafeEnabled { + mi := &file_token_v1_token_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RefreshReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshReq) ProtoMessage() {} + +func (x *RefreshReq) ProtoReflect() protoreflect.Message { + mi := &file_token_v1_token_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 RefreshReq.ProtoReflect.Descriptor instead. +func (*RefreshReq) Descriptor() ([]byte, []int) { + return file_token_v1_token_proto_rawDescGZIP(), []int{1} +} + +func (x *RefreshReq) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +// If a token is not carrying any information itself, the type +// field can be used to determine how to validate the token. +// Also, different tokens can be encoded in different ways. +type Token struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AccessToken string `protobuf:"bytes,1,opt,name=accessToken,proto3" json:"accessToken,omitempty"` + RefreshToken *string `protobuf:"bytes,2,opt,name=refreshToken,proto3,oneof" json:"refreshToken,omitempty"` + AccessType string `protobuf:"bytes,3,opt,name=accessType,proto3" json:"accessType,omitempty"` +} + +func (x *Token) Reset() { + *x = Token{} + if protoimpl.UnsafeEnabled { + mi := &file_token_v1_token_proto_msgTypes[2] + 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_token_v1_token_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 Token.ProtoReflect.Descriptor instead. +func (*Token) Descriptor() ([]byte, []int) { + return file_token_v1_token_proto_rawDescGZIP(), []int{2} +} + +func (x *Token) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *Token) GetRefreshToken() string { + if x != nil && x.RefreshToken != nil { + return *x.RefreshToken + } + return "" +} + +func (x *Token) GetAccessType() string { + if x != nil { + return x.AccessType + } + return "" +} + +var File_token_v1_token_proto protoreflect.FileDescriptor + +var file_token_v1_token_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x76, 0x31, + 0x22, 0x37, 0x0a, 0x08, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x31, 0x0a, 0x0a, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x83, 0x01, 0x0a, + 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x27, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01, + 0x01, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, + 0x65, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x32, 0x72, 0x0a, 0x0c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, + 0x0f, 0x2e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0x00, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x14, 0x2e, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x52, 0x65, 0x71, 0x1a, 0x0f, 0x2e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x62, 0x73, 0x6d, 0x61, 0x63, 0x68, 0x2f, 0x6d, 0x61, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_token_v1_token_proto_rawDescOnce sync.Once + file_token_v1_token_proto_rawDescData = file_token_v1_token_proto_rawDesc +) + +func file_token_v1_token_proto_rawDescGZIP() []byte { + file_token_v1_token_proto_rawDescOnce.Do(func() { + file_token_v1_token_proto_rawDescData = protoimpl.X.CompressGZIP(file_token_v1_token_proto_rawDescData) + }) + return file_token_v1_token_proto_rawDescData +} + +var file_token_v1_token_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_token_v1_token_proto_goTypes = []any{ + (*IssueReq)(nil), // 0: token.v1.IssueReq + (*RefreshReq)(nil), // 1: token.v1.RefreshReq + (*Token)(nil), // 2: token.v1.Token +} +var file_token_v1_token_proto_depIdxs = []int32{ + 0, // 0: token.v1.TokenService.Issue:input_type -> token.v1.IssueReq + 1, // 1: token.v1.TokenService.Refresh:input_type -> token.v1.RefreshReq + 2, // 2: token.v1.TokenService.Issue:output_type -> token.v1.Token + 2, // 3: token.v1.TokenService.Refresh:output_type -> token.v1.Token + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] 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_token_v1_token_proto_init() } +func file_token_v1_token_proto_init() { + if File_token_v1_token_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_token_v1_token_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*IssueReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_token_v1_token_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*RefreshReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_token_v1_token_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*Token); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_token_v1_token_proto_msgTypes[2].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_token_v1_token_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_token_v1_token_proto_goTypes, + DependencyIndexes: file_token_v1_token_proto_depIdxs, + MessageInfos: file_token_v1_token_proto_msgTypes, + }.Build() + File_token_v1_token_proto = out.File + file_token_v1_token_proto_rawDesc = nil + file_token_v1_token_proto_goTypes = nil + file_token_v1_token_proto_depIdxs = nil +} diff --git a/internal/grpc/token/v1/token_grpc.pb.go b/internal/grpc/token/v1/token_grpc.pb.go new file mode 100644 index 0000000000..bd0768a51e --- /dev/null +++ b/internal/grpc/token/v1/token_grpc.pb.go @@ -0,0 +1,151 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.27.1 +// source: token/v1/token.proto + +package v1 + +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.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + TokenService_Issue_FullMethodName = "/token.v1.TokenService/Issue" + TokenService_Refresh_FullMethodName = "/token.v1.TokenService/Refresh" +) + +// TokenServiceClient is the client API for TokenService 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 TokenServiceClient interface { + Issue(ctx context.Context, in *IssueReq, opts ...grpc.CallOption) (*Token, error) + Refresh(ctx context.Context, in *RefreshReq, opts ...grpc.CallOption) (*Token, error) +} + +type tokenServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTokenServiceClient(cc grpc.ClientConnInterface) TokenServiceClient { + return &tokenServiceClient{cc} +} + +func (c *tokenServiceClient) Issue(ctx context.Context, in *IssueReq, opts ...grpc.CallOption) (*Token, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Token) + err := c.cc.Invoke(ctx, TokenService_Issue_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tokenServiceClient) Refresh(ctx context.Context, in *RefreshReq, opts ...grpc.CallOption) (*Token, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Token) + err := c.cc.Invoke(ctx, TokenService_Refresh_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TokenServiceServer is the server API for TokenService service. +// All implementations must embed UnimplementedTokenServiceServer +// for forward compatibility +type TokenServiceServer interface { + Issue(context.Context, *IssueReq) (*Token, error) + Refresh(context.Context, *RefreshReq) (*Token, error) + mustEmbedUnimplementedTokenServiceServer() +} + +// UnimplementedTokenServiceServer must be embedded to have forward compatible implementations. +type UnimplementedTokenServiceServer struct { +} + +func (UnimplementedTokenServiceServer) Issue(context.Context, *IssueReq) (*Token, error) { + return nil, status.Errorf(codes.Unimplemented, "method Issue not implemented") +} +func (UnimplementedTokenServiceServer) Refresh(context.Context, *RefreshReq) (*Token, error) { + return nil, status.Errorf(codes.Unimplemented, "method Refresh not implemented") +} +func (UnimplementedTokenServiceServer) mustEmbedUnimplementedTokenServiceServer() {} + +// UnsafeTokenServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TokenServiceServer will +// result in compilation errors. +type UnsafeTokenServiceServer interface { + mustEmbedUnimplementedTokenServiceServer() +} + +func RegisterTokenServiceServer(s grpc.ServiceRegistrar, srv TokenServiceServer) { + s.RegisterService(&TokenService_ServiceDesc, srv) +} + +func _TokenService_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.(TokenServiceServer).Issue(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TokenService_Issue_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TokenServiceServer).Issue(ctx, req.(*IssueReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _TokenService_Refresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RefreshReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TokenServiceServer).Refresh(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TokenService_Refresh_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TokenServiceServer).Refresh(ctx, req.(*RefreshReq)) + } + return interceptor(ctx, in, info, handler) +} + +// TokenService_ServiceDesc is the grpc.ServiceDesc for TokenService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TokenService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "token.v1.TokenService", + HandlerType: (*TokenServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Issue", + Handler: _TokenService_Issue_Handler, + }, + { + MethodName: "Refresh", + Handler: _TokenService_Refresh_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "token/v1/token.proto", +} diff --git a/internal/proto/auth/v1/auth.proto b/internal/proto/auth/v1/auth.proto new file mode 100644 index 0000000000..cd24f57dc0 --- /dev/null +++ b/internal/proto/auth/v1/auth.proto @@ -0,0 +1,42 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package auth.v1; +option go_package = "github.com/absmach/magistrala/internal/grpc/auth/v1"; + +// AuthService is a service that provides authentication and authorization +// functionalities for magistrala services. +service AuthService { + rpc Authorize(AuthZReq) returns (AuthZRes) {} + rpc Authenticate(AuthNReq) returns (AuthNRes) {} +} + + +message AuthNReq { + string token = 1; +} + +message AuthNRes { + string id = 1; // IMPROVEMENT NOTE: change name from "id" to "subject" , sub in jwt = user id + domain id // + string user_id = 2; // user id + string domain_id = 3; // domain id +} + +message AuthZReq { + string domain = 1; // Domain + string subject_type = 2; // Client or User + string subject_kind = 3; // ID or Token + string subject_relation = 4; // Subject relation + string subject = 5; // Subject value (id or token, depending on kind) + string relation = 6; // Relation to filter + string permission = 7; // Action + string object = 8; // Object ID + string object_type = 9; // Client, User, Group +} + +message AuthZRes { + bool authorized = 1; + string id = 2; +} diff --git a/internal/proto/channels/v1/channels.proto b/internal/proto/channels/v1/channels.proto new file mode 100644 index 0000000000..7e444f5205 --- /dev/null +++ b/internal/proto/channels/v1/channels.proto @@ -0,0 +1,38 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package channels.v1; +option go_package = "github.com/absmach/magistrala/internal/grpc/channels/v1"; + + +service ChannelsService { + rpc Authorize(AuthzReq)returns(AuthzRes) {} + rpc RemoveClientConnections(RemoveClientConnectionsReq)returns(RemoveClientConnectionsRes) {} + rpc UnsetParentGroupFromChannels(UnsetParentGroupFromChannelsReq)returns(UnsetParentGroupFromChannelsRes){} +} + +message RemoveClientConnectionsReq { + string client_id = 1; +} + +message RemoveClientConnectionsRes {} + +message UnsetParentGroupFromChannelsReq { + string parent_group_id = 1; +} + +message UnsetParentGroupFromChannelsRes {} + +message AuthzReq { + string domain_id = 1; + string client_id = 2; + string client_type = 3; + string channel_id = 4; + uint32 type = 5; +} + +message AuthzRes { + bool authorized = 1; +} diff --git a/internal/proto/clients/v1/clients.proto b/internal/proto/clients/v1/clients.proto new file mode 100644 index 0000000000..8474e06eea --- /dev/null +++ b/internal/proto/clients/v1/clients.proto @@ -0,0 +1,45 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package clients.v1; +option go_package = "github.com/absmach/magistrala/internal/grpc/clients/v1"; + +import "common/v1/common.proto"; + +// ClientsService is a service that provides clients authorization functionalities +// for magistrala services. +service ClientsService { + // Authorize checks if the client is authorized to perform + rpc Authenticate(AuthnReq) returns (AuthnRes) {} + rpc RetrieveEntity(common.v1.RetrieveEntityReq) returns (common.v1.RetrieveEntityRes) {} + rpc RetrieveEntities(common.v1.RetrieveEntitiesReq) returns (common.v1.RetrieveEntitiesRes) {} + rpc AddConnections(common.v1.AddConnectionsReq)returns(common.v1.AddConnectionsRes) {} + rpc RemoveConnections(common.v1.RemoveConnectionsReq)returns(common.v1.RemoveConnectionsRes) {} + rpc RemoveChannelConnections(RemoveChannelConnectionsReq)returns(RemoveChannelConnectionsRes) {} + rpc UnsetParentGroupFromClient(UnsetParentGroupFromClientReq)returns(UnsetParentGroupFromClientRes){} +} + + +message AuthnReq { + string client_id = 1; + string client_secret = 2; +} + +message AuthnRes { + bool authenticated = 1; + string id = 2; +} + +message RemoveChannelConnectionsReq { + string channel_id = 1; +} + +message RemoveChannelConnectionsRes {} + +message UnsetParentGroupFromClientReq { + string parent_group_id = 1; +} + +message UnsetParentGroupFromClientRes {} diff --git a/internal/proto/common/v1/common.proto b/internal/proto/common/v1/common.proto new file mode 100644 index 0000000000..3660070c5e --- /dev/null +++ b/internal/proto/common/v1/common.proto @@ -0,0 +1,56 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package common.v1; +option go_package = "github.com/absmach/magistrala/internal/grpc/common/v1"; + + +message RetrieveEntitiesReq { + repeated string ids = 1; +} + +message RetrieveEntitiesRes { + uint64 total = 1; + uint64 limit = 2; + uint64 offset =3; + repeated EntityBasic entities = 4; +} + +message RetrieveEntityReq{ + string id = 1; +} + +message RetrieveEntityRes { + EntityBasic entity = 1; +} + +message EntityBasic { + string id = 1; + string domain_id = 2; + uint32 status = 3; +} + +message AddConnectionsReq { + repeated Connection connections = 1; +} + +message AddConnectionsRes { + bool ok = 1; +} + +message RemoveConnectionsReq { + repeated Connection connections = 1; +} + +message RemoveConnectionsRes { + bool ok = 1; +} + +message Connection { + string client_id = 1; + string channel_id = 2; + string domain_id = 3; + uint32 type = 4; +} diff --git a/internal/proto/domains/v1/domains.proto b/internal/proto/domains/v1/domains.proto new file mode 100644 index 0000000000..716bfd2849 --- /dev/null +++ b/internal/proto/domains/v1/domains.proto @@ -0,0 +1,20 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package domains.v1; +option go_package = "github.com/absmach/magistrala/internal/grpc/domains/v1"; + + +// DomainsService is a service that provides access to domains +// functionalities for magistrala services. +service DomainsService { + rpc DeleteUserFromDomains(DeleteUserReq) returns (DeleteUserRes) {} +} + +message DeleteUserRes { bool deleted = 1; } + +message DeleteUserReq{ + string id = 1; +} diff --git a/internal/proto/groups/v1/groups.proto b/internal/proto/groups/v1/groups.proto new file mode 100644 index 0000000000..d703de76d1 --- /dev/null +++ b/internal/proto/groups/v1/groups.proto @@ -0,0 +1,15 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package groups.v1; +option go_package = "github.com/absmach/magistrala/internal/grpc/groups/v1"; + +import "common/v1/common.proto"; + +// GroupssService is a service that provides groups functionalities +// for magistrala services. +service GroupsService { + rpc RetrieveEntity(common.v1.RetrieveEntityReq) returns (common.v1.RetrieveEntityRes) {} +} diff --git a/internal/proto/token/v1/token.proto b/internal/proto/token/v1/token.proto new file mode 100644 index 0000000000..d1fbcc9fa2 --- /dev/null +++ b/internal/proto/token/v1/token.proto @@ -0,0 +1,30 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package token.v1; +option go_package = "github.com/absmach/magistrala/internal/grpc/token/v1"; + +service TokenService { + rpc Issue(IssueReq) returns (Token) {} + rpc Refresh(RefreshReq) returns (Token) {} +} + +message IssueReq { + string user_id = 1; + uint32 type = 3; +} + +message RefreshReq { + string refresh_token = 1; +} + +// If a token is not carrying any information itself, the type +// field can be used to determine how to validate the token. +// Also, different tokens can be encoded in different ways. +message Token { + string accessToken = 1; + optional string refreshToken = 2; + string accessType = 3; +} diff --git a/invitations/service.go b/invitations/service.go index 5b81d7ea68..89c4e00836 100644 --- a/invitations/service.go +++ b/invitations/service.go @@ -7,20 +7,20 @@ import ( "context" "time" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/authn" svcerr "github.com/absmach/magistrala/pkg/errors/service" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" ) type service struct { - token magistrala.TokenServiceClient + token grpcTokenV1.TokenServiceClient repo Repository sdk mgsdk.SDK } -func NewService(token magistrala.TokenServiceClient, repo Repository, sdk mgsdk.SDK) Service { +func NewService(token grpcTokenV1.TokenServiceClient, repo Repository, sdk mgsdk.SDK) Service { return &service{ token: token, repo: repo, @@ -35,7 +35,7 @@ func (svc *service) SendInvitation(ctx context.Context, session authn.Session, i invitation.InvitedBy = session.UserID - joinToken, err := svc.token.Issue(ctx, &magistrala.IssueReq{UserId: session.UserID, Type: uint32(auth.InvitationKey)}) + joinToken, err := svc.token.Issue(ctx, &grpcTokenV1.IssueReq{UserId: session.UserID, Type: uint32(auth.InvitationKey)}) if err != nil { return err } diff --git a/invitations/service_test.go b/invitations/service_test.go index 92538652c3..6c6104b548 100644 --- a/invitations/service_test.go +++ b/invitations/service_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/absmach/magistrala" authmocks "github.com/absmach/magistrala/auth/mocks" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/invitations" "github.com/absmach/magistrala/invitations/mocks" @@ -108,17 +108,15 @@ func TestSendInvitation(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall1 := token.On("Issue", context.Background(), mock.Anything).Return(&magistrala.Token{AccessToken: tc.req.Token}, tc.issueErr) - repocall2 := repo.On("Create", context.Background(), mock.Anything).Return(tc.repoErr) - if tc.req.Resend { - repocall2 = repo.On("UpdateToken", context.Background(), mock.Anything).Return(tc.repoErr) - } - err := svc.SendInvitation(context.Background(), tc.session, tc.req) - assert.Equal(t, tc.err, err, tc.desc) - repocall1.Unset() - repocall2.Unset() - }) + repocall1 := token.On("Issue", context.Background(), mock.Anything).Return(&grpcTokenV1.Token{AccessToken: tc.req.Token}, tc.issueErr) + repocall2 := repo.On("Create", context.Background(), mock.Anything).Return(tc.repoErr) + if tc.req.Resend { + repocall2 = repo.On("UpdateToken", context.Background(), mock.Anything).Return(tc.repoErr) + } + err := svc.SendInvitation(context.Background(), tc.session, tc.req) + assert.Equal(t, tc.err, err, tc.desc) + repocall1.Unset() + repocall2.Unset() } } diff --git a/journal/journal.go b/journal/journal.go index cc8d946601..92c7a822ad 100644 --- a/journal/journal.go +++ b/journal/journal.go @@ -18,7 +18,7 @@ type EntityType uint8 const ( UserEntity EntityType = iota GroupEntity - ThingEntity + ClientEntity ChannelEntity ) @@ -26,7 +26,7 @@ const ( const ( userEntityType = "user" groupEntityType = "group" - thingEntityType = "thing" + clientEntityType = "client" channelEntityType = "channel" ) @@ -37,8 +37,8 @@ func (e EntityType) String() string { return userEntityType case GroupEntity: return groupEntityType - case ThingEntity: - return thingEntityType + case ClientEntity: + return clientEntityType case ChannelEntity: return channelEntityType default: @@ -53,8 +53,8 @@ func (e EntityType) AuthString() string { return policies.UserType case GroupEntity, ChannelEntity: return policies.GroupType - case ThingEntity: - return policies.ThingType + case ClientEntity: + return policies.ClientType default: return "" } @@ -67,8 +67,8 @@ func ToEntityType(entityType string) (EntityType, error) { return UserEntity, nil case groupEntityType: return GroupEntity, nil - case thingEntityType: - return ThingEntity, nil + case clientEntityType: + return ClientEntity, nil case channelEntityType: return ChannelEntity, nil default: @@ -83,8 +83,8 @@ func (e EntityType) Query() string { return "((operation LIKE 'user.%' AND attributes->>'id' = :entity_id) OR (attributes->>'user_id' = :entity_id))" case GroupEntity, ChannelEntity: return "((operation LIKE 'group.%' AND attributes->>'id' = :entity_id) OR (attributes->>'group_id' = :entity_id))" - case ThingEntity: - return "((operation LIKE 'thing.%' AND attributes->>'id' = :entity_id) OR (attributes->>'thing_id' = :entity_id))" + case ClientEntity: + return "((operation LIKE 'client.%' AND attributes->>'id' = :entity_id) OR (attributes->>'client_id' = :entity_id))" default: return "" } @@ -95,7 +95,7 @@ type Journal struct { ID string `json:"id,omitempty" db:"id"` Operation string `json:"operation,omitempty" db:"operation,omitempty"` OccurredAt time.Time `json:"occurred_at,omitempty" db:"occurred_at,omitempty"` - Attributes map[string]interface{} `json:"attributes,omitempty" db:"attributes,omitempty"` // This is extra information about the journal for example thing_id, user_id, group_id etc. + Attributes map[string]interface{} `json:"attributes,omitempty" db:"attributes,omitempty"` // This is extra information about the journal for example client_id, user_id, group_id etc. Metadata map[string]interface{} `json:"metadata,omitempty" db:"metadata,omitempty"` // This is decoded metadata from the journal. } diff --git a/journal/journal_test.go b/journal/journal_test.go index 0772ed0091..27c47e8fa6 100644 --- a/journal/journal_test.go +++ b/journal/journal_test.go @@ -71,10 +71,10 @@ func TestEntityType(t *testing.T) { authString: "user", }, { - desc: "ThingEntity", - e: journal.ThingEntity, - str: "thing", - authString: "thing", + desc: "ClientEntity", + e: journal.ClientEntity, + str: "client", + authString: "client", }, { desc: "GroupEntity", @@ -112,9 +112,9 @@ func TestToEntityType(t *testing.T) { expected: journal.UserEntity, }, { - desc: "ThingEntity", - entityType: "thing", - expected: journal.ThingEntity, + desc: "ClientEntity", + entityType: "client", + expected: journal.ClientEntity, }, { desc: "GroupEntity", diff --git a/journal/postgres/init.go b/journal/postgres/init.go index adad7979c4..a44a4a8865 100644 --- a/journal/postgres/init.go +++ b/journal/postgres/init.go @@ -24,7 +24,7 @@ func Migration() *migrate.MemoryMigrationSource { )`, `CREATE INDEX idx_journal_default_user_filter ON journal(operation, (attributes->>'id'), (attributes->>'user_id'), occurred_at DESC);`, `CREATE INDEX idx_journal_default_group_filter ON journal(operation, (attributes->>'id'), (attributes->>'group_id'), occurred_at DESC);`, - `CREATE INDEX idx_journal_default_thing_filter ON journal(operation, (attributes->>'id'), (attributes->>'thing_id'), occurred_at DESC);`, + `CREATE INDEX idx_journal_default_client_filter ON journal(operation, (attributes->>'id'), (attributes->>'client_id'), occurred_at DESC);`, `CREATE INDEX idx_journal_default_channel_filter ON journal(operation, (attributes->>'id'), (attributes->>'channel_id'), occurred_at DESC);`, }, Down: []string{ diff --git a/journal/postgres/journal_test.go b/journal/postgres/journal_test.go index 3b58b26b91..754bc28749 100644 --- a/journal/postgres/journal_test.go +++ b/journal/postgres/journal_test.go @@ -43,20 +43,20 @@ var ( } entityID = testsutil.GenerateUUID(&testing.T{}) - thingOperation = "thing.create" - thingAttributesV1 = map[string]interface{}{ + clientOperation = "client.create" + clientAttributesV1 = map[string]interface{}{ "id": entityID, "status": "enabled", "created_at": time.Now().Add(-time.Hour), - "name": "thing", + "name": "client", "tags": []interface{}{"tag1", "tag2"}, "domain": testsutil.GenerateUUID(&testing.T{}), "metadata": payload, "identity": testsutil.GenerateUUID(&testing.T{}), } - thingAttributesV2 = map[string]interface{}{ - "thing_id": entityID, - "metadata": payload, + clientAttributesV2 = map[string]interface{}{ + "client_id": entityID, + "metadata": payload, } userAttributesV1 = map[string]interface{}{ "id": entityID, @@ -300,14 +300,14 @@ func TestJournalRetrieveAll(t *testing.T) { Metadata: payload, } if i%2 == 0 { - j.Operation = fmt.Sprintf("%s-%d", thingOperation, i) - j.Attributes = thingAttributesV1 + j.Operation = fmt.Sprintf("%s-%d", clientOperation, i) + j.Attributes = clientAttributesV1 } if i%3 == 0 { j.Attributes = userAttributesV2 } if i%5 == 0 { - j.Attributes = thingAttributesV2 + j.Attributes = clientAttributesV2 } err := repo.Save(context.Background(), j) require.Nil(t, err, fmt.Sprintf("create journal unexpected error: %s", err)) @@ -630,18 +630,18 @@ func TestJournalRetrieveAll(t *testing.T) { }, }, { - desc: "with thing entity type", + desc: "with client entity type", page: journal.Page{ Offset: 0, Limit: 10, EntityID: entityID, - EntityType: journal.ThingEntity, + EntityType: journal.ClientEntity, }, response: journal.JournalsPage{ - Total: uint64(len(extractEntities(items, journal.ThingEntity, entityID))), + Total: uint64(len(extractEntities(items, journal.ClientEntity, entityID))), Offset: 0, Limit: 10, - Journals: extractEntities(items, journal.ThingEntity, entityID)[:10], + Journals: extractEntities(items, journal.ClientEntity, entityID)[:10], }, }, { @@ -712,8 +712,8 @@ func extractEntities(journals []journal.Journal, entityType journal.EntityType, if strings.HasPrefix(j.Operation, "group.") && j.Attributes["id"] == entityID || j.Attributes["group_id"] == entityID { entities = append(entities, j) } - case journal.ThingEntity: - if strings.HasPrefix(j.Operation, "thing.") && j.Attributes["id"] == entityID || j.Attributes["thing_id"] == entityID { + case journal.ClientEntity: + if strings.HasPrefix(j.Operation, "client.") && j.Attributes["id"] == entityID || j.Attributes["client_id"] == entityID { entities = append(entities, j) } case journal.ChannelEntity: diff --git a/journal/service_test.go b/journal/service_test.go index 594a4b5485..d15927f1ce 100644 --- a/journal/service_test.go +++ b/journal/service_test.go @@ -78,7 +78,7 @@ func TestReadAll(t *testing.T) { Offset: 0, Limit: 10, EntityID: testsutil.GenerateUUID(t), - EntityType: journal.ThingEntity, + EntityType: journal.ClientEntity, } cases := []struct { diff --git a/mqtt/README.md b/mqtt/README.md index 49a66d837a..48bbeb1571 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -6,37 +6,37 @@ MQTT adapter provides an MQTT API for sending messages through the platform. MQT The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ---------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------- | -| MG_MQTT_ADAPTER_LOG_LEVEL | Log level for the MQTT Adapter (debug, info, warn, error) | info | -| MG_MQTT_ADAPTER_MQTT_PORT | mProxy port | 1883 | -| MG_MQTT_ADAPTER_MQTT_TARGET_HOST | MQTT broker host | localhost | -| MG_MQTT_ADAPTER_MQTT_TARGET_PORT | MQTT broker port | 1883 | -| MG_MQTT_ADAPTER_MQTT_QOS | MQTT broker QoS | 1 | -| MG_MQTT_ADAPTER_FORWARDER_TIMEOUT | MQTT forwarder for multiprotocol communication timeout | 30s | -| MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK | URL of broker health check | "" | -| MG_MQTT_ADAPTER_WS_PORT | mProxy MQTT over WS port | 8080 | -| MG_MQTT_ADAPTER_WS_TARGET_HOST | MQTT broker host for MQTT over WS | localhost | -| MG_MQTT_ADAPTER_WS_TARGET_PORT | MQTT broker port for MQTT over WS | 8080 | -| MG_MQTT_ADAPTER_WS_TARGET_PATH | MQTT broker MQTT over WS path | /mqtt | -| MG_MQTT_ADAPTER_INSTANCE | Instance name for MQTT adapter | "" | -| MG_THINGS_AUTH_GRPC_URL | Things service Auth gRPC URL | | -| MG_THINGS_AUTH_GRPC_TIMEOUT | Things service Auth gRPC request timeout in seconds | 1s | -| MG_THINGS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded things service Auth gRPC client certificate file | "" | -| MG_THINGS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded things service Auth gRPC client key file | "" | -| MG_THINGS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded things server Auth gRPC server trusted CA certificate file | "" | -| MG_ES_URL | Event sourcing URL | | -| MG_MESSAGE_BROKER_URL | Message broker instance URL | | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_MQTT_ADAPTER_INSTANCE_ID | Service instance ID | "" | +| Variable | Description | Default | +| ---------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | +| MG_MQTT_ADAPTER_LOG_LEVEL | Log level for the MQTT Adapter (debug, info, warn, error) | info | +| MG_MQTT_ADAPTER_MQTT_PORT | mProxy port | 1883 | +| MG_MQTT_ADAPTER_MQTT_TARGET_HOST | MQTT broker host | localhost | +| MG_MQTT_ADAPTER_MQTT_TARGET_PORT | MQTT broker port | 1883 | +| MG_MQTT_ADAPTER_MQTT_QOS | MQTT broker QoS | 1 | +| MG_MQTT_ADAPTER_FORWARDER_TIMEOUT | MQTT forwarder for multiprotocol communication timeout | 30s | +| MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK | URL of broker health check | "" | +| MG_MQTT_ADAPTER_WS_PORT | mProxy MQTT over WS port | 8080 | +| MG_MQTT_ADAPTER_WS_TARGET_HOST | MQTT broker host for MQTT over WS | localhost | +| MG_MQTT_ADAPTER_WS_TARGET_PORT | MQTT broker port for MQTT over WS | 8080 | +| MG_MQTT_ADAPTER_WS_TARGET_PATH | MQTT broker MQTT over WS path | /mqtt | +| MG_MQTT_ADAPTER_INSTANCE | Instance name for MQTT adapter | "" | +| MG_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| MG_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| MG_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | +| MG_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | +| MG_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | +| MG_ES_URL | Event sourcing URL | | +| MG_MESSAGE_BROKER_URL | Message broker instance URL | | +| MG_JAEGER_URL | Jaeger server URL | | +| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_MQTT_ADAPTER_INSTANCE_ID | Service instance ID | "" | ## Deployment The service itself is distributed as Docker container. Check the [`mqtt-adapter`](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yml) service section in docker-compose file to see how service is deployed. -Running this service outside of container requires working instance of the message broker service, things service and Jaeger server. +Running this service outside of container requires working instance of the message broker service, clients service and Jaeger server. To start the service outside of the container, execute the following shell script: ```bash @@ -64,11 +64,11 @@ MG_MQTT_ADAPTER_WS_TARGET_HOST=localhost \ MG_MQTT_ADAPTER_WS_TARGET_PORT=8080 \ MG_MQTT_ADAPTER_WS_TARGET_PATH=/mqtt \ MG_MQTT_ADAPTER_INSTANCE="" \ -MG_THINGS_AUTH_GRPC_URL=localhost:7000 \ -MG_THINGS_AUTH_GRPC_TIMEOUT=1s \ -MG_THINGS_AUTH_GRPC_CLIENT_CERT="" \ -MG_THINGS_AUTH_GRPC_CLIENT_KEY="" \ -MG_THINGS_AUTH_GRPC_SERVER_CERTS="" \ +MG_CLIENTS_AUTH_GRPC_URL=localhost:7000 \ +MG_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ +MG_CLIENTS_AUTH_GRPC_CLIENT_CERT="" \ +MG_CLIENTS_AUTH_GRPC_CLIENT_KEY="" \ +MG_CLIENTS_AUTH_GRPC_SERVER_CERTS="" \ MG_ES_URL=nats://localhost:4222 \ MG_MESSAGE_BROKER_URL=nats://localhost:4222 \ MG_JAEGER_URL=http://localhost:14268/api/traces \ @@ -78,6 +78,6 @@ MG_MQTT_ADAPTER_INSTANCE_ID="" \ $GOBIN/magistrala-mqtt ``` -Setting `MG_THINGS_AUTH_GRPC_CLIENT_CERT` and `MG_THINGS_AUTH_GRPC_CLIENT_KEY` will enable TLS against the things service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_THINGS_AUTH_GRPC_SERVER_CERTS` will enable TLS against the things service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. +Setting `MG_CLIENTS_AUTH_GRPC_CLIENT_CERT` and `MG_CLIENTS_AUTH_GRPC_CLIENT_KEY` will enable TLS against the clients service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_CLIENTS_AUTH_GRPC_SERVER_CERTS` will enable TLS against the clients service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. For more information about service capabilities and its usage, please check out the API documentation [API](https://github.com/absmach/magistrala/blob/main/api/asyncapi/mqtt.yml). diff --git a/mqtt/events/events.go b/mqtt/events/events.go index 9ae960bed1..d86fefb247 100644 --- a/mqtt/events/events.go +++ b/mqtt/events/events.go @@ -15,7 +15,7 @@ type mqttEvent struct { func (me mqttEvent) Encode() (map[string]interface{}, error) { return map[string]interface{}{ - "thing_id": me.clientID, + "client_id": me.clientID, "operation": me.operation, "instance": me.instance, }, nil diff --git a/mqtt/handler.go b/mqtt/handler.go index e3999fbbd2..72cc84659c 100644 --- a/mqtt/handler.go +++ b/mqtt/handler.go @@ -12,8 +12,10 @@ import ( "strings" "time" - "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" "github.com/absmach/magistrala/mqtt/events" + "github.com/absmach/magistrala/pkg/connections" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" @@ -53,23 +55,28 @@ var ( ErrFailedPublishToMsgBroker = errors.New("failed to publish to magistrala message broker") ) -var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?]*)?(\?.*)?$`) +var ( + errInvalidUserId = errors.New("invalid user id") + channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?]*)?(\?.*)?$`) +) // Event implements events.Event interface. type handler struct { publisher messaging.Publisher - things magistrala.ThingsServiceClient + clients grpcClientsV1.ClientsServiceClient + channels grpcChannelsV1.ChannelsServiceClient logger *slog.Logger es events.EventStore } // NewHandler creates new Handler entity. -func NewHandler(publisher messaging.Publisher, es events.EventStore, logger *slog.Logger, thingsClient magistrala.ThingsServiceClient) session.Handler { +func NewHandler(publisher messaging.Publisher, es events.EventStore, logger *slog.Logger, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) session.Handler { return &handler{ es: es, logger: logger, publisher: publisher, - things: thingsClient, + clients: clients, + channels: channels, } } @@ -87,6 +94,18 @@ func (h *handler) AuthConnect(ctx context.Context) error { pwd := string(s.Password) + res, err := h.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ClientSecret: pwd}) + if err != nil { + return errors.Wrap(svcerr.ErrAuthentication, err) + } + if !res.GetAuthenticated() { + return svcerr.ErrAuthentication + } + + if s.Username != "" && res.GetId() != s.Username { + return errInvalidUserId + } + if err := h.es.Connect(ctx, pwd); err != nil { h.logger.Error(errors.Wrap(ErrFailedPublishConnectEvent, err).Error()) } @@ -105,7 +124,7 @@ func (h *handler) AuthPublish(ctx context.Context, topic *string, payload *[]byt return ErrClientNotInitialized } - return h.authAccess(ctx, string(s.Password), *topic, policies.PublishPermission) + return h.authAccess(ctx, string(s.Username), *topic, connections.Publish) } // AuthSubscribe is called on device subscribe, @@ -119,8 +138,8 @@ func (h *handler) AuthSubscribe(ctx context.Context, topics *[]string) error { return ErrMissingTopicSub } - for _, v := range *topics { - if err := h.authAccess(ctx, string(s.Password), v, policies.SubscribePermission); err != nil { + for _, topic := range *topics { + if err := h.authAccess(ctx, string(s.Username), topic, connections.Subscribe); err != nil { return err } } @@ -210,7 +229,7 @@ func (h *handler) Disconnect(ctx context.Context) error { return nil } -func (h *handler) authAccess(ctx context.Context, password, topic, action string) error { +func (h *handler) authAccess(ctx context.Context, clientID, topic string, msgType connections.ConnType) error { // Topics are in the format: // channels//messages//.../ct/ if !channelRegExp.MatchString(topic) { @@ -224,12 +243,13 @@ func (h *handler) authAccess(ctx context.Context, password, topic, action string chanID := channelParts[1] - ar := &magistrala.ThingsAuthzReq{ - Permission: action, - ThingKey: password, + ar := &grpcChannelsV1.AuthzReq{ + Type: uint32(msgType), + ClientId: clientID, + ClientType: policies.ClientType, ChannelId: chanID, } - res, err := h.things.Authorize(ctx, ar) + res, err := h.channels.Authorize(ctx, ar) if err != nil { return err } diff --git a/mqtt/handler_test.go b/mqtt/handler_test.go index 8f0ff9543e..8446d478ab 100644 --- a/mqtt/handler_test.go +++ b/mqtt/handler_test.go @@ -10,22 +10,19 @@ import ( "log" "testing" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/testsutil" + chmocks "github.com/absmach/magistrala/channels/mocks" + climocks "github.com/absmach/magistrala/clients/mocks" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/mqtt" "github.com/absmach/magistrala/mqtt/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - thmocks "github.com/absmach/magistrala/things/mocks" "github.com/absmach/mgate/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) const ( - thingID = "513d02d2-16c1-4f23-98be-9e12f8fee898" - thingID1 = "513d02d2-16c1-4f23-98be-9e12f8fee899" password = "password" password1 = "password1" chanID = "123e4567-e89b-12d3-a456-000000000001" @@ -49,15 +46,15 @@ var ( logBuffer = bytes.Buffer{} sessionClient = session.Session{ ID: clientID, - Username: thingID, + Username: clientID, Password: []byte(password), } sessionClientSub = session.Session{ ID: clientID1, - Username: thingID1, + Username: clientID1, Password: []byte(password1), } - invalidThingSessionClient = session.Session{ + invalidClientSessionClient = session.Session{ ID: clientID, Username: invalidID, Password: []byte(password), @@ -65,7 +62,7 @@ var ( ) func TestAuthConnect(t *testing.T) { - handler, _, eventStore := newHandler() + handler, _, _, eventStore := newHandler() cases := []struct { desc string @@ -82,7 +79,7 @@ func TestAuthConnect(t *testing.T) { err: mqtt.ErrMissingClientID, session: &session.Session{ ID: "", - Username: thingID, + Username: clientID, Password: []byte(password), }, }, @@ -91,14 +88,14 @@ func TestAuthConnect(t *testing.T) { err: nil, session: &session.Session{ ID: clientID, - Username: thingID, + Username: clientID, Password: []byte(""), }, }, { desc: "connect with valid password and invalid username", err: nil, - session: &invalidThingSessionClient, + session: &invalidClientSessionClient, }, { desc: "connect with valid username and password", @@ -121,7 +118,7 @@ func TestAuthConnect(t *testing.T) { } func TestAuthPublish(t *testing.T) { - handler, things, _ := newHandler() + handler, _, _, _ := newHandler() cases := []struct { desc string @@ -161,19 +158,17 @@ func TestAuthPublish(t *testing.T) { } for _, tc := range cases { - repocall := things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.ThingsAuthzRes{Authorized: true, Id: testsutil.GenerateUUID(t)}, tc.err) ctx := context.TODO() if tc.session != nil { ctx = session.NewContext(ctx, tc.session) } err := handler.AuthPublish(ctx, tc.topic, &tc.payload) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repocall.Unset() } } func TestAuthSubscribe(t *testing.T) { - handler, things, _ := newHandler() + handler, _, _, _ := newHandler() cases := []struct { desc string @@ -214,19 +209,17 @@ func TestAuthSubscribe(t *testing.T) { } for _, tc := range cases { - repocall := things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.ThingsAuthzRes{Authorized: true, Id: testsutil.GenerateUUID(t)}, tc.err) ctx := context.TODO() if tc.session != nil { ctx = session.NewContext(ctx, tc.session) } err := handler.AuthSubscribe(ctx, tc.topic) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repocall.Unset() } } func TestConnect(t *testing.T) { - handler, _, _ := newHandler() + handler, _, _, _ := newHandler() logBuffer.Reset() cases := []struct { @@ -260,7 +253,7 @@ func TestConnect(t *testing.T) { } func TestPublish(t *testing.T) { - handler, _, _ := newHandler() + handler, _, _, _ := newHandler() logBuffer.Reset() malformedSubtopics := topic + "/" + subtopic + "%" @@ -339,7 +332,7 @@ func TestPublish(t *testing.T) { } func TestSubscribe(t *testing.T) { - handler, _, _ := newHandler() + handler, _, _, _ := newHandler() logBuffer.Reset() cases := []struct { @@ -375,7 +368,7 @@ func TestSubscribe(t *testing.T) { } func TestUnsubscribe(t *testing.T) { - handler, _, _ := newHandler() + handler, _, _, _ := newHandler() logBuffer.Reset() cases := []struct { @@ -411,7 +404,7 @@ func TestUnsubscribe(t *testing.T) { } func TestDisconnect(t *testing.T) { - handler, _, eventStore := newHandler() + handler, _, _, eventStore := newHandler() logBuffer.Reset() cases := []struct { @@ -450,12 +443,13 @@ func TestDisconnect(t *testing.T) { } } -func newHandler() (session.Handler, *thmocks.ThingsServiceClient, *mocks.EventStore) { +func newHandler() (session.Handler, *climocks.ClientsServiceClient, *chmocks.ChannelsServiceClient, *mocks.EventStore) { logger, err := mglog.New(&logBuffer, "debug") if err != nil { log.Fatalf("failed to create logger: %s", err) } - things := new(thmocks.ThingsServiceClient) + clients := new(climocks.ClientsServiceClient) + channels := new(chmocks.ChannelsServiceClient) eventStore := new(mocks.EventStore) - return mqtt.NewHandler(mocks.NewPublisher(), eventStore, logger, things), things, eventStore + return mqtt.NewHandler(mocks.NewPublisher(), eventStore, logger, clients, channels), clients, channels, eventStore } diff --git a/pkg/apiutil/errors.go b/pkg/apiutil/errors.go index 2b53375122..00c7d1ef9e 100644 --- a/pkg/apiutil/errors.go +++ b/pkg/apiutil/errors.go @@ -21,6 +21,27 @@ var ( // ErrMissingID indicates missing entity ID. ErrMissingID = errors.New("missing entity id") + // ErrMissingClientID indicates missing client ID. + ErrMissingClientID = errors.New("missing cient id") + + // ErrMissingChannelID indicates missing client ID. + ErrMissingChannelID = errors.New("missing channel id") + + // ErrMissingConnectionType indicates missing connection tpye. + ErrMissingConnectionType = errors.New("missing connection type") + + // ErrMissingParentGroupID indicates missing parent group ID. + ErrMissingParentGroupID = errors.New("missing parent group id") + + // ErrMissingChildrenGroupIDs indicates missing children group IDs. + ErrMissingChildrenGroupIDs = errors.New("missing children group ids") + + // ErrSelfParentingNotAllowed indicates child id is same as parent id. + ErrSelfParentingNotAllowed = errors.New("self parenting not allowed") + + // ErrInvalidChildGroupID indicates invalid child group ID. + ErrInvalidChildGroupID = errors.New("invalid child group id") + // ErrInvalidAuthKey indicates invalid auth key. ErrInvalidAuthKey = errors.New("invalid auth key") @@ -39,6 +60,9 @@ var ( // ErrLimitSize indicates that an invalid limit. ErrLimitSize = errors.New("invalid limit size") + // ErrLevel indicates that an invalid level. + ErrLevel = errors.New("invalid level") + // ErrOffsetSize indicates an invalid offset. ErrOffsetSize = errors.New("invalid offset size") @@ -54,6 +78,15 @@ var ( // ErrEmptyList indicates that entity data is empty. ErrEmptyList = errors.New("empty list provided") + // ErrMissingRoleName indicates that role name are empty + ErrMissingRoleName = errors.New("empty role name") + + // ErrMissingRoleOperations indicates that role operations are empty + ErrMissingRoleOperations = errors.New("empty role operations") + + // ErrMissingRoleMembers indicates that role members are empty + ErrMissingRoleMembers = errors.New("empty role members") + // ErrMalformedPolicy indicates that policies are malformed. ErrMalformedPolicy = errors.New("malformed policy") diff --git a/pkg/apiutil/token.go b/pkg/apiutil/token.go index 563b60a177..d0ff3589da 100644 --- a/pkg/apiutil/token.go +++ b/pkg/apiutil/token.go @@ -11,8 +11,8 @@ import ( // BearerPrefix represents the token prefix for Bearer authentication scheme. const BearerPrefix = "Bearer " -// ThingPrefix represents the key prefix for Thing authentication scheme. -const ThingPrefix = "Thing " +// ClientPrefix represents the key prefix for Client authentication scheme. +const ClientPrefix = "Client " // ExtractBearerToken returns value of the bearer token. If there is no bearer token - an empty value is returned. func ExtractBearerToken(r *http.Request) string { @@ -25,13 +25,13 @@ func ExtractBearerToken(r *http.Request) string { return strings.TrimPrefix(token, BearerPrefix) } -// ExtractThingKey returns value of the thing key. If there is no thing key - an empty value is returned. -func ExtractThingKey(r *http.Request) string { +// ExtractClientSecret returns value of the client secret. If it's not present - an empty value is returned. +func ExtractClientSecret(r *http.Request) string { token := r.Header.Get("Authorization") - if !strings.HasPrefix(token, ThingPrefix) { + if !strings.HasPrefix(token, ClientPrefix) { return "" } - return strings.TrimPrefix(token, ThingPrefix) + return strings.TrimPrefix(token, ClientPrefix) } diff --git a/pkg/apiutil/token_test.go b/pkg/apiutil/token_test.go index 6194b9bb7a..a14ca3f9c0 100644 --- a/pkg/apiutil/token_test.go +++ b/pkg/apiutil/token_test.go @@ -61,7 +61,7 @@ func TestExtractBearerToken(t *testing.T) { } } -func TestExtractThingKey(t *testing.T) { +func TestExtractClientSecret(t *testing.T) { cases := []struct { desc string request *http.Request @@ -71,7 +71,7 @@ func TestExtractThingKey(t *testing.T) { desc: "valid bearer token", request: &http.Request{ Header: map[string][]string{ - "Authorization": {"Thing 123"}, + "Authorization": {"Client 123"}, }, }, token: "123", @@ -105,7 +105,7 @@ func TestExtractThingKey(t *testing.T) { for _, c := range cases { t.Run(c.desc, func(t *testing.T) { - token := apiutil.ExtractThingKey(c.request) + token := apiutil.ExtractClientSecret(c.request) assert.Equal(t, c.token, token) }) } diff --git a/pkg/authn/authsvc/authn.go b/pkg/authn/authsvc/authn.go index 88b44c518c..b9a2821e0a 100644 --- a/pkg/authn/authsvc/authn.go +++ b/pkg/authn/authsvc/authn.go @@ -6,8 +6,8 @@ package authsvc import ( "context" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth/api/grpc/auth" + grpcAuthV1 "github.com/absmach/magistrala/internal/grpc/auth/v1" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/grpcclient" @@ -15,7 +15,7 @@ import ( ) type authentication struct { - authSvcClient magistrala.AuthServiceClient + authSvcClient grpcAuthV1.AuthServiceClient } var _ authn.Authentication = (*authentication)(nil) @@ -38,7 +38,7 @@ func NewAuthentication(ctx context.Context, cfg grpcclient.Config) (authn.Authen } func (a authentication) Authenticate(ctx context.Context, token string) (authn.Session, error) { - res, err := a.authSvcClient.Authenticate(ctx, &magistrala.AuthNReq{Token: token}) + res, err := a.authSvcClient.Authenticate(ctx, &grpcAuthV1.AuthNReq{Token: token}) if err != nil { return authn.Session{}, errors.Wrap(errors.ErrAuthentication, err) } diff --git a/pkg/authz/authsvc/authz.go b/pkg/authz/authsvc/authz.go index 47db088e2f..07f15ebe06 100644 --- a/pkg/authz/authsvc/authz.go +++ b/pkg/authz/authsvc/authz.go @@ -6,8 +6,8 @@ package authsvc import ( "context" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth/api/grpc/auth" + grpcAuthV1 "github.com/absmach/magistrala/internal/grpc/auth/v1" "github.com/absmach/magistrala/pkg/authz" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/grpcclient" @@ -15,7 +15,7 @@ import ( ) type authorization struct { - authSvcClient magistrala.AuthServiceClient + authSvcClient grpcAuthV1.AuthServiceClient } var _ authz.Authorization = (*authorization)(nil) @@ -38,7 +38,7 @@ func NewAuthorization(ctx context.Context, cfg grpcclient.Config) (authz.Authori } func (a authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error { - req := magistrala.AuthZReq{ + req := grpcAuthV1.AuthZReq{ Domain: pr.Domain, SubjectType: pr.SubjectType, SubjectKind: pr.SubjectKind, @@ -53,7 +53,7 @@ func (a authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error if err != nil { return errors.Wrap(errors.ErrAuthorization, err) } - if !res.Authorized { + if !res.GetAuthorized() { return errors.ErrAuthorization } return nil diff --git a/pkg/authz/authz.go b/pkg/authz/authz.go index a76993ef45..5a38a35f11 100644 --- a/pkg/authz/authz.go +++ b/pkg/authz/authz.go @@ -13,11 +13,11 @@ type PolicyReq struct { Subject string `json:"subject"` // SubjectType contains the subject type. Supported subject types are - // platform, group, domain, thing, users. + // platform, group, domain, client, users. SubjectType string `json:"subject_type"` // SubjectKind contains the subject kind. Supported subject kinds are - // token, users, platform, things, channels, groups, domain. + // token, users, platform, clients, channels, groups, domain. SubjectKind string `json:"subject_kind"` // SubjectRelation contains subject relations. @@ -27,11 +27,11 @@ type PolicyReq struct { Object string `json:"object"` // ObjectKind contains the object kind. Supported object kinds are - // users, platform, things, channels, groups, domain. + // users, platform, clients, channels, groups, domain. ObjectKind string `json:"object_kind"` // ObjectType contains the object type. Supported object types are - // platform, group, domain, thing, users. + // platform, group, domain, client, users. ObjectType string `json:"object_type"` // Relation contains the relation. Supported relations are administrator, editor, contributor, member, guest, parent_group,group,domain. diff --git a/pkg/connections/type.go b/pkg/connections/type.go new file mode 100644 index 0000000000..b9819a91c8 --- /dev/null +++ b/pkg/connections/type.go @@ -0,0 +1,74 @@ +package connections + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/absmach/magistrala/pkg/errors" +) + +var errInvalidConnType = errors.New("invalid connection type") + +type ConnType uint8 + +const ( + Invalid ConnType = iota + Publish + Subscribe +) + +func (c *ConnType) UnmarshalJSON(bytes []byte) error { + var cstr string + if err := json.Unmarshal(bytes, &cstr); err != nil { + return err + } + + nc, err := ParseConnType(cstr) + if err != nil { + return err + } + *c = nc + return nil +} + +func CheckConnType(c ConnType) error { + switch c { + case Publish: + return nil + case Subscribe: + return nil + default: + return fmt.Errorf("Unknown connection type %d", c) + } +} + +func (c ConnType) String() string { + switch c { + case Publish: + return "Publish" + case Subscribe: + return "Subscribe" + default: + return fmt.Sprintf("Unknown connection type %d", c) + } +} + +func NewType(c uint) (ConnType, error) { + if err := CheckConnType(ConnType(c)); err != nil { + return Invalid, err + } + return ConnType(c), nil + +} + +func ParseConnType(c string) (ConnType, error) { + switch strings.ToLower(c) { + case "publish": + return Publish, nil + case "subscribe": + return Subscribe, nil + default: + return Invalid, errors.Wrap(errInvalidConnType, fmt.Errorf("%s", c)) + } +} diff --git a/pkg/errors/repository/types.go b/pkg/errors/repository/types.go index a189ae9e6f..d67f79079d 100644 --- a/pkg/errors/repository/types.go +++ b/pkg/errors/repository/types.go @@ -34,6 +34,8 @@ var ( // ErrFailedToRetrieveAllGroups failed to retrieve groups. ErrFailedToRetrieveAllGroups = errors.New("failed to retrieve all groups") + ErrRoleMigration = errors.New("role migration initialization failed") + // ErrMissingNames indicates missing first and last names. ErrMissingNames = errors.New("missing first or last name") ) diff --git a/pkg/errors/service/types.go b/pkg/errors/service/types.go index 2eb33acec1..6ce699bd97 100644 --- a/pkg/errors/service/types.go +++ b/pkg/errors/service/types.go @@ -75,4 +75,10 @@ var ( // ErrMissingUsername indicates that the user's names are missing. ErrMissingUsername = errors.New("missing usernames") + + // ErrEnableUser indicates error in enabling user. + ErrEnableUser = errors.New("failed to enable user") + + // ErrDisableUser indicates error in disabling user. + ErrDisableUser = errors.New("failed to disable user") ) diff --git a/pkg/groups/groups.go b/pkg/groups/groups.go deleted file mode 100644 index 8719424cf8..0000000000 --- a/pkg/groups/groups.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package groups - -import ( - "context" - "time" - - "github.com/absmach/magistrala/pkg/authn" -) - -// MaxLevel represents the maximum group hierarchy level. -const MaxLevel = uint64(5) - -// 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 domain. -type Group struct { - ID string `json:"id"` - Domain string `json:"domain_id,omitempty"` - Parent string `json:"parent_id,omitempty"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Level int `json:"level,omitempty"` - Path string `json:"path,omitempty"` - Children []*Group `json:"children,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - Status Status `json:"status"` - Permissions []string `json:"permissions,omitempty"` -} - -type Member struct { - ID string `json:"id"` - Type string `json:"type"` -} - -// Memberships contains page related metadata as well as list of memberships that -// belong to this page. -type MembersPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Members []Member `json:"members"` -} - -// Page contains page related metadata as well as list -// of Groups that belong to the page. -type Page struct { - PageMeta - Path string - Level uint64 - ParentID string - Permission string - ListPerms bool - Direction int64 // ancestors (+1) or descendants (-1) - Groups []Group -} - -// Metadata represents arbitrary JSON. -type Metadata map[string]interface{} - -// Repository specifies a group persistence API. -// -//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" --unroll-variadic=false -type Repository 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 Page) (Page, error) - - // RetrieveByIDs retrieves group by ids and query. - RetrieveByIDs(ctx context.Context, gm Page, ids ...string) (Page, error) - - // ChangeStatus changes groups status to active or inactive - ChangeStatus(ctx context.Context, group Group) (Group, error) - - // AssignParentGroup assigns parent group id to a given group id - AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error - - // UnassignParentGroup unassign parent group id fr given group id - UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error - - // Delete a group - Delete(ctx context.Context, groupID string) error -} - -//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" --unroll-variadic=false -type Service interface { - // CreateGroup creates new group. - CreateGroup(ctx context.Context, session authn.Session, kind string, g Group) (Group, error) - - // UpdateGroup updates the group identified by the provided ID. - UpdateGroup(ctx context.Context, session authn.Session, g Group) (Group, error) - - // ViewGroup retrieves data about the group identified by ID. - ViewGroup(ctx context.Context, session authn.Session, id string) (Group, error) - - // ViewGroupPerms retrieves permissions on the group id for the given authorized token. - ViewGroupPerms(ctx context.Context, session authn.Session, id string) ([]string, error) - - // ListGroups retrieves a list of groups basesd on entity type and entity id. - ListGroups(ctx context.Context, session authn.Session, memberKind, memberID string, gm Page) (Page, error) - - // ListMembers retrieves everything that is assigned to a group identified by groupID. - ListMembers(ctx context.Context, session authn.Session, groupID, permission, memberKind string) (MembersPage, error) - - // EnableGroup logically enables the group identified with the provided ID. - EnableGroup(ctx context.Context, session authn.Session, id string) (Group, error) - - // DisableGroup logically disables the group identified with the provided ID. - DisableGroup(ctx context.Context, session authn.Session, id string) (Group, error) - - // DeleteGroup delete the given group id - DeleteGroup(ctx context.Context, session authn.Session, id string) error - - // Assign member to group - Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) - - // Unassign member from group - Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) -} diff --git a/pkg/groups/mocks/repository.go b/pkg/groups/mocks/repository.go deleted file mode 100644 index 918b852cb4..0000000000 --- a/pkg/groups/mocks/repository.go +++ /dev/null @@ -1,253 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - groups "github.com/absmach/magistrala/pkg/groups" - mock "github.com/stretchr/testify/mock" -) - -// Repository is an autogenerated mock type for the Repository type -type Repository struct { - mock.Mock -} - -// AssignParentGroup provides a mock function with given fields: ctx, parentGroupID, groupIDs -func (_m *Repository) AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { - ret := _m.Called(ctx, parentGroupID, groupIDs) - - if len(ret) == 0 { - panic("no return value specified for AssignParentGroup") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { - r0 = rf(ctx, parentGroupID, groupIDs...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChangeStatus provides a mock function with given fields: ctx, group -func (_m *Repository) ChangeStatus(ctx context.Context, group groups.Group) (groups.Group, error) { - ret := _m.Called(ctx, group) - - if len(ret) == 0 { - panic("no return value specified for ChangeStatus") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { - return rf(ctx, group) - } - if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { - r0 = rf(ctx, group) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { - r1 = rf(ctx, group) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Delete provides a mock function with given fields: ctx, groupID -func (_m *Repository) Delete(ctx context.Context, groupID string) error { - ret := _m.Called(ctx, groupID) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, groupID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// RetrieveAll provides a mock function with given fields: ctx, gm -func (_m *Repository) RetrieveAll(ctx context.Context, gm groups.Page) (groups.Page, error) { - ret := _m.Called(ctx, gm) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAll") - } - - var r0 groups.Page - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, groups.Page) (groups.Page, error)); ok { - return rf(ctx, gm) - } - if rf, ok := ret.Get(0).(func(context.Context, groups.Page) groups.Page); ok { - r0 = rf(ctx, gm) - } else { - r0 = ret.Get(0).(groups.Page) - } - - if rf, ok := ret.Get(1).(func(context.Context, groups.Page) error); ok { - r1 = rf(ctx, gm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveByID provides a mock function with given fields: ctx, id -func (_m *Repository) RetrieveByID(ctx context.Context, id string) (groups.Group, error) { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByID") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (groups.Group, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string) groups.Group); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveByIDs provides a mock function with given fields: ctx, gm, ids -func (_m *Repository) RetrieveByIDs(ctx context.Context, gm groups.Page, ids ...string) (groups.Page, error) { - ret := _m.Called(ctx, gm, ids) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByIDs") - } - - var r0 groups.Page - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, groups.Page, ...string) (groups.Page, error)); ok { - return rf(ctx, gm, ids...) - } - if rf, ok := ret.Get(0).(func(context.Context, groups.Page, ...string) groups.Page); ok { - r0 = rf(ctx, gm, ids...) - } else { - r0 = ret.Get(0).(groups.Page) - } - - if rf, ok := ret.Get(1).(func(context.Context, groups.Page, ...string) error); ok { - r1 = rf(ctx, gm, ids...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, g -func (_m *Repository) Save(ctx context.Context, g groups.Group) (groups.Group, error) { - ret := _m.Called(ctx, g) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { - return rf(ctx, g) - } - if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { - r0 = rf(ctx, g) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { - r1 = rf(ctx, g) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UnassignParentGroup provides a mock function with given fields: ctx, parentGroupID, groupIDs -func (_m *Repository) UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { - ret := _m.Called(ctx, parentGroupID, groupIDs) - - if len(ret) == 0 { - panic("no return value specified for UnassignParentGroup") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { - r0 = rf(ctx, parentGroupID, groupIDs...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Update provides a mock function with given fields: ctx, g -func (_m *Repository) Update(ctx context.Context, g groups.Group) (groups.Group, error) { - ret := _m.Called(ctx, g) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, groups.Group) (groups.Group, error)); ok { - return rf(ctx, g) - } - if rf, ok := ret.Get(0).(func(context.Context, groups.Group) groups.Group); ok { - r0 = rf(ctx, g) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, groups.Group) error); ok { - r1 = rf(ctx, g) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *Repository { - mock := &Repository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/groups/mocks/service.go b/pkg/groups/mocks/service.go deleted file mode 100644 index 9fd1418911..0000000000 --- a/pkg/groups/mocks/service.go +++ /dev/null @@ -1,314 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - authn "github.com/absmach/magistrala/pkg/authn" - - groups "github.com/absmach/magistrala/pkg/groups" - - mock "github.com/stretchr/testify/mock" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// Assign provides a mock function with given fields: ctx, session, groupID, relation, memberKind, memberIDs -func (_m *Service) Assign(ctx context.Context, session authn.Session, groupID string, relation string, memberKind string, memberIDs ...string) error { - ret := _m.Called(ctx, session, groupID, relation, memberKind, memberIDs) - - if len(ret) == 0 { - panic("no return value specified for Assign") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string, ...string) error); ok { - r0 = rf(ctx, session, groupID, relation, memberKind, memberIDs...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// CreateGroup provides a mock function with given fields: ctx, session, kind, g -func (_m *Service) CreateGroup(ctx context.Context, session authn.Session, kind string, g groups.Group) (groups.Group, error) { - ret := _m.Called(ctx, session, kind, g) - - if len(ret) == 0 { - panic("no return value specified for CreateGroup") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, groups.Group) (groups.Group, error)); ok { - return rf(ctx, session, kind, g) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, groups.Group) groups.Group); ok { - r0 = rf(ctx, session, kind, g) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, groups.Group) error); ok { - r1 = rf(ctx, session, kind, g) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DeleteGroup provides a mock function with given fields: ctx, session, id -func (_m *Service) DeleteGroup(ctx context.Context, session authn.Session, id string) error { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for DeleteGroup") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// DisableGroup provides a mock function with given fields: ctx, session, id -func (_m *Service) DisableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for DisableGroup") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (groups.Group, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) groups.Group); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// EnableGroup provides a mock function with given fields: ctx, session, id -func (_m *Service) EnableGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for EnableGroup") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (groups.Group, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) groups.Group); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListGroups provides a mock function with given fields: ctx, session, memberKind, memberID, gm -func (_m *Service) ListGroups(ctx context.Context, session authn.Session, memberKind string, memberID string, gm groups.Page) (groups.Page, error) { - ret := _m.Called(ctx, session, memberKind, memberID, gm) - - if len(ret) == 0 { - panic("no return value specified for ListGroups") - } - - var r0 groups.Page - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, groups.Page) (groups.Page, error)); ok { - return rf(ctx, session, memberKind, memberID, gm) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, groups.Page) groups.Page); ok { - r0 = rf(ctx, session, memberKind, memberID, gm) - } else { - r0 = ret.Get(0).(groups.Page) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, groups.Page) error); ok { - r1 = rf(ctx, session, memberKind, memberID, gm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListMembers provides a mock function with given fields: ctx, session, groupID, permission, memberKind -func (_m *Service) ListMembers(ctx context.Context, session authn.Session, groupID string, permission string, memberKind string) (groups.MembersPage, error) { - ret := _m.Called(ctx, session, groupID, permission, memberKind) - - if len(ret) == 0 { - panic("no return value specified for ListMembers") - } - - var r0 groups.MembersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) (groups.MembersPage, error)); ok { - return rf(ctx, session, groupID, permission, memberKind) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) groups.MembersPage); ok { - r0 = rf(ctx, session, groupID, permission, memberKind) - } else { - r0 = ret.Get(0).(groups.MembersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, string) error); ok { - r1 = rf(ctx, session, groupID, permission, memberKind) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Unassign provides a mock function with given fields: ctx, session, groupID, relation, memberKind, memberIDs -func (_m *Service) Unassign(ctx context.Context, session authn.Session, groupID string, relation string, memberKind string, memberIDs ...string) error { - ret := _m.Called(ctx, session, groupID, relation, memberKind, memberIDs) - - if len(ret) == 0 { - panic("no return value specified for Unassign") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string, ...string) error); ok { - r0 = rf(ctx, session, groupID, relation, memberKind, memberIDs...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UpdateGroup provides a mock function with given fields: ctx, session, g -func (_m *Service) UpdateGroup(ctx context.Context, session authn.Session, g groups.Group) (groups.Group, error) { - ret := _m.Called(ctx, session, g) - - if len(ret) == 0 { - panic("no return value specified for UpdateGroup") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, groups.Group) (groups.Group, error)); ok { - return rf(ctx, session, g) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, groups.Group) groups.Group); ok { - r0 = rf(ctx, session, g) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, groups.Group) error); ok { - r1 = rf(ctx, session, g) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ViewGroup provides a mock function with given fields: ctx, session, id -func (_m *Service) ViewGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for ViewGroup") - } - - var r0 groups.Group - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (groups.Group, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) groups.Group); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(groups.Group) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ViewGroupPerms provides a mock function with given fields: ctx, session, id -func (_m *Service) ViewGroupPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for ViewGroupPerms") - } - - var r0 []string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) ([]string, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) []string); ok { - r0 = rf(ctx, session, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]string) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewService(t interface { - mock.TestingT - Cleanup(func()) -}) *Service { - mock := &Service{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/groups/page.go b/pkg/groups/page.go deleted file mode 100644 index e49ec6690e..0000000000 --- a/pkg/groups/page.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package groups - -// PageMeta contains page metadata that helps navigation. -type PageMeta struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Name string `json:"name,omitempty"` - ID string `json:"id,omitempty"` - DomainID string `json:"domain_id,omitempty"` - Tag string `json:"tag,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Status Status `json:"status,omitempty"` -} diff --git a/pkg/grpcclient/client.go b/pkg/grpcclient/client.go index 5c29571115..b92d6bd352 100644 --- a/pkg/grpcclient/client.go +++ b/pkg/grpcclient/client.go @@ -6,10 +6,16 @@ package grpcclient import ( "context" - "github.com/absmach/magistrala" - domainsgrpc "github.com/absmach/magistrala/auth/api/grpc/domains" tokengrpc "github.com/absmach/magistrala/auth/api/grpc/token" - thingsauth "github.com/absmach/magistrala/things/api/grpc" + channelsgrpc "github.com/absmach/magistrala/channels/api/grpc" + clientsauth "github.com/absmach/magistrala/clients/api/grpc" + domainsgrpc "github.com/absmach/magistrala/domains/api/grpc" + groupsgrpc "github.com/absmach/magistrala/groups/api/grpc" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + grpcDomainsV1 "github.com/absmach/magistrala/internal/grpc/domains/v1" + grpcGroupsV1 "github.com/absmach/magistrala/internal/grpc/groups/v1" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" grpchealth "google.golang.org/grpc/health/grpc_health_v1" ) @@ -18,7 +24,7 @@ import ( // For example: // // tokenClient, tokenHandler, err := grpcclient.SetupTokenClient(ctx, grpcclient.Config{}). -func SetupTokenClient(ctx context.Context, cfg Config) (magistrala.TokenServiceClient, Handler, error) { +func SetupTokenClient(ctx context.Context, cfg Config) (grpcTokenV1.TokenServiceClient, Handler, error) { client, err := NewHandler(cfg) if err != nil { return nil, nil, err @@ -26,6 +32,7 @@ func SetupTokenClient(ctx context.Context, cfg Config) (magistrala.TokenServiceC health := grpchealth.NewHealthClient(client.Connection()) resp, err := health.Check(ctx, &grpchealth.HealthCheckRequest{ + // Health Service name is the svcName provided during gRPC server creation `grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerAuthServiceServer, logger)` Service: "auth", }) if err != nil || resp.GetStatus() != grpchealth.HealthCheckResponse_SERVING { @@ -40,41 +47,53 @@ func SetupTokenClient(ctx context.Context, cfg Config) (magistrala.TokenServiceC // For example: // // domainsClient, domainsHandler, err := grpcclient.SetupDomainsClient(ctx, grpcclient.Config{}). -func SetupDomainsClient(ctx context.Context, cfg Config) (magistrala.DomainsServiceClient, Handler, error) { +func SetupDomainsClient(ctx context.Context, cfg Config) (grpcDomainsV1.DomainsServiceClient, Handler, error) { client, err := NewHandler(cfg) if err != nil { return nil, nil, err } - health := grpchealth.NewHealthClient(client.Connection()) - resp, err := health.Check(ctx, &grpchealth.HealthCheckRequest{ - Service: "auth", - }) - if err != nil || resp.GetStatus() != grpchealth.HealthCheckResponse_SERVING { - return nil, nil, ErrSvcNotServing + return domainsgrpc.NewDomainsClient(client.Connection(), cfg.Timeout), client, nil +} + +// SetupClientsClient loads clients gRPC configuration and creates new clients gRPC client. +// +// For example: +// +// clientClient, clientHandler, err := grpcclient.SetupClients(ctx, grpcclient.Config{}). +func SetupClientsClient(ctx context.Context, cfg Config) (grpcClientsV1.ClientsServiceClient, Handler, error) { + client, err := NewHandler(cfg) + if err != nil { + return nil, nil, err } - return domainsgrpc.NewDomainsClient(client.Connection(), cfg.Timeout), client, nil + return clientsauth.NewClient(client.Connection(), cfg.Timeout), client, nil } -// SetupThingsClient loads things gRPC configuration and creates new things gRPC client. +// SetupChannelsClient loads channels gRPC configuration and creates new channels gRPC client. // // For example: // -// thingClient, thingHandler, err := grpcclient.SetupThings(ctx, grpcclient.Config{}). -func SetupThingsClient(ctx context.Context, cfg Config) (magistrala.ThingsServiceClient, Handler, error) { +// channelClient, channelHandler, err := grpcclient.SetupChannelsClient(ctx, grpcclient.Config{}). +func SetupChannelsClient(ctx context.Context, cfg Config) (grpcChannelsV1.ChannelsServiceClient, Handler, error) { client, err := NewHandler(cfg) if err != nil { return nil, nil, err } - health := grpchealth.NewHealthClient(client.Connection()) - resp, err := health.Check(ctx, &grpchealth.HealthCheckRequest{ - Service: "things", - }) - if err != nil || resp.GetStatus() != grpchealth.HealthCheckResponse_SERVING { - return nil, nil, ErrSvcNotServing + return channelsgrpc.NewClient(client.Connection(), cfg.Timeout), client, nil +} + +// SetupGroupsClient loads groups gRPC configuration and creates new groups gRPC client. +// +// For example: +// +// groupClient, groupHandler, err := grpcclient.SetupGroupsClient(ctx, grpcclient.Config{}). +func SetupGroupsClient(ctx context.Context, cfg Config) (grpcGroupsV1.GroupsServiceClient, Handler, error) { + client, err := NewHandler(cfg) + if err != nil { + return nil, nil, err } - return thingsauth.NewClient(client.Connection(), cfg.Timeout), client, nil + return groupsgrpc.NewClient(client.Connection(), cfg.Timeout), client, nil } diff --git a/pkg/grpcclient/client_test.go b/pkg/grpcclient/client_test.go index acc0ebbe31..5c60dcb51b 100644 --- a/pkg/grpcclient/client_test.go +++ b/pkg/grpcclient/client_test.go @@ -9,17 +9,20 @@ import ( "testing" "time" - "github.com/absmach/magistrala" - domainsgrpcapi "github.com/absmach/magistrala/auth/api/grpc/domains" tokengrpcapi "github.com/absmach/magistrala/auth/api/grpc/token" "github.com/absmach/magistrala/auth/mocks" + clientsgrpcapi "github.com/absmach/magistrala/clients/api/grpc" + climocks "github.com/absmach/magistrala/clients/private/mocks" + domainsgrpcapi "github.com/absmach/magistrala/domains/api/grpc" + domainsMocks "github.com/absmach/magistrala/domains/mocks" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + grpcDomainsV1 "github.com/absmach/magistrala/internal/grpc/domains/v1" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/grpcclient" "github.com/absmach/magistrala/pkg/server" grpcserver "github.com/absmach/magistrala/pkg/server/grpc" - thingsgrpcapi "github.com/absmach/magistrala/things/api/grpc" - thmocks "github.com/absmach/magistrala/things/mocks" "github.com/stretchr/testify/assert" "google.golang.org/grpc" ) @@ -28,7 +31,7 @@ func TestSetupToken(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() registerAuthServiceServer := func(srv *grpc.Server) { - magistrala.RegisterTokenServiceServer(srv, tokengrpcapi.NewTokenServer(new(mocks.Service))) + grpcTokenV1.RegisterTokenServiceServer(srv, tokengrpcapi.NewTokenServer(new(mocks.Service))) } gs := grpcserver.NewServer(ctx, cancel, "auth", server.Config{Port: "12345"}, registerAuthServiceServer, mglog.NewMock()) go func() { @@ -75,14 +78,14 @@ func TestSetupToken(t *testing.T) { } } -func TestSetupThingsClient(t *testing.T) { +func TestSetupClientsClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - registerThingsServiceServer := func(srv *grpc.Server) { - magistrala.RegisterThingsServiceServer(srv, thingsgrpcapi.NewServer(new(thmocks.Service))) + registerClientsServiceServer := func(srv *grpc.Server) { + grpcClientsV1.RegisterClientsServiceServer(srv, clientsgrpcapi.NewServer(new(climocks.Service))) } - gs := grpcserver.NewServer(ctx, cancel, "things", server.Config{Port: "12345"}, registerThingsServiceServer, mglog.NewMock()) + gs := grpcserver.NewServer(ctx, cancel, "clients", server.Config{Port: "12345"}, registerClientsServiceServer, mglog.NewMock()) go func() { err := gs.Start() assert.Nil(t, err, fmt.Sprintf(`"Unexpected error creating server %s"`, err)) @@ -117,7 +120,7 @@ func TestSetupThingsClient(t *testing.T) { for _, c := range cases { t.Run(c.desc, func(t *testing.T) { - client, handler, err := grpcclient.SetupThingsClient(context.Background(), c.config) + client, handler, err := grpcclient.SetupClientsClient(context.Background(), c.config) assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s", err, c.err)) if err == nil { assert.NotNil(t, client) @@ -131,7 +134,7 @@ func TestSetupDomainsClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() registerDomainsServiceServer := func(srv *grpc.Server) { - magistrala.RegisterDomainsServiceServer(srv, domainsgrpcapi.NewDomainsServer(new(mocks.Service))) + grpcDomainsV1.RegisterDomainsServiceServer(srv, domainsgrpcapi.NewDomainsServer(new(domainsMocks.Service))) } gs := grpcserver.NewServer(ctx, cancel, "auth", server.Config{Port: "12345"}, registerDomainsServiceServer, mglog.NewMock()) go func() { diff --git a/pkg/grpcclient/connect.go b/pkg/grpcclient/connect.go index e8678ed1b5..12c41cbf12 100644 --- a/pkg/grpcclient/connect.go +++ b/pkg/grpcclient/connect.go @@ -33,11 +33,12 @@ var ( ) type Config struct { - URL string `env:"URL" envDefault:""` - Timeout time.Duration `env:"TIMEOUT" envDefault:"1s"` - ClientCert string `env:"CLIENT_CERT" envDefault:""` - ClientKey string `env:"CLIENT_KEY" envDefault:""` - ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""` + URL string `env:"URL" envDefault:""` + Timeout time.Duration `env:"TIMEOUT" envDefault:"1s"` + ClientCert string `env:"CLIENT_CERT" envDefault:""` + ClientKey string `env:"CLIENT_KEY" envDefault:""` + ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""` + BypassHealthCheck bool } // Handler is used to handle gRPC connection. diff --git a/pkg/messaging/nats/tracing/doc.go b/pkg/messaging/nats/tracing/doc.go index 5f8df0d9e0..fdae8fb3ba 100644 --- a/pkg/messaging/nats/tracing/doc.go +++ b/pkg/messaging/nats/tracing/doc.go @@ -1,11 +1,11 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package tracing provides tracing instrumentation for Magistrala things policies service. +// Package tracing provides tracing instrumentation for Magistrala clients policies service. // -// This package provides tracing middleware for Magistrala things policies service. +// This package provides tracing middleware for Magistrala clients policies service. // It can be used to trace incoming requests and add tracing capabilities to -// Magistrala things policies service. +// Magistrala clients policies service. // // For more details about tracing instrumentation for Magistrala messaging refer // to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. diff --git a/pkg/messaging/rabbitmq/tracing/doc.go b/pkg/messaging/rabbitmq/tracing/doc.go index 5f8df0d9e0..fdae8fb3ba 100644 --- a/pkg/messaging/rabbitmq/tracing/doc.go +++ b/pkg/messaging/rabbitmq/tracing/doc.go @@ -1,11 +1,11 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package tracing provides tracing instrumentation for Magistrala things policies service. +// Package tracing provides tracing instrumentation for Magistrala clients policies service. // -// This package provides tracing middleware for Magistrala things policies service. +// This package provides tracing middleware for Magistrala clients policies service. // It can be used to trace incoming requests and add tracing capabilities to -// Magistrala things policies service. +// Magistrala clients policies service. // // For more details about tracing instrumentation for Magistrala messaging refer // to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. diff --git a/pkg/messaging/tracing/doc.go b/pkg/messaging/tracing/doc.go index 5f8df0d9e0..fdae8fb3ba 100644 --- a/pkg/messaging/tracing/doc.go +++ b/pkg/messaging/tracing/doc.go @@ -1,11 +1,11 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package tracing provides tracing instrumentation for Magistrala things policies service. +// Package tracing provides tracing instrumentation for Magistrala clients policies service. // -// This package provides tracing middleware for Magistrala things policies service. +// This package provides tracing middleware for Magistrala clients policies service. // It can be used to trace incoming requests and add tracing capabilities to -// Magistrala things policies service. +// Magistrala clients policies service. // // For more details about tracing instrumentation for Magistrala messaging refer // to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. diff --git a/pkg/policies/evaluator.go b/pkg/policies/evaluator.go index c6288697c4..3747fad459 100644 --- a/pkg/policies/evaluator.go +++ b/pkg/policies/evaluator.go @@ -13,16 +13,18 @@ const ( NewGroupKind = "new_group" ChannelsKind = "channels" NewChannelKind = "new_channel" - ThingsKind = "things" - NewThingKind = "new_thing" + ClientsKind = "clients" + NewClientKind = "new_client" UsersKind = "users" DomainsKind = "domains" PlatformKind = "platform" ) const ( + RoleType = "role" GroupType = "group" - ThingType = "thing" + ClientType = "client" + ChannelType = "channel" UserType = "user" DomainType = "domain" PlatformType = "platform" diff --git a/pkg/policies/service.go b/pkg/policies/service.go index 446926c137..f0f21173af 100644 --- a/pkg/policies/service.go +++ b/pkg/policies/service.go @@ -16,25 +16,28 @@ type Policy struct { Subject string `json:"subject"` // SubjectType contains the subject type. Supported subject types are - // platform, group, domain, thing, users. + // platform, group, domain, client, users. SubjectType string `json:"subject_type"` // SubjectKind contains the subject kind. Supported subject kinds are - // token, users, platform, things, channels, groups, domain. + // token, users, platform, clients, channels, groups, domain. SubjectKind string `json:"subject_kind"` // SubjectRelation contains subject relations. SubjectRelation string `json:"subject_relation,omitempty"` + // ObjectPrefix contains the Optional Object Prefix which is used for delete with filter. + ObjectPrefix string `json:"object_prefix"` + // Object contains the object ID. Object string `json:"object"` // ObjectKind contains the object kind. Supported object kinds are - // users, platform, things, channels, groups, domain. + // users, platform, clients, channels, groups, domain. ObjectKind string `json:"object_kind"` // ObjectType contains the object type. Supported object types are - // platform, group, domain, thing, users. + // platform, group, domain, client, users. ObjectType string `json:"object_type"` // Relation contains the relation. Supported relations are administrator, editor, contributor, member, guest, parent_group,group,domain. @@ -102,3 +105,10 @@ type Service interface { // ListPermissions lists permission betweeen given subject and object . ListPermissions(ctx context.Context, pr Policy, permissionsFilter []string) (Permissions, error) } + +func EncodeDomainUserID(domainID, userID string) string { + if domainID == "" || userID == "" { + return "" + } + return domainID + "_" + userID +} diff --git a/pkg/policies/spicedb/service.go b/pkg/policies/spicedb/service.go index 6abbf59651..a24a8ef931 100644 --- a/pkg/policies/spicedb/service.go +++ b/pkg/policies/spicedb/service.go @@ -33,7 +33,7 @@ var ( ) var ( - defThingsFilterPermissions = []string{ + defClientsFilterPermissions = []string{ policies.AdminPermission, policies.DeletePermission, policies.EditPermission, @@ -142,8 +142,9 @@ func (ps *policyService) AddPolicies(ctx context.Context, prs []policies.Policy) func (ps *policyService) DeletePolicyFilter(ctx context.Context, pr policies.Policy) error { req := &v1.DeleteRelationshipsRequest{ RelationshipFilter: &v1.RelationshipFilter{ - ResourceType: pr.ObjectType, - OptionalResourceId: pr.Object, + ResourceType: pr.ObjectType, + OptionalResourceId: pr.Object, + OptionalResourceIdPrefix: pr.ObjectPrefix, }, } @@ -297,8 +298,8 @@ func (ps *policyService) CountSubjects(ctx context.Context, pr policies.Policy) func (ps *policyService) ListPermissions(ctx context.Context, pr policies.Policy, permissionsFilter []string) (policies.Permissions, error) { if len(permissionsFilter) == 0 { switch pr.ObjectType { - case policies.ThingType: - permissionsFilter = defThingsFilterPermissions + case policies.ClientType: + permissionsFilter = defClientsFilterPermissions case policies.GroupType: permissionsFilter = defGroupsFilterPermissions case policies.PlatformType: @@ -328,9 +329,9 @@ func (ps *policyService) policyValidation(pr policies.Policy) error { func (ps *policyService) addPolicyPreCondition(ctx context.Context, pr policies.Policy) ([]*v1.Precondition, error) { // Checks are required for following ( -> means adding) // 1.) user -> group (both user groups and channels) - // 2.) user -> thing + // 2.) user -> client // 3.) group -> group (both for adding parent_group and channels) - // 4.) group (channel) -> thing + // 4.) group (channel) -> client // 5.) user -> domain switch { @@ -341,12 +342,12 @@ func (ps *policyService) addPolicyPreCondition(ctx context.Context, pr policies. case pr.SubjectType == policies.UserType && pr.ObjectType == policies.GroupType: return ps.userGroupPreConditions(ctx, pr) - // 2.) user -> thing + // 2.) user -> client // Checks : // - USER with ANY RELATION to DOMAIN - // - THING with DOMAIN RELATION to DOMAIN - case pr.SubjectType == policies.UserType && pr.ObjectType == policies.ThingType: - return ps.userThingPreConditions(ctx, pr) + // - CLIENT with DOMAIN RELATION to DOMAIN + case pr.SubjectType == policies.UserType && pr.ObjectType == policies.ClientType: + return ps.userClientPreConditions(ctx, pr) // 3.) group -> group (both for adding parent_group and channels) // Checks : @@ -354,13 +355,13 @@ func (ps *policyService) addPolicyPreCondition(ctx context.Context, pr policies. case pr.SubjectType == policies.GroupType && pr.ObjectType == policies.GroupType: return groupPreConditions(pr) - // 4.) group (channel) -> thing + // 4.) group (channel) -> client // Checks : // - GROUP (channel) with DOMAIN RELATION to DOMAIN // - NO GROUP should not have PARENT_GROUP RELATION with GROUP (channel) - // - THING with DOMAIN RELATION to DOMAIN - case pr.SubjectType == policies.GroupType && pr.ObjectType == policies.ThingType: - return channelThingPreCondition(pr) + // - CLIENT with DOMAIN RELATION to DOMAIN + // case pr.SubjectType == policies.GroupType && pr.ObjectType == policies.ClientType: + // return channelClientPreCondition(pr) // 5.) user -> domain // Checks : @@ -368,8 +369,8 @@ func (ps *policyService) addPolicyPreCondition(ctx context.Context, pr policies. case pr.SubjectType == policies.UserType && pr.ObjectType == policies.DomainType: return ps.userDomainPreConditions(ctx, pr) - // Check thing and group not belongs to other domain before adding to domain - case pr.SubjectType == policies.DomainType && pr.Relation == policies.DomainRelation && (pr.ObjectType == policies.ThingType || pr.ObjectType == policies.GroupType): + // Check client and group not belongs to other domain before adding to domain + case pr.SubjectType == policies.DomainType && pr.Relation == policies.DomainRelation && (pr.ObjectType == policies.ClientType || pr.ObjectType == policies.GroupType): preconds := []*v1.Precondition{ { Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, @@ -463,14 +464,14 @@ func (ps *policyService) userGroupPreConditions(ctx context.Context, pr policies return preconds, nil } -func (ps *policyService) userThingPreConditions(ctx context.Context, pr policies.Policy) ([]*v1.Precondition, error) { +func (ps *policyService) userClientPreConditions(ctx context.Context, pr policies.Policy) ([]*v1.Precondition, error) { var preconds []*v1.Precondition - // user should not have any relation with thing + // user should not have any relation with client preconds = append(preconds, &v1.Precondition{ Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, Filter: &v1.RelationshipFilter{ - ResourceType: policies.ThingType, + ResourceType: policies.ClientType, OptionalResourceId: pr.Object, OptionalSubjectFilter: &v1.SubjectFilter{ SubjectType: policies.UserType, @@ -504,14 +505,14 @@ func (ps *policyService) userThingPreConditions(ctx context.Context, pr policies }) } switch { - // For New thing - // - THING without DOMAIN RELATION to ANY DOMAIN - case pr.ObjectKind == policies.NewThingKind: + // For New client + // - CLIENT without DOMAIN RELATION to ANY DOMAIN + case pr.ObjectKind == policies.NewClientKind: preconds = append(preconds, &v1.Precondition{ Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, Filter: &v1.RelationshipFilter{ - ResourceType: policies.ThingType, + ResourceType: policies.ClientType, OptionalResourceId: pr.Object, OptionalRelation: policies.DomainRelation, OptionalSubjectFilter: &v1.SubjectFilter{ @@ -521,13 +522,13 @@ func (ps *policyService) userThingPreConditions(ctx context.Context, pr policies }, ) default: - // For existing thing - // - THING without DOMAIN RELATION to ANY DOMAIN + // For existing client + // - CLIENT without DOMAIN RELATION to ANY DOMAIN preconds = append(preconds, &v1.Precondition{ Operation: v1.Precondition_OPERATION_MUST_MATCH, Filter: &v1.RelationshipFilter{ - ResourceType: policies.ThingType, + ResourceType: policies.ClientType, OptionalResourceId: pr.Object, OptionalRelation: policies.DomainRelation, OptionalSubjectFilter: &v1.SubjectFilter{ @@ -847,7 +848,7 @@ func groupPreConditions(pr policies.Policy) ([]*v1.Precondition, error) { return precond, nil } -func channelThingPreCondition(pr policies.Policy) ([]*v1.Precondition, error) { +func channelClientPreCondition(pr policies.Policy) ([]*v1.Precondition, error) { if pr.SubjectKind != policies.ChannelsKind { return nil, errors.Wrap(errors.ErrMalformedEntity, errInvalidSubject) } @@ -878,7 +879,7 @@ func channelThingPreCondition(pr policies.Policy) ([]*v1.Precondition, error) { { Operation: v1.Precondition_OPERATION_MUST_MATCH, Filter: &v1.RelationshipFilter{ - ResourceType: policies.ThingType, + ResourceType: policies.ClientType, OptionalResourceId: pr.Object, OptionalRelation: policies.DomainRelation, OptionalSubjectFilter: &v1.SubjectFilter{ diff --git a/pkg/roles/mocks/provisioner.go b/pkg/roles/mocks/provisioner.go new file mode 100644 index 0000000000..70cda3aa5e --- /dev/null +++ b/pkg/roles/mocks/provisioner.go @@ -0,0 +1,81 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + policies "github.com/absmach/magistrala/pkg/policies" + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// Provisioner is an autogenerated mock type for the Provisioner type +type Provisioner struct { + mock.Mock +} + +// AddNewEntitiesRoles provides a mock function with given fields: ctx, domainID, userID, entityIDs, optionalEntityPolicies, newBuiltInRoleMembers +func (_m *Provisioner) AddNewEntitiesRoles(ctx context.Context, domainID string, userID string, entityIDs []string, optionalEntityPolicies []policies.Policy, newBuiltInRoleMembers map[roles.BuiltInRoleName][]roles.Member) ([]roles.RoleProvision, error) { + ret := _m.Called(ctx, domainID, userID, entityIDs, optionalEntityPolicies, newBuiltInRoleMembers) + + if len(ret) == 0 { + panic("no return value specified for AddNewEntitiesRoles") + } + + var r0 []roles.RoleProvision + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []string, []policies.Policy, map[roles.BuiltInRoleName][]roles.Member) ([]roles.RoleProvision, error)); ok { + return rf(ctx, domainID, userID, entityIDs, optionalEntityPolicies, newBuiltInRoleMembers) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, []string, []policies.Policy, map[roles.BuiltInRoleName][]roles.Member) []roles.RoleProvision); ok { + r0 = rf(ctx, domainID, userID, entityIDs, optionalEntityPolicies, newBuiltInRoleMembers) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.RoleProvision) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, []string, []policies.Policy, map[roles.BuiltInRoleName][]roles.Member) error); ok { + r1 = rf(ctx, domainID, userID, entityIDs, optionalEntityPolicies, newBuiltInRoleMembers) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveEntitiesRoles provides a mock function with given fields: ctx, domainID, userID, entityIDs, optionalFilterDeletePolicies, optionalDeletePolicies +func (_m *Provisioner) RemoveEntitiesRoles(ctx context.Context, domainID string, userID string, entityIDs []string, optionalFilterDeletePolicies []policies.Policy, optionalDeletePolicies []policies.Policy) error { + ret := _m.Called(ctx, domainID, userID, entityIDs, optionalFilterDeletePolicies, optionalDeletePolicies) + + if len(ret) == 0 { + panic("no return value specified for RemoveEntitiesRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []string, []policies.Policy, []policies.Policy) error); ok { + r0 = rf(ctx, domainID, userID, entityIDs, optionalFilterDeletePolicies, optionalDeletePolicies) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewProvisioner creates a new instance of Provisioner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProvisioner(t interface { + mock.TestingT + Cleanup(func()) +}) *Provisioner { + mock := &Provisioner{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/roles/mocks/rolemanager.go b/pkg/roles/mocks/rolemanager.go new file mode 100644 index 0000000000..2abad325d7 --- /dev/null +++ b/pkg/roles/mocks/rolemanager.go @@ -0,0 +1,458 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + authn "github.com/absmach/magistrala/pkg/authn" + + mock "github.com/stretchr/testify/mock" + + roles "github.com/absmach/magistrala/pkg/roles" +) + +// RoleManager is an autogenerated mock type for the RoleManager type +type RoleManager struct { + mock.Mock +} + +// AddRole provides a mock function with given fields: ctx, session, entityID, roleName, optionalActions, optionalMembers +func (_m *RoleManager) AddRole(ctx context.Context, session authn.Session, entityID string, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName, optionalActions, optionalMembers) + + if len(ret) == 0 { + panic("no return value specified for AddRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string, []string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, optionalActions, optionalMembers) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAvailableActions provides a mock function with given fields: ctx, session +func (_m *RoleManager) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + ret := _m.Called(ctx, session) + + if len(ret) == 0 { + panic("no return value specified for ListAvailableActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) ([]string, error)); ok { + return rf(ctx, session) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session) []string); ok { + r0 = rf(ctx, session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session) error); ok { + r1 = rf(ctx, session) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, session, memberID +func (_m *RoleManager) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) error { + ret := _m.Called(ctx, session, memberID) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { + r0 = rf(ctx, session, memberID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *RoleManager) RemoveRole(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RemoveRole") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, session, entityID, limit, offset +func (_m *RoleManager) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, session, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, session, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, session, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRole provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *RoleManager) RetrieveRole(ctx context.Context, session authn.Session, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *RoleManager) RoleAddActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *RoleManager) RoleAddMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) []string); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *RoleManager) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *RoleManager) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) (bool, error) { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) (bool, error)); ok { + return rf(ctx, session, entityID, roleName, members) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) bool); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, []string) error); ok { + r1 = rf(ctx, session, entityID, roleName, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *RoleManager) RoleListActions(ctx context.Context, session authn.Session, entityID string, roleName string) ([]string, error) { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) ([]string, error)); ok { + return rf(ctx, session, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) []string); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { + r1 = rf(ctx, session, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, session, entityID, roleName, limit, offset +func (_m *RoleManager) RoleListMembers(ctx context.Context, session authn.Session, entityID string, roleName string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, session, entityID, roleName, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, session, entityID, roleName, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, uint64, uint64) error); ok { + r1 = rf(ctx, session, entityID, roleName, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, session, entityID, roleName, actions +func (_m *RoleManager) RoleRemoveActions(ctx context.Context, session authn.Session, entityID string, roleName string, actions []string) error { + ret := _m.Called(ctx, session, entityID, roleName, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *RoleManager) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, session, entityID, roleName +func (_m *RoleManager) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID string, roleName string) error { + ret := _m.Called(ctx, session, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { + r0 = rf(ctx, session, entityID, roleName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, session, entityID, roleName, members +func (_m *RoleManager) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID string, roleName string, members []string) error { + ret := _m.Called(ctx, session, entityID, roleName, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { + r0 = rf(ctx, session, entityID, roleName, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateRoleName provides a mock function with given fields: ctx, session, entityID, oldRoleName, newRoleName +func (_m *RoleManager) UpdateRoleName(ctx context.Context, session authn.Session, entityID string, oldRoleName string, newRoleName string) (roles.Role, error) { + ret := _m.Called(ctx, session, entityID, oldRoleName, newRoleName) + + if len(ret) == 0 { + panic("no return value specified for UpdateRoleName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) (roles.Role, error)); ok { + return rf(ctx, session, entityID, oldRoleName, newRoleName) + } + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, string) roles.Role); ok { + r0 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, string) error); ok { + r1 = rf(ctx, session, entityID, oldRoleName, newRoleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRoleManager creates a new instance of RoleManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRoleManager(t interface { + mock.TestingT + Cleanup(func()) +}) *RoleManager { + mock := &RoleManager{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/roles/mocks/rolesRepo.go b/pkg/roles/mocks/rolesRepo.go new file mode 100644 index 0000000000..2e34d05d85 --- /dev/null +++ b/pkg/roles/mocks/rolesRepo.go @@ -0,0 +1,494 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + roles "github.com/absmach/magistrala/pkg/roles" + mock "github.com/stretchr/testify/mock" +) + +// Repository is an autogenerated mock type for the Repository type +type Repository struct { + mock.Mock +} + +// AddRoles provides a mock function with given fields: ctx, rps +func (_m *Repository) AddRoles(ctx context.Context, rps []roles.RoleProvision) ([]roles.Role, error) { + ret := _m.Called(ctx, rps) + + if len(ret) == 0 { + panic("no return value specified for AddRoles") + } + + var r0 []roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) ([]roles.Role, error)); ok { + return rf(ctx, rps) + } + if rf, ok := ret.Get(0).(func(context.Context, []roles.RoleProvision) []roles.Role); ok { + r0 = rf(ctx, rps) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.Role) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []roles.RoleProvision) error); ok { + r1 = rf(ctx, rps) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveMemberFromAllRoles provides a mock function with given fields: ctx, members +func (_m *Repository) RemoveMemberFromAllRoles(ctx context.Context, members string) error { + ret := _m.Called(ctx, members) + + if len(ret) == 0 { + panic("no return value specified for RemoveMemberFromAllRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveRoles provides a mock function with given fields: ctx, roleIDs +func (_m *Repository) RemoveRoles(ctx context.Context, roleIDs []string) error { + ret := _m.Called(ctx, roleIDs) + + if len(ret) == 0 { + panic("no return value specified for RemoveRoles") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { + r0 = rf(ctx, roleIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RetrieveAllRoles provides a mock function with given fields: ctx, entityID, limit, offset +func (_m *Repository) RetrieveAllRoles(ctx context.Context, entityID string, limit uint64, offset uint64) (roles.RolePage, error) { + ret := _m.Called(ctx, entityID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAllRoles") + } + + var r0 roles.RolePage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.RolePage, error)); ok { + return rf(ctx, entityID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.RolePage); ok { + r0 = rf(ctx, entityID, limit, offset) + } else { + r0 = ret.Get(0).(roles.RolePage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, entityID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveEntitiesRolesActionsMembers provides a mock function with given fields: ctx, entityIDs +func (_m *Repository) RetrieveEntitiesRolesActionsMembers(ctx context.Context, entityIDs []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error) { + ret := _m.Called(ctx, entityIDs) + + if len(ret) == 0 { + panic("no return value specified for RetrieveEntitiesRolesActionsMembers") + } + + var r0 []roles.EntityActionRole + var r1 []roles.EntityMemberRole + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error)); ok { + return rf(ctx, entityIDs) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []roles.EntityActionRole); ok { + r0 = rf(ctx, entityIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]roles.EntityActionRole) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) []roles.EntityMemberRole); ok { + r1 = rf(ctx, entityIDs) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]roles.EntityMemberRole) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, []string) error); ok { + r2 = rf(ctx, entityIDs) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// RetrieveRole provides a mock function with given fields: ctx, roleID +func (_m *Repository) RetrieveRole(ctx context.Context, roleID string) (roles.Role, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (roles.Role, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) roles.Role); ok { + r0 = rf(ctx, roleID) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveRoleByEntityIDAndName provides a mock function with given fields: ctx, entityID, roleName +func (_m *Repository) RetrieveRoleByEntityIDAndName(ctx context.Context, entityID string, roleName string) (roles.Role, error) { + ret := _m.Called(ctx, entityID, roleName) + + if len(ret) == 0 { + panic("no return value specified for RetrieveRoleByEntityIDAndName") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (roles.Role, error)); ok { + return rf(ctx, entityID, roleName) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) roles.Role); ok { + r0 = rf(ctx, entityID, roleName) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, entityID, roleName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleAddActions(ctx context.Context, role roles.Role, actions []string) ([]string, error) { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleAddActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, actions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleAddMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleAddMembers(ctx context.Context, role roles.Role, members []string) ([]string, error) { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleAddMembers") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) ([]string, error)); ok { + return rf(ctx, role, members) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) []string); ok { + r0 = rf(ctx, role, members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role, []string) error); ok { + r1 = rf(ctx, role, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckActionsExists provides a mock function with given fields: ctx, roleID, actions +func (_m *Repository) RoleCheckActionsExists(ctx context.Context, roleID string, actions []string) (bool, error) { + ret := _m.Called(ctx, roleID, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckActionsExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, actions) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, actions) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, actions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleCheckMembersExists provides a mock function with given fields: ctx, roleID, members +func (_m *Repository) RoleCheckMembersExists(ctx context.Context, roleID string, members []string) (bool, error) { + ret := _m.Called(ctx, roleID, members) + + if len(ret) == 0 { + panic("no return value specified for RoleCheckMembersExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) (bool, error)); ok { + return rf(ctx, roleID, members) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []string) bool); ok { + r0 = rf(ctx, roleID, members) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { + r1 = rf(ctx, roleID, members) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListActions provides a mock function with given fields: ctx, roleID +func (_m *Repository) RoleListActions(ctx context.Context, roleID string) ([]string, error) { + ret := _m.Called(ctx, roleID) + + if len(ret) == 0 { + panic("no return value specified for RoleListActions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { + return rf(ctx, roleID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []string); ok { + r0 = rf(ctx, roleID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, roleID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleListMembers provides a mock function with given fields: ctx, roleID, limit, offset +func (_m *Repository) RoleListMembers(ctx context.Context, roleID string, limit uint64, offset uint64) (roles.MembersPage, error) { + ret := _m.Called(ctx, roleID, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for RoleListMembers") + } + + var r0 roles.MembersPage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (roles.MembersPage, error)); ok { + return rf(ctx, roleID, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) roles.MembersPage); ok { + r0 = rf(ctx, roleID, limit, offset) + } else { + r0 = ret.Get(0).(roles.MembersPage) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, roleID, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RoleRemoveActions provides a mock function with given fields: ctx, role, actions +func (_m *Repository) RoleRemoveActions(ctx context.Context, role roles.Role, actions []string) error { + ret := _m.Called(ctx, role, actions) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, actions) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllActions provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllActions(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllActions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveAllMembers provides a mock function with given fields: ctx, role +func (_m *Repository) RoleRemoveAllMembers(ctx context.Context, role roles.Role) error { + ret := _m.Called(ctx, role) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveAllMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) error); ok { + r0 = rf(ctx, role) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RoleRemoveMembers provides a mock function with given fields: ctx, role, members +func (_m *Repository) RoleRemoveMembers(ctx context.Context, role roles.Role, members []string) error { + ret := _m.Called(ctx, role, members) + + if len(ret) == 0 { + panic("no return value specified for RoleRemoveMembers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role, []string) error); ok { + r0 = rf(ctx, role, members) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateRole provides a mock function with given fields: ctx, ro +func (_m *Repository) UpdateRole(ctx context.Context, ro roles.Role) (roles.Role, error) { + ret := _m.Called(ctx, ro) + + if len(ret) == 0 { + panic("no return value specified for UpdateRole") + } + + var r0 roles.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) (roles.Role, error)); ok { + return rf(ctx, ro) + } + if rf, ok := ret.Get(0).(func(context.Context, roles.Role) roles.Role); ok { + r0 = rf(ctx, ro) + } else { + r0 = ret.Get(0).(roles.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, roles.Role) error); ok { + r1 = rf(ctx, ro) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *Repository { + mock := &Repository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/roles/provisionmanage.go b/pkg/roles/provisionmanage.go new file mode 100644 index 0000000000..ee19fca4b4 --- /dev/null +++ b/pkg/roles/provisionmanage.go @@ -0,0 +1,630 @@ +package roles + +import ( + "context" + "fmt" + "time" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" +) + +var ( + errRemoveOptionalDeletePolicies = errors.New("failed to delete the additional requested policies") + errRemoveOptionalFilterDeletePolicies = errors.New("failed to filter delete the additional requested policies") + errRollbackRoles = errors.New("failed to rollback roles") +) + +type roleProvisionerManger interface { + RoleManager + Provisioner +} + +var _ roleProvisionerManger = (*ProvisionManageService)(nil) + +type ProvisionManageService struct { + entityType string + repo Repository + sidProvider magistrala.IDProvider + policy policies.Service + actions []Action + builtInRoles map[BuiltInRoleName][]Action +} + +func NewProvisionManageService(entityType string, repo Repository, policy policies.Service, sidProvider magistrala.IDProvider, actions []Action, builtInRoles map[BuiltInRoleName][]Action) (ProvisionManageService, error) { + rm := ProvisionManageService{ + entityType: entityType, + repo: repo, + sidProvider: sidProvider, + policy: policy, + actions: actions, + builtInRoles: builtInRoles, + } + return rm, nil +} + +func toRolesActions(actions []string) []Action { + roActions := []Action{} + for _, action := range actions { + roActions = append(roActions, Action(action)) + } + return roActions +} + +func roleActionsToString(roActions []Action) []string { + actions := []string{} + for _, roAction := range roActions { + actions = append(actions, roAction.String()) + } + return actions +} + +func roleMembersToString(roMems []Member) []string { + mems := []string{} + for _, roMem := range roMems { + mems = append(mems, roMem.String()) + } + return mems +} + +func (r ProvisionManageService) isActionAllowed(action Action) bool { + for _, cap := range r.actions { + if cap == action { + return true + } + } + return false +} +func (r ProvisionManageService) validateActions(actions []Action) error { + for _, ac := range actions { + action := Action(ac) + if !r.isActionAllowed(action) { + return errors.Wrap(svcerr.ErrMalformedEntity, fmt.Errorf("invalid action %s ", action)) + } + } + return nil +} + +func (r ProvisionManageService) RemoveEntitiesRoles(ctx context.Context, domainID, userID string, entityIDs []string, optionalFilterDeletePolicies []policies.Policy, optionalDeletePolicies []policies.Policy) error { + ears, emrs, err := r.repo.RetrieveEntitiesRolesActionsMembers(ctx, entityIDs) + if err != nil { + return err + } + + deletePolicies := []policies.Policy{} + for _, ear := range ears { + deletePolicies = append(deletePolicies, policies.Policy{ + Subject: ear.RoleID, + SubjectRelation: policies.MemberRelation, + SubjectType: policies.RoleType, + Relation: ear.Action, + ObjectType: r.entityType, + Object: ear.EntityID, + }) + } + for _, emr := range emrs { + deletePolicies = append(deletePolicies, policies.Policy{ + Subject: policies.EncodeDomainUserID(domainID, emr.MemberID), + SubjectType: policies.UserType, + Relation: policies.MemberRelation, + ObjectType: policies.RoleType, + Object: emr.RoleID, + }) + } + + if err := r.policy.DeletePolicies(ctx, deletePolicies); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + if len(optionalDeletePolicies) > 1 { + if err := r.policy.DeletePolicies(ctx, optionalDeletePolicies); err != nil { + return errors.Wrap(errRemoveOptionalDeletePolicies, err) + } + } + + for _, optionalFilterDeletePolicy := range optionalFilterDeletePolicies { + if err := r.policy.DeletePolicyFilter(ctx, optionalFilterDeletePolicy); err != nil { + return errors.Wrap(errRemoveOptionalFilterDeletePolicies, err) + } + } + return nil +} + +func (r ProvisionManageService) AddNewEntitiesRoles(ctx context.Context, domainID, userID string, entityIDs []string, optionalEntityPolicies []policies.Policy, newBuiltInRoleMembers map[BuiltInRoleName][]Member) (retRolesProvision []RoleProvision, retErr error) { + var newRolesProvision []RoleProvision + prs := []policies.Policy{} + + for _, entityID := range entityIDs { + for defaultRole, defaultRoleMembers := range newBuiltInRoleMembers { + actions, ok := r.builtInRoles[defaultRole] + if !ok { + return []RoleProvision{}, fmt.Errorf("default role %s not found in in-built roles", defaultRole) + } + + // There an option to have id as entityID_roleName where in roleName all space are removed with _ and starts with letter and supports only alphanumeric, space and hyphen + sid, err := r.sidProvider.ID() + if err != nil { + return []RoleProvision{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + + id := r.entityType + "_" + sid + if err := r.validateActions(actions); err != nil { + return []RoleProvision{}, errors.Wrap(svcerr.ErrMalformedEntity, err) + } + + members := roleMembersToString(defaultRoleMembers) + caps := roleActionsToString(actions) + + newRolesProvision = append(newRolesProvision, RoleProvision{ + Role: Role{ + ID: id, + Name: defaultRole.String(), + EntityID: entityID, + CreatedAt: time.Now(), + CreatedBy: userID, + }, + OptionalActions: caps, + OptionalMembers: members, + }) + + for _, cap := range caps { + prs = append(prs, policies.Policy{ + SubjectType: policies.RoleType, + SubjectRelation: policies.MemberRelation, + Subject: id, + Relation: cap, + Object: entityID, + ObjectType: r.entityType, + }) + } + + for _, member := range members { + prs = append(prs, policies.Policy{ + SubjectType: policies.UserType, + Subject: policies.EncodeDomainUserID(domainID, member), + Relation: policies.MemberRelation, + Object: id, + ObjectType: policies.RoleType, + }) + } + } + } + prs = append(prs, optionalEntityPolicies...) + + if len(prs) > 0 { + if err := r.policy.AddPolicies(ctx, prs); err != nil { + return []RoleProvision{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + defer func() { + if retErr != nil { + if errRollBack := r.policy.DeletePolicies(ctx, prs); errRollBack != nil { + retErr = errors.Wrap(retErr, errors.Wrap(errRollbackRoles, errRollBack)) + } + } + }() + } + + if _, err := r.repo.AddRoles(ctx, newRolesProvision); err != nil { + return []RoleProvision{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + + return newRolesProvision, nil +} + +func (r ProvisionManageService) AddRole(ctx context.Context, session authn.Session, entityID string, roleName string, optionalActions []string, optionalMembers []string) (retRole Role, retErr error) { + + // ToDo: Research: Discuss: There an option to have id as eentityType_entityID_roleName where in roleName all space are removed with _ and starts with letter and supports only alphanumeric, space and hyphen + sid, err := r.sidProvider.ID() + if err != nil { + return Role{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + + id := r.entityType + "_" + sid + + if err := r.validateActions(toRolesActions(optionalActions)); err != nil { + return Role{}, errors.Wrap(svcerr.ErrMalformedEntity, err) + } + + newRoleProvisions := []RoleProvision{ + { + Role: Role{ + ID: id, + Name: roleName, + EntityID: entityID, + CreatedAt: time.Now(), + CreatedBy: session.UserID, + }, + OptionalActions: optionalActions, + OptionalMembers: optionalMembers, + }, + } + prs := []policies.Policy{} + + for _, cap := range optionalActions { + prs = append(prs, policies.Policy{ + SubjectType: policies.RoleType, + SubjectRelation: policies.MemberRelation, + Subject: id, + Relation: cap, + Object: entityID, + ObjectType: r.entityType, + }) + } + + for _, member := range optionalMembers { + prs = append(prs, policies.Policy{ + SubjectType: policies.UserType, + Subject: policies.EncodeDomainUserID(session.DomainID, member), + Relation: policies.MemberRelation, + Object: id, + ObjectType: policies.RoleType, + }) + } + + if len(prs) > 0 { + if err := r.policy.AddPolicies(ctx, prs); err != nil { + return Role{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + + defer func() { + if retErr != nil { + if errRollBack := r.policy.DeletePolicies(ctx, prs); errRollBack != nil { + retErr = errors.Wrap(retErr, errors.Wrap(errRollbackRoles, errRollBack)) + } + } + }() + } + + newRoles, err := r.repo.AddRoles(context.Background(), newRoleProvisions) + if err != nil { + return Role{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + + if len(newRoles) == 0 { + return Role{}, svcerr.ErrCreateEntity + } + + return newRoles[0], nil +} + +func (r ProvisionManageService) RemoveRole(ctx context.Context, session authn.Session, entityID, roleName string) error { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + req := policies.Policy{ + SubjectType: policies.RoleType, + Subject: ro.ID, + } + // ToDo: Add Role as Object in DeletePolicyFilter + if err := r.policy.DeletePolicyFilter(ctx, req); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + if err := r.repo.RemoveRoles(ctx, []string{ro.ID}); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + return nil +} + +func (r ProvisionManageService) UpdateRoleName(ctx context.Context, session authn.Session, entityID, oldRoleName, newRoleName string) (Role, error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, oldRoleName) + if err != nil { + return Role{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + ro, err = r.repo.UpdateRole(ctx, Role{ + ID: ro.ID, + EntityID: entityID, + Name: newRoleName, + UpdatedBy: session.UserID, + UpdatedAt: time.Now(), + }) + if err != nil { + return Role{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + return ro, nil +} + +func (r ProvisionManageService) RetrieveRole(ctx context.Context, session authn.Session, entityID, roleName string) (Role, error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return Role{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + return ro, nil +} + +func (r ProvisionManageService) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit, offset uint64) (RolePage, error) { + ros, err := r.repo.RetrieveAllRoles(ctx, entityID, limit, offset) + if err != nil { + return RolePage{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + return ros, nil +} + +func (r ProvisionManageService) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + acts := []string{} + for _, a := range r.actions { + acts = append(acts, string(a)) + } + return acts, nil +} + +func (r ProvisionManageService) RoleAddActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (retActs []string, retErr error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return []string{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + + if len(actions) == 0 { + return []string{}, svcerr.ErrMalformedEntity + } + + if err := r.validateActions(toRolesActions(actions)); err != nil { + return []string{}, errors.Wrap(svcerr.ErrMalformedEntity, err) + } + + prs := []policies.Policy{} + for _, cap := range actions { + prs = append(prs, policies.Policy{ + SubjectType: policies.RoleType, + SubjectRelation: policies.MemberRelation, + Subject: ro.ID, + Relation: cap, + Object: entityID, + ObjectType: r.entityType, + }) + } + + if err := r.policy.AddPolicies(ctx, prs); err != nil { + return []string{}, errors.Wrap(svcerr.ErrAddPolicies, err) + } + + defer func() { + if retErr != nil { + if errRollBack := r.policy.DeletePolicies(ctx, prs); errRollBack != nil { + retErr = errors.Wrap(retErr, errors.Wrap(errRollbackRoles, errRollBack)) + } + } + }() + + ro.UpdatedAt = time.Now() + ro.UpdatedBy = session.UserID + + resActs, err := r.repo.RoleAddActions(ctx, ro, actions) + if err != nil { + return []string{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + return resActs, nil +} + +func (r ProvisionManageService) RoleListActions(ctx context.Context, session authn.Session, entityID, roleName string) ([]string, error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return []string{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + + acts, err := r.repo.RoleListActions(ctx, ro.ID) + if err != nil { + return []string{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + return acts, nil + +} + +func (r ProvisionManageService) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (bool, error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return false, errors.Wrap(svcerr.ErrViewEntity, err) + } + + result, err := r.repo.RoleCheckActionsExists(ctx, ro.ID, actions) + if err != nil { + return true, errors.Wrap(svcerr.ErrViewEntity, err) + } + return result, nil +} + +func (r ProvisionManageService) RoleRemoveActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (err error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + if len(actions) == 0 { + return svcerr.ErrMalformedEntity + } + + prs := []policies.Policy{} + for _, op := range actions { + prs = append(prs, policies.Policy{ + SubjectType: policies.RoleType, + SubjectRelation: policies.MemberRelation, + Subject: ro.ID, + Relation: op, + Object: entityID, + ObjectType: r.entityType, + }) + } + + if err := r.policy.DeletePolicies(ctx, prs); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + ro.UpdatedAt = time.Now() + ro.UpdatedBy = session.UserID + if err := r.repo.RoleRemoveActions(ctx, ro, actions); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + return nil +} + +func (r ProvisionManageService) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID, roleName string) error { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + prs := policies.Policy{ + SubjectType: policies.RoleType, + Subject: ro.ID, + } + + if err := r.policy.DeletePolicyFilter(ctx, prs); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + ro.UpdatedAt = time.Now() + ro.UpdatedBy = session.UserID + + if err := r.repo.RoleRemoveAllActions(ctx, ro); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + return nil +} + +func (r ProvisionManageService) RoleAddMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (retMems []string, retErr error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return []string{}, errors.Wrap(svcerr.ErrUpdateEntity, err) + } + + if len(members) == 0 { + return []string{}, svcerr.ErrMalformedEntity + } + + prs := []policies.Policy{} + for _, mem := range members { + prs = append(prs, policies.Policy{ + SubjectType: policies.UserType, + Subject: policies.EncodeDomainUserID(session.DomainID, mem), + Relation: policies.MemberRelation, + Object: ro.ID, + ObjectType: policies.RoleType, + }) + } + + if err := r.policy.AddPolicies(ctx, prs); err != nil { + return []string{}, errors.Wrap(svcerr.ErrAddPolicies, err) + } + + defer func() { + if retErr != nil { + if errRollBack := r.policy.DeletePolicies(ctx, prs); errRollBack != nil { + retErr = errors.Wrap(retErr, errors.Wrap(errRollbackRoles, errRollBack)) + } + } + }() + + ro.UpdatedAt = time.Now() + ro.UpdatedBy = session.UserID + + mems, err := r.repo.RoleAddMembers(ctx, ro, members) + if err != nil { + return []string{}, errors.Wrap(svcerr.ErrCreateEntity, err) + } + return mems, nil +} + +func (r ProvisionManageService) RoleListMembers(ctx context.Context, session authn.Session, entityID, roleName string, limit, offset uint64) (MembersPage, error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + + mp, err := r.repo.RoleListMembers(ctx, ro.ID, limit, offset) + if err != nil { + return MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) + } + return mp, nil +} + +func (r ProvisionManageService) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (bool, error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return false, errors.Wrap(svcerr.ErrViewEntity, err) + } + + result, err := r.repo.RoleCheckMembersExists(ctx, ro.ID, members) + if err != nil { + return true, errors.Wrap(svcerr.ErrViewEntity, err) + } + return result, nil +} + +func (r ProvisionManageService) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (err error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + if len(members) == 0 { + return svcerr.ErrMalformedEntity + } + + prs := []policies.Policy{} + for _, mem := range members { + prs = append(prs, policies.Policy{ + SubjectType: policies.UserType, + Subject: policies.EncodeDomainUserID(session.DomainID, mem), + Relation: policies.MemberRelation, + Object: ro.ID, + ObjectType: policies.RoleType, + }) + } + + if err := r.policy.DeletePolicies(ctx, prs); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + ro.UpdatedAt = time.Now() + // ro.UpdatedBy = userID + if err := r.repo.RoleRemoveMembers(ctx, ro, members); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + return nil +} + +func (r ProvisionManageService) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleName string) (err error) { + ro, err := r.repo.RetrieveRoleByEntityIDAndName(ctx, entityID, roleName) + if err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + prs := policies.Policy{ + ObjectType: policies.RoleType, + Object: ro.ID, + SubjectType: policies.UserType, + } + + if err := r.policy.DeletePolicyFilter(ctx, prs); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + ro.UpdatedAt = time.Now() + ro.UpdatedBy = session.UserID + + if err := r.repo.RoleRemoveAllMembers(ctx, ro); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + return nil +} + +func (r ProvisionManageService) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, member string) (err error) { + if err := r.repo.RemoveMemberFromAllRoles(ctx, member); err != nil { + return errors.Wrap(svcerr.ErrRemoveEntity, err) + } + + prs := policies.Policy{ + ObjectType: policies.RoleType, + ObjectPrefix: r.entityType + "_", + SubjectType: policies.UserType, + } + + if err := r.policy.DeletePolicyFilter(ctx, prs); err != nil { + return errors.Wrap(svcerr.ErrDeletePolicies, err) + } + + return fmt.Errorf("not implemented") +} diff --git a/pkg/roles/repo/doc.go b/pkg/roles/repo/doc.go new file mode 100644 index 0000000000..13812d96ca --- /dev/null +++ b/pkg/roles/repo/doc.go @@ -0,0 +1,4 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package repo diff --git a/pkg/roles/repo/postgres/doc.go b/pkg/roles/repo/postgres/doc.go new file mode 100644 index 0000000000..2112922075 --- /dev/null +++ b/pkg/roles/repo/postgres/doc.go @@ -0,0 +1,4 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres diff --git a/pkg/roles/repo/postgres/init.go b/pkg/roles/repo/postgres/init.go new file mode 100644 index 0000000000..8c1727b299 --- /dev/null +++ b/pkg/roles/repo/postgres/init.go @@ -0,0 +1,63 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "fmt" + + _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access + migrate "github.com/rubenv/sql-migrate" +) + +// Migration of Auth service. +func Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName string) (*migrate.MemoryMigrationSource, error) { + + // ToDo: need to add check in database to check table exists and column exits as primary key. For this Migration function need database. + // ToDo: Add entity name in all table prefix. This helps when all entities uses same database + // ToDo: Add table name prefix option in service and repo. So each entity can have its own tables in same database + if entityTableName == "" || entityIDColumnName == "" { + return nil, fmt.Errorf("invalid entity Table Name or column name") + } + + return &migrate.MemoryMigrationSource{ + Migrations: []*migrate.Migration{ + { + Id: fmt.Sprintf("%s_roles_1", rolesTableNamePrefix), + Up: []string{ + fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s_roles ( + id VARCHAR(254) NOT NULL PRIMARY KEY, + name varchar(200) NOT NULL, + entity_id VARCHAR(36) NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + updated_by VARCHAR(254), + created_by VARCHAR(254), + CONSTRAINT unique_role_name_entity_id_constraint UNIQUE ( name, entity_id), + CONSTRAINT fk_entity_id FOREIGN KEY(entity_id) REFERENCES %s(%s) ON DELETE CASCADE + );`, rolesTableNamePrefix, entityTableName, entityIDColumnName), + + fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s_role_actions ( + role_id VARCHAR(254) NOT NULL, + action VARCHAR(254) NOT NULL, + CONSTRAINT unique_domain_role_action_constraint UNIQUE ( role_id, action), + CONSTRAINT fk_%s_roles_id FOREIGN KEY(role_id) REFERENCES %s_roles(id) ON DELETE CASCADE + + );`, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix), + + fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s_role_members ( + role_id VARCHAR(254) NOT NULL, + member_id VARCHAR(254) NOT NULL, + CONSTRAINT unique_role_member_constraint UNIQUE (role_id, member_id), + CONSTRAINT fk_%s_roles_id FOREIGN KEY(role_id) REFERENCES %s_roles(id) ON DELETE CASCADE + );`, rolesTableNamePrefix, rolesTableNamePrefix, rolesTableNamePrefix), + }, + Down: []string{ + fmt.Sprintf(`DROP TABLE IF EXISTS %s_roles`, rolesTableNamePrefix), + fmt.Sprintf(`DROP TABLE IF EXISTS %s_roles_actions`, rolesTableNamePrefix), + fmt.Sprintf(`DROP TABLE IF EXISTS %s_roles_members`, rolesTableNamePrefix), + }, + }, + }, + }, nil +} diff --git a/pkg/roles/repo/postgres/roles.go b/pkg/roles/repo/postgres/roles.go new file mode 100644 index 0000000000..2ad149cfb2 --- /dev/null +++ b/pkg/roles/repo/postgres/roles.go @@ -0,0 +1,772 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + "github.com/absmach/magistrala/pkg/postgres" + "github.com/absmach/magistrala/pkg/roles" +) + +var _ roles.Repository = (*Repository)(nil) + +type Repository struct { + db postgres.Database + tableNamePrefix string + entityTableName string + entityIDColumnName string +} + +// NewRepository instantiates a PostgreSQL +// implementation of Roles repository. +func NewRepository(db postgres.Database, tableNamePrefix, entityTableName, entityIDColumnName string) Repository { + return Repository{ + db: db, + tableNamePrefix: tableNamePrefix, + entityTableName: entityTableName, + entityIDColumnName: entityIDColumnName, + } +} + +type dbPage struct { + ID string `db:"id"` + Name string `db:"name"` + EntityID string `db:"entity_id"` + RoleID string `db:"role_id"` + Limit uint64 `db:"limit"` + Offset uint64 `db:"offset"` +} +type dbRole struct { + ID string `db:"id"` + Name string `db:"name"` + EntityID string `db:"entity_id"` + CreatedBy *string `db:"created_by"` + CreatedAt sql.NullTime `db:"created_at"` + UpdatedBy *string `db:"updated_by"` + UpdatedAt sql.NullTime `db:"updated_at"` +} + +type dbEntityActionRole struct { + EntityID string `db:"entity_id"` + Action string `db:"action"` + RoleID string `db:"role_id"` +} +type dbEntityMemberRole struct { + EntityID string `db:"entity_id"` + MemberID string `db:"member_id"` + RoleID string `db:"role_id"` +} + +func dbToEntityActionRole(dbs []dbEntityActionRole) []roles.EntityActionRole { + var r []roles.EntityActionRole + for _, d := range dbs { + r = append(r, roles.EntityActionRole{ + EntityID: d.EntityID, + Action: d.Action, + RoleID: d.RoleID, + }) + } + return r +} + +func dbToEntityMemberRole(dbs []dbEntityMemberRole) []roles.EntityMemberRole { + var r []roles.EntityMemberRole + for _, d := range dbs { + r = append(r, roles.EntityMemberRole{ + EntityID: d.EntityID, + MemberID: d.MemberID, + RoleID: d.RoleID, + }) + } + return r +} + +type dbRoleAction struct { + RoleID string `db:"role_id"` + Action string `db:"action"` +} + +type dbRoleMember struct { + RoleID string `db:"role_id"` + MemberID string `db:"member_id"` +} + +func toDBRoles(role roles.Role) dbRole { + var createdBy *string + if role.CreatedBy != "" { + createdBy = &role.UpdatedBy + } + var createdAt sql.NullTime + if role.CreatedAt != (time.Time{}) && !role.CreatedAt.IsZero() { + createdAt = sql.NullTime{Time: role.CreatedAt, Valid: true} + } + + var updatedBy *string + if role.UpdatedBy != "" { + updatedBy = &role.UpdatedBy + } + var updatedAt sql.NullTime + if role.UpdatedAt != (time.Time{}) && !role.UpdatedAt.IsZero() { + updatedAt = sql.NullTime{Time: role.UpdatedAt, Valid: true} + } + + return dbRole{ + ID: role.ID, + Name: role.Name, + EntityID: role.EntityID, + CreatedBy: createdBy, + CreatedAt: createdAt, + UpdatedBy: updatedBy, + UpdatedAt: updatedAt, + } +} + +func toRole(r dbRole) roles.Role { + + var createdBy string + if r.CreatedBy != nil { + createdBy = *r.CreatedBy + } + var createdAt time.Time + if r.CreatedAt.Valid { + createdAt = r.CreatedAt.Time + } + + var updatedBy string + if r.UpdatedBy != nil { + updatedBy = *r.UpdatedBy + } + var updatedAt time.Time + if r.UpdatedAt.Valid { + updatedAt = r.UpdatedAt.Time + } + + return roles.Role{ + ID: r.ID, + Name: r.Name, + EntityID: r.EntityID, + CreatedBy: createdBy, + CreatedAt: createdAt, + UpdatedBy: updatedBy, + UpdatedAt: updatedAt, + } + +} +func (repo *Repository) AddRoles(ctx context.Context, rps []roles.RoleProvision) ([]roles.Role, error) { + + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return []roles.Role{}, errors.Wrap(repoerr.ErrCreateEntity, err) + } + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(errors.Wrap(apiutil.ErrRollbackTx, errRollback), err) + } + } + }() + + var retRoles []roles.Role + + for _, rp := range rps { + + q := fmt.Sprintf(`INSERT INTO %s_roles (id, name, entity_id, created_by, created_at, updated_by, updated_at) + VALUES (:id, :name, :entity_id, :created_by, :created_at, :updated_by, :updated_at);`, repo.tableNamePrefix) + + if _, err := tx.NamedExec(q, toDBRoles(rp.Role)); err != nil { + return []roles.Role{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + retRoles = append(retRoles, rp.Role) + + if len(rp.OptionalActions) > 0 { + capq := fmt.Sprintf(`INSERT INTO %s_role_actions (role_id, action) + VALUES (:role_id, :action) + RETURNING role_id, action`, repo.tableNamePrefix) + + rCaps := []dbRoleAction{} + for _, cap := range rp.OptionalActions { + rCaps = append(rCaps, dbRoleAction{ + RoleID: rp.ID, + Action: string(cap), + }) + } + if _, err := tx.NamedExec(capq, rCaps); err != nil { + return []roles.Role{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + } + + if len(rp.OptionalMembers) > 0 { + mq := fmt.Sprintf(`INSERT INTO %s_role_members (role_id, member_id) + VALUES (:role_id, :member_id) + RETURNING role_id, member_id`, repo.tableNamePrefix) + + rMems := []dbRoleMember{} + for _, m := range rp.OptionalMembers { + rMems = append(rMems, dbRoleMember{ + RoleID: rp.ID, + MemberID: m, + }) + } + if _, err := tx.NamedExec(mq, rMems); err != nil { + return []roles.Role{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + } + } + + if err := tx.Commit(); err != nil { + return []roles.Role{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + return retRoles, nil +} + +func (repo *Repository) RemoveRoles(ctx context.Context, roleIDs []string) error { + q := fmt.Sprintf("DELETE FROM %s_roles WHERE id = ANY(:role_ids) ;", repo.tableNamePrefix) + + params := map[string]interface{}{ + "role_ids": roleIDs, + } + result, err := repo.db.NamedExecContext(ctx, q, params) + if err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + if rows, _ := result.RowsAffected(); rows == 0 { + return repoerr.ErrNotFound + } + + return nil +} + +// Update only role name, don't update ID +func (repo *Repository) UpdateRole(ctx context.Context, role roles.Role) (roles.Role, error) { + var query []string + var upq string + if role.Name != "" { + query = append(query, "name = :name,") + } + + if len(query) > 0 { + upq = strings.Join(query, " ") + } + + q := fmt.Sprintf(`UPDATE %s_roles SET %s updated_at = :updated_at, updated_by = :updated_by + WHERE id = :id + RETURNING id, name, entity_id, created_by, created_at, updated_by, updated_at`, + repo.tableNamePrefix, upq) + + row, err := repo.db.NamedQueryContext(ctx, q, toDBRoles(role)) + + if err != nil { + return roles.Role{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) + } + defer row.Close() + + dbr := dbRole{} + if row.Next() { + if err := row.StructScan(&dbr); err != nil { + return roles.Role{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + } + return toRole(dbr), nil + } + + return roles.Role{}, repoerr.ErrNotFound +} + +func (repo *Repository) RetrieveRole(ctx context.Context, roleID string) (roles.Role, error) { + q := fmt.Sprintf(`SELECT id, name, entity_id, created_by, created_at, updated_by, updated_at + FROM %s_roles WHERE id = :id`, repo.tableNamePrefix) + + dbr := dbRole{ + ID: roleID, + } + + rows, err := repo.db.NamedQueryContext(ctx, q, dbr) + if err != nil { + return roles.Role{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + dbr = dbRole{} + if rows.Next() { + if err = rows.StructScan(&dbr); err != nil { + return roles.Role{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + return toRole(dbr), nil + } + + return roles.Role{}, repoerr.ErrNotFound +} + +func (repo *Repository) RetrieveRoleByEntityIDAndName(ctx context.Context, entityID, roleName string) (roles.Role, error) { + q := fmt.Sprintf(`SELECT id, name, entity_id, created_by, created_at, updated_by, updated_at + FROM %s_roles WHERE entity_id = :entity_id and name = :name`, repo.tableNamePrefix) + + dbr := dbRole{ + EntityID: entityID, + Name: roleName, + } + + rows, err := repo.db.NamedQueryContext(ctx, q, dbr) + if err != nil { + return roles.Role{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + dbr = dbRole{} + if rows.Next() { + if err = rows.StructScan(&dbr); err != nil { + return roles.Role{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + return toRole(dbr), nil + } + + return roles.Role{}, repoerr.ErrNotFound +} +func (repo *Repository) RetrieveAllRoles(ctx context.Context, entityID string, limit, offset uint64) (roles.RolePage, error) { + q := fmt.Sprintf(`SELECT id, name, entity_id, created_by, created_at, updated_by, updated_at + FROM %s_roles WHERE entity_id = :entity_id ORDER BY created_at LIMIT :limit OFFSET :offset;`, repo.tableNamePrefix) + + dbp := dbPage{ + EntityID: entityID, + Limit: limit, + Offset: offset, + } + + rows, err := repo.db.NamedQueryContext(ctx, q, dbp) + if err != nil { + return roles.RolePage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + items := []roles.Role{} + for rows.Next() { + dbr := dbRole{} + if err := rows.StructScan(&dbr); err != nil { + return roles.RolePage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + items = append(items, toRole(dbr)) + } + cq := fmt.Sprintf(`SELECT COUNT(*) FROM %s_roles WHERE entity_id = :entity_id`, repo.tableNamePrefix) + + total, err := postgres.Total(ctx, repo.db, cq, dbp) + if err != nil { + return roles.RolePage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + page := roles.RolePage{ + Roles: items, + Total: total, + Offset: offset, + Limit: limit, + } + + return page, nil +} + +func (repo *Repository) RoleAddActions(ctx context.Context, role roles.Role, actions []string) (caps []string, err error) { + + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return []string{}, errors.Wrap(repoerr.ErrCreateEntity, err) + } + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(errors.Wrap(apiutil.ErrRollbackTx, errRollback), err) + } + } + }() + + capq := fmt.Sprintf(`INSERT INTO %s_role_actions (role_id, action) + VALUES (:role_id, :action) + RETURNING role_id, action`, repo.tableNamePrefix) + + rCaps := []dbRoleAction{} + for _, cap := range actions { + rCaps = append(rCaps, dbRoleAction{ + RoleID: role.ID, + Action: string(cap), + }) + } + if _, err := tx.NamedExecContext(ctx, capq, rCaps); err != nil { + return []string{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + upq := fmt.Sprintf(`UPDATE %s_roles SET updated_at = :updated_at, updated_by = :updated_by WHERE id = :id;`, repo.tableNamePrefix) + if _, err := tx.NamedExecContext(ctx, upq, toDBRoles(role)); err != nil { + return []string{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + if err := tx.Commit(); err != nil { + return []string{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + return repo.RoleListActions(ctx, role.ID) +} + +func (repo *Repository) RoleListActions(ctx context.Context, roleID string) ([]string, error) { + q := fmt.Sprintf(`SELECT role_id, action FROM %s_role_actions WHERE role_id = :role_id ;`, repo.tableNamePrefix) + + dbrcap := dbRoleAction{ + RoleID: roleID, + } + + rows, err := repo.db.NamedQueryContext(ctx, q, dbrcap) + if err != nil { + return []string{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + items := []string{} + for rows.Next() { + dbrcap = dbRoleAction{} + if err := rows.StructScan(&dbrcap); err != nil { + return []string{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + items = append(items, dbrcap.Action) + } + return items, nil +} + +func (repo *Repository) RoleCheckActionsExists(ctx context.Context, roleID string, actions []string) (bool, error) { + q := fmt.Sprintf(`SELECT COUNT(*) FROM %s_role_actions WHERE role_id = :role_id AND action IN (:actions)`, repo.tableNamePrefix) + + params := map[string]interface{}{ + "role_id": roleID, + "actions": actions, + } + var count int + query, err := repo.db.NamedQueryContext(ctx, q, params) + if err != nil { + return false, errors.Wrap(repoerr.ErrViewEntity, err) + } + + defer query.Close() + + if query.Next() { + if err := query.Scan(&count); err != nil { + return false, errors.Wrap(repoerr.ErrViewEntity, err) + } + } + + // Check if the count matches the number of actions provided + if count != len(actions) { + return false, nil + } + + return true, nil +} + +func (repo *Repository) RoleRemoveActions(ctx context.Context, role roles.Role, actions []string) (err error) { + + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(errors.Wrap(apiutil.ErrRollbackTx, errRollback), err) + } + } + }() + + q := fmt.Sprintf(`DELETE FROM %s_role_actions WHERE role_id = :role_id AND action = ANY(:actions)`, repo.tableNamePrefix) + + params := map[string]interface{}{ + "role_id": role.ID, + "actions": actions, + } + + if _, err := tx.NamedExec(q, params); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + + upq := fmt.Sprintf(`UPDATE %s_roles SET updated_at = :updated_at, updated_by = :updated_by WHERE id = :id;`, repo.tableNamePrefix) + if _, err := tx.NamedExec(upq, toDBRoles(role)); err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + + if err := tx.Commit(); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + + return nil +} + +func (repo *Repository) RoleRemoveAllActions(ctx context.Context, role roles.Role) error { + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(errors.Wrap(apiutil.ErrRollbackTx, errRollback), err) + } + } + }() + + q := fmt.Sprintf(`DELETE FROM %s_role_actions WHERE role_id = :role_id `, repo.tableNamePrefix) + + dbrcap := dbRoleAction{RoleID: role.ID} + + if _, err := tx.NamedExec(q, dbrcap); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + + upq := fmt.Sprintf(`UPDATE %s_roles SET updated_at = :updated_at, updated_by = :updated_by WHERE id = :id;`, repo.tableNamePrefix) + if _, err := tx.NamedExec(upq, toDBRoles(role)); err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + + if err := tx.Commit(); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + + return nil +} + +func (repo *Repository) RoleAddMembers(ctx context.Context, role roles.Role, members []string) ([]string, error) { + mq := fmt.Sprintf(`INSERT INTO %s_role_members (role_id, member_id) + VALUES (:role_id, :member_id) + RETURNING role_id, member_id`, repo.tableNamePrefix) + + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return []string{}, errors.Wrap(repoerr.ErrCreateEntity, err) + } + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(errors.Wrap(apiutil.ErrRollbackTx, errRollback), err) + } + } + }() + + rMems := []dbRoleMember{} + for _, m := range members { + rMems = append(rMems, dbRoleMember{ + RoleID: role.ID, + MemberID: m, + }) + } + if _, err := tx.NamedExec(mq, rMems); err != nil { + return []string{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + upq := fmt.Sprintf(`UPDATE %s_roles SET updated_at = :updated_at, updated_by = :updated_by WHERE id = :id;`, repo.tableNamePrefix) + if _, err := tx.NamedExec(upq, toDBRoles(role)); err != nil { + return []string{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + if err := tx.Commit(); err != nil { + return []string{}, postgres.HandleError(repoerr.ErrCreateEntity, err) + } + + return members, nil +} + +func (repo *Repository) RoleListMembers(ctx context.Context, roleID string, limit, offset uint64) (roles.MembersPage, error) { + q := fmt.Sprintf(`SELECT role_id, member_id FROM %s_role_members WHERE role_id = :role_id LIMIT :limit OFFSET :offset;`, repo.tableNamePrefix) + + dbp := dbPage{ + RoleID: roleID, + Limit: limit, + Offset: offset, + } + + rows, err := repo.db.NamedQueryContext(ctx, q, dbp) + if err != nil { + return roles.MembersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + items := []string{} + for rows.Next() { + dbrmems := dbRoleMember{} + if err := rows.StructScan(&dbrmems); err != nil { + return roles.MembersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + items = append(items, dbrmems.MemberID) + } + + cq := fmt.Sprintf(`SELECT COUNT(*) FROM %s_role_members WHERE role_id = :role_id`, repo.tableNamePrefix) + + total, err := postgres.Total(ctx, repo.db, cq, dbp) + if err != nil { + return roles.MembersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) + } + + return roles.MembersPage{ + Members: items, + Total: total, + Offset: offset, + Limit: limit, + }, nil + +} + +func (repo *Repository) RoleCheckMembersExists(ctx context.Context, roleID string, members []string) (bool, error) { + q := fmt.Sprintf(`SELECT COUNT(*) FROM %s_role_members WHERE role_id = :role_id AND action IN (:members)`, repo.tableNamePrefix) + + params := map[string]interface{}{ + "role_id": roleID, + "members": members, + } + var count int + query, err := repo.db.NamedQueryContext(ctx, q, params) + if err != nil { + return false, errors.Wrap(repoerr.ErrViewEntity, err) + } + + defer query.Close() + + if query.Next() { + if err := query.Scan(&count); err != nil { + return false, errors.Wrap(repoerr.ErrViewEntity, err) + } + } + + if count != len(members) { + return false, nil + } + + return true, nil +} + +func (repo *Repository) RoleRemoveMembers(ctx context.Context, role roles.Role, members []string) (err error) { + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(errors.Wrap(apiutil.ErrRollbackTx, errRollback), err) + } + } + }() + + q := fmt.Sprintf(`DELETE FROM %s_role_members WHERE role_id = :role_id AND member_id = ANY(:member_ids)`, repo.tableNamePrefix) + + params := map[string]interface{}{ + "role_id": role.ID, + "member_ids": members, + } + + if _, err := tx.NamedExec(q, params); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + + upq := fmt.Sprintf(`UPDATE %s_roles SET updated_at = :updated_at, updated_by = :updated_by WHERE id = :id;`, repo.tableNamePrefix) + if _, err := tx.NamedExec(upq, toDBRoles(role)); err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + + if err := tx.Commit(); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (repo *Repository) RoleRemoveAllMembers(ctx context.Context, role roles.Role) (err error) { + tx, err := repo.db.BeginTxx(ctx, nil) + if err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + defer func() { + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + err = errors.Wrap(errors.Wrap(apiutil.ErrRollbackTx, errRollback), err) + } + } + }() + q := fmt.Sprintf(`DELETE FROM %s_role_members WHERE role_id = :role_id `, repo.tableNamePrefix) + + dbrcap := dbRoleAction{RoleID: role.ID} + + if _, err := repo.db.NamedExecContext(ctx, q, dbrcap); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + + upq := fmt.Sprintf(`UPDATE %s_roles SET updated_at = :updated_at, updated_by = :updated_by WHERE id = :id;`, repo.tableNamePrefix) + if _, err := tx.NamedExec(upq, toDBRoles(role)); err != nil { + return postgres.HandleError(repoerr.ErrRemoveEntity, err) + } + + if err := tx.Commit(); err != nil { + return errors.Wrap(repoerr.ErrRemoveEntity, err) + } + return nil +} + +func (repo *Repository) RetrieveEntitiesRolesActionsMembers(ctx context.Context, entityIDs []string) ([]roles.EntityActionRole, []roles.EntityMemberRole, error) { + params := map[string]interface{}{ + "entity_ids": entityIDs, + } + + clientsActionsRolesQuery := fmt.Sprintf(`SELECT e.%s AS entity_id , era."action" AS "action", er.id AS role_id + FROM %s e + JOIN %s_roles er ON er.entity_id = e.%s + JOIN %s_role_actions era ON era.role_id = er.id + WHERE e.%s = ANY(:entity_ids); + `, repo.entityIDColumnName, repo.entityTableName, repo.tableNamePrefix, repo.entityIDColumnName, repo.tableNamePrefix, repo.entityIDColumnName) + rows, err := repo.db.NamedQueryContext(ctx, clientsActionsRolesQuery, params) + if err != nil { + return []roles.EntityActionRole{}, []roles.EntityMemberRole{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + defer rows.Close() + dbears := []dbEntityActionRole{} + for rows.Next() { + dbear := dbEntityActionRole{} + if err = rows.StructScan(&dbear); err != nil { + return []roles.EntityActionRole{}, []roles.EntityMemberRole{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + dbears = append(dbears, dbear) + } + clientsMembersRolesQuery := fmt.Sprintf(`SELECT e.%s AS entity_id , erm.member_id AS member_id, er.id AS role_id + FROM %s e + JOIN %s_roles er ON er.entity_id = e.%s + JOIN %s_role_members erm ON erm.role_id = er.id + WHERE e.%s = ANY(:entity_ids); + `, repo.entityIDColumnName, repo.entityTableName, repo.tableNamePrefix, repo.entityIDColumnName, repo.tableNamePrefix, repo.entityIDColumnName) + + rows, err = repo.db.NamedQueryContext(ctx, clientsMembersRolesQuery, params) + if err != nil { + return []roles.EntityActionRole{}, []roles.EntityMemberRole{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + defer rows.Close() + dbemrs := []dbEntityMemberRole{} + for rows.Next() { + dbemr := dbEntityMemberRole{} + if err = rows.StructScan(&dbemr); err != nil { + return []roles.EntityActionRole{}, []roles.EntityMemberRole{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + + dbemrs = append(dbemrs, dbemr) + } + return dbToEntityActionRole(dbears), dbToEntityMemberRole(dbemrs), nil +} +func (repo *Repository) RemoveMemberFromAllRoles(ctx context.Context, memberID string) (err error) { + + return nil +} diff --git a/pkg/roles/rolemanager/api/decoders.go b/pkg/roles/rolemanager/api/decoders.go new file mode 100644 index 0000000000..fb73797df8 --- /dev/null +++ b/pkg/roles/rolemanager/api/decoders.go @@ -0,0 +1,200 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "context" + "encoding/json" + "net/http" + "strings" + + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/errors" + "github.com/go-chi/chi/v5" +) + +type Decoder struct { + entityIDTemplate string +} + +func NewDecoder(entityIDTemplate string) Decoder { + return Decoder{entityIDTemplate} +} +func (d Decoder) DecodeCreateRole(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + req := createRoleReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func (d Decoder) DecodeListRoles(_ context.Context, r *http.Request) (interface{}, error) { + o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + req := listRolesReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + limit: l, + offset: o, + } + return req, nil +} + +func (d Decoder) DecodeViewRole(_ context.Context, r *http.Request) (interface{}, error) { + req := viewRoleReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + return req, nil +} + +func (d Decoder) DecodeUpdateRole(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + req := updateRoleReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func (d Decoder) DecodeDeleteRole(_ context.Context, r *http.Request) (interface{}, error) { + req := deleteRoleReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + return req, nil +} +func (d Decoder) DecodeListAvailableActions(_ context.Context, r *http.Request) (interface{}, error) { + req := listAvailableActionsReq{ + token: apiutil.ExtractBearerToken(r), + } + return req, nil +} + +func (d Decoder) DecodeAddRoleActions(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + req := addRoleActionsReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func (d Decoder) DecodeListRoleActions(_ context.Context, r *http.Request) (interface{}, error) { + req := listRoleActionsReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + return req, nil +} + +func (d Decoder) DecodeDeleteRoleActions(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + req := deleteRoleActionsReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func (d Decoder) DecodeDeleteAllRoleActions(_ context.Context, r *http.Request) (interface{}, error) { + req := deleteAllRoleActionsReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + return req, nil +} + +func (d Decoder) DecodeAddRoleMembers(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + req := addRoleMembersReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func (d Decoder) DecodeListRoleMembers(_ context.Context, r *http.Request) (interface{}, error) { + o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) + if err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + req := listRoleMembersReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + limit: l, + offset: o, + } + return req, nil +} + +func (d Decoder) DecodeDeleteRoleMembers(_ context.Context, r *http.Request) (interface{}, error) { + if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { + return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + } + req := deleteRoleMembersReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + } + return req, nil +} + +func (d Decoder) DecodeDeleteAllRoleMembers(_ context.Context, r *http.Request) (interface{}, error) { + req := deleteAllRoleMembersReq{ + token: apiutil.ExtractBearerToken(r), + entityID: chi.URLParam(r, d.entityIDTemplate), + roleName: chi.URLParam(r, "roleName"), + } + return req, nil +} diff --git a/pkg/roles/rolemanager/api/endpoints.go b/pkg/roles/rolemanager/api/endpoints.go new file mode 100644 index 0000000000..52efe71e08 --- /dev/null +++ b/pkg/roles/rolemanager/api/endpoints.go @@ -0,0 +1,278 @@ +package http + +import ( + "context" + + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/errors" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/roles" + "github.com/go-kit/kit/endpoint" +) + +func CreateRoleEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(createRoleReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + ro, err := svc.AddRole(ctx, session, req.entityID, req.RoleName, req.OptionalMembers, req.OptionalMembers) + if err != nil { + return nil, err + } + return createRoleRes{Role: ro}, nil + } +} + +func ListRolesEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listRolesReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + ros, err := svc.RetrieveAllRoles(ctx, session, req.entityID, req.limit, req.offset) + if err != nil { + return nil, err + } + return listRolesRes{RolePage: ros}, nil + } +} + +func ViewRoleEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(viewRoleReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + ro, err := svc.RetrieveRole(ctx, session, req.entityID, req.roleName) + if err != nil { + return nil, err + } + return viewRoleRes{Role: ro}, nil + } +} + +func UpdateRoleEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateRoleReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + ro, err := svc.UpdateRoleName(ctx, session, req.entityID, req.roleName, req.Name) + if err != nil { + return nil, err + } + return updateRoleRes{Role: ro}, nil + } +} +func DeleteRoleEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(deleteRoleReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.RemoveRole(ctx, session, req.entityID, req.roleName); err != nil { + return nil, err + } + return deleteRoleRes{}, nil + } +} +func ListAvailableActionsEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listAvailableActionsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + acts, err := svc.ListAvailableActions(ctx, session) + if err != nil { + return nil, err + } + return listAvailableActionsRes{acts}, nil + } +} +func AddRoleActionsEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(addRoleActionsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + caps, err := svc.RoleAddActions(ctx, session, req.entityID, req.roleName, req.Actions) + if err != nil { + return nil, err + } + return addRoleActionsRes{Actions: caps}, nil + } +} +func ListRoleActionsEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listRoleActionsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + caps, err := svc.RoleListActions(ctx, session, req.entityID, req.roleName) + if err != nil { + return nil, err + } + return listRoleActionsRes{Actions: caps}, nil + } +} +func DeleteRoleActionsEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(deleteRoleActionsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.RoleRemoveActions(ctx, session, req.entityID, req.roleName, req.Actions); err != nil { + return nil, err + } + return deleteRoleActionsRes{}, nil + } +} +func DeleteAllRoleActionsEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(deleteAllRoleActionsReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.RoleRemoveAllActions(ctx, session, req.entityID, req.roleName); err != nil { + return nil, err + } + return deleteAllRoleActionsRes{}, nil + } +} +func AddRoleMembersEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(addRoleMembersReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + members, err := svc.RoleAddMembers(ctx, session, req.entityID, req.roleName, req.Members) + if err != nil { + return nil, err + } + return addRoleMembersRes{members}, nil + } +} +func ListRoleMembersEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(listRoleMembersReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + mp, err := svc.RoleListMembers(ctx, session, req.entityID, req.roleName, req.limit, req.offset) + if err != nil { + return nil, err + } + return listRoleMembersRes{mp}, nil + } +} +func DeleteRoleMembersEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(deleteRoleMembersReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.RoleRemoveMembers(ctx, session, req.entityID, req.roleName, req.Members); err != nil { + return nil, err + } + return deleteRoleMembersRes{}, nil + } +} +func DeleteAllRoleMembersEndpoint(svc roles.RoleManager) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(deleteAllRoleMembersReq) + if err := req.validate(); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, err) + } + + session, ok := ctx.Value(api.SessionKey).(authn.Session) + if !ok { + return nil, svcerr.ErrAuthentication + } + + if err := svc.RoleRemoveAllMembers(ctx, session, req.entityID, req.roleName); err != nil { + return nil, err + } + return deleteAllRoleMemberRes{}, nil + } +} diff --git a/pkg/roles/rolemanager/api/requests.go b/pkg/roles/rolemanager/api/requests.go new file mode 100644 index 0000000000..314a71985f --- /dev/null +++ b/pkg/roles/rolemanager/api/requests.go @@ -0,0 +1,298 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/apiutil" +) + +type createRoleReq struct { + token string + entityID string + RoleName string `json:"role_name"` + OptionalActions []string `json:"optional_actions"` + OptionalMembers []string `json:"optional_members"` +} + +func (req createRoleReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if err := api.ValidateUUID(req.entityID); err != nil { + return err + } + if len(req.RoleName) == 0 { + return apiutil.ErrMissingRoleName + } + if len(req.RoleName) > 200 { + return apiutil.ErrNameSize + } + + return nil +} + +type listRolesReq struct { + token string + entityID string + limit uint64 + offset uint64 +} + +func (req listRolesReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.limit > api.MaxLimitSize || req.limit < 1 { + return apiutil.ErrLimitSize + } + return nil +} + +type viewRoleReq struct { + token string + entityID string + roleName string +} + +func (req viewRoleReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + return nil +} + +type updateRoleReq struct { + token string + entityID string + roleName string + Name string `json:"name"` +} + +func (req updateRoleReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" || req.Name == "" { + return apiutil.ErrMissingRoleName + } + return nil +} + +type deleteRoleReq struct { + token string + entityID string + roleName string +} + +func (req deleteRoleReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + return nil +} + +type listAvailableActionsReq struct { + token string +} + +func (req listAvailableActionsReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + return nil +} + +type addRoleActionsReq struct { + token string + entityID string + roleName string + Actions []string `json:"actions"` +} + +func (req addRoleActionsReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + + if len(req.Actions) == 0 { + return apiutil.ErrMissingPolicyEntityType + } + return nil +} + +type listRoleActionsReq struct { + token string + entityID string + roleName string +} + +func (req listRoleActionsReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + return nil +} + +type deleteRoleActionsReq struct { + token string + entityID string + roleName string + Actions []string `json:"actions"` +} + +func (req deleteRoleActionsReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + + if len(req.Actions) == 0 { + return apiutil.ErrMissingPolicyEntityType + } + return nil +} + +type deleteAllRoleActionsReq struct { + token string + entityID string + roleName string +} + +func (req deleteAllRoleActionsReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + return nil +} + +type addRoleMembersReq struct { + token string + entityID string + roleName string + Members []string `json:"members"` +} + +func (req addRoleMembersReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + if len(req.Members) == 0 { + return apiutil.ErrMissingRoleMembers + } + return nil +} + +type listRoleMembersReq struct { + token string + entityID string + roleName string + limit uint64 + offset uint64 +} + +func (req listRoleMembersReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + if req.limit > api.MaxLimitSize || req.limit < 1 { + return apiutil.ErrLimitSize + } + return nil +} + +type deleteRoleMembersReq struct { + token string + entityID string + roleName string + Members []string `json:"members"` +} + +func (req deleteRoleMembersReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + if len(req.Members) == 0 { + return apiutil.ErrMissingRoleMembers + } + return nil +} + +type deleteAllRoleMembersReq struct { + token string + entityID string + roleName string +} + +func (req deleteAllRoleMembersReq) validate() error { + if req.token == "" { + return apiutil.ErrBearerToken + } + if req.entityID == "" { + return apiutil.ErrMissingID + } + if req.roleName == "" { + return apiutil.ErrMissingRoleName + } + return nil +} diff --git a/pkg/roles/rolemanager/api/responses.go b/pkg/roles/rolemanager/api/responses.go new file mode 100644 index 0000000000..2633edb129 --- /dev/null +++ b/pkg/roles/rolemanager/api/responses.go @@ -0,0 +1,243 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "net/http" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/pkg/roles" +) + +var ( + _ magistrala.Response = (*createRoleRes)(nil) + _ magistrala.Response = (*listRolesRes)(nil) + _ magistrala.Response = (*viewRoleRes)(nil) + _ magistrala.Response = (*updateRoleRes)(nil) + _ magistrala.Response = (*deleteRoleRes)(nil) + _ magistrala.Response = (*listAvailableActionsRes)(nil) + _ magistrala.Response = (*addRoleActionsRes)(nil) + _ magistrala.Response = (*listRoleActionsRes)(nil) + _ magistrala.Response = (*deleteRoleActionsRes)(nil) + _ magistrala.Response = (*deleteAllRoleActionsRes)(nil) + _ magistrala.Response = (*addRoleMembersRes)(nil) + _ magistrala.Response = (*listRoleMembersRes)(nil) + _ magistrala.Response = (*deleteRoleMembersRes)(nil) + _ magistrala.Response = (*deleteAllRoleMemberRes)(nil) +) + +type createRoleRes struct { + roles.Role +} + +func (res createRoleRes) Code() int { + return http.StatusCreated +} + +func (res createRoleRes) Headers() map[string]string { + return map[string]string{} +} + +func (res createRoleRes) Empty() bool { + return false +} + +type listRolesRes struct { + roles.RolePage +} + +func (res listRolesRes) Code() int { + return http.StatusOK +} + +func (res listRolesRes) Headers() map[string]string { + return map[string]string{} +} + +func (res listRolesRes) Empty() bool { + return false +} + +type viewRoleRes struct { + roles.Role +} + +func (res viewRoleRes) Code() int { + return http.StatusOK +} + +func (res viewRoleRes) Headers() map[string]string { + return map[string]string{} +} + +func (res viewRoleRes) Empty() bool { + return false +} + +type updateRoleRes struct { + roles.Role +} + +func (res updateRoleRes) Code() int { + return http.StatusOK +} + +func (res updateRoleRes) Headers() map[string]string { + return map[string]string{} +} + +func (res updateRoleRes) Empty() bool { + return false +} + +type deleteRoleRes struct { +} + +func (res deleteRoleRes) Code() int { + return http.StatusNoContent +} + +func (res deleteRoleRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteRoleRes) Empty() bool { + return true +} + +type listAvailableActionsRes struct { + AvailableActions []string `json:"available_actions"` +} + +func (res listAvailableActionsRes) Code() int { + return http.StatusOK +} + +func (res listAvailableActionsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res listAvailableActionsRes) Empty() bool { + return false +} + +type addRoleActionsRes struct { + Actions []string `json:"actions"` +} + +func (res addRoleActionsRes) Code() int { + return http.StatusOK +} + +func (res addRoleActionsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res addRoleActionsRes) Empty() bool { + return false +} + +type listRoleActionsRes struct { + Actions []string `json:"actions"` +} + +func (res listRoleActionsRes) Code() int { + return http.StatusOK +} + +func (res listRoleActionsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res listRoleActionsRes) Empty() bool { + return false +} + +type deleteRoleActionsRes struct{} + +func (res deleteRoleActionsRes) Code() int { + return http.StatusNoContent +} + +func (res deleteRoleActionsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteRoleActionsRes) Empty() bool { + return true +} + +type deleteAllRoleActionsRes struct{} + +func (res deleteAllRoleActionsRes) Code() int { + return http.StatusNoContent +} + +func (res deleteAllRoleActionsRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteAllRoleActionsRes) Empty() bool { + return true +} + +type addRoleMembersRes struct { + Members []string `json:"members"` +} + +func (res addRoleMembersRes) Code() int { + return http.StatusOK +} + +func (res addRoleMembersRes) Headers() map[string]string { + return map[string]string{} +} + +func (res addRoleMembersRes) Empty() bool { + return false +} + +type listRoleMembersRes struct { + roles.MembersPage +} + +func (res listRoleMembersRes) Code() int { + return http.StatusOK +} + +func (res listRoleMembersRes) Headers() map[string]string { + return map[string]string{} +} + +func (res listRoleMembersRes) Empty() bool { + return false +} + +type deleteRoleMembersRes struct{} + +func (res deleteRoleMembersRes) Code() int { + return http.StatusNoContent +} + +func (res deleteRoleMembersRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteRoleMembersRes) Empty() bool { + return true +} + +type deleteAllRoleMemberRes struct{} + +func (res deleteAllRoleMemberRes) Code() int { + return http.StatusNoContent +} + +func (res deleteAllRoleMemberRes) Headers() map[string]string { + return map[string]string{} +} + +func (res deleteAllRoleMemberRes) Empty() bool { + return true +} diff --git a/pkg/roles/rolemanager/api/router.go b/pkg/roles/rolemanager/api/router.go new file mode 100644 index 0000000000..4b427df516 --- /dev/null +++ b/pkg/roles/rolemanager/api/router.go @@ -0,0 +1,128 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "github.com/absmach/magistrala/internal/api" + "github.com/absmach/magistrala/pkg/roles" + "github.com/go-chi/chi/v5" + kithttp "github.com/go-kit/kit/transport/http" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +func EntityRoleMangerRouter(svc roles.RoleManager, d Decoder, r chi.Router, opts []kithttp.ServerOption) chi.Router { + r.Route("/roles", func(r chi.Router) { + + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + CreateRoleEndpoint(svc), + d.DecodeCreateRole, + api.EncodeResponse, + opts..., + ), "create_role").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + ListRolesEndpoint(svc), + d.DecodeListRoles, + api.EncodeResponse, + opts..., + ), "list_roles").ServeHTTP) + + r.Route("/{roleName}", func(r chi.Router) { + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + ViewRoleEndpoint(svc), + d.DecodeViewRole, + api.EncodeResponse, + opts..., + ), "view_role").ServeHTTP) + + r.Put("/", otelhttp.NewHandler(kithttp.NewServer( + UpdateRoleEndpoint(svc), + d.DecodeUpdateRole, + api.EncodeResponse, + opts..., + ), "update_role").ServeHTTP) + + r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( + DeleteRoleEndpoint(svc), + d.DecodeDeleteRole, + api.EncodeResponse, + opts..., + ), "delete_role").ServeHTTP) + + r.Route("/actions", func(r chi.Router) { + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + AddRoleActionsEndpoint(svc), + d.DecodeAddRoleActions, + api.EncodeResponse, + opts..., + ), "add_role_actions").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + ListRoleActionsEndpoint(svc), + d.DecodeListRoleActions, + api.EncodeResponse, + opts..., + ), "list_role_actions").ServeHTTP) + + r.Post("/delete", otelhttp.NewHandler(kithttp.NewServer( + DeleteRoleActionsEndpoint(svc), + d.DecodeDeleteRoleActions, + api.EncodeResponse, + opts..., + ), "delete_role_actions").ServeHTTP) + + r.Post("/delete-all", otelhttp.NewHandler(kithttp.NewServer( + DeleteAllRoleActionsEndpoint(svc), + d.DecodeDeleteAllRoleActions, + api.EncodeResponse, + opts..., + ), "delete_all_role_actions").ServeHTTP) + }) + + r.Route("/members", func(r chi.Router) { + r.Post("/", otelhttp.NewHandler(kithttp.NewServer( + AddRoleMembersEndpoint(svc), + d.DecodeAddRoleMembers, + api.EncodeResponse, + opts..., + ), "add_role_members").ServeHTTP) + + r.Get("/", otelhttp.NewHandler(kithttp.NewServer( + ListRoleMembersEndpoint(svc), + d.DecodeListRoleMembers, + api.EncodeResponse, + opts..., + ), "list_role_members").ServeHTTP) + + r.Post("/delete", otelhttp.NewHandler(kithttp.NewServer( + DeleteRoleMembersEndpoint(svc), + d.DecodeDeleteRoleMembers, + api.EncodeResponse, + opts..., + ), "delete_role_members").ServeHTTP) + + r.Post("/delete-all", otelhttp.NewHandler(kithttp.NewServer( + DeleteAllRoleMembersEndpoint(svc), + d.DecodeDeleteAllRoleMembers, + api.EncodeResponse, + opts..., + ), "delete_all_role_members").ServeHTTP) + }) + }) + + }) + + return r +} + +func EntityAvailableActionsRouter(svc roles.RoleManager, d Decoder, r chi.Router, opts []kithttp.ServerOption) chi.Router { + r.Get("/roles/available-actions", otelhttp.NewHandler(kithttp.NewServer( + ListAvailableActionsEndpoint(svc), + d.DecodeListAvailableActions, + api.EncodeResponse, + opts..., + ), "list_available_actions").ServeHTTP) + + return r +} diff --git a/pkg/roles/rolemanager/doc.go b/pkg/roles/rolemanager/doc.go new file mode 100644 index 0000000000..e3d9fc1aec --- /dev/null +++ b/pkg/roles/rolemanager/doc.go @@ -0,0 +1,4 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package rolemanager diff --git a/pkg/roles/rolemanager/events/doc.go b/pkg/roles/rolemanager/events/doc.go new file mode 100644 index 0000000000..a115b5f927 --- /dev/null +++ b/pkg/roles/rolemanager/events/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package events provides the domain concept definitions needed to +// support Magistrala auth service functionality. +package events diff --git a/pkg/roles/rolemanager/events/events.go b/pkg/roles/rolemanager/events/events.go new file mode 100644 index 0000000000..80daff19cb --- /dev/null +++ b/pkg/roles/rolemanager/events/events.go @@ -0,0 +1,4 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package events diff --git a/pkg/roles/rolemanager/events/streams.go b/pkg/roles/rolemanager/events/streams.go new file mode 100644 index 0000000000..12a39c220c --- /dev/null +++ b/pkg/roles/rolemanager/events/streams.go @@ -0,0 +1,82 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package events + +import ( + "context" + + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/events" + "github.com/absmach/magistrala/pkg/roles" +) + +var _ roles.RoleManager = (*RoleManagerEventStore)(nil) + +type RoleManagerEventStore struct { + events.Publisher + svc roles.RoleManager + svcName string +} + +// NewEventStoreMiddleware returns wrapper around auth service that sends +// events to event store. +func NewRoleManagerEventStore(svcName string, svc roles.RoleManager, publisher events.Publisher) RoleManagerEventStore { + return RoleManagerEventStore{ + svcName: svcName, + svc: svc, + Publisher: publisher, + } +} + +func (res *RoleManagerEventStore) AddRole(ctx context.Context, session authn.Session, entityID, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + return res.svc.AddRole(ctx, session, entityID, roleName, optionalActions, optionalMembers) +} +func (res *RoleManagerEventStore) RemoveRole(ctx context.Context, session authn.Session, entityID, roleName string) error { + return res.svc.RemoveRole(ctx, session, entityID, roleName) +} +func (res *RoleManagerEventStore) UpdateRoleName(ctx context.Context, session authn.Session, entityID, oldRoleName, newRoleName string) (roles.Role, error) { + return res.svc.UpdateRoleName(ctx, session, entityID, oldRoleName, newRoleName) +} +func (res *RoleManagerEventStore) RetrieveRole(ctx context.Context, session authn.Session, entityID, roleName string) (roles.Role, error) { + return res.svc.RetrieveRole(ctx, session, entityID, roleName) +} +func (res *RoleManagerEventStore) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit, offset uint64) (roles.RolePage, error) { + return res.svc.RetrieveAllRoles(ctx, session, entityID, limit, offset) +} +func (res *RoleManagerEventStore) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + return res.svc.ListAvailableActions(ctx, session) +} +func (res *RoleManagerEventStore) RoleAddActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (ops []string, err error) { + return res.svc.RoleAddActions(ctx, session, entityID, roleName, actions) +} +func (res *RoleManagerEventStore) RoleListActions(ctx context.Context, session authn.Session, entityID, roleName string) ([]string, error) { + return res.svc.RoleListActions(ctx, session, entityID, roleName) +} +func (res *RoleManagerEventStore) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (bool, error) { + return res.svc.RoleCheckActionsExists(ctx, session, entityID, roleName, actions) +} +func (res *RoleManagerEventStore) RoleRemoveActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (err error) { + return res.svc.RoleRemoveActions(ctx, session, entityID, roleName, actions) +} +func (res *RoleManagerEventStore) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID, roleName string) error { + return res.svc.RoleRemoveAllActions(ctx, session, entityID, roleName) +} +func (res *RoleManagerEventStore) RoleAddMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) ([]string, error) { + return res.svc.RoleAddMembers(ctx, session, entityID, roleName, members) +} +func (res *RoleManagerEventStore) RoleListMembers(ctx context.Context, session authn.Session, entityID, roleName string, limit, offset uint64) (roles.MembersPage, error) { + return res.svc.RoleListMembers(ctx, session, entityID, roleName, limit, offset) +} +func (res *RoleManagerEventStore) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (bool, error) { + return res.svc.RoleCheckMembersExists(ctx, session, entityID, roleName, members) +} +func (res *RoleManagerEventStore) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (err error) { + return res.svc.RoleRemoveMembers(ctx, session, entityID, roleName, members) +} +func (res *RoleManagerEventStore) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleName string) (err error) { + return res.svc.RoleRemoveAllMembers(ctx, session, entityID, roleName) +} +func (res *RoleManagerEventStore) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, membersID string) (err error) { + return res.svc.RemoveMemberFromAllRoles(ctx, session, membersID) +} diff --git a/pkg/roles/rolemanager/middleware/authoirzation.go b/pkg/roles/rolemanager/middleware/authoirzation.go new file mode 100644 index 0000000000..92c6e16134 --- /dev/null +++ b/pkg/roles/rolemanager/middleware/authoirzation.go @@ -0,0 +1,273 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "context" + + "github.com/absmach/magistrala/pkg/authn" + mgauthz "github.com/absmach/magistrala/pkg/authz" + "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/roles" + "github.com/absmach/magistrala/pkg/svcutil" +) + +var _ roles.RoleManager = (*RoleManagerAuthorizationMiddleware)(nil) + +type RoleManagerAuthorizationMiddleware struct { + entityType string + svc roles.RoleManager + authz mgauthz.Authorization + opp svcutil.OperationPerm +} + +// AuthorizationMiddleware adds authorization to the clients service. +func NewRoleManagerAuthorizationMiddleware(entityType string, svc roles.RoleManager, authz mgauthz.Authorization, opPerm map[svcutil.Operation]svcutil.Permission) (RoleManagerAuthorizationMiddleware, error) { + opp := roles.NewOperationPerm() + if err := opp.AddOperationPermissionMap(opPerm); err != nil { + return RoleManagerAuthorizationMiddleware{}, err + } + if err := opp.Validate(); err != nil { + return RoleManagerAuthorizationMiddleware{}, err + } + + ram := RoleManagerAuthorizationMiddleware{ + entityType: entityType, + svc: svc, + authz: authz, + opp: opp, + } + if err := ram.validate(); err != nil { + return RoleManagerAuthorizationMiddleware{}, err + } + return ram, nil +} + +func (ram RoleManagerAuthorizationMiddleware) validate() error { + if err := ram.opp.Validate(); err != nil { + return err + } + return nil +} + +func (ram RoleManagerAuthorizationMiddleware) AddRole(ctx context.Context, session authn.Session, entityID, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + if err := ram.authorize(ctx, roles.OpAddRole, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return roles.Role{}, err + } + return ram.svc.AddRole(ctx, session, entityID, roleName, optionalActions, optionalMembers) +} +func (ram RoleManagerAuthorizationMiddleware) RemoveRole(ctx context.Context, session authn.Session, entityID, roleName string) error { + if err := ram.authorize(ctx, roles.OpRemoveRole, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return err + } + return ram.svc.RemoveRole(ctx, session, entityID, roleName) +} +func (ram RoleManagerAuthorizationMiddleware) UpdateRoleName(ctx context.Context, session authn.Session, entityID, oldRoleName, newRoleName string) (roles.Role, error) { + if err := ram.authorize(ctx, roles.OpUpdateRoleName, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return roles.Role{}, err + } + return ram.svc.UpdateRoleName(ctx, session, entityID, oldRoleName, newRoleName) +} +func (ram RoleManagerAuthorizationMiddleware) RetrieveRole(ctx context.Context, session authn.Session, entityID, roleName string) (roles.Role, error) { + if err := ram.authorize(ctx, roles.OpRetrieveRole, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return roles.Role{}, err + } + return ram.svc.RetrieveRole(ctx, session, entityID, roleName) +} +func (ram RoleManagerAuthorizationMiddleware) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit, offset uint64) (roles.RolePage, error) { + if err := ram.authorize(ctx, roles.OpRetrieveAllRoles, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return roles.RolePage{}, err + } + return ram.svc.RetrieveAllRoles(ctx, session, entityID, limit, offset) +} +func (ram RoleManagerAuthorizationMiddleware) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + return ram.svc.ListAvailableActions(ctx, session) +} +func (ram RoleManagerAuthorizationMiddleware) RoleAddActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (ops []string, err error) { + if err := ram.authorize(ctx, roles.OpRoleAddActions, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return []string{}, err + } + + return ram.svc.RoleAddActions(ctx, session, entityID, roleName, actions) +} +func (ram RoleManagerAuthorizationMiddleware) RoleListActions(ctx context.Context, session authn.Session, entityID, roleName string) ([]string, error) { + if err := ram.authorize(ctx, roles.OpRoleListActions, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return []string{}, err + } + + return ram.svc.RoleListActions(ctx, session, entityID, roleName) +} +func (ram RoleManagerAuthorizationMiddleware) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (bool, error) { + if err := ram.authorize(ctx, roles.OpRoleCheckActionsExists, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return false, err + } + return ram.svc.RoleCheckActionsExists(ctx, session, entityID, roleName, actions) +} +func (ram RoleManagerAuthorizationMiddleware) RoleRemoveActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (err error) { + if err := ram.authorize(ctx, roles.OpRoleRemoveActions, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return err + } + return ram.svc.RoleRemoveActions(ctx, session, entityID, roleName, actions) +} +func (ram RoleManagerAuthorizationMiddleware) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID, roleName string) error { + if err := ram.authorize(ctx, roles.OpRoleRemoveAllActions, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return err + } + return ram.svc.RoleRemoveAllActions(ctx, session, entityID, roleName) +} +func (ram RoleManagerAuthorizationMiddleware) RoleAddMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) ([]string, error) { + if err := ram.authorize(ctx, roles.OpRoleAddMembers, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return []string{}, err + } + return ram.svc.RoleAddMembers(ctx, session, entityID, roleName, members) +} +func (ram RoleManagerAuthorizationMiddleware) RoleListMembers(ctx context.Context, session authn.Session, entityID, roleName string, limit, offset uint64) (roles.MembersPage, error) { + if err := ram.authorize(ctx, roles.OpRoleListMembers, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return roles.MembersPage{}, err + } + return ram.svc.RoleListMembers(ctx, session, entityID, roleName, limit, offset) +} +func (ram RoleManagerAuthorizationMiddleware) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (bool, error) { + if err := ram.authorize(ctx, roles.OpRoleCheckMembersExists, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return false, err + } + return ram.svc.RoleCheckMembersExists(ctx, session, entityID, roleName, members) +} +func (ram RoleManagerAuthorizationMiddleware) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (err error) { + if err := ram.authorize(ctx, roles.OpRoleRemoveMembers, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return err + } + return ram.svc.RoleRemoveMembers(ctx, session, entityID, roleName, members) +} +func (ram RoleManagerAuthorizationMiddleware) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleName string) (err error) { + if err := ram.authorize(ctx, roles.OpRoleRemoveAllMembers, mgauthz.PolicyReq{ + Domain: session.DomainID, + Subject: session.DomainUserID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Object: entityID, + ObjectType: ram.entityType, + }); err != nil { + return err + } + return ram.svc.RoleRemoveAllMembers(ctx, session, entityID, roleName) +} + +func (ram RoleManagerAuthorizationMiddleware) authorize(ctx context.Context, op svcutil.Operation, pr mgauthz.PolicyReq) error { + perm, err := ram.opp.GetPermission(op) + if err != nil { + return err + } + + pr.Permission = perm.String() + + if err := ram.authz.Authorize(ctx, pr); err != nil { + return errors.Wrap(errors.ErrAuthorization, err) + } + + return nil +} + +func (ram RoleManagerAuthorizationMiddleware) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) { + return ram.svc.RemoveMemberFromAllRoles(ctx, session, memberID) +} diff --git a/pkg/roles/rolemanager/middleware/logging.go b/pkg/roles/rolemanager/middleware/logging.go new file mode 100644 index 0000000000..f8149a4f3b --- /dev/null +++ b/pkg/roles/rolemanager/middleware/logging.go @@ -0,0 +1,346 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +//go:build !test + +package middleware + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/roles" +) + +var _ roles.RoleManager = (*RoleManagerLoggingMiddleware)(nil) + +type RoleManagerLoggingMiddleware struct { + svcName string + svc roles.RoleManager + logger *slog.Logger +} + +func NewRoleManagerLoggingMiddleware(svcName string, svc roles.RoleManager, logger *slog.Logger) RoleManagerLoggingMiddleware { + return RoleManagerLoggingMiddleware{ + svcName: svcName, + svc: svc, + logger: logger, + } +} + +func (lm *RoleManagerLoggingMiddleware) AddRole(ctx context.Context, session authn.Session, entityID, roleName string, optionalActions []string, optionalMembers []string) (ro roles.Role, err error) { + prefix := fmt.Sprintf("Add %s roles", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_add_role", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + slog.Any("optional_actions", optionalActions), + slog.Any("optional_members", optionalMembers), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.AddRole(ctx, session, entityID, roleName, optionalActions, optionalMembers) +} + +func (lm *RoleManagerLoggingMiddleware) RemoveRole(ctx context.Context, session authn.Session, entityID, roleName string) (err error) { + prefix := fmt.Sprintf("Delete %s role", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_delete_role", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RemoveRole(ctx, session, entityID, roleName) +} + +func (lm *RoleManagerLoggingMiddleware) UpdateRoleName(ctx context.Context, session authn.Session, entityID, oldRoleName, newRoleName string) (ro roles.Role, err error) { + prefix := fmt.Sprintf("Update %s role name", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_update_role_name", + slog.String("entity_id", entityID), + slog.String("old_role_name", oldRoleName), + slog.String("new_role_name", newRoleName), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.UpdateRoleName(ctx, session, entityID, oldRoleName, newRoleName) +} + +func (lm *RoleManagerLoggingMiddleware) RetrieveRole(ctx context.Context, session authn.Session, entityID, roleName string) (ro roles.Role, err error) { + prefix := fmt.Sprintf("Retrieve %s role", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_update_role_name", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RetrieveRole(ctx, session, entityID, roleName) +} + +func (lm *RoleManagerLoggingMiddleware) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit, offset uint64) (rp roles.RolePage, err error) { + prefix := fmt.Sprintf("List %s roles", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_roles_retrieve_all", + slog.String("entity_id", entityID), + slog.Uint64("limit", limit), + slog.Uint64("offset", offset), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RetrieveAllRoles(ctx, session, entityID, limit, offset) +} + +func (lm *RoleManagerLoggingMiddleware) ListAvailableActions(ctx context.Context, session authn.Session) (acts []string, err error) { + prefix := fmt.Sprintf("List %s available actions", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName + "_list_available_actions"), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.ListAvailableActions(ctx, session) +} + +func (lm *RoleManagerLoggingMiddleware) RoleAddActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (caps []string, err error) { + prefix := fmt.Sprintf("%s role add actions", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_role_add_actions", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + slog.Any("actions", actions), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RoleAddActions(ctx, session, entityID, roleName, actions) +} + +func (lm *RoleManagerLoggingMiddleware) RoleListActions(ctx context.Context, session authn.Session, entityID, roleName string) (roOps []string, err error) { + prefix := fmt.Sprintf("%s role list actions", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_list_role_actions", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RoleListActions(ctx, session, entityID, roleName) +} + +func (lm *RoleManagerLoggingMiddleware) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (bool, error) { + return lm.svc.RoleCheckActionsExists(ctx, session, entityID, roleName, actions) +} + +func (lm *RoleManagerLoggingMiddleware) RoleRemoveActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (err error) { + prefix := fmt.Sprintf("%s role remove actions", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_role_remove_actions", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + slog.Any("actions", actions), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RoleRemoveActions(ctx, session, entityID, roleName, actions) +} + +func (lm *RoleManagerLoggingMiddleware) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID, roleName string) (err error) { + prefix := fmt.Sprintf("%s role remove all actions", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_role_remove_all_actions", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RoleRemoveAllActions(ctx, session, entityID, roleName) +} + +func (lm *RoleManagerLoggingMiddleware) RoleAddMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (mems []string, err error) { + prefix := fmt.Sprintf("%s role add members", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_role_add_members", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + slog.Any("members", members), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RoleAddMembers(ctx, session, entityID, roleName, members) +} + +func (lm *RoleManagerLoggingMiddleware) RoleListMembers(ctx context.Context, session authn.Session, entityID, roleName string, limit, offset uint64) (mp roles.MembersPage, err error) { + prefix := fmt.Sprintf("%s role list members", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_role_add_members", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + slog.Uint64("limit", limit), + slog.Uint64("offset", offset), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RoleListMembers(ctx, session, entityID, roleName, limit, offset) +} + +func (lm *RoleManagerLoggingMiddleware) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (bool, error) { + return lm.svc.RoleCheckMembersExists(ctx, session, entityID, roleName, members) +} + +func (lm *RoleManagerLoggingMiddleware) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (err error) { + prefix := fmt.Sprintf("%s role remove members", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_role_remove_members", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + slog.Any("members", members), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RoleRemoveMembers(ctx, session, entityID, roleName, members) +} + +func (lm *RoleManagerLoggingMiddleware) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleName string) (err error) { + prefix := fmt.Sprintf("%s role remove all members", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_role_remove_all_members", + slog.String("entity_id", entityID), + slog.String("role_name", roleName), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RoleRemoveAllMembers(ctx, session, entityID, roleName) +} + +func (lm *RoleManagerLoggingMiddleware) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) { + prefix := fmt.Sprintf("%s remove members from all roles", lm.svcName) + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group(lm.svcName+"_remove_members_from_all_roles", + slog.Any("member_id", memberID), + ), + } + if err != nil { + args = append(args, slog.String("error", err.Error())) + lm.logger.Warn(prefix+" failed", args...) + return + } + lm.logger.Info(prefix+" completed successfully", args...) + }(time.Now()) + return lm.svc.RemoveMemberFromAllRoles(ctx, session, memberID) +} diff --git a/pkg/roles/rolemanager/middleware/meterics.go b/pkg/roles/rolemanager/middleware/meterics.go new file mode 100644 index 0000000000..48228a7214 --- /dev/null +++ b/pkg/roles/rolemanager/middleware/meterics.go @@ -0,0 +1,84 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +//go:build !test + +package middleware + +import ( + "context" + + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/roles" + "github.com/go-kit/kit/metrics" +) + +var _ roles.RoleManager = (*RoleManagerMetricsMiddleware)(nil) + +type RoleManagerMetricsMiddleware struct { + svcName string + svc roles.RoleManager + counter metrics.Counter + latency metrics.Histogram +} + +func NewRoleManagerMetricsMiddleware(svcName string, svc roles.RoleManager, counter metrics.Counter, latency metrics.Histogram) RoleManagerMetricsMiddleware { + return RoleManagerMetricsMiddleware{ + svcName: svcName, + svc: svc, + counter: counter, + latency: latency, + } +} + +func (rmm *RoleManagerMetricsMiddleware) AddRole(ctx context.Context, session authn.Session, entityID, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + return rmm.svc.AddRole(ctx, session, entityID, roleName, optionalActions, optionalMembers) +} +func (rmm *RoleManagerMetricsMiddleware) RemoveRole(ctx context.Context, session authn.Session, entityID, roleName string) error { + return rmm.svc.RemoveRole(ctx, session, entityID, roleName) +} +func (rmm *RoleManagerMetricsMiddleware) UpdateRoleName(ctx context.Context, session authn.Session, entityID, oldRoleName, newRoleName string) (roles.Role, error) { + return rmm.svc.UpdateRoleName(ctx, session, entityID, oldRoleName, newRoleName) +} +func (rmm *RoleManagerMetricsMiddleware) RetrieveRole(ctx context.Context, session authn.Session, entityID, roleName string) (roles.Role, error) { + return rmm.svc.RetrieveRole(ctx, session, entityID, roleName) +} +func (rmm *RoleManagerMetricsMiddleware) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit, offset uint64) (roles.RolePage, error) { + return rmm.svc.RetrieveAllRoles(ctx, session, entityID, limit, offset) +} +func (rmm *RoleManagerMetricsMiddleware) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + return rmm.svc.ListAvailableActions(ctx, session) +} +func (rmm *RoleManagerMetricsMiddleware) RoleAddActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (caps []string, err error) { + return rmm.svc.RoleAddActions(ctx, session, entityID, roleName, actions) +} +func (rmm *RoleManagerMetricsMiddleware) RoleListActions(ctx context.Context, session authn.Session, entityID, roleName string) ([]string, error) { + return rmm.svc.RoleListActions(ctx, session, entityID, roleName) +} +func (rmm *RoleManagerMetricsMiddleware) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (bool, error) { + return rmm.svc.RoleCheckActionsExists(ctx, session, entityID, roleName, actions) +} +func (rmm *RoleManagerMetricsMiddleware) RoleRemoveActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (err error) { + return rmm.svc.RoleRemoveActions(ctx, session, entityID, roleName, actions) +} +func (rmm *RoleManagerMetricsMiddleware) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID, roleName string) error { + return rmm.svc.RoleRemoveAllActions(ctx, session, entityID, roleName) +} +func (rmm *RoleManagerMetricsMiddleware) RoleAddMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) ([]string, error) { + return rmm.svc.RoleAddMembers(ctx, session, entityID, roleName, members) +} +func (rmm *RoleManagerMetricsMiddleware) RoleListMembers(ctx context.Context, session authn.Session, entityID, roleName string, limit, offset uint64) (roles.MembersPage, error) { + return rmm.svc.RoleListMembers(ctx, session, entityID, roleName, limit, offset) +} +func (rmm *RoleManagerMetricsMiddleware) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (bool, error) { + return rmm.svc.RoleCheckMembersExists(ctx, session, entityID, roleName, members) +} +func (rmm *RoleManagerMetricsMiddleware) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (err error) { + return rmm.svc.RoleRemoveMembers(ctx, session, entityID, roleName, members) +} +func (rmm *RoleManagerMetricsMiddleware) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleName string) (err error) { + return rmm.svc.RoleRemoveAllMembers(ctx, session, entityID, roleName) +} +func (rmm *RoleManagerMetricsMiddleware) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) { + return rmm.svc.RemoveMemberFromAllRoles(ctx, session, memberID) +} diff --git a/pkg/roles/rolemanager/tracing/tracing.go b/pkg/roles/rolemanager/tracing/tracing.go new file mode 100644 index 0000000000..2db8a92fde --- /dev/null +++ b/pkg/roles/rolemanager/tracing/tracing.go @@ -0,0 +1,73 @@ +package tracing + +import ( + "context" + + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/roles" + "go.opentelemetry.io/otel/trace" +) + +var _ roles.RoleManager = (*RoleManagerTracing)(nil) + +type RoleManagerTracing struct { + svcName string + roles roles.RoleManager + tracer trace.Tracer +} + +func NewRoleManagerTracing(svcName string, svc roles.RoleManager, tracer trace.Tracer) RoleManagerTracing { + return RoleManagerTracing{svcName, svc, tracer} +} + +func (rtm *RoleManagerTracing) AddRole(ctx context.Context, session authn.Session, entityID, roleName string, optionalActions []string, optionalMembers []string) (roles.Role, error) { + return rtm.roles.AddRole(ctx, session, entityID, roleName, optionalActions, optionalMembers) +} +func (rtm *RoleManagerTracing) RemoveRole(ctx context.Context, session authn.Session, entityID, roleName string) error { + return rtm.roles.RemoveRole(ctx, session, entityID, roleName) +} +func (rtm *RoleManagerTracing) UpdateRoleName(ctx context.Context, session authn.Session, entityID, oldRoleName, newRoleName string) (roles.Role, error) { + return rtm.roles.UpdateRoleName(ctx, session, entityID, oldRoleName, newRoleName) +} +func (rtm *RoleManagerTracing) RetrieveRole(ctx context.Context, session authn.Session, entityID, roleName string) (roles.Role, error) { + return rtm.roles.RetrieveRole(ctx, session, entityID, roleName) +} +func (rtm *RoleManagerTracing) RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit, offset uint64) (roles.RolePage, error) { + return rtm.roles.RetrieveAllRoles(ctx, session, entityID, limit, offset) +} +func (rtm *RoleManagerTracing) ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) { + return rtm.roles.ListAvailableActions(ctx, session) +} +func (rtm *RoleManagerTracing) RoleAddActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (ops []string, err error) { + return rtm.roles.RoleAddActions(ctx, session, entityID, roleName, actions) +} +func (rtm *RoleManagerTracing) RoleListActions(ctx context.Context, session authn.Session, entityID, roleName string) ([]string, error) { + return rtm.roles.RoleListActions(ctx, session, entityID, roleName) +} +func (rtm *RoleManagerTracing) RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (bool, error) { + return rtm.roles.RoleCheckActionsExists(ctx, session, entityID, roleName, actions) +} +func (rtm *RoleManagerTracing) RoleRemoveActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (err error) { + return rtm.roles.RoleRemoveActions(ctx, session, entityID, roleName, actions) +} +func (rtm *RoleManagerTracing) RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID, roleName string) error { + return rtm.roles.RoleRemoveAllActions(ctx, session, entityID, roleName) +} +func (rtm *RoleManagerTracing) RoleAddMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) ([]string, error) { + return rtm.roles.RoleAddMembers(ctx, session, entityID, roleName, members) +} +func (rtm *RoleManagerTracing) RoleListMembers(ctx context.Context, session authn.Session, entityID, roleName string, limit, offset uint64) (roles.MembersPage, error) { + return rtm.roles.RoleListMembers(ctx, session, entityID, roleName, limit, offset) +} +func (rtm *RoleManagerTracing) RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (bool, error) { + return rtm.roles.RoleCheckMembersExists(ctx, session, entityID, roleName, members) +} +func (rtm *RoleManagerTracing) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (err error) { + return rtm.roles.RoleRemoveMembers(ctx, session, entityID, roleName, members) +} +func (rtm *RoleManagerTracing) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleName string) (err error) { + return rtm.roles.RoleRemoveAllMembers(ctx, session, entityID, roleName) +} +func (rtm *RoleManagerTracing) RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) { + return rtm.roles.RemoveMemberFromAllRoles(ctx, session, memberID) +} diff --git a/pkg/roles/roles.go b/pkg/roles/roles.go new file mode 100644 index 0000000000..ffe2c87804 --- /dev/null +++ b/pkg/roles/roles.go @@ -0,0 +1,255 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package roles + +import ( + "context" + "time" + + "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/policies" + "github.com/absmach/magistrala/pkg/svcutil" +) + +type Action string + +func (ac Action) String() string { + return string(ac) +} + +type Member string + +func (mem Member) String() string { + return string(mem) +} + +type RoleName string + +func (r RoleName) String() string { + return string(r) +} + +type BuiltInRoleName RoleName + +func (b BuiltInRoleName) ToRoleName() RoleName { + return RoleName(b) +} + +func (b BuiltInRoleName) String() string { + return string(b) +} + +type Role struct { + ID string `json:"id"` + Name string `json:"name"` + EntityID string `json:"entity_id"` + CreatedBy string `json:"created_by"` + CreatedAt time.Time `json:"created_at"` + UpdatedBy string `json:"updated_by"` + UpdatedAt time.Time `json:"updated_at"` +} + +type RoleProvision struct { + Role + OptionalActions []string `json:"-"` + OptionalMembers []string `json:"-"` +} + +type RolePage struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` + Roles []Role `json:"roles"` +} + +type MembersPage struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` + Members []string `json:"members"` +} + +type EntityActionRole struct { + EntityID string `json:"entity_id"` + Action string `json:"action"` + RoleID string `json:"role_id"` +} +type EntityMemberRole struct { + EntityID string `json:"entity_id"` + MemberID string `json:"member_id"` + RoleID string `json:"role_id"` +} + +//go:generate mockery --name Provisioner --output=./mocks --filename provisioner.go --quiet --note "Copyright (c) Abstract Machines" +type Provisioner interface { + AddNewEntitiesRoles(ctx context.Context, domainID, userID string, entityIDs []string, optionalEntityPolicies []policies.Policy, newBuiltInRoleMembers map[BuiltInRoleName][]Member) ([]RoleProvision, error) + RemoveEntitiesRoles(ctx context.Context, domainID, userID string, entityIDs []string, optionalFilterDeletePolicies []policies.Policy, optionalDeletePolicies []policies.Policy) error +} + +//go:generate mockery --name RoleManager --output=./mocks --filename rolemanager.go --quiet --note "Copyright (c) Abstract Machines" +type RoleManager interface { + + // Add New role to entity + AddRole(ctx context.Context, session authn.Session, entityID, roleName string, optionalActions []string, optionalMembers []string) (Role, error) + + // Remove removes the roles of entity. + RemoveRole(ctx context.Context, session authn.Session, entityID, roleName string) error + + // UpdateName update the name of the entity role. + UpdateRoleName(ctx context.Context, session authn.Session, entityID, oldRoleName, newRoleName string) (Role, error) + + RetrieveRole(ctx context.Context, session authn.Session, entityID, roleName string) (Role, error) + + RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit, offset uint64) (RolePage, error) + + ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) + + RoleAddActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (ops []string, err error) + + RoleListActions(ctx context.Context, session authn.Session, entityID, roleName string) ([]string, error) + + RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (bool, error) + + RoleRemoveActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (err error) + + RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID, roleName string) error + + RoleAddMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) ([]string, error) + + RoleListMembers(ctx context.Context, session authn.Session, entityID, roleName string, limit, offset uint64) (MembersPage, error) + + RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (bool, error) + + RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (err error) + + RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleName string) (err error) + + RemoveMemberFromAllRoles(ctx context.Context, session authn.Session, memberID string) (err error) +} + +//go:generate mockery --name Repository --output=./mocks --filename rolesRepo.go --quiet --note "Copyright (c) Abstract Machines" +type Repository interface { + AddRoles(ctx context.Context, rps []RoleProvision) ([]Role, error) + RemoveRoles(ctx context.Context, roleIDs []string) error + UpdateRole(ctx context.Context, ro Role) (Role, error) + RetrieveRole(ctx context.Context, roleID string) (Role, error) + RetrieveRoleByEntityIDAndName(ctx context.Context, entityID, roleName string) (Role, error) + RetrieveAllRoles(ctx context.Context, entityID string, limit, offset uint64) (RolePage, error) + RoleAddActions(ctx context.Context, role Role, actions []string) (ops []string, err error) + RoleListActions(ctx context.Context, roleID string) ([]string, error) + RoleCheckActionsExists(ctx context.Context, roleID string, actions []string) (bool, error) + RoleRemoveActions(ctx context.Context, role Role, actions []string) (err error) + RoleRemoveAllActions(ctx context.Context, role Role) error + RoleAddMembers(ctx context.Context, role Role, members []string) ([]string, error) + RoleListMembers(ctx context.Context, roleID string, limit, offset uint64) (MembersPage, error) + RoleCheckMembersExists(ctx context.Context, roleID string, members []string) (bool, error) + RoleRemoveMembers(ctx context.Context, role Role, members []string) (err error) + RoleRemoveAllMembers(ctx context.Context, role Role) (err error) + RetrieveEntitiesRolesActionsMembers(ctx context.Context, entityIDs []string) ([]EntityActionRole, []EntityMemberRole, error) + RemoveMemberFromAllRoles(ctx context.Context, members string) (err error) +} + +type Roles interface { + + // Add New role to entity + AddRole(ctx context.Context, session authn.Session, entityID, roleName string, optionalActions []string, optionalMembers []string) (Role, error) + + // Remove removes the roles of entity. + RemoveRole(ctx context.Context, session authn.Session, entityID, roleName string) error + + // UpdateName update the name of the entity role. + UpdateRoleName(ctx context.Context, session authn.Session, entityID, oldRoleName, newRoleName string) (Role, error) + + RetrieveRole(ctx context.Context, session authn.Session, entityID, roleName string) (Role, error) + + RetrieveAllRoles(ctx context.Context, session authn.Session, entityID string, limit, offset uint64) (RolePage, error) + + ListAvailableActions(ctx context.Context, session authn.Session) ([]string, error) + + RoleAddActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (ops []string, err error) + + RoleListActions(ctx context.Context, session authn.Session, entityID, roleName string) ([]string, error) + + RoleCheckActionsExists(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (bool, error) + + RoleRemoveActions(ctx context.Context, session authn.Session, entityID, roleName string, actions []string) (err error) + + RoleRemoveAllActions(ctx context.Context, session authn.Session, entityID, roleName string) error + + RoleAddMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) ([]string, error) + + RoleListMembers(ctx context.Context, session authn.Session, entityID, roleName string, limit, offset uint64) (MembersPage, error) + + RoleCheckMembersExists(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (bool, error) + + RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleName string, members []string) (err error) + + RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleName string) (err error) + + RemoveMembersFromAllRoles(ctx context.Context, session authn.Session, members []string) (err error) + + RemoveMembersFromRoles(ctx context.Context, session authn.Session, members []string, roleNames []string) (err error) + + RemoveActionsFromAllRoles(ctx context.Context, session authn.Session, actions []string) (err error) + + RemoveActionsFromRoles(ctx context.Context, session authn.Session, actions []string, roleNames []string) (err error) +} + +const ( + OpAddRole svcutil.Operation = iota + OpRemoveRole + OpUpdateRoleName + OpRetrieveRole + OpRetrieveAllRoles + OpRoleAddActions + OpRoleListActions + OpRoleCheckActionsExists + OpRoleRemoveActions + OpRoleRemoveAllActions + OpRoleAddMembers + OpRoleListMembers + OpRoleCheckMembersExists + OpRoleRemoveMembers + OpRoleRemoveAllMembers +) + +var expectedOperations = []svcutil.Operation{ + OpAddRole, + OpRemoveRole, + OpUpdateRoleName, + OpRetrieveRole, + OpRetrieveAllRoles, + OpRoleAddActions, + OpRoleListActions, + OpRoleCheckActionsExists, + OpRoleRemoveActions, + OpRoleRemoveAllActions, + OpRoleAddMembers, + OpRoleListMembers, + OpRoleCheckMembersExists, + OpRoleRemoveMembers, + OpRoleRemoveAllMembers, +} + +var operationNames = []string{ + "OpAddRole", + "OpRemoveRole", + "OpUpdateRoleName", + "OpRetrieveRole", + "OpRetrieveAllRoles", + "OpRoleAddActions", + "OpRoleListActions", + "OpRoleCheckActionsExists", + "OpRoleRemoveActions", + "OpRoleRemoveAllActions", + "OpRoleAddMembers", + "OpRoleListMembers", + "OpRoleCheckMembersExists", + "OpRoleRemoveMembers", + "OpRoleRemoveAllMembers", +} + +func NewOperationPerm() svcutil.OperationPerm { + return svcutil.NewOperationPerm(expectedOperations, operationNames) +} diff --git a/pkg/sdk/README.md b/pkg/sdk/README.md index c5a945c7de..76c62e6c60 100644 --- a/pkg/sdk/README.md +++ b/pkg/sdk/README.md @@ -1,5 +1,5 @@ # Magistrala SDK kits -This directory contains drivers for Magistrala HTTP API. Drivers facilitate system administration - CRUD operations on things, channels and their connections, i.e. provision of Magistrala entities. They can be used also for messaging. +This directory contains drivers for Magistrala HTTP API. Drivers facilitate system administration - CRUD operations on clients, channels and their connections, i.e. provision of Magistrala entities. They can be used also for messaging. Drivers are written in different languages in order to enable the faster application development in the respective language. diff --git a/pkg/sdk/go/README.md b/pkg/sdk/go/README.md index f82f782f99..96583a3842 100644 --- a/pkg/sdk/go/README.md +++ b/pkg/sdk/go/README.md @@ -8,8 +8,9 @@ Does both system administration (provisioning) and messaging. Import `"github.com/absmach/magistrala/sdk/go"` in your Go package. -```` -import "github.com/absmach/magistrala/pkg/sdk/go"``` +```go +import "github.com/absmach/magistrala/pkg/sdk/go" +``` Then call SDK Go functions to interact with the system. @@ -20,20 +21,20 @@ FUNCTIONS func NewMgxSDK(host, port string, tls bool) *MgxSDK -func (sdk *MgxSDK) Channel(id, token string) (things.Channel, error) +func (sdk *MgxSDK) Channel(id, token string) (clients.Channel, error) Channel - gets channel by ID -func (sdk *MgxSDK) Channels(token string) ([]things.Channel, error) +func (sdk *MgxSDK) Channels(token string) ([]clients.Channel, error) Channels - gets all channels func (sdk *MgxSDK) Connect(struct{[]string, []string}, token string) error - Connect - connect things to channels + Connect - connect clients to channels func (sdk *MgxSDK) CreateChannel(data, token string) (string, error) CreateChannel - creates new channel and generates UUID -func (sdk *MgxSDK) CreateThing(data, token string) (string, error) - CreateThing - creates new thing and generates thing UUID +func (sdk *MgxSDK) CreateClient(data, token string) (string, error) + CreateClient - creates new client and generates client UUID func (sdk *MgxSDK) CreateToken(user, pwd string) (string, error) CreateToken - create user token @@ -53,11 +54,11 @@ func (sdk *MgxSDK) UpdatePassword(user, pwd string) error func (sdk *MgxSDK) DeleteChannel(id, token string) error DeleteChannel - removes channel -func (sdk *MgxSDK) DeleteThing(id, token string) error - DeleteThing - removes thing +func (sdk *MgxSDK) DeleteClient(id, token string) error + DeleteClient - removes client -func (sdk *MgxSDK) DisconnectThing(thingID, chanID, token string) error - DisconnectThing - connect thing to a channel +func (sdk *MgxSDK) DisconnectClient(clientID, chanID, token string) error + DisconnectClient - connect client to a channel func (sdk *MgxSDK) SendMessage(chanID, msg, token string) error SendMessage - send message on Magistrala channel @@ -66,18 +67,18 @@ func (sdk *MgxSDK) SetContentType(ct ContentType) error SetContentType - set message content type. Available options are SenML JSON, custom JSON and custom binary (octet-stream). -func (sdk *MgxSDK) Thing(id, token string) (Thing, error) - Thing - gets thing by ID +func (sdk *MgxSDK) Client(id, token string) (Client, error) + Client - gets client by ID -func (sdk *MgxSDK) Things(token string) ([]Thing, error) - Things - gets all things +func (sdk *MgxSDK) Clients(token string) ([]Client, error) + Clients - gets all clients func (sdk *MgxSDK) UpdateChannel(channel Channel, token string) error UpdateChannel - update a channel -func (sdk *MgxSDK) UpdateThing(thing Thing, token string) error - UpdateThing - updates thing by ID +func (sdk *MgxSDK) UpdateClient(client Client, token string) error + UpdateClient - updates client by ID func (sdk *MgxSDK) Health() (magistrala.Health, error) - Health - things service health check -```` + Health - clients service health check +``` diff --git a/pkg/sdk/go/bootstrap.go b/pkg/sdk/go/bootstrap.go index 7fd9ba9605..cd884a3334 100644 --- a/pkg/sdk/go/bootstrap.go +++ b/pkg/sdk/go/bootstrap.go @@ -19,31 +19,31 @@ import ( ) const ( - configsEndpoint = "things/configs" - bootstrapEndpoint = "things/bootstrap" - whitelistEndpoint = "things/state" - bootstrapCertsEndpoint = "things/configs/certs" - bootstrapConnEndpoint = "things/configs/connections" + configsEndpoint = "clients/configs" + bootstrapEndpoint = "clients/bootstrap" + whitelistEndpoint = "clients/state" + bootstrapCertsEndpoint = "clients/configs/certs" + bootstrapConnEndpoint = "clients/configs/connections" secureEndpoint = "secure" ) // BootstrapConfig represents Configuration entity. It wraps information about external entity // as well as info about corresponding Magistrala entities. -// MGThing represents corresponding Magistrala Thing ID. -// MGKey is key of corresponding Magistrala Thing. -// MGChannels is a list of Magistrala Channels corresponding Magistrala Thing connects to. +// MGClient represents corresponding Magistrala Client ID. +// MGKey is key of corresponding Magistrala Client. +// MGChannels is a list of Magistrala Channels corresponding Magistrala Client connects to. type BootstrapConfig struct { - Channels interface{} `json:"channels,omitempty"` - ExternalID string `json:"external_id,omitempty"` - ExternalKey string `json:"external_key,omitempty"` - ThingID string `json:"thing_id,omitempty"` - ThingKey string `json:"thing_key,omitempty"` - Name string `json:"name,omitempty"` - ClientCert string `json:"client_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` - CACert string `json:"ca_cert,omitempty"` - Content string `json:"content,omitempty"` - State int `json:"state,omitempty"` + Channels interface{} `json:"channels,omitempty"` + ExternalID string `json:"external_id,omitempty"` + ExternalKey string `json:"external_key,omitempty"` + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + Name string `json:"name,omitempty"` + ClientCert string `json:"client_cert,omitempty"` + ClientKey string `json:"client_key,omitempty"` + CACert string `json:"ca_cert,omitempty"` + Content string `json:"content,omitempty"` + State int `json:"state,omitempty"` } func (ts *BootstrapConfig) UnmarshalJSON(data []byte) error { @@ -67,27 +67,27 @@ func (ts *BootstrapConfig) UnmarshalJSON(data []byte) error { } if err := json.Unmarshal(data, &struct { - ExternalID *string `json:"external_id,omitempty"` - ExternalKey *string `json:"external_key,omitempty"` - ThingID *string `json:"thing_id,omitempty"` - ThingKey *string `json:"thing_key,omitempty"` - Name *string `json:"name,omitempty"` - ClientCert *string `json:"client_cert,omitempty"` - ClientKey *string `json:"client_key,omitempty"` - CACert *string `json:"ca_cert,omitempty"` - Content *string `json:"content,omitempty"` - State *int `json:"state,omitempty"` + ExternalID *string `json:"external_id,omitempty"` + ExternalKey *string `json:"external_key,omitempty"` + ClientID *string `json:"client_id,omitempty"` + ClientSecret *string `json:"client_secret,omitempty"` + Name *string `json:"name,omitempty"` + ClientCert *string `json:"client_cert,omitempty"` + ClientKey *string `json:"client_key,omitempty"` + CACert *string `json:"ca_cert,omitempty"` + Content *string `json:"content,omitempty"` + State *int `json:"state,omitempty"` }{ - ExternalID: &ts.ExternalID, - ExternalKey: &ts.ExternalKey, - ThingID: &ts.ThingID, - ThingKey: &ts.ThingKey, - Name: &ts.Name, - ClientCert: &ts.ClientCert, - ClientKey: &ts.ClientKey, - CACert: &ts.CACert, - Content: &ts.Content, - State: &ts.State, + ExternalID: &ts.ExternalID, + ExternalKey: &ts.ExternalKey, + ClientID: &ts.ClientID, + ClientSecret: &ts.ClientSecret, + Name: &ts.Name, + ClientCert: &ts.ClientCert, + ClientKey: &ts.ClientKey, + CACert: &ts.CACert, + Content: &ts.Content, + State: &ts.State, }); err != nil { return err } @@ -108,7 +108,7 @@ func (sdk mgSDK) AddBootstrap(cfg BootstrapConfig, domainID, token string) (stri return "", sdkerr } - id := strings.TrimPrefix(headers.Get("Location"), "/things/configs/") + id := strings.TrimPrefix(headers.Get("Location"), "/clients/configs/") return id, nil } @@ -133,8 +133,8 @@ func (sdk mgSDK) Bootstraps(pm PageMetadata, domainID, token string) (BootstrapP return bb, nil } -func (sdk mgSDK) Whitelist(thingID string, state int, domainID, token string) errors.SDKError { - if thingID == "" { +func (sdk mgSDK) Whitelist(clientID string, state int, domainID, token string) errors.SDKError { + if clientID == "" { return errors.NewSDKError(apiutil.ErrMissingID) } @@ -143,7 +143,7 @@ func (sdk mgSDK) Whitelist(thingID string, state int, domainID, token string) er return errors.NewSDKError(err) } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, whitelistEndpoint, thingID) + url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, whitelistEndpoint, clientID) _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusCreated, http.StatusOK) @@ -170,10 +170,10 @@ func (sdk mgSDK) ViewBootstrap(id, domainID, token string) (BootstrapConfig, err } func (sdk mgSDK) UpdateBootstrap(cfg BootstrapConfig, domainID, token string) errors.SDKError { - if cfg.ThingID == "" { + if cfg.ClientID == "" { return errors.NewSDKError(apiutil.ErrMissingID) } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint, cfg.ThingID) + url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint, cfg.ClientID) data, err := json.Marshal(cfg) if err != nil { @@ -247,7 +247,7 @@ func (sdk mgSDK) Bootstrap(externalID, externalKey string) (BootstrapConfig, err } url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, bootstrapEndpoint, externalID) - _, body, err := sdk.processRequest(http.MethodGet, url, ThingPrefix+externalKey, nil, nil, http.StatusOK) + _, body, err := sdk.processRequest(http.MethodGet, url, ClientPrefix+externalKey, nil, nil, http.StatusOK) if err != nil { return BootstrapConfig{}, err } @@ -271,7 +271,7 @@ func (sdk mgSDK) BootstrapSecure(externalID, externalKey, cryptoKey string) (Boo return BootstrapConfig{}, errors.NewSDKError(err) } - _, body, sdkErr := sdk.processRequest(http.MethodGet, url, ThingPrefix+encExtKey, nil, nil, http.StatusOK) + _, body, sdkErr := sdk.processRequest(http.MethodGet, url, ClientPrefix+encExtKey, nil, nil, http.StatusOK) if sdkErr != nil { return BootstrapConfig{}, sdkErr } diff --git a/pkg/sdk/go/bootstrap_test.go b/pkg/sdk/go/bootstrap_test.go index b091bc976d..600a9e545c 100644 --- a/pkg/sdk/go/bootstrap_test.go +++ b/pkg/sdk/go/bootstrap_test.go @@ -32,8 +32,8 @@ import ( var ( externalId = testsutil.GenerateUUID(&testing.T{}) externalKey = testsutil.GenerateUUID(&testing.T{}) - thingId = testsutil.GenerateUUID(&testing.T{}) - thingKey = testsutil.GenerateUUID(&testing.T{}) + clientId = testsutil.GenerateUUID(&testing.T{}) + clientSecret = testsutil.GenerateUUID(&testing.T{}) channel1Id = testsutil.GenerateUUID(&testing.T{}) channel2Id = testsutil.GenerateUUID(&testing.T{}) clientCert = "newcert" @@ -44,7 +44,7 @@ var ( bsName = "test" encKey = []byte("1234567891011121") bootstrapConfig = bootstrap.Config{ - ThingID: thingId, + ClientID: clientId, Name: "test", ClientCert: clientCert, ClientKey: clientKey, @@ -63,21 +63,21 @@ var ( State: bootstrap.Inactive, } sdkBootstrapConfig = sdk.BootstrapConfig{ - Channels: []string{channel1Id, channel2Id}, - ExternalID: externalId, - ExternalKey: externalKey, - ThingID: thingId, - ThingKey: thingKey, - Name: bsName, - ClientCert: clientCert, - ClientKey: clientKey, - CACert: caCert, - Content: content, - State: state, + Channels: []string{channel1Id, channel2Id}, + ExternalID: externalId, + ExternalKey: externalKey, + ClientID: clientId, + ClientSecret: clientSecret, + Name: bsName, + ClientCert: clientCert, + ClientKey: clientKey, + CACert: caCert, + Content: content, + State: state, } sdkBootsrapConfigRes = sdk.BootstrapConfig{ - ThingID: thingId, - ThingKey: thingKey, + ClientID: clientId, + ClientSecret: clientSecret, Channels: []sdk.Channel{ { ID: channel1Id, @@ -91,16 +91,16 @@ var ( CACert: caCert, } readConfigResponse = struct { - ThingID string `json:"thing_id"` - ThingKey string `json:"thing_key"` - Channels []readerChannelRes `json:"channels"` - Content string `json:"content,omitempty"` - ClientCert string `json:"client_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` - CACert string `json:"ca_cert,omitempty"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Channels []readerChannelRes `json:"channels"` + Content string `json:"content,omitempty"` + ClientCert string `json:"client_cert,omitempty"` + ClientKey string `json:"client_key,omitempty"` + CACert string `json:"ca_cert,omitempty"` }{ - ThingID: thingId, - ThingKey: thingKey, + ClientID: clientId, + ClientSecret: clientSecret, Channels: []readerChannelRes{ { ID: channel1Id, @@ -146,10 +146,10 @@ func TestAddBootstrap(t *testing.T) { mgsdk := sdk.NewSDK(conf) neID := sdkBootstrapConfig - neID.ThingID = "non-existent" + neID.ClientID = "non-existent" neReqId := bootstrapConfig - neReqId.ThingID = "non-existent" + neReqId.ClientID = "non-existent" cases := []struct { desc string @@ -192,15 +192,15 @@ func TestAddBootstrap(t *testing.T) { Channels: map[string]interface{}{ "channel1": make(chan int), }, - ExternalID: externalId, - ExternalKey: externalKey, - ThingID: thingId, - ThingKey: thingKey, - Name: bsName, - ClientCert: clientCert, - ClientKey: clientKey, - CACert: caCert, - Content: content, + ExternalID: externalId, + ExternalKey: externalKey, + ClientID: clientId, + ClientSecret: clientSecret, + Name: bsName, + ClientCert: clientCert, + ClientKey: clientKey, + CACert: caCert, + Content: content, }, svcReq: bootstrap.Config{}, svcRes: bootstrap.Config{}, @@ -228,7 +228,7 @@ func TestAddBootstrap(t *testing.T) { err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "add with non-existent thing Id", + desc: "add with non-existent client Id", domainID: domainID, token: validToken, cfg: neID, @@ -249,7 +249,7 @@ func TestAddBootstrap(t *testing.T) { resp, err := mgsdk.AddBootstrap(tc.cfg, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if err == nil { - assert.Equal(t, bootstrapConfig.ThingID, resp) + assert.Equal(t, bootstrapConfig.ClientID, resp) ok := svcCall.Parent.AssertCalled(t, "Add", mock.Anything, tc.session, tc.token, tc.svcReq) assert.True(t, ok) } @@ -277,7 +277,7 @@ func TestListBootstraps(t *testing.T) { ID: channel2Id, }, }, - ThingID: thingId, + ClientID: clientId, Name: bsName, ExternalID: externalId, ExternalKey: externalKey, @@ -423,7 +423,7 @@ func TestWhiteList(t *testing.T) { domainID string token string session mgauthn.Session - thingID string + clientID string state int svcReq bootstrap.State svcErr error @@ -434,7 +434,7 @@ func TestWhiteList(t *testing.T) { desc: "whitelist to active state successfully", domainID: domainID, token: validToken, - thingID: thingId, + clientID: clientId, state: active, svcReq: bootstrap.Active, svcErr: nil, @@ -444,7 +444,7 @@ func TestWhiteList(t *testing.T) { desc: "whitelist to inactive state successfully", domainID: domainID, token: validToken, - thingID: thingId, + clientID: clientId, state: inactive, svcReq: bootstrap.Inactive, svcErr: nil, @@ -454,7 +454,7 @@ func TestWhiteList(t *testing.T) { desc: "whitelist with invalid token", domainID: domainID, token: invalidToken, - thingID: thingId, + clientID: clientId, state: active, svcReq: bootstrap.Active, authenticateErr: svcerr.ErrAuthentication, @@ -464,7 +464,7 @@ func TestWhiteList(t *testing.T) { desc: "whitelist with empty token", domainID: domainID, token: "", - thingID: thingId, + clientID: clientId, state: active, svcReq: bootstrap.Active, svcErr: nil, @@ -474,17 +474,17 @@ func TestWhiteList(t *testing.T) { desc: "whitelist with invalid state", domainID: domainID, token: validToken, - thingID: thingId, + clientID: clientId, state: -1, svcReq: bootstrap.Active, svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBootstrapState), http.StatusBadRequest), }, { - desc: "whitelist with empty thing Id", + desc: "whitelist with empty client Id", domainID: domainID, token: validToken, - thingID: "", + clientID: "", state: 1, svcReq: bootstrap.Active, svcErr: nil, @@ -497,11 +497,11 @@ func TestWhiteList(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := bsvc.On("ChangeState", mock.Anything, tc.session, tc.token, tc.thingID, tc.svcReq).Return(tc.svcErr) - err := mgsdk.Whitelist(tc.thingID, tc.state, tc.domainID, tc.token) + svcCall := bsvc.On("ChangeState", mock.Anything, tc.session, tc.token, tc.clientID, tc.svcReq).Return(tc.svcErr) + err := mgsdk.Whitelist(tc.clientID, tc.state, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ChangeState", mock.Anything, tc.session, tc.token, tc.thingID, tc.svcReq) + ok := svcCall.Parent.AssertCalled(t, "ChangeState", mock.Anything, tc.session, tc.token, tc.clientID, tc.svcReq) assert.True(t, ok) } svcCall.Unset() @@ -520,7 +520,7 @@ func TestViewBootstrap(t *testing.T) { mgsdk := sdk.NewSDK(conf) viewBoostrapRes := sdk.BootstrapConfig{ - ThingID: thingId, + ClientID: clientId, Channels: sdkBootsrapConfigRes.Channels, ExternalID: externalId, ExternalKey: externalKey, @@ -545,7 +545,7 @@ func TestViewBootstrap(t *testing.T) { desc: "view successfully", domainID: domainID, token: validToken, - id: thingId, + id: clientId, svcResp: bootstrapConfig, svcErr: nil, response: viewBoostrapRes, @@ -555,7 +555,7 @@ func TestViewBootstrap(t *testing.T) { desc: "view with invalid token", domainID: domainID, token: invalidToken, - id: thingId, + id: clientId, svcResp: bootstrap.Config{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.BootstrapConfig{}, @@ -565,14 +565,14 @@ func TestViewBootstrap(t *testing.T) { desc: "view with empty token", domainID: domainID, token: "", - id: thingId, + id: clientId, svcResp: bootstrap.Config{}, svcErr: nil, response: sdk.BootstrapConfig{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "view with non-existent thing Id", + desc: "view with non-existent client Id", domainID: domainID, token: validToken, id: invalid, @@ -585,9 +585,9 @@ func TestViewBootstrap(t *testing.T) { desc: "view with response that cannot be unmarshalled", domainID: domainID, token: validToken, - id: thingId, + id: clientId, svcResp: bootstrap.Config{ - ThingID: thingId, + ClientID: clientId, Channels: []bootstrap.Channel{ { ID: channel1Id, @@ -602,7 +602,7 @@ func TestViewBootstrap(t *testing.T) { err: errors.NewSDKError(errJsonEOF), }, { - desc: "view with empty thing Id", + desc: "view with empty client Id", domainID: domainID, token: validToken, id: "", @@ -658,9 +658,9 @@ func TestUpdateBootstrap(t *testing.T) { token: validToken, cfg: sdkBootstrapConfig, svcReq: bootstrap.Config{ - ThingID: thingId, - Name: bsName, - Content: content, + ClientID: clientId, + Name: bsName, + Content: content, }, svcErr: nil, err: nil, @@ -671,9 +671,9 @@ func TestUpdateBootstrap(t *testing.T) { token: invalidToken, cfg: sdkBootstrapConfig, svcReq: bootstrap.Config{ - ThingID: thingId, - Name: bsName, - Content: content, + ClientID: clientId, + Name: bsName, + Content: content, }, authenticationErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -695,30 +695,30 @@ func TestUpdateBootstrap(t *testing.T) { Channels: map[string]interface{}{ "channel1": make(chan int), }, - ExternalID: externalId, - ExternalKey: externalKey, - ThingID: thingId, - ThingKey: thingKey, - Name: bsName, - ClientCert: clientCert, - ClientKey: clientKey, - CACert: caCert, - Content: content, + ExternalID: externalId, + ExternalKey: externalKey, + ClientID: clientId, + ClientSecret: clientSecret, + Name: bsName, + ClientCert: clientCert, + ClientKey: clientKey, + CACert: caCert, + Content: content, }, svcReq: bootstrap.Config{ - ThingID: thingId, - Name: bsName, - Content: content, + ClientID: clientId, + Name: bsName, + Content: content, }, svcErr: nil, err: errors.NewSDKError(errMarshalChan), }, { - desc: "update with non-existent thing Id", + desc: "update with non-existent client Id", domainID: domainID, token: validToken, cfg: sdk.BootstrapConfig{ - ThingID: invalid, + ClientID: invalid, Channels: []sdk.Channel{ { ID: channel1Id, @@ -730,19 +730,19 @@ func TestUpdateBootstrap(t *testing.T) { Name: bsName, }, svcReq: bootstrap.Config{ - ThingID: invalid, - Name: bsName, - Content: content, + ClientID: invalid, + Name: bsName, + Content: content, }, svcErr: svcerr.ErrNotFound, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), }, { - desc: "update with empty thing Id", + desc: "update with empty client Id", domainID: domainID, token: validToken, cfg: sdk.BootstrapConfig{ - ThingID: "", + ClientID: "", Channels: []sdk.Channel{ { ID: channel1Id, @@ -754,22 +754,22 @@ func TestUpdateBootstrap(t *testing.T) { Name: bsName, }, svcReq: bootstrap.Config{ - ThingID: "", - Name: bsName, - Content: content, + ClientID: "", + Name: bsName, + Content: content, }, svcErr: nil, err: errors.NewSDKError(apiutil.ErrMissingID), }, { - desc: "update with config with only thing Id", + desc: "update with config with only client Id", domainID: domainID, token: validToken, cfg: sdk.BootstrapConfig{ - ThingID: thingId, + ClientID: clientId, }, svcReq: bootstrap.Config{ - ThingID: thingId, + ClientID: clientId, }, svcErr: nil, err: nil, @@ -804,7 +804,7 @@ func TestUpdateBootstrapCerts(t *testing.T) { mgsdk := sdk.NewSDK(conf) updateconfigRes := sdk.BootstrapConfig{ - ThingID: thingId, + ClientID: clientId, ClientCert: clientCert, CACert: caCert, ClientKey: clientKey, @@ -829,7 +829,7 @@ func TestUpdateBootstrapCerts(t *testing.T) { desc: "update certs successfully", domainID: domainID, token: validToken, - id: thingId, + id: clientId, clientCert: clientCert, clientKey: clientKey, caCert: caCert, @@ -842,7 +842,7 @@ func TestUpdateBootstrapCerts(t *testing.T) { desc: "update certs with invalid token", domainID: domainID, token: validToken, - id: thingId, + id: clientId, clientCert: clientCert, clientKey: clientKey, caCert: caCert, @@ -854,7 +854,7 @@ func TestUpdateBootstrapCerts(t *testing.T) { desc: "update certs with empty token", domainID: domainID, token: "", - id: thingId, + id: clientId, clientCert: clientCert, clientKey: clientKey, caCert: caCert, @@ -863,7 +863,7 @@ func TestUpdateBootstrapCerts(t *testing.T) { err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "update certs with non-existent thing Id", + desc: "update certs with non-existent client Id", domainID: domainID, token: validToken, id: invalid, @@ -878,7 +878,7 @@ func TestUpdateBootstrapCerts(t *testing.T) { desc: "update certs with empty certs", domainID: domainID, token: validToken, - id: thingId, + id: clientId, clientCert: "", clientKey: "", caCert: "", @@ -942,7 +942,7 @@ func TestUpdateBootstrapConnection(t *testing.T) { desc: "update connection successfully", domainID: domainID, token: validToken, - id: thingId, + id: clientId, channels: []string{channel1Id, channel2Id}, svcErr: nil, err: nil, @@ -951,7 +951,7 @@ func TestUpdateBootstrapConnection(t *testing.T) { desc: "update connection with invalid token", domainID: domainID, token: invalidToken, - id: thingId, + id: clientId, channels: []string{channel1Id, channel2Id}, authenticateErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -960,13 +960,13 @@ func TestUpdateBootstrapConnection(t *testing.T) { desc: "update connection with empty token", domainID: domainID, token: "", - id: thingId, + id: clientId, channels: []string{channel1Id, channel2Id}, svcErr: nil, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "update connection with non-existent thing Id", + desc: "update connection with non-existent client Id", domainID: domainID, token: validToken, id: invalid, @@ -978,7 +978,7 @@ func TestUpdateBootstrapConnection(t *testing.T) { desc: "update connection with non-existent channel Id", domainID: domainID, token: validToken, - id: thingId, + id: clientId, channels: []string{invalid}, svcErr: svcerr.ErrNotFound, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), @@ -987,7 +987,7 @@ func TestUpdateBootstrapConnection(t *testing.T) { desc: "update connection with empty channels", domainID: domainID, token: validToken, - id: thingId, + id: clientId, channels: []string{}, svcErr: svcerr.ErrUpdateEntity, err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), @@ -1044,7 +1044,7 @@ func TestRemoveBootstrap(t *testing.T) { desc: "remove successfully", domainID: domainID, token: validToken, - id: thingId, + id: clientId, svcErr: nil, err: nil, }, @@ -1052,12 +1052,12 @@ func TestRemoveBootstrap(t *testing.T) { desc: "remove with invalid token", domainID: domainID, token: invalidToken, - id: thingId, + id: clientId, authenticateErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, { - desc: "remove with non-existent thing Id", + desc: "remove with non-existent client Id", domainID: domainID, token: validToken, id: invalid, @@ -1068,7 +1068,7 @@ func TestRemoveBootstrap(t *testing.T) { desc: "remove removed bootstrap", domainID: domainID, token: validToken, - id: thingId, + id: clientId, svcErr: svcerr.ErrNotFound, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), }, @@ -1076,7 +1076,7 @@ func TestRemoveBootstrap(t *testing.T) { desc: "remove with empty token", domainID: domainID, token: "", - id: thingId, + id: clientId, svcErr: nil, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, diff --git a/pkg/sdk/go/certs.go b/pkg/sdk/go/certs.go index 35d68509a2..d09578cf74 100644 --- a/pkg/sdk/go/certs.go +++ b/pkg/sdk/go/certs.go @@ -25,12 +25,12 @@ type Cert struct { Key string `json:"key,omitempty"` Revoked bool `json:"revoked,omitempty"` ExpiryTime time.Time `json:"expiry_time,omitempty"` - ThingID string `json:"thing_id,omitempty"` + ClientID string `json:"client_id,omitempty"` } -func (sdk mgSDK) IssueCert(thingID, validity, domainID, token string) (Cert, errors.SDKError) { +func (sdk mgSDK) IssueCert(clientID, validity, domainID, token string) (Cert, errors.SDKError) { r := certReq{ - ThingID: thingID, + ClientID: clientID, Validity: validity, } d, err := json.Marshal(r) @@ -68,11 +68,11 @@ func (sdk mgSDK) ViewCert(id, domainID, token string) (Cert, errors.SDKError) { return cert, nil } -func (sdk mgSDK) ViewCertByThing(thingID, domainID, token string) (CertSerials, errors.SDKError) { - if thingID == "" { +func (sdk mgSDK) ViewCertByClient(clientID, domainID, token string) (CertSerials, errors.SDKError) { + if clientID == "" { return CertSerials{}, errors.NewSDKError(apiutil.ErrMissingID) } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.certsURL, domainID, serialsEndpoint, thingID) + url := fmt.Sprintf("%s/%s/%s/%s", sdk.certsURL, domainID, serialsEndpoint, clientID) _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) if err != nil { @@ -103,6 +103,6 @@ func (sdk mgSDK) RevokeCert(id, domainID, token string) (time.Time, errors.SDKEr } type certReq struct { - ThingID string `json:"thing_id"` + ClientID string `json:"client_id"` Validity string `json:"ttl"` } diff --git a/pkg/sdk/go/certs_test.go b/pkg/sdk/go/certs_test.go index 13055db6e2..aa5ded094e 100644 --- a/pkg/sdk/go/certs_test.go +++ b/pkg/sdk/go/certs_test.go @@ -30,7 +30,7 @@ const instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" var ( valid = "valid" - thingID = testsutil.GenerateUUID(&testing.T{}) + clientID = testsutil.GenerateUUID(&testing.T{}) OwnerID = testsutil.GenerateUUID(&testing.T{}) serial = testsutil.GenerateUUID(&testing.T{}) ttl = "10h" @@ -44,13 +44,13 @@ func generateTestCerts(t *testing.T) (certs.Cert, sdk.Cert) { expirationTime, err := time.Parse(time.RFC3339, "2032-01-01T00:00:00Z") assert.Nil(t, err, fmt.Sprintf("failed to parse expiration time: %v", err)) c := certs.Cert{ - ThingID: thingID, + ClientID: clientID, SerialNumber: serial, ExpiryTime: expirationTime, Certificate: valid, } sc := sdk.Cert{ - ThingID: thingID, + ClientID: clientID, SerialNumber: serial, Key: valid, Certificate: valid, @@ -83,7 +83,7 @@ func TestIssueCert(t *testing.T) { cases := []struct { desc string - thingID string + clientID string duration string domainID string token string @@ -94,8 +94,8 @@ func TestIssueCert(t *testing.T) { err errors.SDKError }{ { - desc: "create new cert with thing id and duration", - thingID: thingID, + desc: "create new cert with client id and duration", + clientID: clientID, duration: ttl, domainID: validID, token: validToken, @@ -104,8 +104,8 @@ func TestIssueCert(t *testing.T) { err: nil, }, { - desc: "create new cert with empty thing id and duration", - thingID: "", + desc: "create new cert with empty client id and duration", + clientID: "", duration: ttl, domainID: validID, token: validToken, @@ -114,8 +114,8 @@ func TestIssueCert(t *testing.T) { err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "create new cert with invalid thing id and duration", - thingID: invalid, + desc: "create new cert with invalid client id and duration", + clientID: invalid, duration: ttl, domainID: validID, token: validToken, @@ -124,8 +124,8 @@ func TestIssueCert(t *testing.T) { err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, certs.ErrFailedCertCreation), http.StatusBadRequest), }, { - desc: "create new cert with thing id and empty duration", - thingID: thingID, + desc: "create new cert with client id and empty duration", + clientID: clientID, duration: "", domainID: validID, token: validToken, @@ -134,8 +134,8 @@ func TestIssueCert(t *testing.T) { err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingCertData), http.StatusBadRequest), }, { - desc: "create new cert with thing id and malformed duration", - thingID: thingID, + desc: "create new cert with client id and malformed duration", + clientID: clientID, duration: invalid, domainID: validID, token: validToken, @@ -145,7 +145,7 @@ func TestIssueCert(t *testing.T) { }, { desc: "create new cert with empty token", - thingID: thingID, + clientID: clientID, duration: ttl, domainID: validID, token: "", @@ -155,7 +155,7 @@ func TestIssueCert(t *testing.T) { }, { desc: "create new cert with invalid token", - thingID: thingID, + clientID: clientID, domainID: domainID, duration: ttl, token: invalidToken, @@ -165,7 +165,7 @@ func TestIssueCert(t *testing.T) { }, { desc: "create new empty cert", - thingID: "", + clientID: "", duration: "", domainID: validID, token: validToken, @@ -181,12 +181,12 @@ func TestIssueCert(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("IssueCert", mock.Anything, tc.domainID, tc.token, tc.thingID, tc.duration).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.IssueCert(tc.thingID, tc.duration, tc.domainID, tc.token) + svcCall := svc.On("IssueCert", mock.Anything, tc.domainID, tc.token, tc.clientID, tc.duration).Return(tc.svcRes, tc.svcErr) + resp, err := mgsdk.IssueCert(tc.clientID, tc.duration, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { assert.Equal(t, tc.svcRes.SerialNumber, resp.SerialNumber) - ok := svcCall.Parent.AssertCalled(t, "IssueCert", mock.Anything, tc.domainID, tc.token, tc.thingID, tc.duration) + ok := svcCall.Parent.AssertCalled(t, "IssueCert", mock.Anything, tc.domainID, tc.token, tc.clientID, tc.duration) assert.True(t, ok) } svcCall.Unset() @@ -279,7 +279,7 @@ func TestViewCert(t *testing.T) { } } -func TestViewCertByThing(t *testing.T) { +func TestViewCertByClient(t *testing.T) { ts, svc, auth := setupCerts() defer ts.Close() @@ -291,14 +291,14 @@ func TestViewCertByThing(t *testing.T) { mgsdk := sdk.NewSDK(sdkConf) - viewCertThingRes := sdk.CertSerials{ + viewCertClientRes := sdk.CertSerials{ Certs: []sdk.Cert{{ SerialNumber: serial, }}, } cases := []struct { desc string - thingID string + clientID string domainID string token string session mgauthn.Session @@ -309,7 +309,7 @@ func TestViewCertByThing(t *testing.T) { }{ { desc: "view existing cert", - thingID: thingID, + clientID: clientID, domainID: domainID, token: validToken, svcRes: certs.CertPage{Certificates: []certs.Cert{{SerialNumber: serial}}}, @@ -318,7 +318,7 @@ func TestViewCertByThing(t *testing.T) { }, { desc: "view non-existent cert", - thingID: invalid, + clientID: invalid, domainID: domainID, token: validToken, svcRes: certs.CertPage{Certificates: []certs.Cert{}}, @@ -327,7 +327,7 @@ func TestViewCertByThing(t *testing.T) { }, { desc: "view cert with invalid token", - thingID: thingID, + clientID: clientID, domainID: domainID, token: invalidToken, svcRes: certs.CertPage{Certificates: []certs.Cert{}}, @@ -336,7 +336,7 @@ func TestViewCertByThing(t *testing.T) { }, { desc: "view cert with empty token", - thingID: thingID, + clientID: clientID, domainID: domainID, token: "", svcRes: certs.CertPage{Certificates: []certs.Cert{}}, @@ -344,8 +344,8 @@ func TestViewCertByThing(t *testing.T) { err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "view cert with empty thing id", - thingID: "", + desc: "view cert with empty client id", + clientID: "", domainID: domainID, token: validToken, svcRes: certs.CertPage{Certificates: []certs.Cert{}}, @@ -359,12 +359,12 @@ func TestViewCertByThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ListSerials", mock.Anything, tc.thingID, certs.PageMetadata{Revoked: defRevoke, Offset: defOffset, Limit: defLimit}).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ViewCertByThing(tc.thingID, tc.domainID, tc.token) + svcCall := svc.On("ListSerials", mock.Anything, tc.clientID, certs.PageMetadata{Revoked: defRevoke, Offset: defOffset, Limit: defLimit}).Return(tc.svcRes, tc.svcErr) + resp, err := mgsdk.ViewCertByClient(tc.clientID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - assert.Equal(t, viewCertThingRes, resp) - ok := svcCall.Parent.AssertCalled(t, "ListSerials", mock.Anything, tc.thingID, certs.PageMetadata{Revoked: defRevoke, Offset: defOffset, Limit: defLimit}) + assert.Equal(t, viewCertClientRes, resp) + ok := svcCall.Parent.AssertCalled(t, "ListSerials", mock.Anything, tc.clientID, certs.PageMetadata{Revoked: defRevoke, Offset: defOffset, Limit: defLimit}) assert.True(t, ok) } svcCall.Unset() @@ -387,7 +387,7 @@ func TestRevokeCert(t *testing.T) { cases := []struct { desc string - thingID string + clientID string domainID string token string session mgauthn.Session @@ -398,7 +398,7 @@ func TestRevokeCert(t *testing.T) { }{ { desc: "revoke cert successfully", - thingID: thingID, + clientID: clientID, domainID: validID, token: validToken, svcResp: certs.Revoke{RevocationTime: time.Now()}, @@ -407,7 +407,7 @@ func TestRevokeCert(t *testing.T) { }, { desc: "revoke cert with invalid token", - thingID: thingID, + clientID: clientID, domainID: validID, token: invalidToken, svcResp: certs.Revoke{}, @@ -416,7 +416,7 @@ func TestRevokeCert(t *testing.T) { }, { desc: "revoke non-existing cert", - thingID: invalid, + clientID: invalid, domainID: validID, token: validToken, svcResp: certs.Revoke{}, @@ -425,7 +425,7 @@ func TestRevokeCert(t *testing.T) { }, { desc: "revoke cert with empty token", - thingID: thingID, + clientID: clientID, domainID: validID, token: "", svcResp: certs.Revoke{}, @@ -434,7 +434,7 @@ func TestRevokeCert(t *testing.T) { }, { desc: "revoke deleted cert", - thingID: thingID, + clientID: clientID, domainID: validID, token: validToken, svcResp: certs.Revoke{}, @@ -448,12 +448,12 @@ func TestRevokeCert(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("RevokeCert", mock.Anything, tc.domainID, tc.token, tc.thingID).Return(tc.svcResp, tc.svcErr) - resp, err := mgsdk.RevokeCert(tc.thingID, tc.domainID, tc.token) + svcCall := svc.On("RevokeCert", mock.Anything, tc.domainID, tc.token, tc.clientID).Return(tc.svcResp, tc.svcErr) + resp, err := mgsdk.RevokeCert(tc.clientID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if err == nil { assert.NotEmpty(t, resp) - ok := svcCall.Parent.AssertCalled(t, "RevokeCert", mock.Anything, tc.domainID, tc.token, tc.thingID) + ok := svcCall.Parent.AssertCalled(t, "RevokeCert", mock.Anything, tc.domainID, tc.token, tc.clientID) assert.True(t, ok) } svcCall.Unset() diff --git a/pkg/sdk/go/channels.go b/pkg/sdk/go/channels.go index d68b92c846..fa8b11adc1 100644 --- a/pkg/sdk/go/channels.go +++ b/pkg/sdk/go/channels.go @@ -37,7 +37,7 @@ func (sdk mgSDK) CreateChannel(c Channel, domainID, token string) (Channel, erro if err != nil { return Channel{}, errors.NewSDKError(err) } - url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint) + url := fmt.Sprintf("%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint) _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) if sdkerr != nil { @@ -54,7 +54,7 @@ func (sdk mgSDK) CreateChannel(c Channel, domainID, token string) (Channel, erro func (sdk mgSDK) Channels(pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) { endpoint := fmt.Sprintf("%s/%s", domainID, channelsEndpoint) - url, err := sdk.withQueryParams(sdk.thingsURL, endpoint, pm) + url, err := sdk.withQueryParams(sdk.clientsURL, endpoint, pm) if err != nil { return ChannelsPage{}, errors.NewSDKError(err) } @@ -72,8 +72,8 @@ func (sdk mgSDK) Channels(pm PageMetadata, domainID, token string) (ChannelsPage return cp, nil } -func (sdk mgSDK) ChannelsByThing(thingID string, pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) { - url, err := sdk.withQueryParams(fmt.Sprintf("%s/%s/things/%s", sdk.thingsURL, domainID, thingID), channelsEndpoint, pm) +func (sdk mgSDK) ChannelsByClient(clientID string, pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) { + url, err := sdk.withQueryParams(fmt.Sprintf("%s/%s/clients/%s", sdk.clientsURL, domainID, clientID), channelsEndpoint, pm) if err != nil { return ChannelsPage{}, errors.NewSDKError(err) } @@ -95,7 +95,7 @@ func (sdk mgSDK) Channel(id, domainID, token string) (Channel, errors.SDKError) if id == "" { return Channel{}, errors.NewSDKError(apiutil.ErrMissingID) } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, id) + url := fmt.Sprintf("%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, id) _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) if err != nil { @@ -111,7 +111,7 @@ func (sdk mgSDK) Channel(id, domainID, token string) (Channel, errors.SDKError) } func (sdk mgSDK) ChannelPermissions(id, domainID, token string) (Channel, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, id, permissionsEndpoint) + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, id, permissionsEndpoint) _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) if err != nil { @@ -130,7 +130,7 @@ func (sdk mgSDK) UpdateChannel(c Channel, domainID, token string) (Channel, erro if c.ID == "" { return Channel{}, errors.NewSDKError(apiutil.ErrMissingID) } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, c.ID) + url := fmt.Sprintf("%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, c.ID) data, err := json.Marshal(c) if err != nil { @@ -156,7 +156,7 @@ func (sdk mgSDK) AddUserToChannel(channelID string, req UsersRelationRequest, do return errors.NewSDKError(err) } - url := fmt.Sprintf("%s/%s/%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, usersEndpoint, assignEndpoint) + url := fmt.Sprintf("%s/%s/%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, channelID, usersEndpoint, assignEndpoint) _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) return sdkerr @@ -168,7 +168,7 @@ func (sdk mgSDK) RemoveUserFromChannel(channelID string, req UsersRelationReques return errors.NewSDKError(err) } - url := fmt.Sprintf("%s/%s/%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, usersEndpoint, unassignEndpoint) + url := fmt.Sprintf("%s/%s/%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, channelID, usersEndpoint, unassignEndpoint) _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) return sdkerr @@ -197,7 +197,7 @@ func (sdk mgSDK) AddUserGroupToChannel(channelID string, req UserGroupsRequest, return errors.NewSDKError(err) } - url := fmt.Sprintf("%s/%s/%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, groupsEndpoint, assignEndpoint) + url := fmt.Sprintf("%s/%s/%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, channelID, groupsEndpoint, assignEndpoint) _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) return sdkerr @@ -209,7 +209,7 @@ func (sdk mgSDK) RemoveUserGroupFromChannel(channelID string, req UserGroupsRequ return errors.NewSDKError(err) } - url := fmt.Sprintf("%s/%s/%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, groupsEndpoint, unassignEndpoint) + url := fmt.Sprintf("%s/%s/%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, channelID, groupsEndpoint, unassignEndpoint) _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) return sdkerr @@ -238,7 +238,7 @@ func (sdk mgSDK) Connect(conn Connection, domainID, token string) errors.SDKErro return errors.NewSDKError(err) } - url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, domainID, connectEndpoint) + url := fmt.Sprintf("%s/%s/%s", sdk.clientsURL, domainID, connectEndpoint) _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) @@ -251,23 +251,23 @@ func (sdk mgSDK) Disconnect(connIDs Connection, domainID, token string) errors.S return errors.NewSDKError(err) } - url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, domainID, disconnectEndpoint) + url := fmt.Sprintf("%s/%s/%s", sdk.clientsURL, domainID, disconnectEndpoint) _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) return sdkerr } -func (sdk mgSDK) ConnectThing(thingID, channelID, domainID, token string) errors.SDKError { - url := fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, thingsEndpoint, thingID, connectEndpoint) +func (sdk mgSDK) ConnectClient(clientID, channelID, domainID, token string) errors.SDKError { + url := fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, channelID, clientsEndpoint, clientID, connectEndpoint) _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusCreated) return sdkerr } -func (sdk mgSDK) DisconnectThing(thingID, channelID, domainID, token string) errors.SDKError { - url := fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, thingsEndpoint, thingID, disconnectEndpoint) +func (sdk mgSDK) DisconnectClient(clientID, channelID, domainID, token string) errors.SDKError { + url := fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, channelID, clientsEndpoint, clientID, disconnectEndpoint) _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusNoContent) @@ -286,13 +286,13 @@ func (sdk mgSDK) DeleteChannel(id, domainID, token string) errors.SDKError { if id == "" { return errors.NewSDKError(apiutil.ErrMissingID) } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, id) + url := fmt.Sprintf("%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, id) _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) return sdkerr } func (sdk mgSDK) changeChannelStatus(id, status, domainID, token string) (Channel, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, id, status) + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.clientsURL, domainID, channelsEndpoint, id, status) _, body, err := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusOK) if err != nil { diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index d4b02dc686..66bc72d672 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -12,6 +12,11 @@ import ( "time" authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/channels" + chmocks "github.com/absmach/magistrala/channels/mocks" + "github.com/absmach/magistrala/clients" + thapi "github.com/absmach/magistrala/clients/api/http" + climocks "github.com/absmach/magistrala/clients/mocks" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" @@ -19,13 +24,9 @@ import ( authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" - gmocks "github.com/absmach/magistrala/pkg/groups/mocks" oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" policies "github.com/absmach/magistrala/pkg/policies" sdk "github.com/absmach/magistrala/pkg/sdk/go" - thapi "github.com/absmach/magistrala/things/api/http" - thmocks "github.com/absmach/magistrala/things/mocks" usapi "github.com/absmach/magistrala/users/api" usmocks "github.com/absmach/magistrala/users/mocks" "github.com/go-chi/chi/v5" @@ -40,10 +41,10 @@ var ( channel = generateTestChannel(&testing.T{}) ) -func setupChannels() (*httptest.Server, *gmocks.Service, *authnmocks.Authentication) { - tsvc := new(thmocks.Service) +func setupChannels() (*httptest.Server, *chmocks.Service, *authnmocks.Authentication) { + tsvc := new(climocks.Service) usvc := new(usmocks.Service) - gsvc := new(gmocks.Service) + gsvc := new(chmocks.Service) logger := mglog.NewMock() provider := new(oauth2mocks.Provider) provider.On("Name").Return("test") @@ -52,7 +53,7 @@ func setupChannels() (*httptest.Server, *gmocks.Service, *authnmocks.Authenticat mux := chi.NewRouter() - thapi.MakeHandler(tsvc, gsvc, authn, mux, logger, "") + thapi.MakeHandler(tsvc, authn, mux, logger, "") usapi.MakeHandler(usvc, authn, token, true, gsvc, mux, logger, "", passRegex, provider) return httptest.NewServer(mux), gsvc, authn } @@ -61,17 +62,17 @@ func TestCreateChannel(t *testing.T) { ts, gsvc, auth := setupChannels() defer ts.Close() - group := convertChannel(channel) - createGroupReq := groups.Group{ + channel := convertChannel(channel) + createGroupReq := channels.Channel{ Name: channel.Name, - Metadata: groups.Metadata{"role": "client"}, - Status: groups.EnabledStatus, + Metadata: clients.Metadata{"role": "client"}, + Status: clients.EnabledStatus, } channelReq := sdk.Channel{ Name: channel.Name, Metadata: validMetadata, - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), } channelKind := "new_channel" @@ -82,12 +83,12 @@ func TestCreateChannel(t *testing.T) { pChannel.ParentID = parentID iGroup := group - iGroup.Metadata = groups.Metadata{ + iGroup.Metadata = clients.Metadata{ "test": make(chan int), } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) cases := []struct { @@ -96,8 +97,8 @@ func TestCreateChannel(t *testing.T) { domainID string token string session mgauthn.Session - createGroupReq groups.Group - svcRes groups.Group + createGroupReq channels.Channel + svcRes channels.Channel svcErr error authenticateRes mgauthn.Session authenticateErr error @@ -121,7 +122,7 @@ func TestCreateChannel(t *testing.T) { domainID: domainID, token: validToken, createGroupReq: createGroupReq, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: svcerr.ErrCreateEntity, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), @@ -136,8 +137,8 @@ func TestCreateChannel(t *testing.T) { }, domainID: domainID, token: validToken, - createGroupReq: groups.Group{}, - svcRes: groups.Group{}, + createGroupReq: channels.Channel{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -147,14 +148,14 @@ func TestCreateChannel(t *testing.T) { channelReq: sdk.Channel{ Name: channel.Name, ParentID: parentID, - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), }, domainID: domainID, token: validToken, - createGroupReq: groups.Group{ + createGroupReq: channels.Channel{ Name: channel.Name, Parent: parentID, - Status: groups.EnabledStatus, + Status: clients.EnabledStatus, }, svcRes: pGroup, svcErr: nil, @@ -166,16 +167,16 @@ func TestCreateChannel(t *testing.T) { channelReq: sdk.Channel{ Name: channel.Name, ParentID: wrongID, - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), }, domainID: domainID, token: validToken, - createGroupReq: groups.Group{ + createGroupReq: channels.Channel{ Name: channel.Name, Parent: wrongID, - Status: groups.EnabledStatus, + Status: clients.EnabledStatus, }, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: svcerr.ErrCreateEntity, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), @@ -183,12 +184,12 @@ func TestCreateChannel(t *testing.T) { { desc: "create channel with missing name", channelReq: sdk.Channel{ - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), }, domainID: domainID, token: validToken, - createGroupReq: groups.Group{}, - svcRes: groups.Group{}, + createGroupReq: channels.Channel{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), @@ -203,19 +204,19 @@ func TestCreateChannel(t *testing.T) { Metadata: validMetadata, CreatedAt: group.CreatedAt, UpdatedAt: group.UpdatedAt, - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), }, domainID: domainID, token: validToken, - createGroupReq: groups.Group{ + createGroupReq: channels.Channel{ ID: group.ID, Parent: parentID, Name: channel.Name, Description: description, - Metadata: groups.Metadata{"role": "client"}, + Metadata: clients.Metadata{"role": "client"}, CreatedAt: group.CreatedAt, UpdatedAt: group.UpdatedAt, - Status: groups.EnabledStatus, + Status: clients.EnabledStatus, }, svcRes: pGroup, svcErr: nil, @@ -260,7 +261,7 @@ func TestListChannels(t *testing.T) { var chs []sdk.Channel conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -268,8 +269,8 @@ func TestListChannels(t *testing.T) { gr := sdk.Channel{ ID: generateUUID(t), Name: fmt.Sprintf("channel_%d", i), - Metadata: sdk.Metadata{"name": fmt.Sprintf("thing_%d", i)}, - Status: groups.EnabledStatus.String(), + Metadata: sdk.Metadata{"name": fmt.Sprintf("client_%d", i)}, + Status: clients.EnabledStatus.String(), } chs = append(chs, gr) } @@ -279,15 +280,15 @@ func TestListChannels(t *testing.T) { domainID string token string session mgauthn.Session - status groups.Status + status channels.Status total uint64 offset uint64 limit uint64 level int name string metadata sdk.Metadata - groupsPageMeta groups.Page - svcRes groups.Page + groupsPageMeta channels.Page + svcRes channels.Page svcErr error authenticateRes mgauthn.Session authenticateErr error @@ -301,16 +302,14 @@ func TestListChannels(t *testing.T) { limit: limit, offset: offset, total: total, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ + groupsPageMeta: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: offset, Limit: limit, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: uint64(len(chs[offset:limit])), }, Groups: convertChannels(chs[offset:limit]), @@ -329,15 +328,13 @@ func TestListChannels(t *testing.T) { domainID: domainID, offset: offset, limit: limit, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ + groupsPageMeta: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: offset, Limit: limit, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{}, + svcRes: channels.Page{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -348,8 +345,8 @@ func TestListChannels(t *testing.T) { domainID: validID, offset: offset, limit: limit, - groupsPageMeta: groups.Page{}, - svcRes: groups.Page{}, + groupsPageMeta: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -360,16 +357,14 @@ func TestListChannels(t *testing.T) { domainID: domainID, offset: offset, limit: 0, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ + groupsPageMeta: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: offset, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: uint64(len(chs[offset:])), }, Groups: convertChannels(chs[offset:limit]), @@ -389,8 +384,8 @@ func TestListChannels(t *testing.T) { domainID: domainID, offset: offset, limit: 110, - groupsPageMeta: groups.Page{}, - svcRes: groups.Page{}, + groupsPageMeta: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), @@ -402,17 +397,14 @@ func TestListChannels(t *testing.T) { offset: 0, limit: 1, level: 1, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ + groupsPageMeta: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: offset, Limit: 1, }, - Level: 1, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: 1, }, Groups: convertChannels(chs[0:1]), @@ -432,18 +424,16 @@ func TestListChannels(t *testing.T) { domainID: domainID, offset: 0, limit: 10, - metadata: sdk.Metadata{"name": "thing_89"}, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ + metadata: sdk.Metadata{"name": "client_89"}, + groupsPageMeta: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: offset, Limit: 10, - Metadata: groups.Metadata{"name": "thing_89"}, + Metadata: clients.Metadata{"name": "client_89"}, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: 1, }, Groups: convertChannels([]sdk.Channel{chs[89]}), @@ -466,8 +456,8 @@ func TestListChannels(t *testing.T) { metadata: sdk.Metadata{ "test": make(chan int), }, - groupsPageMeta: groups.Page{}, - svcRes: groups.Page{}, + groupsPageMeta: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.ChannelsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -478,21 +468,19 @@ func TestListChannels(t *testing.T) { domainID: domainID, offset: 0, limit: 10, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ + groupsPageMeta: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: 1, }, - Groups: []groups.Group{{ + Groups: []channels.Channel{{ ID: generateUUID(t), - Metadata: groups.Metadata{ + Metadata: clients.Metadata{ "test": make(chan int), }, }}, @@ -535,7 +523,7 @@ func TestViewChannel(t *testing.T) { groupRes := convertChannel(channel) conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -545,7 +533,7 @@ func TestViewChannel(t *testing.T) { token string session mgauthn.Session channelID string - svcRes groups.Group + svcRes channels.Channel svcErr error authenticateErr error response sdk.Channel @@ -566,7 +554,7 @@ func TestViewChannel(t *testing.T) { domainID: domainID, token: invalidToken, channelID: groupRes.ID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -576,7 +564,7 @@ func TestViewChannel(t *testing.T) { domainID: domainID, token: "", channelID: groupRes.ID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -586,7 +574,7 @@ func TestViewChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: wrongID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: svcerr.ErrViewEntity, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), @@ -596,7 +584,7 @@ func TestViewChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: "", - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKError(apiutil.ErrMissingID), @@ -606,9 +594,9 @@ func TestViewChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: groupRes.ID, - svcRes: groups.Group{ + svcRes: channels.Channel{ ID: generateUUID(t), - Metadata: groups.Metadata{ + Metadata: clients.Metadata{ "test": make(chan int), }, }, @@ -643,7 +631,7 @@ func TestUpdateChannel(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -659,7 +647,7 @@ func TestUpdateChannel(t *testing.T) { dChannel.Description = newDescription mGroup := group - mGroup.Metadata = groups.Metadata{ + mGroup.Metadata = clients.Metadata{ "field": "value2", } mChannel := channel @@ -670,7 +658,7 @@ func TestUpdateChannel(t *testing.T) { aGroup := group aGroup.Name = newName aGroup.Description = newDescription - aGroup.Metadata = groups.Metadata{"field": "value2"} + aGroup.Metadata = clients.Metadata{"field": "value2"} aChannel := channel aChannel.Name = newName aChannel.Description = newDescription @@ -682,8 +670,8 @@ func TestUpdateChannel(t *testing.T) { token string session mgauthn.Session channelReq sdk.Channel - updateGroupReq groups.Group - svcRes groups.Group + updateGroupReq channels.Channel + svcRes channels.Channel svcErr error authenticateErr error response sdk.Channel @@ -697,7 +685,7 @@ func TestUpdateChannel(t *testing.T) { ID: channel.ID, Name: newName, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: group.ID, Name: newName, }, @@ -714,7 +702,7 @@ func TestUpdateChannel(t *testing.T) { ID: channel.ID, Description: newDescription, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: group.ID, Description: newDescription, }, @@ -733,9 +721,9 @@ func TestUpdateChannel(t *testing.T) { "field": "value2", }, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: group.ID, - Metadata: groups.Metadata{"field": "value2"}, + Metadata: clients.Metadata{"field": "value2"}, }, svcRes: mGroup, svcErr: nil, @@ -752,11 +740,11 @@ func TestUpdateChannel(t *testing.T) { Description: newDescription, Metadata: sdk.Metadata{"field": "value2"}, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: group.ID, Name: newName, Description: newDescription, - Metadata: groups.Metadata{"field": "value2"}, + Metadata: clients.Metadata{"field": "value2"}, }, svcRes: aGroup, svcErr: nil, @@ -771,11 +759,11 @@ func TestUpdateChannel(t *testing.T) { ID: wrongID, Name: newName, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: wrongID, Name: newName, }, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: svcerr.ErrNotFound, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), @@ -788,11 +776,11 @@ func TestUpdateChannel(t *testing.T) { ID: wrongID, Description: newDescription, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: wrongID, Description: newDescription, }, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: svcerr.ErrNotFound, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), @@ -807,11 +795,11 @@ func TestUpdateChannel(t *testing.T) { "field": "value2", }, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: wrongID, - Metadata: groups.Metadata{"field": "value2"}, + Metadata: clients.Metadata{"field": "value2"}, }, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: svcerr.ErrNotFound, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), @@ -824,11 +812,11 @@ func TestUpdateChannel(t *testing.T) { ID: channel.ID, Name: newName, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: group.ID, Name: newName, }, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -841,11 +829,11 @@ func TestUpdateChannel(t *testing.T) { ID: channel.ID, Name: newName, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: group.ID, Name: newName, }, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -858,8 +846,8 @@ func TestUpdateChannel(t *testing.T) { ID: channel.ID, Name: strings.Repeat("a", 1025), }, - updateGroupReq: groups.Group{}, - svcRes: groups.Group{}, + updateGroupReq: channels.Channel{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), @@ -875,8 +863,8 @@ func TestUpdateChannel(t *testing.T) { "test": make(chan int), }, }, - updateGroupReq: groups.Group{}, - svcRes: groups.Group{}, + updateGroupReq: channels.Channel{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -889,13 +877,13 @@ func TestUpdateChannel(t *testing.T) { ID: channel.ID, Name: newName, }, - updateGroupReq: groups.Group{ + updateGroupReq: channels.Channel{ ID: group.ID, Name: newName, }, - svcRes: groups.Group{ + svcRes: channels.Channel{ ID: generateUUID(t), - Metadata: groups.Metadata{ + Metadata: clients.Metadata{ "test": make(chan int), }, }, @@ -910,8 +898,8 @@ func TestUpdateChannel(t *testing.T) { channelReq: sdk.Channel{ Name: newName, }, - updateGroupReq: groups.Group{}, - svcRes: groups.Group{}, + updateGroupReq: channels.Channel{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKError(apiutil.ErrMissingID), @@ -937,12 +925,12 @@ func TestUpdateChannel(t *testing.T) { } } -func TestListChannelsByThing(t *testing.T) { +func TestListChannelsByClient(t *testing.T) { ts, gsvc, auth := setupChannels() defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -954,7 +942,7 @@ func TestListChannelsByThing(t *testing.T) { ID: generateUUID(t), Name: fmt.Sprintf("membership_%d@example.com", i), Metadata: sdk.Metadata{"role": "channel"}, - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), } aChannels = append(aChannels, channel) } @@ -964,10 +952,10 @@ func TestListChannelsByThing(t *testing.T) { domainID string token string session mgauthn.Session - thingID string + ClientID string pageMeta sdk.PageMetadata - listGroupsReq groups.Page - svcRes groups.Page + listGroupsReq channels.Page + svcRes channels.Page svcErr error authenticateErr error response sdk.ChannelsPage @@ -977,21 +965,19 @@ func TestListChannelsByThing(t *testing.T) { desc: "list channels successfully", domainID: domainID, token: validToken, - thingID: testsutil.GenerateUUID(t), + ClientID: testsutil.GenerateUUID(t), pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: nChannels, }, - Groups: convertChannels(aChannels), + Channels: convertChannels(aChannels), }, svcErr: nil, response: sdk.ChannelsPage{ @@ -1006,24 +992,22 @@ func TestListChannelsByThing(t *testing.T) { desc: "list channel with offset and limit", domainID: domainID, token: validToken, - thingID: testsutil.GenerateUUID(t), + ClientID: testsutil.GenerateUUID(t), pageMeta: sdk.PageMetadata{ Offset: 6, Limit: nChannels, }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 6, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: uint64(len(aChannels[6 : nChannels-1])), }, - Groups: convertChannels(aChannels[6 : nChannels-1]), + Channels: convertChannels(aChannels[6 : nChannels-1]), }, svcErr: nil, response: sdk.ChannelsPage{ @@ -1038,26 +1022,24 @@ func TestListChannelsByThing(t *testing.T) { desc: "list channel with given name", domainID: domainID, token: validToken, - thingID: testsutil.GenerateUUID(t), + ClientID: testsutil.GenerateUUID(t), pageMeta: sdk.PageMetadata{ Name: "membership_8@example.com", Offset: 0, Limit: nChannels, }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Name: "membership_8@example.com", Offset: 0, Limit: nChannels, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: 1, }, - Groups: convertChannels([]sdk.Channel{aChannels[8]}), + Channels: convertChannels([]sdk.Channel{aChannels[8]}), }, svcErr: nil, response: sdk.ChannelsPage{ @@ -1072,17 +1054,15 @@ func TestListChannelsByThing(t *testing.T) { desc: "list channels with invalid token", domainID: domainID, token: invalidToken, - thingID: testsutil.GenerateUUID(t), + ClientID: testsutil.GenerateUUID(t), pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{}, + svcRes: channels.Page{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -1091,10 +1071,10 @@ func TestListChannelsByThing(t *testing.T) { desc: "list channels with empty token", domainID: domainID, token: "", - thingID: testsutil.GenerateUUID(t), + ClientID: testsutil.GenerateUUID(t), pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, + listGroupsReq: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -1103,12 +1083,12 @@ func TestListChannelsByThing(t *testing.T) { desc: "list channels with limit greater than max", domainID: domainID, token: validToken, - thingID: testsutil.GenerateUUID(t), + ClientID: testsutil.GenerateUUID(t), pageMeta: sdk.PageMetadata{ Limit: 110, }, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, + listGroupsReq: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), @@ -1117,14 +1097,14 @@ func TestListChannelsByThing(t *testing.T) { desc: "list channels with invalid metadata", domainID: domainID, token: validToken, - thingID: testsutil.GenerateUUID(t), + ClientID: testsutil.GenerateUUID(t), pageMeta: sdk.PageMetadata{ Metadata: sdk.Metadata{ "test": make(chan int), }, }, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, + listGroupsReq: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.ChannelsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -1133,26 +1113,24 @@ func TestListChannelsByThing(t *testing.T) { desc: "list channels with service response that can't be unmarshalled", domainID: domainID, token: validToken, - thingID: testsutil.GenerateUUID(t), + ClientID: testsutil.GenerateUUID(t), pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 10, }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: 1, }, - Groups: []groups.Group{{ + Channels: []channels.Channel{{ ID: generateUUID(t), - Metadata: groups.Metadata{ + Metadata: clients.Metadata{ "test": make(chan int), }, }}, @@ -1168,12 +1146,12 @@ func TestListChannelsByThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := gsvc.On("ListGroups", mock.Anything, tc.session, policies.ThingsKind, tc.thingID, tc.listGroupsReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ChannelsByThing(tc.thingID, tc.pageMeta, tc.domainID, tc.token) + svcCall := gsvc.On("ListGroups", mock.Anything, tc.session, policies.ClientsKind, tc.ClientID, tc.listGroupsReq).Return(tc.svcRes, tc.svcErr) + resp, err := mgsdk.ChannelsByClient(tc.ClientID, tc.pageMeta, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListGroups", mock.Anything, tc.session, policies.ThingsKind, tc.thingID, tc.listGroupsReq) + ok := svcCall.Parent.AssertCalled(t, "ListGroups", mock.Anything, tc.session, policies.ClientsKind, tc.ClientID, tc.listGroupsReq) assert.True(t, ok) } svcCall.Unset() @@ -1188,7 +1166,7 @@ func TestEnableChannel(t *testing.T) { group := convertChannel(channel) conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1198,7 +1176,7 @@ func TestEnableChannel(t *testing.T) { token string session mgauthn.Session channelID string - svcRes groups.Group + svcRes channels.Channel svcErr error authenticateErr error response sdk.Channel @@ -1219,7 +1197,7 @@ func TestEnableChannel(t *testing.T) { domainID: domainID, token: invalidToken, channelID: channel.ID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -1229,7 +1207,7 @@ func TestEnableChannel(t *testing.T) { domainID: domainID, token: "", channelID: channel.ID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -1239,7 +1217,7 @@ func TestEnableChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: wrongID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: svcerr.ErrNotFound, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), @@ -1249,7 +1227,7 @@ func TestEnableChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: "", - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), @@ -1259,9 +1237,9 @@ func TestEnableChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: channel.ID, - svcRes: groups.Group{ + svcRes: channels.Channel{ ID: generateUUID(t), - Metadata: groups.Metadata{ + Metadata: clients.Metadata{ "test": make(chan int), }, }, @@ -1295,15 +1273,15 @@ func TestDisableChannel(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) group := convertChannel(channel) dGroup := group - dGroup.Status = groups.DisabledStatus + dGroup.Status = clients.DisabledStatus dChannel := channel - dChannel.Status = groups.DisabledStatus.String() + dChannel.Status = clients.DisabledStatus.String() cases := []struct { desc string @@ -1311,7 +1289,7 @@ func TestDisableChannel(t *testing.T) { token string session mgauthn.Session channelID string - svcRes groups.Group + svcRes channels.Channel svcErr error authenticateErr error response sdk.Channel @@ -1332,7 +1310,7 @@ func TestDisableChannel(t *testing.T) { domainID: domainID, token: invalidToken, channelID: channel.ID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -1342,7 +1320,7 @@ func TestDisableChannel(t *testing.T) { domainID: domainID, token: "", channelID: channel.ID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -1352,7 +1330,7 @@ func TestDisableChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: wrongID, - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: svcerr.ErrNotFound, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), @@ -1362,7 +1340,7 @@ func TestDisableChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: "", - svcRes: groups.Group{}, + svcRes: channels.Channel{}, svcErr: nil, response: sdk.Channel{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), @@ -1372,9 +1350,9 @@ func TestDisableChannel(t *testing.T) { domainID: domainID, token: validToken, channelID: channel.ID, - svcRes: groups.Group{ + svcRes: channels.Channel{ ID: generateUUID(t), - Metadata: groups.Metadata{ + Metadata: clients.Metadata{ "test": make(chan int), }, }, @@ -1408,7 +1386,7 @@ func TestDeleteChannel(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1487,7 +1465,7 @@ func TestChannelPermissions(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1582,7 +1560,7 @@ func TestAddUserToChannel(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1706,7 +1684,7 @@ func TestRemoveUserFromChannel(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1818,7 +1796,7 @@ func TestAddUserGroupToChannel(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1928,7 +1906,7 @@ func TestRemoveUserGroupFromChannel(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -2043,14 +2021,14 @@ func TestListChannelUserGroups(t *testing.T) { mgsdk := sdk.NewSDK(conf) nGroups := uint64(10) - aGroups := []sdk.Group{} + aGroups := []sdk.Channel{} for i := uint64(1); i < nGroups; i++ { - group := sdk.Group{ + group := sdk.Channel{ ID: generateUUID(t), Name: fmt.Sprintf("group_%d", i), Metadata: sdk.Metadata{"role": "group"}, - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), } aGroups = append(aGroups, group) } @@ -2062,44 +2040,40 @@ func TestListChannelUserGroups(t *testing.T) { session mgauthn.Session channelID string pageMeta sdk.PageMetadata - listGroupsReq groups.Page - svcRes groups.Page + listGroupsReq channels.Page + svcRes channels.Page svcErr error authenticateErr error response sdk.GroupsPage err errors.SDKError }{ { - desc: "list user groups successfully", + desc: "list user channels successfully", domainID: domainID, token: validToken, channelID: channel.ID, pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: nGroups, }, - Groups: convertGroups(aGroups), }, svcErr: nil, response: sdk.GroupsPage{ PageRes: sdk.PageRes{ Total: nGroups, }, - Groups: aGroups, }, err: nil, }, { - desc: "list user groups with offset and limit", + desc: "list user channels with offset and limit", domainID: domainID, token: validToken, channelID: channel.ID, @@ -2107,112 +2081,102 @@ func TestListChannelUserGroups(t *testing.T) { Offset: 6, Limit: nGroups, }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 6, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: uint64(len(aGroups[6 : nGroups-1])), }, - Groups: convertGroups(aGroups[6 : nGroups-1]), }, svcErr: nil, response: sdk.GroupsPage{ PageRes: sdk.PageRes{ Total: uint64(len(aGroups[6 : nGroups-1])), }, - Groups: aGroups[6 : nGroups-1], }, err: nil, }, { - desc: "list user groups with invalid token", + desc: "list user channels with invalid token", domainID: domainID, token: invalidToken, channelID: channel.ID, pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{}, + svcRes: channels.Page{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.GroupsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, { - desc: "list user groups with empty token", + desc: "list user channels with empty token", domainID: domainID, token: "", channelID: channel.ID, pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.GroupsPage{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "list user groups with limit greater than max", + desc: "list user channels with limit greater than max", domainID: domainID, token: validToken, channelID: channel.ID, pageMeta: sdk.PageMetadata{ Limit: 110, }, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, + listGroupsReq: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.GroupsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), }, { - desc: "list user groups with invalid channel id", + desc: "list user channels with invalid channel id", domainID: domainID, token: validToken, channelID: wrongID, pageMeta: sdk.PageMetadata{ DomainID: domainID, }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{}, + svcRes: channels.Page{}, svcErr: svcerr.ErrAuthorization, response: sdk.GroupsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "list users groups with level exceeding max", + desc: "list users channels with level exceeding max", domainID: domainID, token: validToken, channelID: channel.ID, pageMeta: sdk.PageMetadata{ Level: 10, }, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, + listGroupsReq: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.GroupsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidLevel), http.StatusBadRequest), @@ -2229,36 +2193,28 @@ func TestListChannelUserGroups(t *testing.T) { "test": make(chan int), }, }, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, + listGroupsReq: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.GroupsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), }, { - desc: "list user groups with service response that can't be unmarshalled", + desc: "list user channels with service response that can't be unmarshalled", domainID: domainID, token: validToken, channelID: channel.ID, pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ + listGroupsReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: 1, }, - Groups: []groups.Group{ - { - ID: generateUUID(t), - Metadata: groups.Metadata{"test": make(chan int)}, - }, - }, }, svcErr: nil, response: sdk.GroupsPage{}, @@ -2290,11 +2246,11 @@ func TestConnect(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) - thingID := generateUUID(t) + clientID := generateUUID(t) cases := []struct { desc string @@ -2313,7 +2269,7 @@ func TestConnect(t *testing.T) { token: validToken, connection: sdk.Connection{ ChannelID: channel.ID, - ThingID: thingID, + ClientID: clientID, }, svcErr: nil, err: nil, @@ -2324,7 +2280,7 @@ func TestConnect(t *testing.T) { token: invalidToken, connection: sdk.Connection{ ChannelID: channel.ID, - ThingID: thingID, + ClientID: clientID, }, authenticateErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -2335,7 +2291,7 @@ func TestConnect(t *testing.T) { token: "", connection: sdk.Connection{ ChannelID: channel.ID, - ThingID: thingID, + ClientID: clientID, }, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, @@ -2345,7 +2301,7 @@ func TestConnect(t *testing.T) { token: validToken, connection: sdk.Connection{ ChannelID: wrongID, - ThingID: thingID, + ClientID: clientID, }, svcErr: svcerr.ErrAuthorization, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), @@ -2356,18 +2312,18 @@ func TestConnect(t *testing.T) { token: validToken, connection: sdk.Connection{ ChannelID: "", - ThingID: thingID, + ClientID: clientID, }, svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "connect with empty thing id", + desc: "connect with empty client id", domainID: domainID, token: validToken, connection: sdk.Connection{ ChannelID: channel.ID, - ThingID: "", + ClientID: "", }, svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), @@ -2379,11 +2335,11 @@ func TestConnect(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := gsvc.On("Assign", mock.Anything, tc.session, tc.connection.ChannelID, policies.GroupRelation, policies.ThingsKind, []string{tc.connection.ThingID}).Return(tc.svcErr) + svcCall := gsvc.On("Assign", mock.Anything, tc.session, tc.connection.ChannelID, policies.GroupRelation, policies.ClientsKind, []string{tc.connection.ClientID}).Return(tc.svcErr) err := mgsdk.Connect(tc.connection, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Assign", mock.Anything, tc.session, tc.connection.ChannelID, policies.GroupRelation, policies.ThingsKind, []string{tc.connection.ThingID}) + ok := svcCall.Parent.AssertCalled(t, "Assign", mock.Anything, tc.session, tc.connection.ChannelID, policies.GroupRelation, policies.ClientsKind, []string{tc.connection.ClientID}) assert.True(t, ok) } svcCall.Unset() @@ -2397,11 +2353,11 @@ func TestDisconnect(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) - thingID := generateUUID(t) + clientID := generateUUID(t) cases := []struct { desc string @@ -2420,7 +2376,7 @@ func TestDisconnect(t *testing.T) { token: validToken, disconnect: sdk.Connection{ ChannelID: channel.ID, - ThingID: thingID, + ClientID: clientID, }, svcErr: nil, err: nil, @@ -2431,7 +2387,7 @@ func TestDisconnect(t *testing.T) { token: invalidToken, disconnect: sdk.Connection{ ChannelID: channel.ID, - ThingID: thingID, + ClientID: clientID, }, authenticateErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -2442,7 +2398,7 @@ func TestDisconnect(t *testing.T) { token: "", disconnect: sdk.Connection{ ChannelID: channel.ID, - ThingID: thingID, + ClientID: clientID, }, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, @@ -2452,7 +2408,7 @@ func TestDisconnect(t *testing.T) { token: validToken, disconnect: sdk.Connection{ ChannelID: wrongID, - ThingID: thingID, + ClientID: clientID, }, svcErr: svcerr.ErrAuthorization, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), @@ -2463,18 +2419,18 @@ func TestDisconnect(t *testing.T) { token: validToken, disconnect: sdk.Connection{ ChannelID: "", - ThingID: thingID, + ClientID: clientID, }, svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "disconnect with empty thing id", + desc: "disconnect with empty client id", domainID: domainID, token: validToken, disconnect: sdk.Connection{ ChannelID: channel.ID, - ThingID: "", + ClientID: "", }, svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), @@ -2486,11 +2442,11 @@ func TestDisconnect(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := gsvc.On("Unassign", mock.Anything, tc.session, tc.disconnect.ChannelID, policies.GroupRelation, policies.ThingsKind, []string{tc.disconnect.ThingID}).Return(tc.svcErr) + svcCall := gsvc.On("Unassign", mock.Anything, tc.session, tc.disconnect.ChannelID, policies.GroupRelation, policies.ClientsKind, []string{tc.disconnect.ClientID}).Return(tc.svcErr) err := mgsdk.Disconnect(tc.disconnect, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Unassign", mock.Anything, tc.session, tc.disconnect.ChannelID, policies.GroupRelation, policies.ThingsKind, []string{tc.disconnect.ThingID}) + ok := svcCall.Parent.AssertCalled(t, "Unassign", mock.Anything, tc.session, tc.disconnect.ChannelID, policies.GroupRelation, policies.ClientsKind, []string{tc.disconnect.ClientID}) assert.True(t, ok) } svcCall.Unset() @@ -2499,16 +2455,16 @@ func TestDisconnect(t *testing.T) { } } -func TestConnectThing(t *testing.T) { +func TestConnectClient(t *testing.T) { ts, gsvc, auth := setupChannels() defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) - thingID := generateUUID(t) + clientID := generateUUID(t) cases := []struct { desc string @@ -2516,7 +2472,7 @@ func TestConnectThing(t *testing.T) { token string session mgauthn.Session channelID string - thingID string + clientID string svcErr error authenticateRes mgauthn.Session authenticateErr error @@ -2527,7 +2483,7 @@ func TestConnectThing(t *testing.T) { domainID: domainID, token: validToken, channelID: channel.ID, - thingID: thingID, + clientID: clientID, svcErr: nil, err: nil, }, @@ -2536,7 +2492,7 @@ func TestConnectThing(t *testing.T) { domainID: domainID, token: invalidToken, channelID: channel.ID, - thingID: thingID, + clientID: clientID, authenticateErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, @@ -2545,7 +2501,7 @@ func TestConnectThing(t *testing.T) { domainID: domainID, token: "", channelID: channel.ID, - thingID: thingID, + clientID: clientID, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { @@ -2553,7 +2509,7 @@ func TestConnectThing(t *testing.T) { domainID: domainID, token: validToken, channelID: wrongID, - thingID: thingID, + clientID: clientID, svcErr: svcerr.ErrAuthorization, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, @@ -2562,16 +2518,16 @@ func TestConnectThing(t *testing.T) { domainID: domainID, token: validToken, channelID: "", - thingID: thingID, + clientID: clientID, svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "connect with empty thing id", + desc: "connect with empty client id", domainID: domainID, token: validToken, channelID: channel.ID, - thingID: "", + clientID: "", svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, @@ -2582,11 +2538,11 @@ func TestConnectThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := gsvc.On("Assign", mock.Anything, tc.session, tc.channelID, policies.GroupRelation, policies.ThingsKind, []string{tc.thingID}).Return(tc.svcErr) - err := mgsdk.ConnectThing(tc.thingID, tc.channelID, tc.domainID, tc.token) + svcCall := gsvc.On("Assign", mock.Anything, tc.session, tc.channelID, policies.GroupRelation, policies.ClientsKind, []string{tc.clientID}).Return(tc.svcErr) + err := mgsdk.ConnectClient(tc.clientID, tc.channelID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Assign", mock.Anything, tc.session, tc.channelID, policies.GroupRelation, policies.ThingsKind, []string{tc.thingID}) + ok := svcCall.Parent.AssertCalled(t, "Assign", mock.Anything, tc.session, tc.channelID, policies.GroupRelation, policies.ClientsKind, []string{tc.clientID}) assert.True(t, ok) } svcCall.Unset() @@ -2595,16 +2551,16 @@ func TestConnectThing(t *testing.T) { } } -func TestDisconnectThing(t *testing.T) { +func TestDisconnectClient(t *testing.T) { ts, gsvc, auth := setupChannels() defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) - thingID := generateUUID(t) + clientID := generateUUID(t) cases := []struct { desc string @@ -2612,7 +2568,7 @@ func TestDisconnectThing(t *testing.T) { token string session mgauthn.Session channelID string - thingID string + clientID string svcErr error authenticateErr error err errors.SDKError @@ -2622,7 +2578,7 @@ func TestDisconnectThing(t *testing.T) { domainID: domainID, token: validToken, channelID: channel.ID, - thingID: thingID, + clientID: clientID, svcErr: nil, err: nil, }, @@ -2631,7 +2587,7 @@ func TestDisconnectThing(t *testing.T) { domainID: domainID, token: invalidToken, channelID: channel.ID, - thingID: thingID, + clientID: clientID, authenticateErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, @@ -2640,7 +2596,7 @@ func TestDisconnectThing(t *testing.T) { domainID: domainID, token: "", channelID: channel.ID, - thingID: thingID, + clientID: clientID, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { @@ -2648,7 +2604,7 @@ func TestDisconnectThing(t *testing.T) { domainID: domainID, token: validToken, channelID: wrongID, - thingID: thingID, + clientID: clientID, svcErr: svcerr.ErrAuthorization, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, @@ -2657,16 +2613,16 @@ func TestDisconnectThing(t *testing.T) { domainID: domainID, token: validToken, channelID: "", - thingID: thingID, + clientID: clientID, svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "disconnect with empty thing id", + desc: "disconnect with empty client id", domainID: domainID, token: validToken, channelID: channel.ID, - thingID: "", + clientID: "", svcErr: nil, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, @@ -2677,11 +2633,11 @@ func TestDisconnectThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := gsvc.On("Unassign", mock.Anything, tc.session, tc.channelID, policies.GroupRelation, policies.ThingsKind, []string{tc.thingID}).Return(tc.svcErr) - err := mgsdk.DisconnectThing(tc.thingID, tc.channelID, tc.domainID, tc.token) + svcCall := gsvc.On("Unassign", mock.Anything, tc.session, tc.channelID, policies.GroupRelation, policies.ClientsKind, []string{tc.clientID}).Return(tc.svcErr) + err := mgsdk.DisconnectClient(tc.clientID, tc.channelID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Unassign", mock.Anything, tc.session, tc.channelID, policies.GroupRelation, policies.ThingsKind, []string{tc.thingID}) + ok := svcCall.Parent.AssertCalled(t, "Unassign", mock.Anything, tc.session, tc.channelID, policies.GroupRelation, policies.ClientsKind, []string{tc.clientID}) assert.True(t, ok) } svcCall.Unset() @@ -2695,7 +2651,7 @@ func TestListGroupChannels(t *testing.T) { defer ts.Close() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -2703,7 +2659,7 @@ func TestListGroupChannels(t *testing.T) { ID: testsutil.GenerateUUID(t), Name: "group_channel", Metadata: sdk.Metadata{"role": "group"}, - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), } cases := []struct { @@ -2713,8 +2669,8 @@ func TestListGroupChannels(t *testing.T) { session mgauthn.Session groupID string pageMeta sdk.PageMetadata - svcReq groups.Page - svcRes groups.Page + svcReq channels.Page + svcRes channels.Page svcErr error authenticateErr error response sdk.ChannelsPage @@ -2729,19 +2685,16 @@ func TestListGroupChannels(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ + svcReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: 1, }, - Groups: []groups.Group{convertChannel(groupChannel)}, }, svcErr: nil, response: sdk.ChannelsPage{ @@ -2761,15 +2714,13 @@ func TestListGroupChannels(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ + svcReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{}, + svcRes: channels.Page{}, authenticateErr: svcerr.ErrAuthentication, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -2783,8 +2734,8 @@ func TestListGroupChannels(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, + svcReq: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -2798,15 +2749,13 @@ func TestListGroupChannels(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ + svcReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{}, + svcRes: channels.Page{}, svcErr: svcerr.ErrAuthorization, response: sdk.ChannelsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), @@ -2823,8 +2772,8 @@ func TestListGroupChannels(t *testing.T) { "test": make(chan int), }, }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, + svcReq: channels.Page{}, + svcRes: channels.Page{}, svcErr: nil, response: sdk.ChannelsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -2838,24 +2787,16 @@ func TestListGroupChannels(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ + svcReq: channels.Page{ + PageMetadata: channels.PageMetadata{ Offset: 0, Limit: 10, }, - Permission: "view", - Direction: -1, }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ + svcRes: channels.Page{ + PageMetadata: channels.PageMetadata{ Total: 1, }, - Groups: []groups.Group{ - { - ID: generateUUID(t), - Metadata: groups.Metadata{"test": make(chan int)}, - }, - }, }, svcErr: nil, response: sdk.ChannelsPage{}, @@ -2894,7 +2835,7 @@ func generateTestChannel(t *testing.T) sdk.Channel { Metadata: sdk.Metadata{"role": "client"}, CreatedAt: createdAt, UpdatedAt: updatedAt, - Status: groups.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), } return ch } diff --git a/pkg/sdk/go/clients.go b/pkg/sdk/go/clients.go new file mode 100644 index 0000000000..278aadc8b9 --- /dev/null +++ b/pkg/sdk/go/clients.go @@ -0,0 +1,302 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package sdk + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/absmach/magistrala/pkg/apiutil" + "github.com/absmach/magistrala/pkg/errors" +) + +const ( + permissionsEndpoint = "permissions" + clientsEndpoint = "clients" + connectEndpoint = "connect" + disconnectEndpoint = "disconnect" + identifyEndpoint = "identify" + shareEndpoint = "share" + unshareEndpoint = "unshare" +) + +// Client represents magistrala client. +type Client struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Credentials ClientCredentials `json:"credentials"` + Tags []string `json:"tags,omitempty"` + DomainID string `json:"domain_id,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + Status string `json:"status,omitempty"` + Permissions []string `json:"permissions,omitempty"` +} + +type ClientCredentials struct { + Identity string `json:"identity,omitempty"` + Secret string `json:"secret,omitempty"` +} + +func (sdk mgSDK) CreateClient(client Client, domainID, token string) (Client, errors.SDKError) { + data, err := json.Marshal(client) + if err != nil { + return Client{}, errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint) + + _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) + if sdkerr != nil { + return Client{}, sdkerr + } + + client = Client{} + if err := json.Unmarshal(body, &client); err != nil { + return Client{}, errors.NewSDKError(err) + } + + return client, nil +} + +func (sdk mgSDK) CreateClients(clients []Client, domainID, token string) ([]Client, errors.SDKError) { + data, err := json.Marshal(clients) + if err != nil { + return []Client{}, errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint, "bulk") + + _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) + if sdkerr != nil { + return []Client{}, sdkerr + } + + var ctr createClientsRes + if err := json.Unmarshal(body, &ctr); err != nil { + return []Client{}, errors.NewSDKError(err) + } + + return ctr.Clients, nil +} + +func (sdk mgSDK) Clients(pm PageMetadata, domainID, token string) (ClientsPage, errors.SDKError) { + endpoint := fmt.Sprintf("%s/%s", domainID, clientsEndpoint) + url, err := sdk.withQueryParams(sdk.clientsURL, endpoint, pm) + if err != nil { + return ClientsPage{}, errors.NewSDKError(err) + } + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return ClientsPage{}, sdkerr + } + + var cp ClientsPage + if err := json.Unmarshal(body, &cp); err != nil { + return ClientsPage{}, errors.NewSDKError(err) + } + + return cp, nil +} + +func (sdk mgSDK) ClientsByChannel(chanID string, pm PageMetadata, domainID, token string) (ClientsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.clientsURL, fmt.Sprintf("%s/channels/%s/%s", domainID, chanID, clientsEndpoint), pm) + if err != nil { + return ClientsPage{}, errors.NewSDKError(err) + } + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return ClientsPage{}, sdkerr + } + + var tp ClientsPage + if err := json.Unmarshal(body, &tp); err != nil { + return ClientsPage{}, errors.NewSDKError(err) + } + + return tp, nil +} + +func (sdk mgSDK) Client(id, domainID, token string) (Client, errors.SDKError) { + if id == "" { + return Client{}, errors.NewSDKError(apiutil.ErrMissingID) + } + url := fmt.Sprintf("%s/%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint, id) + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return Client{}, sdkerr + } + + var t Client + if err := json.Unmarshal(body, &t); err != nil { + return Client{}, errors.NewSDKError(err) + } + + return t, nil +} + +func (sdk mgSDK) ClientPermissions(id, domainID, token string) (Client, errors.SDKError) { + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint, id, permissionsEndpoint) + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return Client{}, sdkerr + } + + var t Client + if err := json.Unmarshal(body, &t); err != nil { + return Client{}, errors.NewSDKError(err) + } + + return t, nil +} + +func (sdk mgSDK) UpdateClient(t Client, domainID, token string) (Client, errors.SDKError) { + if t.ID == "" { + return Client{}, errors.NewSDKError(apiutil.ErrMissingID) + } + url := fmt.Sprintf("%s/%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint, t.ID) + + data, err := json.Marshal(t) + if err != nil { + return Client{}, errors.NewSDKError(err) + } + + _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) + if sdkerr != nil { + return Client{}, sdkerr + } + + t = Client{} + if err := json.Unmarshal(body, &t); err != nil { + return Client{}, errors.NewSDKError(err) + } + + return t, nil +} + +func (sdk mgSDK) UpdateClientTags(t Client, domainID, token string) (Client, errors.SDKError) { + data, err := json.Marshal(t) + if err != nil { + return Client{}, errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s/tags", sdk.clientsURL, domainID, clientsEndpoint, t.ID) + + _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) + if sdkerr != nil { + return Client{}, sdkerr + } + + t = Client{} + if err := json.Unmarshal(body, &t); err != nil { + return Client{}, errors.NewSDKError(err) + } + + return t, nil +} + +func (sdk mgSDK) UpdateClientSecret(id, secret, domainID, token string) (Client, errors.SDKError) { + ucsr := updateClientSecretReq{Secret: secret} + + data, err := json.Marshal(ucsr) + if err != nil { + return Client{}, errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s/secret", sdk.clientsURL, domainID, clientsEndpoint, id) + + _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) + if sdkerr != nil { + return Client{}, sdkerr + } + + var t Client + if err = json.Unmarshal(body, &t); err != nil { + return Client{}, errors.NewSDKError(err) + } + + return t, nil +} + +func (sdk mgSDK) EnableClient(id, domainID, token string) (Client, errors.SDKError) { + return sdk.changeClientStatus(id, enableEndpoint, domainID, token) +} + +func (sdk mgSDK) DisableClient(id, domainID, token string) (Client, errors.SDKError) { + return sdk.changeClientStatus(id, disableEndpoint, domainID, token) +} + +func (sdk mgSDK) changeClientStatus(id, status, domainID, token string) (Client, errors.SDKError) { + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint, id, status) + + _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return Client{}, sdkerr + } + + t := Client{} + if err := json.Unmarshal(body, &t); err != nil { + return Client{}, errors.NewSDKError(err) + } + + return t, nil +} + +func (sdk mgSDK) ShareClient(clientID string, req UsersRelationRequest, domainID, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint, clientID, shareEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) + return sdkerr +} + +func (sdk mgSDK) UnshareClient(clientID string, req UsersRelationRequest, domainID, token string) errors.SDKError { + data, err := json.Marshal(req) + if err != nil { + return errors.NewSDKError(err) + } + + url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint, clientID, unshareEndpoint) + + _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) + return sdkerr +} + +func (sdk mgSDK) ListClientUsers(clientID string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", domainID, clientsEndpoint, clientID, usersEndpoint), pm) + if err != nil { + return UsersPage{}, errors.NewSDKError(err) + } + + _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) + if sdkerr != nil { + return UsersPage{}, sdkerr + } + up := UsersPage{} + if err := json.Unmarshal(body, &up); err != nil { + return UsersPage{}, errors.NewSDKError(err) + } + + return up, nil +} + +func (sdk mgSDK) DeleteClient(id, domainID, token string) errors.SDKError { + if id == "" { + return errors.NewSDKError(apiutil.ErrMissingID) + } + url := fmt.Sprintf("%s/%s/%s/%s", sdk.clientsURL, domainID, clientsEndpoint, id) + _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) + return sdkerr +} diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/clients_test.go similarity index 56% rename from pkg/sdk/go/things_test.go rename to pkg/sdk/go/clients_test.go index 5a83b63fba..4f437ef13c 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/clients_test.go @@ -11,6 +11,9 @@ import ( "testing" "time" + "github.com/absmach/magistrala/clients" + api "github.com/absmach/magistrala/clients/api/http" + "github.com/absmach/magistrala/clients/mocks" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" @@ -18,44 +21,39 @@ import ( authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - gmocks "github.com/absmach/magistrala/pkg/groups/mocks" policies "github.com/absmach/magistrala/pkg/policies" sdk "github.com/absmach/magistrala/pkg/sdk/go" - mgthings "github.com/absmach/magistrala/things" - api "github.com/absmach/magistrala/things/api/http" - "github.com/absmach/magistrala/things/mocks" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) -func setupThings() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { +func setupClients() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { tsvc := new(mocks.Service) - gsvc := new(gmocks.Service) logger := mglog.NewMock() mux := chi.NewRouter() authn := new(authnmocks.Authentication) - api.MakeHandler(tsvc, gsvc, authn, mux, logger, "") + api.MakeHandler(tsvc, authn, mux, logger, "") return httptest.NewServer(mux), tsvc, authn } -func TestCreateThing(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestCreateClient(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) - createThingReq := sdk.Thing{ - Name: thing.Name, - Tags: thing.Tags, - Credentials: thing.Credentials, - Metadata: thing.Metadata, - Status: thing.Status, + client := generateTestClient(t) + createClientReq := sdk.Client{ + Name: client.Name, + Tags: client.Tags, + Credentials: client.Credentials, + Metadata: client.Metadata, + Status: client.Status, } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -65,125 +63,125 @@ func TestCreateThing(t *testing.T) { domainID string token string session mgauthn.Session - createThingReq sdk.Thing - svcReq mgthings.Client - svcRes []mgthings.Client + createClientReq sdk.Client + svcReq clients.Client + svcRes []clients.Client svcErr error authenticateErr error - response sdk.Thing + response sdk.Client err errors.SDKError }{ { - desc: "create new thing successfully", - domainID: domainID, - token: validToken, - createThingReq: createThingReq, - svcReq: convertThing(createThingReq), - svcRes: []mgthings.Client{convertThing(thing)}, - svcErr: nil, - response: thing, - err: nil, + desc: "create new client successfully", + domainID: domainID, + token: validToken, + createClientReq: createClientReq, + svcReq: convertClient(createClientReq), + svcRes: []clients.Client{convertClient(client)}, + svcErr: nil, + response: client, + err: nil, }, { - desc: "create new thing with invalid token", + desc: "create new client with invalid token", domainID: domainID, token: invalidToken, - createThingReq: createThingReq, - svcReq: convertThing(createThingReq), - svcRes: []mgthings.Client{}, + createClientReq: createClientReq, + svcReq: convertClient(createClientReq), + svcRes: []clients.Client{}, authenticateErr: svcerr.ErrAuthentication, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, { - desc: "create new thing with empty token", - domainID: domainID, - token: "", - createThingReq: createThingReq, - svcReq: convertThing(createThingReq), - svcRes: []mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + desc: "create new client with empty token", + domainID: domainID, + token: "", + createClientReq: createClientReq, + svcReq: convertClient(createClientReq), + svcRes: []clients.Client{}, + svcErr: nil, + response: sdk.Client{}, + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "create an existing thing", - domainID: domainID, - token: validToken, - createThingReq: createThingReq, - svcReq: convertThing(createThingReq), - svcRes: []mgthings.Client{}, - svcErr: svcerr.ErrCreateEntity, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), + desc: "create an existing client", + domainID: domainID, + token: validToken, + createClientReq: createClientReq, + svcReq: convertClient(createClientReq), + svcRes: []clients.Client{}, + svcErr: svcerr.ErrCreateEntity, + response: sdk.Client{}, + err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), }, { - desc: "create a thing with name too long", + desc: "create a client with name too long", domainID: domainID, token: validToken, - createThingReq: sdk.Thing{ + createClientReq: sdk.Client{ Name: strings.Repeat("a", 1025), - Tags: thing.Tags, - Credentials: thing.Credentials, - Metadata: thing.Metadata, - Status: thing.Status, + Tags: client.Tags, + Credentials: client.Credentials, + Metadata: client.Metadata, + Status: client.Status, }, - svcReq: mgthings.Client{}, - svcRes: []mgthings.Client{}, + svcReq: clients.Client{}, + svcRes: []clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), }, { - desc: "create a thing with invalid id", + desc: "create a client with invalid id", domainID: domainID, token: validToken, - createThingReq: sdk.Thing{ + createClientReq: sdk.Client{ ID: "123456789", - Name: thing.Name, - Tags: thing.Tags, - Credentials: thing.Credentials, - Metadata: thing.Metadata, - Status: thing.Status, - }, - svcReq: mgthings.Client{}, - svcRes: []mgthings.Client{}, + Name: client.Name, + Tags: client.Tags, + Credentials: client.Credentials, + Metadata: client.Metadata, + Status: client.Status, + }, + svcReq: clients.Client{}, + svcRes: []clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidIDFormat), http.StatusBadRequest), }, { - desc: "create a thing with a request that can't be marshalled", + desc: "create a client with a request that can't be marshalled", domainID: domainID, token: validToken, - createThingReq: sdk.Thing{ + createClientReq: sdk.Client{ Name: "test", Metadata: map[string]interface{}{ "test": make(chan int), }, }, - svcReq: mgthings.Client{}, - svcRes: []mgthings.Client{}, + svcReq: clients.Client{}, + svcRes: []clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), }, { - desc: "create a thing with a response that can't be unmarshalled", - domainID: domainID, - token: validToken, - createThingReq: createThingReq, - svcReq: convertThing(createThingReq), - svcRes: []mgthings.Client{{ - Name: thing.Name, - Tags: thing.Tags, - Credentials: mgthings.Credentials(thing.Credentials), - Metadata: mgthings.Metadata{ + desc: "create a client with a response that can't be unmarshalled", + domainID: domainID, + token: validToken, + createClientReq: createClientReq, + svcReq: convertClient(createClientReq), + svcRes: []clients.Client{{ + Name: client.Name, + Tags: client.Tags, + Credentials: clients.Credentials(client.Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -194,7 +192,7 @@ func TestCreateThing(t *testing.T) { } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) svcCall := tsvc.On("CreateClients", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.CreateThing(tc.createThingReq, tc.domainID, tc.token) + resp, err := mgsdk.CreateClient(tc.createClientReq, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { @@ -207,94 +205,94 @@ func TestCreateThing(t *testing.T) { } } -func TestCreateThings(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestCreateClients(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - things := []sdk.Thing{} + sdkClients := []sdk.Client{} for i := 0; i < 3; i++ { - thing := generateTestThing(t) - things = append(things, thing) + client := generateTestClient(t) + sdkClients = append(sdkClients, client) } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - createThingsRequest []sdk.Thing - svcReq []mgthings.Client - svcRes []mgthings.Client - svcErr error - authenticateErr error - response []sdk.Thing - err errors.SDKError + desc string + domainID string + token string + session mgauthn.Session + createClientsRequest []sdk.Client + svcReq []clients.Client + svcRes []clients.Client + svcErr error + authenticateErr error + response []sdk.Client + err errors.SDKError }{ { - desc: "create new things successfully", - domainID: domainID, - token: validToken, - createThingsRequest: things, - svcReq: convertThings(things...), - svcRes: convertThings(things...), - svcErr: nil, - response: things, - err: nil, - }, - { - desc: "create new things with invalid token", - domainID: domainID, - token: invalidToken, - createThingsRequest: things, - svcReq: convertThings(things...), - svcRes: []mgthings.Client{}, - authenticateErr: svcerr.ErrAuthentication, - response: []sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "create new things with empty token", - domainID: domainID, - token: "", - createThingsRequest: things, - svcReq: convertThings(things...), - svcRes: []mgthings.Client{}, - svcErr: nil, - response: []sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "create new things with a request that can't be marshalled", - domainID: domainID, - token: validToken, - createThingsRequest: []sdk.Thing{{Name: "test", Metadata: map[string]interface{}{"test": make(chan int)}}}, - svcReq: convertThings(things...), - svcRes: []mgthings.Client{}, - svcErr: nil, - response: []sdk.Thing{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "create new things with a response that can't be unmarshalled", - domainID: domainID, - token: validToken, - createThingsRequest: things, - svcReq: convertThings(things...), - svcRes: []mgthings.Client{{ - Name: things[0].Name, - Tags: things[0].Tags, - Credentials: mgthings.Credentials(things[0].Credentials), - Metadata: mgthings.Metadata{ + desc: "create new clients successfully", + domainID: domainID, + token: validToken, + createClientsRequest: sdkClients, + svcReq: convertClients(sdkClients...), + svcRes: convertClients(sdkClients...), + svcErr: nil, + response: sdkClients, + err: nil, + }, + { + desc: "create new clients with invalid token", + domainID: domainID, + token: invalidToken, + createClientsRequest: sdkClients, + svcReq: convertClients(sdkClients...), + svcRes: []clients.Client{}, + authenticateErr: svcerr.ErrAuthentication, + response: []sdk.Client{}, + err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), + }, + { + desc: "create new clients with empty token", + domainID: domainID, + token: "", + createClientsRequest: sdkClients, + svcReq: convertClients(sdkClients...), + svcRes: []clients.Client{}, + svcErr: nil, + response: []sdk.Client{}, + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + }, + { + desc: "create new clients with a request that can't be marshalled", + domainID: domainID, + token: validToken, + createClientsRequest: []sdk.Client{{Name: "test", Metadata: map[string]interface{}{"test": make(chan int)}}}, + svcReq: convertClients(sdkClients...), + svcRes: []clients.Client{}, + svcErr: nil, + response: []sdk.Client{}, + err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), + }, + { + desc: "create new clients with a response that can't be unmarshalled", + domainID: domainID, + token: validToken, + createClientsRequest: sdkClients, + svcReq: convertClients(sdkClients...), + svcRes: []clients.Client{{ + Name: sdkClients[0].Name, + Tags: sdkClients[0].Tags, + Credentials: clients.Credentials(sdkClients[0].Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }}, svcErr: nil, - response: []sdk.Thing{}, + response: []sdk.Client{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -305,7 +303,7 @@ func TestCreateThings(t *testing.T) { } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) svcCall := tsvc.On("CreateClients", mock.Anything, tc.session, tc.svcReq[0], tc.svcReq[1], tc.svcReq[2]).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.CreateThings(tc.createThingsRequest, tc.domainID, tc.token) + resp, err := mgsdk.CreateClients(tc.createClientsRequest, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { @@ -318,22 +316,22 @@ func TestCreateThings(t *testing.T) { } } -func TestListThings(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestListClients(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - var things []sdk.Thing + var sdkClients []sdk.Client for i := 10; i < 100; i++ { - thing := generateTestThing(t) + c := generateTestClient(t) if i == 50 { - thing.Status = mgthings.DisabledStatus.String() - thing.Tags = []string{"tag1", "tag2"} + c.Status = clients.DisabledStatus.String() + c.Tags = []string{"tag1", "tag2"} } - things = append(things, thing) + sdkClients = append(sdkClients, c) } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -343,77 +341,77 @@ func TestListThings(t *testing.T) { domainID string session mgauthn.Session pageMeta sdk.PageMetadata - svcReq mgthings.Page - svcRes mgthings.ClientsPage + svcReq clients.Page + svcRes clients.ClientsPage svcErr error authenticateErr error - response sdk.ThingsPage + response sdk.ClientsPage err errors.SDKError }{ { - desc: "list all things successfully", + desc: "list all clients successfully", domainID: domainID, token: validToken, pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 100, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ + svcRes: clients.ClientsPage{ + Page: clients.Page{ Offset: 0, Limit: 100, - Total: uint64(len(things)), + Total: uint64(len(sdkClients)), }, - Clients: convertThings(things...), + Clients: convertClients(sdkClients...), }, svcErr: nil, - response: sdk.ThingsPage{ + response: sdk.ClientsPage{ PageRes: sdk.PageRes{ Limit: 100, - Total: uint64(len(things)), + Total: uint64(len(sdkClients)), }, - Things: things, + Clients: sdkClients, }, }, { - desc: "list all things with an invalid token", + desc: "list all clients with an invalid token", domainID: domainID, token: invalidToken, pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 100, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.ClientsPage{}, + svcRes: clients.ClientsPage{}, authenticateErr: svcerr.ErrAuthentication, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, { - desc: "list all things with limit greater than max", + desc: "list all clients with limit greater than max", domainID: domainID, token: validToken, pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 1000, }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, + svcReq: clients.Page{}, + svcRes: clients.ClientsPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), }, { - desc: "list all things with name size greater than max", + desc: "list all clients with name size greater than max", domainID: domainID, token: validToken, pageMeta: sdk.PageMetadata{ @@ -421,47 +419,47 @@ func TestListThings(t *testing.T) { Limit: 100, Name: strings.Repeat("a", 1025), }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, + svcReq: clients.Page{}, + svcRes: clients.ClientsPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), }, { - desc: "list all things with status", + desc: "list all clients with status", domainID: domainID, token: validToken, pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 100, - Status: mgthings.DisabledStatus.String(), + Status: clients.DisabledStatus.String(), }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, - Status: mgthings.DisabledStatus, + Status: clients.DisabledStatus, }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ + svcRes: clients.ClientsPage{ + Page: clients.Page{ Offset: 0, Limit: 100, Total: 1, }, - Clients: convertThings(things[50]), + Clients: convertClients(sdkClients[50]), }, svcErr: nil, - response: sdk.ThingsPage{ + response: sdk.ClientsPage{ PageRes: sdk.PageRes{ Limit: 100, Total: 1, }, - Things: []sdk.Thing{things[50]}, + Clients: []sdk.Client{sdkClients[50]}, }, err: nil, }, { - desc: "list all things with tags", + desc: "list all clients with tags", domainID: domainID, token: validToken, pageMeta: sdk.PageMetadata{ @@ -469,32 +467,32 @@ func TestListThings(t *testing.T) { Limit: 100, Tag: "tag1", }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, Tag: "tag1", }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ + svcRes: clients.ClientsPage{ + Page: clients.Page{ Offset: 0, Limit: 100, Total: 1, }, - Clients: convertThings(things[50]), + Clients: convertClients(sdkClients[50]), }, svcErr: nil, - response: sdk.ThingsPage{ + response: sdk.ClientsPage{ PageRes: sdk.PageRes{ Limit: 100, Total: 1, }, - Things: []sdk.Thing{things[50]}, + Clients: []sdk.Client{sdkClients[50]}, }, err: nil, }, { - desc: "list all things with invalid metadata", + desc: "list all clients with invalid metadata", domainID: domainID, token: validToken, pageMeta: sdk.PageMetadata{ @@ -504,42 +502,42 @@ func TestListThings(t *testing.T) { "test": make(chan int), }, }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, + svcReq: clients.Page{}, + svcRes: clients.ClientsPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), }, { - desc: "list all things with response that can't be unmarshalled", + desc: "list all clients with response that can't be unmarshalled", domainID: domainID, token: validToken, pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 100, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ + svcRes: clients.ClientsPage{ + Page: clients.Page{ Offset: 0, Limit: 100, Total: 1, }, - Clients: []mgthings.Client{{ - Name: things[0].Name, - Tags: things[0].Tags, - Credentials: mgthings.Credentials(things[0].Credentials), - Metadata: mgthings.Metadata{ + Clients: []clients.Client{{ + Name: sdkClients[0].Name, + Tags: sdkClients[0].Tags, + Credentials: clients.Credentials(sdkClients[0].Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }}, }, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -550,7 +548,7 @@ func TestListThings(t *testing.T) { } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) svcCall := tsvc.On("ListClients", mock.Anything, tc.session, mock.Anything, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Things(tc.pageMeta, tc.domainID, tc.token) + resp, err := mgsdk.Clients(tc.pageMeta, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { @@ -563,21 +561,21 @@ func TestListThings(t *testing.T) { } } -func TestListThingsByChannel(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestListClientsByChannel(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - var things []sdk.Thing + var sdkClients []sdk.Client for i := 10; i < 100; i++ { - thing := generateTestThing(t) + c := generateTestClient(t) if i == 50 { - thing.Status = mgthings.DisabledStatus.String() + c.Status = clients.DisabledStatus.String() } - things = append(things, thing) + sdkClients = append(sdkClients, c) } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -588,15 +586,15 @@ func TestListThingsByChannel(t *testing.T) { session mgauthn.Session channelID string pageMeta sdk.PageMetadata - svcReq mgthings.Page - svcRes mgthings.MembersPage + svcReq clients.Page + svcRes clients.MembersPage svcErr error authenticateErr error - response sdk.ThingsPage + response sdk.ClientsPage err errors.SDKError }{ { - desc: "list things successfully", + desc: "list clients successfully", domainID: domainID, token: validToken, channelID: validID, @@ -604,30 +602,30 @@ func TestListThingsByChannel(t *testing.T) { Offset: 0, Limit: 100, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.MembersPage{ - Page: mgthings.Page{ + svcRes: clients.MembersPage{ + Page: clients.Page{ Offset: 0, Limit: 100, - Total: uint64(len(things)), + Total: uint64(len(sdkClients)), }, - Members: convertThings(things...), + Members: convertClients(sdkClients...), }, svcErr: nil, - response: sdk.ThingsPage{ + response: sdk.ClientsPage{ PageRes: sdk.PageRes{ Limit: 100, - Total: uint64(len(things)), + Total: uint64(len(sdkClients)), }, - Things: things, + Clients: sdkClients, }, }, { - desc: "list things with an invalid token", + desc: "list clients with an invalid token", domainID: domainID, token: invalidToken, channelID: validID, @@ -635,18 +633,18 @@ func TestListThingsByChannel(t *testing.T) { Offset: 0, Limit: 100, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.MembersPage{}, + svcRes: clients.MembersPage{}, authenticateErr: svcerr.ErrAuthentication, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, { - desc: "list things with empty token", + desc: "list clients with empty token", domainID: domainID, token: "", channelID: validID, @@ -654,48 +652,48 @@ func TestListThingsByChannel(t *testing.T) { Offset: 0, Limit: 100, }, - svcReq: mgthings.Page{}, - svcRes: mgthings.MembersPage{}, + svcReq: clients.Page{}, + svcRes: clients.MembersPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "list things with status", + desc: "list clients with status", domainID: domainID, token: validToken, channelID: validID, pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 100, - Status: mgthings.DisabledStatus.String(), + Status: clients.DisabledStatus.String(), }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, - Status: mgthings.DisabledStatus, + Status: clients.DisabledStatus, }, - svcRes: mgthings.MembersPage{ - Page: mgthings.Page{ + svcRes: clients.MembersPage{ + Page: clients.Page{ Offset: 0, Limit: 100, Total: 1, }, - Members: convertThings(things[50]), + Members: convertClients(sdkClients[50]), }, svcErr: nil, - response: sdk.ThingsPage{ + response: sdk.ClientsPage{ PageRes: sdk.PageRes{ Limit: 100, Total: 1, }, - Things: []sdk.Thing{things[50]}, + Clients: []sdk.Client{sdkClients[50]}, }, err: nil, }, { - desc: "list things with empty channel id", + desc: "list clients with empty channel id", domainID: domainID, token: validToken, channelID: "", @@ -703,14 +701,14 @@ func TestListThingsByChannel(t *testing.T) { Offset: 0, Limit: 100, }, - svcReq: mgthings.Page{}, - svcRes: mgthings.MembersPage{}, + svcReq: clients.Page{}, + svcRes: clients.MembersPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "list things with invalid metadata", + desc: "list clients with invalid metadata", domainID: domainID, token: validToken, channelID: validID, @@ -721,14 +719,14 @@ func TestListThingsByChannel(t *testing.T) { "test": make(chan int), }, }, - svcReq: mgthings.Page{}, - svcRes: mgthings.MembersPage{}, + svcReq: clients.Page{}, + svcRes: clients.MembersPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), }, { - desc: "list things with response that can't be unmarshalled", + desc: "list clients with response that can't be unmarshalled", domainID: domainID, token: validToken, channelID: validID, @@ -736,28 +734,28 @@ func TestListThingsByChannel(t *testing.T) { Offset: 0, Limit: 100, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.MembersPage{ - Page: mgthings.Page{ + svcRes: clients.MembersPage{ + Page: clients.Page{ Offset: 0, Limit: 100, Total: 1, }, - Members: []mgthings.Client{{ - Name: things[0].Name, - Tags: things[0].Tags, - Credentials: mgthings.Credentials(things[0].Credentials), - Metadata: mgthings.Metadata{ + Members: []clients.Client{{ + Name: sdkClients[0].Name, + Tags: sdkClients[0].Tags, + Credentials: clients.Credentials(sdkClients[0].Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }}, }, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -768,7 +766,7 @@ func TestListThingsByChannel(t *testing.T) { } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) svcCall := tsvc.On("ListClientsByGroup", mock.Anything, tc.session, tc.channelID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ThingsByChannel(tc.channelID, tc.pageMeta, tc.domainID, tc.token) + resp, err := mgsdk.ClientsByChannel(tc.channelID, tc.pageMeta, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { @@ -781,13 +779,13 @@ func TestListThingsByChannel(t *testing.T) { } } -func TestViewThing(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestViewClient(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) + sdkClient := generateTestClient(t) conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -796,78 +794,78 @@ func TestViewThing(t *testing.T) { domainID string token string session mgauthn.Session - thingID string - svcRes mgthings.Client + clientID string + svcRes clients.Client svcErr error authenticateErr error - response sdk.Thing + response sdk.Client err errors.SDKError }{ { - desc: "view thing successfully", + desc: "view client successfully", domainID: domainID, token: validToken, - thingID: thing.ID, - svcRes: convertThing(thing), + clientID: sdkClient.ID, + svcRes: convertClient(sdkClient), svcErr: nil, - response: thing, + response: sdkClient, err: nil, }, { - desc: "view thing with an invalid token", + desc: "view client with an invalid token", domainID: domainID, token: invalidToken, - thingID: thing.ID, - svcRes: mgthings.Client{}, + clientID: sdkClient.ID, + svcRes: clients.Client{}, authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "view thing with empty token", + desc: "view client with empty token", domainID: domainID, token: "", - thingID: thing.ID, - svcRes: mgthings.Client{}, + clientID: sdkClient.ID, + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "view thing with an invalid thing id", + desc: "view client with an invalid client id", domainID: domainID, token: validToken, - thingID: wrongID, - svcRes: mgthings.Client{}, + clientID: wrongID, + svcRes: clients.Client{}, svcErr: svcerr.ErrViewEntity, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), }, { - desc: "view thing with empty thing id", + desc: "view client with empty client id", domainID: domainID, token: validToken, - thingID: "", - svcRes: mgthings.Client{}, + clientID: "", + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(apiutil.ErrMissingID), }, { - desc: "view thing with response that can't be unmarshalled", + desc: "view client with response that can't be unmarshalled", domainID: domainID, token: validToken, - thingID: thing.ID, - svcRes: mgthings.Client{ - Name: thing.Name, - Tags: thing.Tags, - Credentials: mgthings.Credentials(thing.Credentials), - Metadata: mgthings.Metadata{ + clientID: sdkClient.ID, + svcRes: clients.Client{ + Name: sdkClient.Name, + Tags: sdkClient.Tags, + Credentials: clients.Credentials(sdkClient.Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -877,12 +875,12 @@ func TestViewThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) - svcCall := tsvc.On("View", mock.Anything, tc.session, tc.thingID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Thing(tc.thingID, tc.domainID, tc.token) + svcCall := tsvc.On("View", mock.Anything, tc.session, tc.clientID).Return(tc.svcRes, tc.svcErr) + resp, err := mgsdk.Client(tc.clientID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "View", mock.Anything, tc.session, tc.thingID) + ok := svcCall.Parent.AssertCalled(t, "View", mock.Anything, tc.session, tc.clientID) assert.True(t, ok) } svcCall.Unset() @@ -891,15 +889,15 @@ func TestViewThing(t *testing.T) { } } -func TestViewThingPermissions(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestViewClientPermissions(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := sdk.Thing{ + client := sdk.Client{ Permissions: []string{policies.ViewPermission}, } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -908,61 +906,61 @@ func TestViewThingPermissions(t *testing.T) { domainID string token string session mgauthn.Session - thingID string + clientID string svcRes []string svcErr error authenticateErr error - response sdk.Thing + response sdk.Client err errors.SDKError }{ { - desc: "view thing permissions successfully", + desc: "view client permissions successfully", domainID: domainID, token: validToken, - thingID: validID, + clientID: validID, svcRes: []string{policies.ViewPermission}, svcErr: nil, - response: thing, + response: client, err: nil, }, { - desc: "view thing permissions with an invalid token", + desc: "view client permissions with an invalid token", domainID: domainID, token: invalidToken, - thingID: validID, + clientID: validID, svcRes: []string{}, authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "view thing permissions with empty token", + desc: "view client permissions with empty token", domainID: domainID, token: "", - thingID: thing.ID, + clientID: client.ID, svcRes: []string{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "view thing permissions with an invalid thing id", + desc: "view client permissions with an invalid client id", domainID: domainID, token: validToken, - thingID: wrongID, + clientID: wrongID, svcRes: []string{}, svcErr: svcerr.ErrViewEntity, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), }, { - desc: "view thing permissions with empty thing id", + desc: "view client permissions with empty client id", domainID: domainID, token: validToken, - thingID: "", + clientID: "", svcRes: []string{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, } @@ -972,12 +970,12 @@ func TestViewThingPermissions(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) - svcCall := tsvc.On("ViewPerms", mock.Anything, tc.session, tc.thingID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ThingPermissions(tc.thingID, tc.domainID, tc.token) + svcCall := tsvc.On("ViewPerms", mock.Anything, tc.session, tc.clientID).Return(tc.svcRes, tc.svcErr) + resp, err := mgsdk.ClientPermissions(tc.clientID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ViewPerms", mock.Anything, tc.session, tc.thingID) + ok := svcCall.Parent.AssertCalled(t, "ViewPerms", mock.Anything, tc.session, tc.clientID) assert.True(t, ok) } svcCall.Unset() @@ -986,24 +984,24 @@ func TestViewThingPermissions(t *testing.T) { } } -func TestUpdateThing(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestUpdateClient(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) - updatedThing := thing - updatedThing.Name = "newName" - updatedThing.Metadata = map[string]interface{}{ + sdkClient := generateTestClient(t) + updatedClient := sdkClient + updatedClient.Name = "newName" + updatedClient.Metadata = map[string]interface{}{ "newKey": "newValue", } - updateThingReq := sdk.Thing{ - ID: thing.ID, - Name: updatedThing.Name, - Metadata: updatedThing.Metadata, + updateClientReq := sdk.Client{ + ID: sdkClient.ID, + Name: updatedClient.Name, + Metadata: updatedClient.Metadata, } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1012,115 +1010,115 @@ func TestUpdateThing(t *testing.T) { domainID string token string session mgauthn.Session - updateThingReq sdk.Thing - svcReq mgthings.Client - svcRes mgthings.Client + updateClientReq sdk.Client + svcReq clients.Client + svcRes clients.Client svcErr error authenticateErr error - response sdk.Thing + response sdk.Client err errors.SDKError }{ { - desc: "update thing successfully", - domainID: domainID, - token: validToken, - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: convertThing(updatedThing), - svcErr: nil, - response: updatedThing, - err: nil, + desc: "update client successfully", + domainID: domainID, + token: validToken, + updateClientReq: updateClientReq, + svcReq: convertClient(updateClientReq), + svcRes: convertClient(updatedClient), + svcErr: nil, + response: updatedClient, + err: nil, }, { - desc: "update thing with an invalid token", + desc: "update client with an invalid token", domainID: domainID, token: invalidToken, - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: mgthings.Client{}, + updateClientReq: updateClientReq, + svcReq: convertClient(updateClientReq), + svcRes: clients.Client{}, authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "update thing with empty token", - domainID: domainID, - token: "", - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + desc: "update client with empty token", + domainID: domainID, + token: "", + updateClientReq: updateClientReq, + svcReq: convertClient(updateClientReq), + svcRes: clients.Client{}, + svcErr: nil, + response: sdk.Client{}, + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "update thing with an invalid thing id", + desc: "update client with an invalid client id", domainID: domainID, token: validToken, - updateThingReq: sdk.Thing{ + updateClientReq: sdk.Client{ ID: wrongID, - Name: updatedThing.Name, + Name: updatedClient.Name, }, - svcReq: convertThing(sdk.Thing{ + svcReq: convertClient(sdk.Client{ ID: wrongID, - Name: updatedThing.Name, + Name: updatedClient.Name, }), - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, svcErr: svcerr.ErrUpdateEntity, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), }, { - desc: "update thing with empty thing id", + desc: "update client with empty client id", domainID: domainID, token: validToken, - updateThingReq: sdk.Thing{ + updateClientReq: sdk.Client{ ID: "", - Name: updatedThing.Name, + Name: updatedClient.Name, }, - svcReq: convertThing(sdk.Thing{ + svcReq: convertClient(sdk.Client{ ID: "", - Name: updatedThing.Name, + Name: updatedClient.Name, }), - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(apiutil.ErrMissingID), }, { - desc: "update thing with a request that can't be marshalled", + desc: "update client with a request that can't be marshalled", domainID: domainID, token: validToken, - updateThingReq: sdk.Thing{ + updateClientReq: sdk.Client{ ID: "test", Metadata: map[string]interface{}{ "test": make(chan int), }, }, - svcReq: mgthings.Client{}, - svcRes: mgthings.Client{}, + svcReq: clients.Client{}, + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), }, { - desc: "update thing with a response that can't be unmarshalled", - domainID: domainID, - token: validToken, - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: mgthings.Client{ - Name: updatedThing.Name, - Tags: updatedThing.Tags, - Credentials: mgthings.Credentials(updatedThing.Credentials), - Metadata: mgthings.Metadata{ + desc: "update client with a response that can't be unmarshalled", + domainID: domainID, + token: validToken, + updateClientReq: updateClientReq, + svcReq: convertClient(updateClientReq), + svcRes: clients.Client{ + Name: updatedClient.Name, + Tags: updatedClient.Tags, + Credentials: clients.Credentials(updatedClient.Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -1131,7 +1129,7 @@ func TestUpdateThing(t *testing.T) { } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) svcCall := tsvc.On("Update", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateThing(tc.updateThingReq, tc.domainID, tc.token) + resp, err := mgsdk.UpdateClient(tc.updateClientReq, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { @@ -1144,20 +1142,20 @@ func TestUpdateThing(t *testing.T) { } } -func TestUpdateThingTags(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestUpdateClientTags(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) - updatedThing := thing - updatedThing.Tags = []string{"newTag1", "newTag2"} - updateThingReq := sdk.Thing{ - ID: thing.ID, - Tags: updatedThing.Tags, + sdkClient := generateTestClient(t) + updatedClient := sdkClient + updatedClient.Tags = []string{"newTag1", "newTag2"} + updateClientReq := sdk.Client{ + ID: sdkClient.ID, + Tags: updatedClient.Tags, } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1166,113 +1164,113 @@ func TestUpdateThingTags(t *testing.T) { domainID string token string session mgauthn.Session - updateThingReq sdk.Thing - svcReq mgthings.Client - svcRes mgthings.Client + updateClientReq sdk.Client + svcReq clients.Client + svcRes clients.Client svcErr error authenticateErr error - response sdk.Thing + response sdk.Client err errors.SDKError }{ { - desc: "update thing tags successfully", - domainID: domainID, - token: validToken, - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: convertThing(updatedThing), - svcErr: nil, - response: updatedThing, - err: nil, + desc: "update client tags successfully", + domainID: domainID, + token: validToken, + updateClientReq: updateClientReq, + svcReq: convertClient(updateClientReq), + svcRes: convertClient(updatedClient), + svcErr: nil, + response: updatedClient, + err: nil, }, { - desc: "update thing tags with an invalid token", + desc: "update client tags with an invalid token", domainID: domainID, token: invalidToken, - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: mgthings.Client{}, + updateClientReq: updateClientReq, + svcReq: convertClient(updateClientReq), + svcRes: clients.Client{}, authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "update thing tags with empty token", - domainID: domainID, - token: "", - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + desc: "update client tags with empty token", + domainID: domainID, + token: "", + updateClientReq: updateClientReq, + svcReq: convertClient(updateClientReq), + svcRes: clients.Client{}, + svcErr: nil, + response: sdk.Client{}, + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "update thing tags with an invalid thing id", + desc: "update client tags with an invalid client id", domainID: domainID, token: validToken, - updateThingReq: sdk.Thing{ + updateClientReq: sdk.Client{ ID: wrongID, - Tags: updatedThing.Tags, + Tags: updatedClient.Tags, }, - svcReq: convertThing(sdk.Thing{ + svcReq: convertClient(sdk.Client{ ID: wrongID, - Tags: updatedThing.Tags, + Tags: updatedClient.Tags, }), - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, svcErr: svcerr.ErrUpdateEntity, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), }, { - desc: "update thing tags with empty thing id", + desc: "update client tags with empty client id", domainID: domainID, token: validToken, - updateThingReq: sdk.Thing{ + updateClientReq: sdk.Client{ ID: "", - Tags: updatedThing.Tags, + Tags: updatedClient.Tags, }, - svcReq: convertThing(sdk.Thing{ + svcReq: convertClient(sdk.Client{ ID: "", - Tags: updatedThing.Tags, + Tags: updatedClient.Tags, }), - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "update thing tags with a request that can't be marshalled", + desc: "update client tags with a request that can't be marshalled", domainID: domainID, token: validToken, - updateThingReq: sdk.Thing{ + updateClientReq: sdk.Client{ ID: "test", Metadata: map[string]interface{}{ "test": make(chan int), }, }, - svcReq: mgthings.Client{}, - svcRes: mgthings.Client{}, + svcReq: clients.Client{}, + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), }, { - desc: "update thing tags with a response that can't be unmarshalled", - domainID: domainID, - token: validToken, - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: mgthings.Client{ - Name: updatedThing.Name, - Tags: updatedThing.Tags, - Credentials: mgthings.Credentials(updatedThing.Credentials), - Metadata: mgthings.Metadata{ + desc: "update client tags with a response that can't be unmarshalled", + domainID: domainID, + token: validToken, + updateClientReq: updateClientReq, + svcReq: convertClient(updateClientReq), + svcRes: clients.Client{ + Name: updatedClient.Name, + Tags: updatedClient.Tags, + Credentials: clients.Credentials(updatedClient.Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -1283,7 +1281,7 @@ func TestUpdateThingTags(t *testing.T) { } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) svcCall := tsvc.On("UpdateTags", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateThingTags(tc.updateThingReq, tc.domainID, tc.token) + resp, err := mgsdk.UpdateClientTags(tc.updateClientReq, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { @@ -1296,17 +1294,17 @@ func TestUpdateThingTags(t *testing.T) { } } -func TestUpdateThingSecret(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestUpdateClientSecret(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) + sdkClient := generateTestClient(t) newSecret := generateUUID(t) - updatedThing := thing - updatedThing.Credentials.Secret = newSecret + updatedClient := sdkClient + updatedClient.Credentials.Secret = newSecret conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1315,96 +1313,96 @@ func TestUpdateThingSecret(t *testing.T) { domainID string token string session mgauthn.Session - thingID string + clientID string newSecret string - svcRes mgthings.Client + svcRes clients.Client svcErr error authenticateErr error - response sdk.Thing + response sdk.Client err errors.SDKError }{ { - desc: "update thing secret successfully", + desc: "update client secret successfully", domainID: domainID, token: validToken, - thingID: thing.ID, + clientID: sdkClient.ID, newSecret: newSecret, - svcRes: convertThing(updatedThing), + svcRes: convertClient(updatedClient), svcErr: nil, - response: updatedThing, + response: updatedClient, err: nil, }, { - desc: "update thing secret with an invalid token", + desc: "update client secret with an invalid token", domainID: domainID, token: invalidToken, - thingID: thing.ID, + clientID: sdkClient.ID, newSecret: newSecret, - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "update thing secret with empty token", + desc: "update client secret with empty token", domainID: domainID, token: "", - thingID: thing.ID, + clientID: sdkClient.ID, newSecret: newSecret, - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "update thing secret with an invalid thing id", + desc: "update client secret with an invalid client id", domainID: domainID, token: validToken, - thingID: wrongID, + clientID: wrongID, newSecret: newSecret, - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, svcErr: svcerr.ErrUpdateEntity, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), }, { - desc: "update thing secret with empty thing id", + desc: "update client secret with empty client id", domainID: domainID, token: validToken, - thingID: "", + clientID: "", newSecret: newSecret, - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "update thing with empty new secret", + desc: "update client with empty new secret", domainID: domainID, token: validToken, - thingID: thing.ID, + clientID: sdkClient.ID, newSecret: "", - svcRes: mgthings.Client{}, + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingSecret), http.StatusBadRequest), }, { - desc: "update thing secret with a response that can't be unmarshalled", + desc: "update client secret with a response that can't be unmarshalled", domainID: domainID, token: validToken, - thingID: thing.ID, + clientID: sdkClient.ID, newSecret: newSecret, - svcRes: mgthings.Client{ - Name: updatedThing.Name, - Tags: updatedThing.Tags, - Credentials: mgthings.Credentials(updatedThing.Credentials), - Metadata: mgthings.Metadata{ + svcRes: clients.Client{ + Name: updatedClient.Name, + Tags: updatedClient.Tags, + Credentials: clients.Credentials(updatedClient.Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -1414,12 +1412,12 @@ func TestUpdateThingSecret(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) - svcCall := tsvc.On("UpdateSecret", mock.Anything, tc.session, tc.thingID, tc.newSecret).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateThingSecret(tc.thingID, tc.newSecret, tc.domainID, tc.token) + svcCall := tsvc.On("UpdateSecret", mock.Anything, tc.session, tc.clientID, tc.newSecret).Return(tc.svcRes, tc.svcErr) + resp, err := mgsdk.UpdateClientSecret(tc.clientID, tc.newSecret, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UpdateSecret", mock.Anything, tc.session, tc.thingID, tc.newSecret) + ok := svcCall.Parent.AssertCalled(t, "UpdateSecret", mock.Anything, tc.session, tc.clientID, tc.newSecret) assert.True(t, ok) } svcCall.Unset() @@ -1428,16 +1426,16 @@ func TestUpdateThingSecret(t *testing.T) { } } -func TestEnableThing(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestEnableClient(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) - enabledThing := thing - enabledThing.Status = mgthings.EnabledStatus.String() + client := generateTestClient(t) + enabledClient := client + enabledClient.Status = clients.EnabledStatus.String() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1446,68 +1444,68 @@ func TestEnableThing(t *testing.T) { domainID string token string session mgauthn.Session - thingID string - svcRes mgthings.Client + clientID string + svcRes clients.Client svcErr error authenticateErr error - response sdk.Thing + response sdk.Client err errors.SDKError }{ { - desc: "enable thing successfully", + desc: "enable client successfully", domainID: domainID, token: validToken, - thingID: thing.ID, - svcRes: convertThing(enabledThing), + clientID: client.ID, + svcRes: convertClient(enabledClient), svcErr: nil, - response: enabledThing, + response: enabledClient, err: nil, }, { - desc: "enable thing with an invalid token", + desc: "enable client with an invalid token", domainID: domainID, token: invalidToken, - thingID: thing.ID, - svcRes: mgthings.Client{}, + clientID: client.ID, + svcRes: clients.Client{}, authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "enable thing with an invalid thing id", + desc: "enable client with an invalid client id", domainID: domainID, token: validToken, - thingID: wrongID, - svcRes: mgthings.Client{}, + clientID: wrongID, + svcRes: clients.Client{}, svcErr: svcerr.ErrEnableClient, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrEnableClient, http.StatusUnprocessableEntity), }, { - desc: "enable thing with empty thing id", + desc: "enable client with empty client id", domainID: domainID, token: validToken, - thingID: "", - svcRes: mgthings.Client{}, + clientID: "", + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "enable thing with a response that can't be unmarshalled", + desc: "enable client with a response that can't be unmarshalled", domainID: domainID, token: validToken, - thingID: thing.ID, - svcRes: mgthings.Client{ - Name: enabledThing.Name, - Tags: enabledThing.Tags, - Credentials: mgthings.Credentials(enabledThing.Credentials), - Metadata: mgthings.Metadata{ + clientID: client.ID, + svcRes: clients.Client{ + Name: enabledClient.Name, + Tags: enabledClient.Tags, + Credentials: clients.Credentials(enabledClient.Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -1517,12 +1515,12 @@ func TestEnableThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) - svcCall := tsvc.On("Enable", mock.Anything, tc.session, tc.thingID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.EnableThing(tc.thingID, tc.domainID, tc.token) + svcCall := tsvc.On("Enable", mock.Anything, tc.session, tc.clientID).Return(tc.svcRes, tc.svcErr) + resp, err := mgsdk.EnableClient(tc.clientID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Enable", mock.Anything, tc.session, tc.thingID) + ok := svcCall.Parent.AssertCalled(t, "Enable", mock.Anything, tc.session, tc.clientID) assert.True(t, ok) } svcCall.Unset() @@ -1531,16 +1529,16 @@ func TestEnableThing(t *testing.T) { } } -func TestDisableThing(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestDisableClient(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) - disabledThing := thing - disabledThing.Status = mgthings.DisabledStatus.String() + client := generateTestClient(t) + disabledClient := client + disabledClient.Status = clients.DisabledStatus.String() conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1549,68 +1547,68 @@ func TestDisableThing(t *testing.T) { domainID string token string session mgauthn.Session - thingID string - svcRes mgthings.Client + clientID string + svcRes clients.Client svcErr error authenticateErr error - response sdk.Thing + response sdk.Client err errors.SDKError }{ { - desc: "disable thing successfully", + desc: "disable client successfully", domainID: domainID, token: validToken, - thingID: thing.ID, - svcRes: convertThing(disabledThing), + clientID: client.ID, + svcRes: convertClient(disabledClient), svcErr: nil, - response: disabledThing, + response: disabledClient, err: nil, }, { - desc: "disable thing with an invalid token", + desc: "disable client with an invalid token", domainID: domainID, token: invalidToken, - thingID: thing.ID, - svcRes: mgthings.Client{}, + clientID: client.ID, + svcRes: clients.Client{}, authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "disable thing with an invalid thing id", + desc: "disable client with an invalid client id", domainID: domainID, token: validToken, - thingID: wrongID, - svcRes: mgthings.Client{}, + clientID: wrongID, + svcRes: clients.Client{}, svcErr: svcerr.ErrDisableClient, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrDisableClient, http.StatusInternalServerError), }, { - desc: "disable thing with empty thing id", + desc: "disable client with empty client id", domainID: domainID, token: validToken, - thingID: "", - svcRes: mgthings.Client{}, + clientID: "", + svcRes: clients.Client{}, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "disable thing with a response that can't be unmarshalled", + desc: "disable client with a response that can't be unmarshalled", domainID: domainID, token: validToken, - thingID: thing.ID, - svcRes: mgthings.Client{ - Name: disabledThing.Name, - Tags: disabledThing.Tags, - Credentials: mgthings.Credentials(disabledThing.Credentials), - Metadata: mgthings.Metadata{ + clientID: client.ID, + svcRes: clients.Client{ + Name: disabledClient.Name, + Tags: disabledClient.Tags, + Credentials: clients.Credentials(disabledClient.Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }, svcErr: nil, - response: sdk.Thing{}, + response: sdk.Client{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -1620,12 +1618,12 @@ func TestDisableThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) - svcCall := tsvc.On("Disable", mock.Anything, tc.session, tc.thingID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.DisableThing(tc.thingID, tc.domainID, tc.token) + svcCall := tsvc.On("Disable", mock.Anything, tc.session, tc.clientID).Return(tc.svcRes, tc.svcErr) + resp, err := mgsdk.DisableClient(tc.clientID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Disable", mock.Anything, tc.session, tc.thingID) + ok := svcCall.Parent.AssertCalled(t, "Disable", mock.Anything, tc.session, tc.clientID) assert.True(t, ok) } svcCall.Unset() @@ -1634,14 +1632,14 @@ func TestDisableThing(t *testing.T) { } } -func TestShareThing(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestShareClient(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) + client := generateTestClient(t) conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1650,17 +1648,17 @@ func TestShareThing(t *testing.T) { domainID string token string session mgauthn.Session - thingID string + clientID string shareReq sdk.UsersRelationRequest authenticateErr error svcErr error err errors.SDKError }{ { - desc: "share thing successfully", + desc: "share client successfully", domainID: domainID, token: validToken, - thingID: thing.ID, + clientID: client.ID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1669,10 +1667,10 @@ func TestShareThing(t *testing.T) { err: nil, }, { - desc: "share thing with an invalid token", + desc: "share client with an invalid token", domainID: domainID, token: invalidToken, - thingID: thing.ID, + clientID: client.ID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1681,10 +1679,10 @@ func TestShareThing(t *testing.T) { err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "share thing with empty token", + desc: "share client with empty token", domainID: domainID, token: "", - thingID: thing.ID, + clientID: client.ID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1693,10 +1691,10 @@ func TestShareThing(t *testing.T) { err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "share thing with an invalid thing id", + desc: "share client with an invalid client id", domainID: domainID, token: validToken, - thingID: wrongID, + clientID: wrongID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1705,10 +1703,10 @@ func TestShareThing(t *testing.T) { err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), }, { - desc: "share thing with empty thing id", + desc: "share client with empty client id", domainID: domainID, token: validToken, - thingID: "", + clientID: "", shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1717,10 +1715,10 @@ func TestShareThing(t *testing.T) { err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), }, { - desc: "share thing with empty relation", + desc: "share client with empty relation", domainID: domainID, token: validToken, - thingID: thing.ID, + clientID: client.ID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: "", @@ -1735,11 +1733,11 @@ func TestShareThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) - svcCall := tsvc.On("Share", mock.Anything, tc.session, tc.thingID, tc.shareReq.Relation, tc.shareReq.UserIDs[0]).Return(tc.svcErr) - err := mgsdk.ShareThing(tc.thingID, tc.shareReq, tc.domainID, tc.token) + svcCall := tsvc.On("Share", mock.Anything, tc.session, tc.clientID, tc.shareReq.Relation, tc.shareReq.UserIDs[0]).Return(tc.svcErr) + err := mgsdk.ShareClient(tc.clientID, tc.shareReq, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Share", mock.Anything, tc.session, tc.thingID, tc.shareReq.Relation, tc.shareReq.UserIDs[0]) + ok := svcCall.Parent.AssertCalled(t, "Share", mock.Anything, tc.session, tc.clientID, tc.shareReq.Relation, tc.shareReq.UserIDs[0]) assert.True(t, ok) } svcCall.Unset() @@ -1748,14 +1746,14 @@ func TestShareThing(t *testing.T) { } } -func TestUnshareThing(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestUnshareClient(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) + client := generateTestClient(t) conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1764,17 +1762,17 @@ func TestUnshareThing(t *testing.T) { domainID string token string session mgauthn.Session - thingID string + clientID string shareReq sdk.UsersRelationRequest authenticateErr error svcErr error err errors.SDKError }{ { - desc: "unshare thing successfully", + desc: "unshare client successfully", domainID: domainID, token: validToken, - thingID: thing.ID, + clientID: client.ID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1783,10 +1781,10 @@ func TestUnshareThing(t *testing.T) { err: nil, }, { - desc: "unshare thing with an invalid token", + desc: "unshare client with an invalid token", domainID: domainID, token: invalidToken, - thingID: thing.ID, + clientID: client.ID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1795,10 +1793,10 @@ func TestUnshareThing(t *testing.T) { err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "unshare thing with empty token", + desc: "unshare client with empty token", domainID: domainID, token: "", - thingID: thing.ID, + clientID: client.ID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1806,10 +1804,10 @@ func TestUnshareThing(t *testing.T) { err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "unshare thing with an invalid thing id", + desc: "unshare client with an invalid client id", domainID: domainID, token: validToken, - thingID: wrongID, + clientID: wrongID, shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1818,10 +1816,10 @@ func TestUnshareThing(t *testing.T) { err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), }, { - desc: "unshare thing with empty thing id", + desc: "unshare client with empty client id", domainID: domainID, token: validToken, - thingID: "", + clientID: "", shareReq: sdk.UsersRelationRequest{ UserIDs: []string{validID}, Relation: policies.EditorRelation, @@ -1836,11 +1834,11 @@ func TestUnshareThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) - svcCall := tsvc.On("Unshare", mock.Anything, tc.session, tc.thingID, tc.shareReq.Relation, tc.shareReq.UserIDs[0]).Return(tc.svcErr) - err := mgsdk.UnshareThing(tc.thingID, tc.shareReq, tc.domainID, tc.token) + svcCall := tsvc.On("Unshare", mock.Anything, tc.session, tc.clientID, tc.shareReq.Relation, tc.shareReq.UserIDs[0]).Return(tc.svcErr) + err := mgsdk.UnshareClient(tc.clientID, tc.shareReq, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Unshare", mock.Anything, tc.session, tc.thingID, tc.shareReq.Relation, tc.shareReq.UserIDs[0]) + ok := svcCall.Parent.AssertCalled(t, "Unshare", mock.Anything, tc.session, tc.clientID, tc.shareReq.Relation, tc.shareReq.UserIDs[0]) assert.True(t, ok) } svcCall.Unset() @@ -1849,14 +1847,14 @@ func TestUnshareThing(t *testing.T) { } } -func TestDeleteThing(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestDeleteClient(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - thing := generateTestThing(t) + client := generateTestClient(t) conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1865,48 +1863,48 @@ func TestDeleteThing(t *testing.T) { domainID string token string session mgauthn.Session - thingID string + clientID string svcErr error authenticateErr error err errors.SDKError }{ { - desc: "delete thing successfully", + desc: "delete client successfully", domainID: domainID, token: validToken, - thingID: thing.ID, + clientID: client.ID, svcErr: nil, err: nil, }, { - desc: "delete thing with an invalid token", + desc: "delete client with an invalid token", domainID: domainID, token: invalidToken, - thingID: thing.ID, + clientID: client.ID, authenticateErr: svcerr.ErrAuthorization, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { - desc: "delete thing with empty token", + desc: "delete client with empty token", domainID: domainID, token: "", - thingID: thing.ID, + clientID: client.ID, svcErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { - desc: "delete thing with an invalid thing id", + desc: "delete client with an invalid client id", domainID: domainID, token: validToken, - thingID: wrongID, + clientID: wrongID, svcErr: svcerr.ErrRemoveEntity, err: errors.NewSDKErrorWithStatus(svcerr.ErrRemoveEntity, http.StatusUnprocessableEntity), }, { - desc: "delete thing with empty thing id", + desc: "delete client with empty client id", domainID: domainID, token: validToken, - thingID: "", + clientID: "", svcErr: nil, err: errors.NewSDKError(apiutil.ErrMissingID), }, @@ -1917,11 +1915,11 @@ func TestDeleteThing(t *testing.T) { tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) - svcCall := tsvc.On("Delete", mock.Anything, tc.session, tc.thingID).Return(tc.svcErr) - err := mgsdk.DeleteThing(tc.thingID, tc.domainID, tc.token) + svcCall := tsvc.On("Delete", mock.Anything, tc.session, tc.clientID).Return(tc.svcErr) + err := mgsdk.DeleteClient(tc.clientID, tc.domainID, tc.token) assert.Equal(t, tc.err, err) if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Delete", mock.Anything, tc.session, tc.thingID) + ok := svcCall.Parent.AssertCalled(t, "Delete", mock.Anything, tc.session, tc.clientID) assert.True(t, ok) } svcCall.Unset() @@ -1930,22 +1928,22 @@ func TestDeleteThing(t *testing.T) { } } -func TestListUserThings(t *testing.T) { - ts, tsvc, auth := setupThings() +func TestListUserClients(t *testing.T) { + ts, tsvc, auth := setupClients() defer ts.Close() - var things []sdk.Thing + var sdkClients []sdk.Client for i := 10; i < 100; i++ { - thing := generateTestThing(t) + c := generateTestClient(t) if i == 50 { - thing.Status = mgthings.DisabledStatus.String() - thing.Tags = []string{"tag1", "tag2"} + c.Status = clients.DisabledStatus.String() + c.Tags = []string{"tag1", "tag2"} } - things = append(things, thing) + sdkClients = append(sdkClients, c) } conf := sdk.Config{ - ThingsURL: ts.URL, + ClientsURL: ts.URL, } mgsdk := sdk.NewSDK(conf) @@ -1955,15 +1953,15 @@ func TestListUserThings(t *testing.T) { session mgauthn.Session userID string pageMeta sdk.PageMetadata - svcReq mgthings.Page - svcRes mgthings.ClientsPage + svcReq clients.Page + svcRes clients.ClientsPage svcErr error authenticateErr error - response sdk.ThingsPage + response sdk.ClientsPage err errors.SDKError }{ { - desc: "list user things successfully", + desc: "list user clients successfully", token: validToken, userID: validID, pageMeta: sdk.PageMetadata{ @@ -1971,30 +1969,30 @@ func TestListUserThings(t *testing.T) { Limit: 100, DomainID: domainID, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ + svcRes: clients.ClientsPage{ + Page: clients.Page{ Offset: 0, Limit: 100, - Total: uint64(len(things)), + Total: uint64(len(sdkClients)), }, - Clients: convertThings(things...), + Clients: convertClients(sdkClients...), }, svcErr: nil, - response: sdk.ThingsPage{ + response: sdk.ClientsPage{ PageRes: sdk.PageRes{ Limit: 100, - Total: uint64(len(things)), + Total: uint64(len(sdkClients)), }, - Things: things, + Clients: sdkClients, }, }, { - desc: "list user things with an invalid token", + desc: "list user clients with an invalid token", token: invalidToken, userID: validID, pageMeta: sdk.PageMetadata{ @@ -2002,18 +2000,18 @@ func TestListUserThings(t *testing.T) { Limit: 100, DomainID: domainID, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.ClientsPage{}, + svcRes: clients.ClientsPage{}, authenticateErr: svcerr.ErrAuthentication, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, { - desc: "list user things with limit greater than max", + desc: "list user clients with limit greater than max", token: validToken, userID: validID, pageMeta: sdk.PageMetadata{ @@ -2021,14 +2019,14 @@ func TestListUserThings(t *testing.T) { Limit: 1000, DomainID: domainID, }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, + svcReq: clients.Page{}, + svcRes: clients.ClientsPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), }, { - desc: "list user things with name size greater than max", + desc: "list user clients with name size greater than max", token: validToken, userID: validID, pageMeta: sdk.PageMetadata{ @@ -2037,48 +2035,48 @@ func TestListUserThings(t *testing.T) { Name: strings.Repeat("a", 1025), DomainID: domainID, }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, + svcReq: clients.Page{}, + svcRes: clients.ClientsPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), }, { - desc: "list user things with status", + desc: "list user clients with status", token: validToken, userID: validID, pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 100, - Status: mgthings.DisabledStatus.String(), + Status: clients.DisabledStatus.String(), DomainID: domainID, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, - Status: mgthings.DisabledStatus, + Status: clients.DisabledStatus, }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ + svcRes: clients.ClientsPage{ + Page: clients.Page{ Offset: 0, Limit: 100, Total: 1, }, - Clients: convertThings(things[50]), + Clients: convertClients(sdkClients[50]), }, svcErr: nil, - response: sdk.ThingsPage{ + response: sdk.ClientsPage{ PageRes: sdk.PageRes{ Limit: 100, Total: 1, }, - Things: []sdk.Thing{things[50]}, + Clients: []sdk.Client{sdkClients[50]}, }, err: nil, }, { - desc: "list user things with tags", + desc: "list user clients with tags", token: validToken, userID: validID, pageMeta: sdk.PageMetadata{ @@ -2087,32 +2085,32 @@ func TestListUserThings(t *testing.T) { Tag: "tag1", DomainID: domainID, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, Tag: "tag1", }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ + svcRes: clients.ClientsPage{ + Page: clients.Page{ Offset: 0, Limit: 100, Total: 1, }, - Clients: convertThings(things[50]), + Clients: convertClients(sdkClients[50]), }, svcErr: nil, - response: sdk.ThingsPage{ + response: sdk.ClientsPage{ PageRes: sdk.PageRes{ Limit: 100, Total: 1, }, - Things: []sdk.Thing{things[50]}, + Clients: []sdk.Client{sdkClients[50]}, }, err: nil, }, { - desc: "list user things with invalid metadata", + desc: "list user clients with invalid metadata", token: validToken, userID: validID, pageMeta: sdk.PageMetadata{ @@ -2123,42 +2121,42 @@ func TestListUserThings(t *testing.T) { }, DomainID: domainID, }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, + svcReq: clients.Page{}, + svcRes: clients.ClientsPage{}, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), }, { - desc: "list user things with response that can't be unmarshalled", + desc: "list user clients with response that can't be unmarshalled", token: validToken, pageMeta: sdk.PageMetadata{ Offset: 0, Limit: 100, DomainID: domainID, }, - svcReq: mgthings.Page{ + svcReq: clients.Page{ Offset: 0, Limit: 100, Permission: policies.ViewPermission, }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ + svcRes: clients.ClientsPage{ + Page: clients.Page{ Offset: 0, Limit: 100, Total: 1, }, - Clients: []mgthings.Client{{ - Name: things[0].Name, - Tags: things[0].Tags, - Credentials: mgthings.Credentials(things[0].Credentials), - Metadata: mgthings.Metadata{ + Clients: []clients.Client{{ + Name: sdkClients[0].Name, + Tags: sdkClients[0].Tags, + Credentials: clients.Credentials(sdkClients[0].Credentials), + Metadata: clients.Metadata{ "test": make(chan int), }, }}, }, svcErr: nil, - response: sdk.ThingsPage{}, + response: sdk.ClientsPage{}, err: errors.NewSDKError(errors.New("unexpected end of JSON input")), }, } @@ -2169,7 +2167,7 @@ func TestListUserThings(t *testing.T) { } authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authenticateErr) svcCall := tsvc.On("ListClients", mock.Anything, tc.session, tc.userID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListUserThings(tc.userID, tc.pageMeta, tc.token) + resp, err := mgsdk.ListUserClients(tc.userID, tc.pageMeta, tc.token) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, resp) if tc.err == nil { @@ -2182,20 +2180,20 @@ func TestListUserThings(t *testing.T) { } } -func generateTestThing(t *testing.T) sdk.Thing { +func generateTestClient(t *testing.T) sdk.Client { createdAt, err := time.Parse(time.RFC3339, "2023-03-03T00:00:00Z") assert.Nil(t, err, fmt.Sprintf("unexpected error %s", err)) updatedAt := createdAt - return sdk.Thing{ + return sdk.Client{ ID: testsutil.GenerateUUID(t), Name: "clientname", Credentials: sdk.ClientCredentials{ - Identity: "thing@example.com", + Identity: "client@example.com", Secret: generateUUID(t), }, Tags: []string{"tag1", "tag2"}, Metadata: validMetadata, - Status: mgthings.EnabledStatus.String(), + Status: clients.EnabledStatus.String(), CreatedAt: createdAt, UpdatedAt: updatedAt, } diff --git a/pkg/sdk/go/domains_test.go b/pkg/sdk/go/domains_test.go index ea1c484ecd..59ebb3c2c7 100644 --- a/pkg/sdk/go/domains_test.go +++ b/pkg/sdk/go/domains_test.go @@ -10,13 +10,14 @@ import ( "testing" "time" - "github.com/absmach/magistrala/auth" - httpapi "github.com/absmach/magistrala/auth/api/http/domains" - authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/domains" + httpapi "github.com/absmach/magistrala/domains/api/http" + "github.com/absmach/magistrala/domains/mocks" internalapi "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" + authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" policies "github.com/absmach/magistrala/pkg/policies" @@ -28,7 +29,7 @@ import ( var ( authDomain, sdkDomain = generateTestDomain(&testing.T{}) - authDomainReq = auth.Domain{ + authDomainReq = domains.Domain{ Name: authDomain.Name, Metadata: authDomain.Metadata, Tags: authDomain.Tags, @@ -43,12 +44,13 @@ var ( updatedDomianName = "updated-domain" ) -func setupDomains() (*httptest.Server, *authmocks.Service) { - svc := new(authmocks.Service) +func setupDomains() (*httptest.Server, *mocks.Service) { + svc := new(mocks.Service) logger := mglog.NewMock() mux := chi.NewRouter() + authn := new(authnmocks.Authentication) - mux = httpapi.MakeHandler(svc, mux, logger) + mux = httpapi.MakeHandler(svc, authn, mux, logger, "") return httptest.NewServer(mux), svc } @@ -67,8 +69,8 @@ func TestCreateDomain(t *testing.T) { desc string token string domain sdk.Domain - svcReq auth.Domain - svcRes auth.Domain + svcReq domains.Domain + svcRes domains.Domain svcErr error response sdk.Domain err error @@ -88,7 +90,7 @@ func TestCreateDomain(t *testing.T) { token: invalidToken, domain: sdkDomainReq, svcReq: authDomainReq, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: svcerr.ErrAuthentication, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -98,7 +100,7 @@ func TestCreateDomain(t *testing.T) { token: "", domain: sdkDomainReq, svcReq: authDomainReq, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: nil, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -112,8 +114,8 @@ func TestCreateDomain(t *testing.T) { Tags: sdkDomain.Tags, Alias: sdkDomain.Alias, }, - svcReq: auth.Domain{}, - svcRes: auth.Domain{}, + svcReq: domains.Domain{}, + svcRes: domains.Domain{}, svcErr: nil, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingName, http.StatusBadRequest), @@ -127,8 +129,8 @@ func TestCreateDomain(t *testing.T) { "key": make(chan int), }, }, - svcReq: auth.Domain{}, - svcRes: auth.Domain{}, + svcReq: domains.Domain{}, + svcRes: domains.Domain{}, svcErr: nil, response: sdk.Domain{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -138,10 +140,10 @@ func TestCreateDomain(t *testing.T) { token: validToken, domain: sdkDomainReq, svcReq: authDomainReq, - svcRes: auth.Domain{ + svcRes: domains.Domain{ ID: authDomain.ID, Name: authDomain.Name, - Metadata: auth.Metadata{ + Metadata: domains.Metadata{ "key": make(chan int), }, }, @@ -186,7 +188,7 @@ func TestUpdateDomain(t *testing.T) { token string domainID string domain sdk.Domain - svcRes auth.Domain + svcRes domains.Domain svcErr error response sdk.Domain err error @@ -212,7 +214,7 @@ func TestUpdateDomain(t *testing.T) { ID: sdkDomain.ID, Name: updatedDomianName, }, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: svcerr.ErrAuthentication, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -225,7 +227,7 @@ func TestUpdateDomain(t *testing.T) { ID: sdkDomain.ID, Name: updatedDomianName, }, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: nil, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -238,7 +240,7 @@ func TestUpdateDomain(t *testing.T) { ID: wrongID, Name: updatedDomianName, }, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: svcerr.ErrAuthorization, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), @@ -250,7 +252,7 @@ func TestUpdateDomain(t *testing.T) { domain: sdk.Domain{ Name: sdkDomain.Name, }, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: nil, response: sdk.Domain{}, err: errors.NewSDKError(apiutil.ErrMissingID), @@ -266,7 +268,7 @@ func TestUpdateDomain(t *testing.T) { "key": make(chan int), }, }, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: nil, response: sdk.Domain{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -279,10 +281,10 @@ func TestUpdateDomain(t *testing.T) { ID: sdkDomain.ID, Name: sdkDomain.Name, }, - svcRes: auth.Domain{ + svcRes: domains.Domain{ ID: authDomain.ID, Name: authDomain.Name, - Metadata: auth.Metadata{ + Metadata: domains.Metadata{ "key": make(chan int), }, }, @@ -321,7 +323,7 @@ func TestViewDomain(t *testing.T) { desc string token string domainID string - svcRes auth.Domain + svcRes domains.Domain svcErr error response sdk.Domain err error @@ -339,7 +341,7 @@ func TestViewDomain(t *testing.T) { desc: "view domain with invalid token", token: invalidToken, domainID: sdkDomain.ID, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: svcerr.ErrAuthentication, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -348,7 +350,7 @@ func TestViewDomain(t *testing.T) { desc: "view domain with empty token", token: "", domainID: sdkDomain.ID, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: nil, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -357,7 +359,7 @@ func TestViewDomain(t *testing.T) { desc: "view domain with invalid domain ID", token: validToken, domainID: wrongID, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: svcerr.ErrAuthorization, response: sdk.Domain{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), @@ -366,7 +368,7 @@ func TestViewDomain(t *testing.T) { desc: "view domain with empty id", token: validToken, domainID: "", - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: nil, response: sdk.Domain{}, err: errors.NewSDKError(apiutil.ErrMissingID), @@ -375,10 +377,10 @@ func TestViewDomain(t *testing.T) { desc: "view domain with response that cannot be unmarshalled", token: validToken, domainID: sdkDomain.ID, - svcRes: auth.Domain{ + svcRes: domains.Domain{ ID: authDomain.ID, Name: authDomain.Name, - Metadata: auth.Metadata{ + Metadata: domains.Metadata{ "key": make(chan int), }, }, @@ -500,8 +502,8 @@ func TestListDomians(t *testing.T) { desc string token string pageMeta sdk.PageMetadata - svcReq auth.Page - svcRes auth.DomainsPage + svcReq domains.Page + svcRes domains.DomainsPage svcErr error response sdk.DomainsPage err error @@ -513,15 +515,15 @@ func TestListDomians(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{ + svcReq: domains.Page{ Offset: 0, Limit: 10, Order: internalapi.DefOrder, Dir: internalapi.DefDir, }, - svcRes: auth.DomainsPage{ + svcRes: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{authDomain}, + Domains: []domains.Domain{authDomain}, }, svcErr: nil, response: sdk.DomainsPage{ @@ -539,13 +541,13 @@ func TestListDomians(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{ + svcReq: domains.Page{ Offset: 0, Limit: 10, Order: internalapi.DefOrder, Dir: internalapi.DefDir, }, - svcRes: auth.DomainsPage{}, + svcRes: domains.DomainsPage{}, svcErr: svcerr.ErrAuthentication, response: sdk.DomainsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -557,8 +559,8 @@ func TestListDomians(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, + svcReq: domains.Page{}, + svcRes: domains.DomainsPage{}, svcErr: nil, response: sdk.DomainsPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), @@ -573,8 +575,8 @@ func TestListDomians(t *testing.T) { "key": make(chan int), }, }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, + svcReq: domains.Page{}, + svcRes: domains.DomainsPage{}, svcErr: nil, response: sdk.DomainsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -586,17 +588,17 @@ func TestListDomians(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{ + svcReq: domains.Page{ Offset: 0, Limit: 10, Order: internalapi.DefOrder, Dir: internalapi.DefDir, }, - svcRes: auth.DomainsPage{ + svcRes: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{{ + Domains: []domains.Domain{{ Name: authDomain.Name, - Metadata: auth.Metadata{"key": make(chan int)}, + Metadata: domains.Metadata{"key": make(chan int)}, }}, }, svcErr: nil, @@ -635,8 +637,8 @@ func TestListUserDomains(t *testing.T) { token string userID string pageMeta sdk.PageMetadata - svcReq auth.Page - svcRes auth.DomainsPage + svcReq domains.Page + svcRes domains.DomainsPage svcErr error response sdk.DomainsPage err error @@ -649,15 +651,15 @@ func TestListUserDomains(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{ + svcReq: domains.Page{ Offset: 0, Limit: 10, Order: internalapi.DefOrder, Dir: internalapi.DefDir, }, - svcRes: auth.DomainsPage{ + svcRes: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{authDomain}, + Domains: []domains.Domain{authDomain}, }, svcErr: nil, response: sdk.DomainsPage{ @@ -676,13 +678,13 @@ func TestListUserDomains(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{ + svcReq: domains.Page{ Offset: 0, Limit: 10, Order: internalapi.DefOrder, Dir: internalapi.DefDir, }, - svcRes: auth.DomainsPage{}, + svcRes: domains.DomainsPage{}, svcErr: svcerr.ErrAuthentication, response: sdk.DomainsPage{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -695,8 +697,8 @@ func TestListUserDomains(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, + svcReq: domains.Page{}, + svcRes: domains.DomainsPage{}, svcErr: nil, response: sdk.DomainsPage{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), @@ -709,8 +711,8 @@ func TestListUserDomains(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, + svcReq: domains.Page{}, + svcRes: domains.DomainsPage{}, svcErr: nil, response: sdk.DomainsPage{}, err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), @@ -723,17 +725,17 @@ func TestListUserDomains(t *testing.T) { Offset: 0, Limit: 10, }, - svcReq: auth.Page{ + svcReq: domains.Page{ Offset: 0, Limit: 10, Order: internalapi.DefOrder, Dir: internalapi.DefDir, }, - svcRes: auth.DomainsPage{ + svcRes: domains.DomainsPage{ Total: 1, - Domains: []auth.Domain{{ + Domains: []domains.Domain{{ Name: authDomain.Name, - Metadata: auth.Metadata{"key": make(chan int)}, + Metadata: domains.Metadata{"key": make(chan int)}, }}, }, svcErr: nil, @@ -751,8 +753,8 @@ func TestListUserDomains(t *testing.T) { "key": make(chan int), }, }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, + svcReq: domains.Page{}, + svcRes: domains.DomainsPage{}, svcErr: nil, response: sdk.DomainsPage{}, err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), @@ -784,14 +786,14 @@ func TestEnableDomain(t *testing.T) { mgsdk := sdk.NewSDK(sdkConf) - enable := auth.EnabledStatus + enable := domains.EnabledStatus cases := []struct { desc string token string domainID string - svcReq auth.DomainReq - svcRes auth.Domain + svcReq domains.DomainReq + svcRes domains.Domain svcErr error err error }{ @@ -799,7 +801,7 @@ func TestEnableDomain(t *testing.T) { desc: "enable domain successfully", token: validToken, domainID: sdkDomain.ID, - svcReq: auth.DomainReq{ + svcReq: domains.DomainReq{ Status: &enable, }, svcRes: authDomain, @@ -810,10 +812,10 @@ func TestEnableDomain(t *testing.T) { desc: "enable domain with invalid token", token: invalidToken, domainID: sdkDomain.ID, - svcReq: auth.DomainReq{ + svcReq: domains.DomainReq{ Status: &enable, }, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, @@ -821,8 +823,8 @@ func TestEnableDomain(t *testing.T) { desc: "enable domain with empty token", token: "", domainID: sdkDomain.ID, - svcReq: auth.DomainReq{}, - svcRes: auth.Domain{}, + svcReq: domains.DomainReq{}, + svcRes: domains.Domain{}, svcErr: nil, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, @@ -830,8 +832,8 @@ func TestEnableDomain(t *testing.T) { desc: "enable domain with empty domain id", token: validToken, domainID: "", - svcReq: auth.DomainReq{}, - svcRes: auth.Domain{}, + svcReq: domains.DomainReq{}, + svcRes: domains.Domain{}, svcErr: nil, err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), }, @@ -861,14 +863,14 @@ func TestDisableDomain(t *testing.T) { mgsdk := sdk.NewSDK(sdkConf) - disable := auth.DisabledStatus + disable := domains.DisabledStatus cases := []struct { desc string token string domainID string - svcReq auth.DomainReq - svcRes auth.Domain + svcReq domains.DomainReq + svcRes domains.Domain svcErr error err error }{ @@ -876,7 +878,7 @@ func TestDisableDomain(t *testing.T) { desc: "disable domain successfully", token: validToken, domainID: sdkDomain.ID, - svcReq: auth.DomainReq{ + svcReq: domains.DomainReq{ Status: &disable, }, svcRes: authDomain, @@ -887,10 +889,10 @@ func TestDisableDomain(t *testing.T) { desc: "disable domain with invalid token", token: invalidToken, domainID: sdkDomain.ID, - svcReq: auth.DomainReq{ + svcReq: domains.DomainReq{ Status: &disable, }, - svcRes: auth.Domain{}, + svcRes: domains.Domain{}, svcErr: svcerr.ErrAuthentication, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), }, @@ -898,8 +900,8 @@ func TestDisableDomain(t *testing.T) { desc: "disable domain with empty token", token: "", domainID: sdkDomain.ID, - svcReq: auth.DomainReq{}, - svcRes: auth.Domain{}, + svcReq: domains.DomainReq{}, + svcRes: domains.Domain{}, svcErr: nil, err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, @@ -907,8 +909,8 @@ func TestDisableDomain(t *testing.T) { desc: "disable domain with empty domain id", token: validToken, domainID: "", - svcReq: auth.DomainReq{}, - svcRes: auth.Domain{}, + svcReq: domains.DomainReq{}, + svcRes: domains.Domain{}, svcErr: nil, err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), }, @@ -1103,17 +1105,17 @@ func TestRemoveUserFromDomain(t *testing.T) { } } -func generateTestDomain(t *testing.T) (auth.Domain, sdk.Domain) { +func generateTestDomain(t *testing.T) (domains.Domain, sdk.Domain) { createdAt, err := time.Parse(time.RFC3339, "2024-04-01T00:00:00Z") assert.Nil(t, err, fmt.Sprintf("Unexpected error parsing time: %s", err)) ownerID := testsutil.GenerateUUID(t) - ad := auth.Domain{ + ad := domains.Domain{ ID: testsutil.GenerateUUID(t), Name: "test-domain", - Metadata: auth.Metadata(validMetadata), + Metadata: domains.Metadata(validMetadata), Tags: []string{"tag1", "tag2"}, Alias: "test-alias", - Status: auth.EnabledStatus, + Status: domains.EnabledStatus, CreatedBy: ownerID, CreatedAt: createdAt, UpdatedBy: ownerID, diff --git a/pkg/sdk/go/groups.go b/pkg/sdk/go/groups.go index 0dcb0ee0d1..dead91937b 100644 --- a/pkg/sdk/go/groups.go +++ b/pkg/sdk/go/groups.go @@ -215,7 +215,7 @@ func (sdk mgSDK) ListGroupUsers(groupID string, pm PageMetadata, domainID, token } func (sdk mgSDK) ListGroupChannels(groupID string, pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s/%s", domainID, groupsEndpoint, groupID, channelsEndpoint), pm) + url, err := sdk.withQueryParams(sdk.clientsURL, fmt.Sprintf("%s/%s/%s/%s", domainID, groupsEndpoint, groupID, channelsEndpoint), pm) if err != nil { return ChannelsPage{}, errors.NewSDKError(err) } diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index 82271465e1..3c0dec0fa6 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -11,7 +11,9 @@ import ( "testing" "time" - authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/groups" + httpapi "github.com/absmach/magistrala/groups/api/http" + "github.com/absmach/magistrala/groups/mocks" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" @@ -19,13 +21,9 @@ import ( authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/groups/mocks" oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" policies "github.com/absmach/magistrala/pkg/policies" sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/absmach/magistrala/users/api" - umocks "github.com/absmach/magistrala/users/mocks" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -39,18 +37,16 @@ var ( ) func setupGroups() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - usvc := new(umocks.Service) - gsvc := new(mocks.Service) + svc := new(mocks.Service) logger := mglog.NewMock() mux := chi.NewRouter() provider := new(oauth2mocks.Provider) provider.On("Name").Return("test") authn := new(authnmocks.Authentication) - token := new(authmocks.TokenServiceClient) - api.MakeHandler(usvc, authn, token, true, gsvc, mux, logger, "", passRegex, provider) + httpapi.MakeHandler(svc, authn, mux, logger, "") - return httptest.NewServer(mux), gsvc, authn + return httptest.NewServer(mux), svc, authn } func TestCreateGroup(t *testing.T) { @@ -327,8 +323,6 @@ func TestListGroups(t *testing.T) { Offset: offset, Limit: 100, }, - Permission: policies.ViewPermission, - Direction: -1, }, svcRes: groups.Page{ PageMeta: groups.PageMeta{ @@ -357,8 +351,6 @@ func TestListGroups(t *testing.T) { Offset: offset, Limit: 100, }, - Permission: policies.ViewPermission, - Direction: -1, }, svcRes: groups.Page{}, authenticateErr: svcerr.ErrAuthentication, @@ -392,8 +384,6 @@ func TestListGroups(t *testing.T) { Offset: offset, Limit: 10, }, - Permission: policies.ViewPermission, - Direction: -1, }, svcRes: groups.Page{ PageMeta: groups.PageMeta{ @@ -443,8 +433,6 @@ func TestListGroups(t *testing.T) { "name": "user_89", }, }, - Permission: policies.ViewPermission, - Direction: -1, }, svcRes: groups.Page{ PageMeta: groups.PageMeta{ @@ -506,8 +494,6 @@ func TestListGroups(t *testing.T) { Offset: offset, Limit: limit, }, - Permission: policies.ViewPermission, - Direction: -1, }, svcRes: groups.Page{ PageMeta: groups.PageMeta{ diff --git a/pkg/sdk/go/health.go b/pkg/sdk/go/health.go index 4334b29408..d69abf1924 100644 --- a/pkg/sdk/go/health.go +++ b/pkg/sdk/go/health.go @@ -32,8 +32,8 @@ type HealthInfo struct { func (sdk mgSDK) Health(service string) (HealthInfo, errors.SDKError) { var url string switch service { - case "things": - url = fmt.Sprintf("%s/health", sdk.thingsURL) + case "clients": + url = fmt.Sprintf("%s/health", sdk.clientsURL) case "users": url = fmt.Sprintf("%s/health", sdk.usersURL) case "bootstrap": diff --git a/pkg/sdk/go/health_test.go b/pkg/sdk/go/health_test.go index f30cf045df..209f0ca265 100644 --- a/pkg/sdk/go/health_test.go +++ b/pkg/sdk/go/health_test.go @@ -11,20 +11,20 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/bootstrap/api" bmocks "github.com/absmach/magistrala/bootstrap/mocks" + chmocks "github.com/absmach/magistrala/channels/mocks" + climocks "github.com/absmach/magistrala/clients/mocks" mglog "github.com/absmach/magistrala/logger" authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" - authzmocks "github.com/absmach/magistrala/pkg/authz/mocks" "github.com/absmach/magistrala/pkg/errors" sdk "github.com/absmach/magistrala/pkg/sdk/go" readersapi "github.com/absmach/magistrala/readers/api" readersmocks "github.com/absmach/magistrala/readers/mocks" - thmocks "github.com/absmach/magistrala/things/mocks" "github.com/stretchr/testify/assert" ) func TestHealth(t *testing.T) { - thingsTs, _, _ := setupThings() - defer thingsTs.Close() + clientsTs, _, _ := setupClients() + defer clientsTs.Close() usersTs, _, _ := setupUsers() defer usersTs.Close() @@ -42,7 +42,7 @@ func TestHealth(t *testing.T) { defer httpAdapterTs.Close() sdkConf := sdk.Config{ - ThingsURL: thingsTs.URL, + ClientsURL: clientsTs.URL, UsersURL: usersTs.URL, CertsURL: certsTs.URL, BootstrapURL: bootstrapTs.URL, @@ -62,11 +62,11 @@ func TestHealth(t *testing.T) { err errors.SDKError }{ { - desc: "get things service health check", - service: "things", + desc: "get clients service health check", + service: "clients", empty: false, err: nil, - description: "things service", + description: "clients service", status: "pass", }, { @@ -135,10 +135,10 @@ func setupMinimalBootstrap() *httptest.Server { func setupMinimalReader() *httptest.Server { repo := new(readersmocks.MessageRepository) - authz := new(authzmocks.Authorization) + channels := new(chmocks.ChannelsServiceClient) authn := new(authnmocks.Authentication) - things := new(thmocks.ThingsServiceClient) + clients := new(climocks.ClientsServiceClient) - mux := readersapi.MakeHandler(repo, authn, authz, things, "test", "") + mux := readersapi.MakeHandler(repo, authn, clients, channels, "test", "") return httptest.NewServer(mux) } diff --git a/pkg/sdk/go/message.go b/pkg/sdk/go/message.go index 0ff16e8d1e..3018a4ac9f 100644 --- a/pkg/sdk/go/message.go +++ b/pkg/sdk/go/message.go @@ -27,7 +27,7 @@ func (sdk mgSDK) SendMessage(chanName, msg, key string) errors.SDKError { reqURL := fmt.Sprintf("%s/channels/%s/messages%s", sdk.httpAdapterURL, chanID, subtopicPart) - _, _, err := sdk.processRequest(http.MethodPost, reqURL, ThingPrefix+key, []byte(msg), nil, http.StatusAccepted) + _, _, err := sdk.processRequest(http.MethodPost, reqURL, ClientPrefix+key, []byte(msg), nil, http.StatusAccepted) return err } diff --git a/pkg/sdk/go/message_test.go b/pkg/sdk/go/message_test.go index 3f5ad3df6b..c7ffe7bbfb 100644 --- a/pkg/sdk/go/message_test.go +++ b/pkg/sdk/go/message_test.go @@ -9,14 +9,15 @@ import ( "net/http/httptest" "testing" - "github.com/absmach/magistrala" + chmocks "github.com/absmach/magistrala/channels/mocks" + climocks "github.com/absmach/magistrala/clients/mocks" adapter "github.com/absmach/magistrala/http" "github.com/absmach/magistrala/http/api" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" mgauthn "github.com/absmach/magistrala/pkg/authn" authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" - authzmocks "github.com/absmach/magistrala/pkg/authz/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" pubsub "github.com/absmach/magistrala/pkg/messaging/mocks" @@ -25,17 +26,18 @@ import ( "github.com/absmach/magistrala/readers" readersapi "github.com/absmach/magistrala/readers/api" readersmocks "github.com/absmach/magistrala/readers/mocks" - thmocks "github.com/absmach/magistrala/things/mocks" "github.com/absmach/mgate" proxy "github.com/absmach/mgate/pkg/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) -func setupMessages() (*httptest.Server, *thmocks.ThingsServiceClient, *pubsub.PubSub) { - things := new(thmocks.ThingsServiceClient) +func setupMessages() (*httptest.Server, *climocks.ClientsServiceClient, *pubsub.PubSub) { + clients := new(climocks.ClientsServiceClient) + channels := new(chmocks.ChannelsServiceClient) pub := new(pubsub.PubSub) - handler := adapter.NewHandler(pub, mglog.NewMock(), things) + authn := new(authnmocks.Authentication) + handler := adapter.NewHandler(pub, authn, clients, channels, mglog.NewMock()) mux := api.MakeHandler(mglog.NewMock(), "") target := httptest.NewServer(mux) @@ -49,25 +51,25 @@ func setupMessages() (*httptest.Server, *thmocks.ThingsServiceClient, *pubsub.Pu return nil, nil, nil } - return httptest.NewServer(http.HandlerFunc(mp.ServeHTTP)), things, pub + return httptest.NewServer(http.HandlerFunc(mp.ServeHTTP)), clients, pub } -func setupReader() (*httptest.Server, *authzmocks.Authorization, *authnmocks.Authentication, *readersmocks.MessageRepository) { +func setupReader() (*httptest.Server, *authnmocks.Authentication, *readersmocks.MessageRepository) { repo := new(readersmocks.MessageRepository) - authz := new(authzmocks.Authorization) authn := new(authnmocks.Authentication) - things := new(thmocks.ThingsServiceClient) + clients := new(climocks.ClientsServiceClient) + channels := new(chmocks.ChannelsServiceClient) - mux := readersapi.MakeHandler(repo, authn, authz, things, "test", "") - return httptest.NewServer(mux), authz, authn, repo + mux := readersapi.MakeHandler(repo, authn, clients, channels, "test", "") + return httptest.NewServer(mux), authn, repo } func TestSendMessage(t *testing.T) { - ts, things, pub := setupMessages() + ts, clients, pub := setupMessages() defer ts.Close() msg := `[{"n":"current","t":-1,"v":1.6}]` - thingKey := "thingKey" + clientKey := "clientKey" channelID := "channelID" sdkConf := sdk.Config{ @@ -79,81 +81,81 @@ func TestSendMessage(t *testing.T) { mgsdk := sdk.NewSDK(sdkConf) cases := []struct { - desc string - chanName string - msg string - thingKey string - authRes *magistrala.ThingsAuthzRes - authErr error - svcErr error - err errors.SDKError + desc string + chanName string + msg string + clientKey string + authRes *grpcClientsV1.AuthnRes + authErr error + svcErr error + err errors.SDKError }{ { - desc: "publish message successfully", - chanName: channelID, - msg: msg, - thingKey: thingKey, - authRes: &magistrala.ThingsAuthzRes{Authorized: true, Id: ""}, - authErr: nil, - svcErr: nil, - err: nil, + desc: "publish message successfully", + chanName: channelID, + msg: msg, + clientKey: clientKey, + authRes: &grpcClientsV1.AuthnRes{Authenticated: true, Id: ""}, + authErr: nil, + svcErr: nil, + err: nil, }, { - desc: "publish message with empty thing key", - chanName: channelID, - msg: msg, - thingKey: "", - authRes: &magistrala.ThingsAuthzRes{Authorized: false, Id: ""}, - authErr: svcerr.ErrAuthorization, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusBadRequest), + desc: "publish message with empty client key", + chanName: channelID, + msg: msg, + clientKey: "", + authRes: &grpcClientsV1.AuthnRes{Authenticated: false, Id: ""}, + authErr: svcerr.ErrAuthorization, + svcErr: nil, + err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusBadRequest), }, { - desc: "publish message with invalid thing key", - chanName: channelID, - msg: msg, - thingKey: "invalid", - authRes: &magistrala.ThingsAuthzRes{Authorized: false, Id: ""}, - authErr: svcerr.ErrAuthorization, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusBadRequest), + desc: "publish message with invalid client key", + chanName: channelID, + msg: msg, + clientKey: "invalid", + authRes: &grpcClientsV1.AuthnRes{Authenticated: false, Id: ""}, + authErr: svcerr.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, + err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusBadRequest), }, { - desc: "publish message with invalid channel ID", - chanName: wrongID, - msg: msg, - thingKey: thingKey, - authRes: &magistrala.ThingsAuthzRes{Authorized: false, Id: ""}, - authErr: svcerr.ErrAuthorization, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusBadRequest), + desc: "publish message with invalid channel ID", + chanName: wrongID, + msg: msg, + clientKey: clientKey, + authRes: &grpcClientsV1.AuthnRes{Authenticated: false, Id: ""}, + authErr: svcerr.ErrAuthorization, + svcErr: svcerr.ErrAuthorization, + err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusBadRequest), }, { - desc: "publish message with empty message body", - chanName: channelID, - msg: "", - thingKey: thingKey, - authRes: &magistrala.ThingsAuthzRes{Authorized: true, Id: ""}, - authErr: nil, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyMessage), http.StatusBadRequest), + desc: "publish message with empty message body", + chanName: channelID, + msg: "", + clientKey: clientKey, + authRes: &grpcClientsV1.AuthnRes{Authenticated: true, Id: ""}, + authErr: nil, + svcErr: nil, + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyMessage), http.StatusBadRequest), }, { - desc: "publish message with channel subtopic", - chanName: channelID + ".subtopic", - msg: msg, - thingKey: thingKey, - authRes: &magistrala.ThingsAuthzRes{Authorized: true, Id: ""}, - authErr: nil, - svcErr: nil, - err: nil, + desc: "publish message with channel subtopic", + chanName: channelID + ".subtopic", + msg: msg, + clientKey: clientKey, + authRes: &grpcClientsV1.AuthnRes{Authenticated: true, Id: ""}, + authErr: nil, + svcErr: nil, + err: nil, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - authCall := things.On("Authorize", mock.Anything, mock.Anything).Return(tc.authRes, tc.authErr) + authCall := clients.On("Authorize", mock.Anything, mock.Anything).Return(tc.authRes, tc.authErr) svcCall := pub.On("Publish", mock.Anything, channelID, mock.Anything).Return(tc.svcErr) - err := mgsdk.SendMessage(tc.chanName, tc.msg, tc.thingKey) + err := mgsdk.SendMessage(tc.chanName, tc.msg, tc.clientKey) assert.Equal(t, tc.err, err) if tc.err == nil { ok := svcCall.Parent.AssertCalled(t, "Publish", mock.Anything, channelID, mock.Anything) diff --git a/pkg/sdk/go/requests.go b/pkg/sdk/go/requests.go index 21e8f62a7a..087aff13f3 100644 --- a/pkg/sdk/go/requests.go +++ b/pkg/sdk/go/requests.go @@ -20,7 +20,7 @@ type resetPasswordReq struct { ConfPass string `json:"confirm_password"` } -type updateThingSecretReq struct { +type updateClientSecretReq struct { Secret string `json:"secret,omitempty"` } @@ -37,9 +37,9 @@ type UserPasswordReq struct { Password string `json:"password,omitempty"` } -// Connection contains thing and channel ID that are connected. +// Connection contains client and channel ID that are connected. type Connection struct { - ThingID string `json:"thing_id,omitempty"` + ClientID string `json:"client_id,omitempty"` ChannelID string `json:"channel_id,omitempty"` } diff --git a/pkg/sdk/go/responses.go b/pkg/sdk/go/responses.go index c51f0426f0..e06d32943d 100644 --- a/pkg/sdk/go/responses.go +++ b/pkg/sdk/go/responses.go @@ -9,8 +9,8 @@ import ( "github.com/absmach/magistrala/pkg/transformers/senml" ) -type createThingsRes struct { - Things []Thing `json:"things"` +type createClientsRes struct { + Clients []Client `json:"clients"` } type PageRes struct { @@ -19,9 +19,9 @@ type PageRes struct { Limit uint64 `json:"limit"` } -// ThingsPage contains list of things in a page with proper metadata. -type ThingsPage struct { - Things []Thing `json:"things"` +// ClientsPage contains list of clients in a page with proper metadata. +type ClientsPage struct { + Clients []Client `json:"clients"` PageRes } diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index ab56dfe528..e41045bbb1 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -38,7 +38,7 @@ const ( BearerPrefix = "Bearer " - ThingPrefix = "Thing " + ClientPrefix = "Client " ) // ContentType represents all possible content types. @@ -350,7 +350,7 @@ type SDK interface { // fmt.Println(channels) ListUserGroups(userID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) - // ListUserThings list all things belongs a particular user id. + // ListUserClients list all clients belongs a particular user id. // // example: // pm := sdk.PageMetadata{ @@ -358,9 +358,9 @@ type SDK interface { // Limit: 10, // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "contributor", "editor", "viewer", "guest", "create" // } - // things, _ := sdk.ListUserThings("user_id_1", pm, "token") - // fmt.Println(things) - ListUserThings(userID string, pm PageMetadata, token string) (ThingsPage, errors.SDKError) + // clients, _ := sdk.ListUserClients("user_id_1", pm, "token") + // fmt.Println(clients) + ListUserClients(userID string, pm PageMetadata, token string) (ClientsPage, errors.SDKError) // SeachUsers filters users and returns a page result. // @@ -374,147 +374,147 @@ type SDK interface { // fmt.Println(users) SearchUsers(pm PageMetadata, token string) (UsersPage, errors.SDKError) - // CreateThing registers new thing and returns its id. + // CreateClient registers new client and returns its id. // // example: - // thing := sdk.Thing{ - // Name: "My Thing", + // client := sdk.Client{ + // Name: "My Client", // Metadata: sdk.Metadata{"domain_1" // "key": "value", // }, // } - // thing, _ := sdk.CreateThing(thing, "domainID", "token") - // fmt.Println(thing) - CreateThing(thing Thing, domainID, token string) (Thing, errors.SDKError) + // client, _ := sdk.CreateClient(client, "domainID", "token") + // fmt.Println(client) + CreateClient(client Client, domainID, token string) (Client, errors.SDKError) - // CreateThings registers new things and returns their ids. + // CreateClients registers new clients and returns their ids. // // example: - // things := []sdk.Thing{ + // clients := []sdk.Client{ // { - // Name: "My Thing 1", + // Name: "My Client 1", // Metadata: sdk.Metadata{ // "key": "value", // }, // }, // { - // Name: "My Thing 2", + // Name: "My Client 2", // Metadata: sdk.Metadata{ // "key": "value", // }, // }, // } - // things, _ := sdk.CreateThings(things, "domainID", "token") - // fmt.Println(things) - CreateThings(things []Thing, domainID, token string) ([]Thing, errors.SDKError) + // clients, _ := sdk.CreateClients(clients, "domainID", "token") + // fmt.Println(clients) + CreateClients(client []Client, domainID, token string) ([]Client, errors.SDKError) - // Filters things and returns a page result. + // Filters clients and returns a page result. // // example: // pm := sdk.PageMetadata{ // Offset: 0, // Limit: 10, - // Name: "My Thing", + // Name: "My Client", // } - // things, _ := sdk.Things(pm, "domainID", "token") - // fmt.Println(things) - Things(pm PageMetadata, domainID, token string) (ThingsPage, errors.SDKError) + // clients, _ := sdk.Clients(pm, "domainID", "token") + // fmt.Println(clients) + Clients(pm PageMetadata, domainID, token string) (ClientsPage, errors.SDKError) - // ThingsByChannel returns page of things that are connected to specified channel. + // ClientByChannel returns page of clients that are connected to specified channel. // // example: // pm := sdk.PageMetadata{ // Offset: 0, // Limit: 10, - // Name: "My Thing", + // Name: "My Client", // } - // things, _ := sdk.ThingsByChannel("channelID", pm, "domainID", "token") - // fmt.Println(things) - ThingsByChannel(chanID string, pm PageMetadata, domainID, token string) (ThingsPage, errors.SDKError) + // clients, _ := sdk.ClientsByChannel("channelID", pm, "domainID", "token") + // fmt.Println(clients) + ClientsByChannel(chanID string, pm PageMetadata, domainID, token string) (ClientsPage, errors.SDKError) - // Thing returns thing object by id. + // Client returns client object by id. // // example: - // thing, _ := sdk.Thing("thingID", "domainID", "token") - // fmt.Println(thing) - Thing(id, domainID, token string) (Thing, errors.SDKError) + // client, _ := sdk.Client("clientID", "domainID", "token") + // fmt.Println(client) + Client(id, domainID, token string) (Client, errors.SDKError) - // ThingPermissions returns user permissions on the thing id. + // ClientPermissions returns user permissions on the client id. // // example: - // thing, _ := sdk.Thing("thingID", "domainID", "token") - // fmt.Println(thing) - ThingPermissions(id, domainID, token string) (Thing, errors.SDKError) + // client, _ := sdk.Client("clientID", "domainID", "token") + // fmt.Println(client) + ClientPermissions(id, domainID, token string) (Client, errors.SDKError) - // UpdateThing updates existing thing. + // UpdateClient updates existing client. // // example: - // thing := sdk.Thing{ - // ID: "thingID", - // Name: "My Thing", + // client := sdk.Client{ + // ID: "clientID", + // Name: "My Client", // Metadata: sdk.Metadata{ // "key": "value", // }, // } - // thing, _ := sdk.UpdateThing(thing, "domainID", "token") - // fmt.Println(thing) - UpdateThing(thing Thing, domainID, token string) (Thing, errors.SDKError) + // client, _ := sdk.UpdateClient(client, "domainID", "token") + // fmt.Println(client) + UpdateClient(client Client, domainID, token string) (Client, errors.SDKError) - // UpdateThingTags updates the client's tags. + // UpdateClientTags updates the client's tags. // // example: - // thing := sdk.Thing{ - // ID: "thingID", + // client := sdk.Client{ + // ID: "clientID", // Tags: []string{"tag1", "tag2"}, // } - // thing, _ := sdk.UpdateThingTags(thing, "domainID", "token") - // fmt.Println(thing) - UpdateThingTags(thing Thing, domainID, token string) (Thing, errors.SDKError) + // client, _ := sdk.UpdateClientTags(client, "domainID", "token") + // fmt.Println(client) + UpdateClientTags(client Client, domainID, token string) (Client, errors.SDKError) - // UpdateThingSecret updates the client's secret + // UpdateClientSecret updates the client's secret // // example: - // thing, err := sdk.UpdateThingSecret("thingID", "newSecret", "domainID," "token") - // fmt.Println(thing) - UpdateThingSecret(id, secret, domainID, token string) (Thing, errors.SDKError) + // client, err := sdk.UpdateClientSecret("clientID", "newSecret", "domainID," "token") + // fmt.Println(client) + UpdateClientSecret(id, secret, domainID, token string) (Client, errors.SDKError) - // EnableThing changes client status to enabled. + // EnableClient changes client status to enabled. // // example: - // thing, _ := sdk.EnableThing("thingID", "domainID", "token") - // fmt.Println(thing) - EnableThing(id, domainID, token string) (Thing, errors.SDKError) + // client, _ := sdk.EnableClient("clientID", "domainID", "token") + // fmt.Println(client) + EnableClient(id, domainID, token string) (Client, errors.SDKError) - // DisableThing changes client status to disabled - soft delete. + // DisableClient changes client status to disabled - soft delete. // // example: - // thing, _ := sdk.DisableThing("thingID", "domainID", "token") - // fmt.Println(thing) - DisableThing(id, domainID, token string) (Thing, errors.SDKError) + // client, _ := sdk.DisableClient("clientID", "domainID", "token") + // fmt.Println(client) + DisableClient(id, domainID, token string) (Client, errors.SDKError) - // ShareThing shares thing with other users. + // ShareClient shares client with other users. // // example: // req := sdk.UsersRelationRequest{ // Relation: "contributor", // available options: "owner", "admin", "editor", "contributor", "guest" // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] // } - // err := sdk.ShareThing("thing_id", req, "domainID","token") + // err := sdk.ShareClient("client_id", req, "domainID","token") // fmt.Println(err) - ShareThing(thingID string, req UsersRelationRequest, domainID, token string) errors.SDKError + ShareClient(id string, req UsersRelationRequest, domainID, token string) errors.SDKError - // UnshareThing unshare a thing with other users. + // UnshareClient unshare a client with other users. // // example: // req := sdk.UsersRelationRequest{ // Relation: "contributor", // available options: "owner", "admin", "editor", "contributor", "guest" // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] // } - // err := sdk.UnshareThing("thing_id", req, "domainID", "token") + // err := sdk.UnshareClient("client_id", req, "domainID", "token") // fmt.Println(err) - UnshareThing(thingID string, req UsersRelationRequest, domainID, token string) errors.SDKError + UnshareClient(id string, req UsersRelationRequest, domainID, token string) errors.SDKError - // ListThingUsers all users in a thing. + // ListClientUsers all users in a client. // // example: // pm := sdk.PageMetadata{ @@ -522,16 +522,16 @@ type SDK interface { // Limit: 10, // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "contributor", "editor", "viewer", "guest", "create" // } - // users, _ := sdk.ListThingUsers("thing_id", pm, "domainID", "token") + // users, _ := sdk.ListClientUsers("client_id", pm, "domainID", "token") // fmt.Println(users) - ListThingUsers(thingID string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) + ListClientUsers(id string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) - // DeleteThing deletes a thing with the given id. + // DeleteClient deletes a client with the given id. // // example: - // err := sdk.DeleteThing("thingID", "domainID", "token") + // err := sdk.DeleteClient("clientID", "domainID", "token") // fmt.Println(err) - DeleteThing(id, domainID, token string) errors.SDKError + DeleteClient(id, domainID, token string) errors.SDKError // CreateGroup creates new group and returns its id. // @@ -702,7 +702,7 @@ type SDK interface { // fmt.Println(channels) Channels(pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) - // ChannelsByThing returns page of channels that are connected to specified thing. + // ChannelsByClient returns page of channels that are connected to specified client. // // example: // pm := sdk.PageMetadata{ @@ -710,9 +710,9 @@ type SDK interface { // Limit: 10, // Name: "My Channel", // } - // channels, _ := sdk.ChannelsByThing("thingID", pm, "domainID" "token") + // channels, _ := sdk.ChannelsByClient("clientID", pm, "domainID" "token") // fmt.Println(channels) - ChannelsByThing(thingID string, pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) + ChannelsByClient(clientID string, pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) // Channel returns channel data by id. // @@ -829,12 +829,12 @@ type SDK interface { // fmt.Println(err) DeleteChannel(id, domainID, token string) errors.SDKError - // Connect bulk connects things to channels specified by id. + // Connect bulk connects clients to channels specified by id. // // example: // conns := sdk.Connection{ // ChannelID: "channel_id_1", - // ThingID: "thing_id_1", + // ClientID: "client_id_1", // } // err := sdk.Connect(conns, "domainID", "token") // fmt.Println(err) @@ -845,35 +845,35 @@ type SDK interface { // example: // conns := sdk.Connection{ // ChannelID: "channel_id_1", - // ThingID: "thing_id_1", + // ClientID: "client_id_1", // } // err := sdk.Disconnect(conns, "domainID", "token") // fmt.Println(err) Disconnect(connIDs Connection, domainID, token string) errors.SDKError - // ConnectThing connects thing to specified channel by id. + // ConnectClient connects client to specified channel by id. // - // The `ConnectThing` method calls the `CreateThingPolicy` method under the hood. + // The `ConnectClient` method calls the `CreateClientPolicy` method under the hood. // // example: - // err := sdk.ConnectThing("thingID", "channelID", "token") + // err := sdk.ConnectClient("clientID", "channelID", "token") // fmt.Println(err) - ConnectThing(thingID, chanID, domainID, token string) errors.SDKError + ConnectClient(clientID, chanID, domainID, token string) errors.SDKError - // DisconnectThing disconnect thing from specified channel by id. + // DisconnectClient disconnect client from specified channel by id. // - // The `DisconnectThing` method calls the `DeleteThingPolicy` method under the hood. + // The `DisconnectClient` method calls the `DeleteClientPolicy` method under the hood. // // example: - // err := sdk.DisconnectThing("thingID", "channelID", "token") + // err := sdk.DisconnectClient("clientID", "channelID", "token") // fmt.Println(err) - DisconnectThing(thingID, chanID, domainID, token string) errors.SDKError + DisconnectClient(clientID, chanID, domainID, token string) errors.SDKError // SendMessage send message to specified channel. // // example: // msg := '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' - // err := sdk.SendMessage("channelID", msg, "thingSecret") + // err := sdk.SendMessage("channelID", msg, "clientSecret") // fmt.Println(err) SendMessage(chanID, msg, key string) errors.SDKError @@ -906,7 +906,7 @@ type SDK interface { // // example: // cfg := sdk.BootstrapConfig{ - // ThingID: "thingID", + // ClientID: "clientID", // Name: "bootstrap", // ExternalID: "externalID", // ExternalKey: "externalKey", @@ -916,7 +916,7 @@ type SDK interface { // fmt.Println(id) AddBootstrap(cfg BootstrapConfig, domainID, token string) (string, errors.SDKError) - // View returns Thing Config with given ID belonging to the user identified by the given token. + // View returns Client Config with given ID belonging to the user identified by the given token. // // example: // bootstrap, _ := sdk.ViewBootstrap("id", "domainID", "token") @@ -927,7 +927,7 @@ type SDK interface { // // example: // cfg := sdk.BootstrapConfig{ - // ThingID: "thingID", + // ClientID: "clientID", // Name: "bootstrap", // ExternalID: "externalID", // ExternalKey: "externalKey", @@ -944,7 +944,7 @@ type SDK interface { // fmt.Println(err) UpdateBootstrapCerts(id string, clientCert, clientKey, ca string, domainID, token string) (BootstrapConfig, errors.SDKError) - // UpdateBootstrapConnection updates connections performs update of the channel list corresponding Thing is connected to. + // UpdateBootstrapConnection updates connections performs update of the channel list corresponding Client is connected to. // // example: // err := sdk.UpdateBootstrapConnection("id", []string{"channel1", "channel2"}, "domainID", "token") @@ -958,7 +958,7 @@ type SDK interface { // fmt.Println(err) RemoveBootstrap(id, domainID, token string) errors.SDKError - // Bootstrap returns Config to the Thing with provided external ID using external key. + // Bootstrap returns Config to the Client with provided external ID using external key. // // example: // bootstrap, _ := sdk.Bootstrap("externalID", "externalKey") @@ -983,19 +983,19 @@ type SDK interface { // fmt.Println(bootstraps) Bootstraps(pm PageMetadata, domainID, token string) (BootstrapPage, errors.SDKError) - // Whitelist updates Thing state Config with given ID belonging to the user identified by the given token. + // Whitelist updates Client state Config with given ID belonging to the user identified by the given token. // // example: - // err := sdk.Whitelist("thingID", 1, "domainID", "token") + // err := sdk.Whitelist("clientID", 1, "domainID", "token") // fmt.Println(err) - Whitelist(thingID string, state int, domainID, token string) errors.SDKError + Whitelist(clientID string, state int, domainID, token string) errors.SDKError - // IssueCert issues a certificate for a thing required for mTLS. + // IssueCert issues a certificate for a client required for mTLS. // // example: - // cert, _ := sdk.IssueCert("thingID", "24h", "domainID", "token") + // cert, _ := sdk.IssueCert("clientID", "24h", "domainID", "token") // fmt.Println(cert) - IssueCert(thingID, validity, domainID, token string) (Cert, errors.SDKError) + IssueCert(clientID, validity, domainID, token string) (Cert, errors.SDKError) // ViewCert returns a certificate given certificate ID // @@ -1004,19 +1004,19 @@ type SDK interface { // fmt.Println(cert) ViewCert(certID, domainID, token string) (Cert, errors.SDKError) - // ViewCertByThing retrieves a list of certificates' serial IDs for a given thing ID. + // ViewCertByClient retrieves a list of certificates' serial IDs for a given client ID. // // example: - // cserial, _ := sdk.ViewCertByThing("thingID", "domainID", "token") + // cserial, _ := sdk.ViewCertByClient("clientID", "domainID", "token") // fmt.Println(cserial) - ViewCertByThing(thingID, domainID, token string) (CertSerials, errors.SDKError) + ViewCertByClient(clientID, domainID, token string) (CertSerials, errors.SDKError) - // RevokeCert revokes certificate for thing with thingID + // RevokeCert revokes certificate for client with clientID // // example: - // tm, _ := sdk.RevokeCert("thingID", "domainID", "token") + // tm, _ := sdk.RevokeCert("clientID", "domainID", "token") // fmt.Println(tm) - RevokeCert(thingID, domainID, token string) (time.Time, errors.SDKError) + RevokeCert(clientID, domainID, token string) (time.Time, errors.SDKError) // CreateSubscription creates a new subscription // @@ -1210,7 +1210,7 @@ type SDK interface { // Journal returns a list of journal logs. // // For example: - // journals, _ := sdk.Journal("thing", "thingID","domainID", PageMetadata{Offset: 0, Limit: 10, Operation: "thing.create"}, "token") + // journals, _ := sdk.Journal("client", "clientID","domainID", PageMetadata{Offset: 0, Limit: 10, Operation: "thing.create"}, "token") // fmt.Println(journals) Journal(entityType, entityID, domainID string, pm PageMetadata, token string) (journal JournalsPage, err error) } @@ -1220,7 +1220,7 @@ type mgSDK struct { certsURL string httpAdapterURL string readerURL string - thingsURL string + clientsURL string usersURL string domainsURL string invitationsURL string @@ -1238,7 +1238,7 @@ type Config struct { CertsURL string HTTPAdapterURL string ReaderURL string - ThingsURL string + ClientsURL string UsersURL string DomainsURL string InvitationsURL string @@ -1257,7 +1257,7 @@ func NewSDK(conf Config) SDK { certsURL: conf.CertsURL, httpAdapterURL: conf.HTTPAdapterURL, readerURL: conf.ReaderURL, - thingsURL: conf.ThingsURL, + clientsURL: conf.ClientsURL, usersURL: conf.UsersURL, domainsURL: conf.DomainsURL, invitationsURL: conf.InvitationsURL, @@ -1293,7 +1293,7 @@ func (sdk mgSDK) processRequest(method, reqUrl, token string, data []byte, heade } if token != "" { - if !strings.Contains(token, ThingPrefix) { + if !strings.Contains(token, ClientPrefix) { token = BearerPrefix + token } req.Header.Set("Authorization", token) diff --git a/pkg/sdk/go/setup_test.go b/pkg/sdk/go/setup_test.go index be8b586cf2..d76b4ad893 100644 --- a/pkg/sdk/go/setup_test.go +++ b/pkg/sdk/go/setup_test.go @@ -10,13 +10,14 @@ import ( "testing" "time" + mgchannels "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/clients" + mggroups "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/invitations" "github.com/absmach/magistrala/journal" - mggroups "github.com/absmach/magistrala/pkg/groups" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/pkg/uuid" - "github.com/absmach/magistrala/things" "github.com/absmach/magistrala/users" "github.com/stretchr/testify/assert" ) @@ -64,11 +65,11 @@ func convertUsers(cs []sdk.User) []users.User { return ccs } -func convertThings(cs ...sdk.Thing) []things.Client { - ccs := []things.Client{} +func convertClients(cs ...sdk.Client) []clients.Client { + ccs := []clients.Client{} for _, c := range cs { - ccs = append(ccs, convertThing(c)) + ccs = append(ccs, convertClient(c)) } return ccs @@ -84,14 +85,14 @@ func convertGroups(cs []sdk.Group) []mggroups.Group { return cgs } -func convertChannels(cs []sdk.Channel) []mggroups.Group { - cgs := []mggroups.Group{} +func convertChannels(cs []sdk.Channel) []mgchannels.Channel { + chs := []mgchannels.Channel{} for _, c := range cs { - cgs = append(cgs, convertChannel(c)) + chs = append(chs, convertChannel(c)) } - return cgs + return chs } func convertGroup(g sdk.Group) mggroups.Group { @@ -162,44 +163,41 @@ func convertUser(c sdk.User) users.User { } } -func convertThing(c sdk.Thing) things.Client { +func convertClient(c sdk.Client) clients.Client { if c.Status == "" { - c.Status = things.EnabledStatus.String() + c.Status = clients.EnabledStatus.String() } - status, err := things.ToStatus(c.Status) + status, err := clients.ToStatus(c.Status) if err != nil { - return things.Client{} + return clients.Client{} } - return things.Client{ + return clients.Client{ ID: c.ID, Name: c.Name, Tags: c.Tags, Domain: c.DomainID, - Credentials: things.Credentials(c.Credentials), - Metadata: things.Metadata(c.Metadata), + Credentials: clients.Credentials(c.Credentials), + Metadata: clients.Metadata(c.Metadata), CreatedAt: c.CreatedAt, UpdatedAt: c.UpdatedAt, Status: status, } } -func convertChannel(g sdk.Channel) mggroups.Group { +func convertChannel(g sdk.Channel) mgchannels.Channel { if g.Status == "" { - g.Status = mggroups.EnabledStatus.String() + g.Status = clients.EnabledStatus.String() } - status, err := mggroups.ToStatus(g.Status) + status, err := clients.ToStatus(g.Status) if err != nil { - return mggroups.Group{} + return mgchannels.Channel{} } - return mggroups.Group{ + return mgchannels.Channel{ ID: g.ID, Domain: g.DomainID, - Parent: g.ParentID, + ParentGroup: g.ParentID, Name: g.Name, - Description: g.Description, - Metadata: mggroups.Metadata(g.Metadata), - Level: g.Level, - Path: g.Path, + Metadata: clients.Metadata(g.Metadata), CreatedAt: g.CreatedAt, UpdatedAt: g.UpdatedAt, Status: status, diff --git a/pkg/sdk/go/things.go b/pkg/sdk/go/things.go deleted file mode 100644 index a8cd234ff2..0000000000 --- a/pkg/sdk/go/things.go +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" -) - -const ( - permissionsEndpoint = "permissions" - thingsEndpoint = "things" - connectEndpoint = "connect" - disconnectEndpoint = "disconnect" - identifyEndpoint = "identify" - shareEndpoint = "share" - unshareEndpoint = "unshare" -) - -// Thing represents magistrala thing. -type Thing struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Credentials ClientCredentials `json:"credentials"` - Tags []string `json:"tags,omitempty"` - DomainID string `json:"domain_id,omitempty"` - Metadata map[string]interface{} `json:"metadata,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - Status string `json:"status,omitempty"` - Permissions []string `json:"permissions,omitempty"` -} - -type ClientCredentials struct { - Identity string `json:"identity,omitempty"` - Secret string `json:"secret,omitempty"` -} - -func (sdk mgSDK) CreateThing(thing Thing, domainID, token string) (Thing, errors.SDKError) { - data, err := json.Marshal(thing) - if err != nil { - return Thing{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return Thing{}, sdkerr - } - - thing = Thing{} - if err := json.Unmarshal(body, &thing); err != nil { - return Thing{}, errors.NewSDKError(err) - } - - return thing, nil -} - -func (sdk mgSDK) CreateThings(things []Thing, domainID, token string) ([]Thing, errors.SDKError) { - data, err := json.Marshal(things) - if err != nil { - return []Thing{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint, "bulk") - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return []Thing{}, sdkerr - } - - var ctr createThingsRes - if err := json.Unmarshal(body, &ctr); err != nil { - return []Thing{}, errors.NewSDKError(err) - } - - return ctr.Things, nil -} - -func (sdk mgSDK) Things(pm PageMetadata, domainID, token string) (ThingsPage, errors.SDKError) { - endpoint := fmt.Sprintf("%s/%s", domainID, thingsEndpoint) - url, err := sdk.withQueryParams(sdk.thingsURL, endpoint, pm) - if err != nil { - return ThingsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return ThingsPage{}, sdkerr - } - - var cp ThingsPage - if err := json.Unmarshal(body, &cp); err != nil { - return ThingsPage{}, errors.NewSDKError(err) - } - - return cp, nil -} - -func (sdk mgSDK) ThingsByChannel(chanID string, pm PageMetadata, domainID, token string) (ThingsPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/channels/%s/%s", domainID, chanID, thingsEndpoint), pm) - if err != nil { - return ThingsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return ThingsPage{}, sdkerr - } - - var tp ThingsPage - if err := json.Unmarshal(body, &tp); err != nil { - return ThingsPage{}, errors.NewSDKError(err) - } - - return tp, nil -} - -func (sdk mgSDK) Thing(id, domainID, token string) (Thing, errors.SDKError) { - if id == "" { - return Thing{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint, id) - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return Thing{}, sdkerr - } - - var t Thing - if err := json.Unmarshal(body, &t); err != nil { - return Thing{}, errors.NewSDKError(err) - } - - return t, nil -} - -func (sdk mgSDK) ThingPermissions(id, domainID, token string) (Thing, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint, id, permissionsEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return Thing{}, sdkerr - } - - var t Thing - if err := json.Unmarshal(body, &t); err != nil { - return Thing{}, errors.NewSDKError(err) - } - - return t, nil -} - -func (sdk mgSDK) UpdateThing(t Thing, domainID, token string) (Thing, errors.SDKError) { - if t.ID == "" { - return Thing{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint, t.ID) - - data, err := json.Marshal(t) - if err != nil { - return Thing{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return Thing{}, sdkerr - } - - t = Thing{} - if err := json.Unmarshal(body, &t); err != nil { - return Thing{}, errors.NewSDKError(err) - } - - return t, nil -} - -func (sdk mgSDK) UpdateThingTags(t Thing, domainID, token string) (Thing, errors.SDKError) { - data, err := json.Marshal(t) - if err != nil { - return Thing{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s/tags", sdk.thingsURL, domainID, thingsEndpoint, t.ID) - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return Thing{}, sdkerr - } - - t = Thing{} - if err := json.Unmarshal(body, &t); err != nil { - return Thing{}, errors.NewSDKError(err) - } - - return t, nil -} - -func (sdk mgSDK) UpdateThingSecret(id, secret, domainID, token string) (Thing, errors.SDKError) { - ucsr := updateThingSecretReq{Secret: secret} - - data, err := json.Marshal(ucsr) - if err != nil { - return Thing{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s/secret", sdk.thingsURL, domainID, thingsEndpoint, id) - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return Thing{}, sdkerr - } - - var t Thing - if err = json.Unmarshal(body, &t); err != nil { - return Thing{}, errors.NewSDKError(err) - } - - return t, nil -} - -func (sdk mgSDK) EnableThing(id, domainID, token string) (Thing, errors.SDKError) { - return sdk.changeThingStatus(id, enableEndpoint, domainID, token) -} - -func (sdk mgSDK) DisableThing(id, domainID, token string) (Thing, errors.SDKError) { - return sdk.changeThingStatus(id, disableEndpoint, domainID, token) -} - -func (sdk mgSDK) changeThingStatus(id, status, domainID, token string) (Thing, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint, id, status) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return Thing{}, sdkerr - } - - t := Thing{} - if err := json.Unmarshal(body, &t); err != nil { - return Thing{}, errors.NewSDKError(err) - } - - return t, nil -} - -func (sdk mgSDK) ShareThing(thingID string, req UsersRelationRequest, domainID, token string) errors.SDKError { - data, err := json.Marshal(req) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint, thingID, shareEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - return sdkerr -} - -func (sdk mgSDK) UnshareThing(thingID string, req UsersRelationRequest, domainID, token string) errors.SDKError { - data, err := json.Marshal(req) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint, thingID, unshareEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - return sdkerr -} - -func (sdk mgSDK) ListThingUsers(thingID string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", domainID, thingsEndpoint, thingID, usersEndpoint), pm) - if err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return UsersPage{}, sdkerr - } - up := UsersPage{} - if err := json.Unmarshal(body, &up); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return up, nil -} - -func (sdk mgSDK) DeleteThing(id, domainID, token string) errors.SDKError { - if id == "" { - return errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, domainID, thingsEndpoint, id) - _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - return sdkerr -} diff --git a/pkg/sdk/go/tokens_test.go b/pkg/sdk/go/tokens_test.go index 809d45367a..912053af9e 100644 --- a/pkg/sdk/go/tokens_test.go +++ b/pkg/sdk/go/tokens_test.go @@ -7,8 +7,8 @@ import ( "net/http" "testing" - "github.com/absmach/magistrala" mgauth "github.com/absmach/magistrala/auth" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/apiutil" mgauthn "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" @@ -33,7 +33,7 @@ func TestIssueToken(t *testing.T) { cases := []struct { desc string login sdk.Login - svcRes *magistrala.Token + svcRes *grpcTokenV1.Token svcErr error response sdk.Token err errors.SDKError @@ -44,7 +44,7 @@ func TestIssueToken(t *testing.T) { Identity: client.Credentials.Username, Secret: client.Credentials.Secret, }, - svcRes: &magistrala.Token{ + svcRes: &grpcTokenV1.Token{ AccessToken: token.AccessToken, RefreshToken: &token.RefreshToken, AccessType: mgauth.AccessKey.String(), @@ -59,7 +59,7 @@ func TestIssueToken(t *testing.T) { Identity: invalidIdentity, Secret: client.Credentials.Secret, }, - svcRes: &magistrala.Token{}, + svcRes: &grpcTokenV1.Token{}, svcErr: svcerr.ErrAuthentication, response: sdk.Token{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), @@ -70,7 +70,7 @@ func TestIssueToken(t *testing.T) { Identity: client.Credentials.Username, Secret: "invalid", }, - svcRes: &magistrala.Token{}, + svcRes: &grpcTokenV1.Token{}, svcErr: svcerr.ErrLogin, response: sdk.Token{}, err: errors.NewSDKErrorWithStatus(svcerr.ErrLogin, http.StatusUnauthorized), @@ -81,7 +81,7 @@ func TestIssueToken(t *testing.T) { Identity: "", Secret: client.Credentials.Secret, }, - svcRes: &magistrala.Token{}, + svcRes: &grpcTokenV1.Token{}, svcErr: nil, response: sdk.Token{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusBadRequest), @@ -92,7 +92,7 @@ func TestIssueToken(t *testing.T) { Identity: client.Credentials.Username, Secret: "", }, - svcRes: &magistrala.Token{}, + svcRes: &grpcTokenV1.Token{}, svcErr: nil, response: sdk.Token{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPass), http.StatusBadRequest), @@ -127,7 +127,7 @@ func TestRefreshToken(t *testing.T) { cases := []struct { desc string token string - svcRes *magistrala.Token + svcRes *grpcTokenV1.Token svcErr error identifyErr error response sdk.Token @@ -136,7 +136,7 @@ func TestRefreshToken(t *testing.T) { { desc: "refresh token successfully", token: token.RefreshToken, - svcRes: &magistrala.Token{ + svcRes: &grpcTokenV1.Token{ AccessToken: token.AccessToken, RefreshToken: &token.RefreshToken, AccessType: token.AccessType, diff --git a/pkg/sdk/go/users.go b/pkg/sdk/go/users.go index 125b8c13df..219a106f78 100644 --- a/pkg/sdk/go/users.go +++ b/pkg/sdk/go/users.go @@ -322,7 +322,7 @@ func (sdk mgSDK) UpdateProfilePicture(user User, token string) (User, errors.SDK } func (sdk mgSDK) ListUserChannels(userID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s/%s", pm.DomainID, usersEndpoint, userID, channelsEndpoint), pm) + url, err := sdk.withQueryParams(sdk.clientsURL, fmt.Sprintf("%s/%s/%s/%s", pm.DomainID, usersEndpoint, userID, channelsEndpoint), pm) if err != nil { return ChannelsPage{}, errors.NewSDKError(err) } @@ -356,18 +356,18 @@ func (sdk mgSDK) ListUserGroups(userID string, pm PageMetadata, token string) (G return gp, nil } -func (sdk mgSDK) ListUserThings(userID string, pm PageMetadata, token string) (ThingsPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.thingsURL, fmt.Sprintf("%s/%s/%s/%s", pm.DomainID, usersEndpoint, userID, thingsEndpoint), pm) +func (sdk mgSDK) ListUserClients(userID string, pm PageMetadata, token string) (ClientsPage, errors.SDKError) { + url, err := sdk.withQueryParams(sdk.clientsURL, fmt.Sprintf("%s/%s/%s/%s", pm.DomainID, usersEndpoint, userID, clientsEndpoint), pm) if err != nil { - return ThingsPage{}, errors.NewSDKError(err) + return ClientsPage{}, errors.NewSDKError(err) } _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) if sdkerr != nil { - return ThingsPage{}, sdkerr + return ClientsPage{}, sdkerr } - tp := ThingsPage{} + tp := ClientsPage{} if err := json.Unmarshal(body, &tp); err != nil { - return ThingsPage{}, errors.NewSDKError(err) + return ClientsPage{}, errors.NewSDKError(err) } return tp, nil diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 7150005318..34a946a471 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -12,6 +12,8 @@ import ( "github.com/absmach/magistrala" authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/groups" + gmocks "github.com/absmach/magistrala/groups/mocks" internalapi "github.com/absmach/magistrala/internal/api" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" @@ -20,8 +22,6 @@ import ( authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" - gmocks "github.com/absmach/magistrala/pkg/groups/mocks" oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" policies "github.com/absmach/magistrala/pkg/policies" sdk "github.com/absmach/magistrala/pkg/sdk/go" diff --git a/pkg/sdk/mocks/sdk.go b/pkg/sdk/mocks/sdk.go index 6e3d21d02d..c5569d49e7 100644 --- a/pkg/sdk/mocks/sdk.go +++ b/pkg/sdk/mocks/sdk.go @@ -326,27 +326,27 @@ func (_m *SDK) Channels(pm sdk.PageMetadata, domainID string, token string) (sdk return r0, r1 } -// ChannelsByThing provides a mock function with given fields: thingID, pm, domainID, token -func (_m *SDK) ChannelsByThing(thingID string, pm sdk.PageMetadata, domainID string, token string) (sdk.ChannelsPage, errors.SDKError) { - ret := _m.Called(thingID, pm, domainID, token) +// ChannelsByClient provides a mock function with given fields: clientID, pm, domainID, token +func (_m *SDK) ChannelsByClient(clientID string, pm sdk.PageMetadata, domainID string, token string) (sdk.ChannelsPage, errors.SDKError) { + ret := _m.Called(clientID, pm, domainID, token) if len(ret) == 0 { - panic("no return value specified for ChannelsByThing") + panic("no return value specified for ChannelsByClient") } var r0 sdk.ChannelsPage var r1 errors.SDKError if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.ChannelsPage, errors.SDKError)); ok { - return rf(thingID, pm, domainID, token) + return rf(clientID, pm, domainID, token) } if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.ChannelsPage); ok { - r0 = rf(thingID, pm, domainID, token) + r0 = rf(clientID, pm, domainID, token) } else { r0 = ret.Get(0).(sdk.ChannelsPage) } if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string, string) errors.SDKError); ok { - r1 = rf(thingID, pm, domainID, token) + r1 = rf(clientID, pm, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -386,6 +386,126 @@ func (_m *SDK) Children(id string, pm sdk.PageMetadata, domainID string, token s return r0, r1 } +// Client provides a mock function with given fields: id, domainID, token +func (_m *SDK) Client(id string, domainID string, token string) (sdk.Client, errors.SDKError) { + ret := _m.Called(id, domainID, token) + + if len(ret) == 0 { + panic("no return value specified for Client") + } + + var r0 sdk.Client + var r1 errors.SDKError + if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Client, errors.SDKError)); ok { + return rf(id, domainID, token) + } + if rf, ok := ret.Get(0).(func(string, string, string) sdk.Client); ok { + r0 = rf(id, domainID, token) + } else { + r0 = ret.Get(0).(sdk.Client) + } + + if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { + r1 = rf(id, domainID, token) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.SDKError) + } + } + + return r0, r1 +} + +// ClientPermissions provides a mock function with given fields: id, domainID, token +func (_m *SDK) ClientPermissions(id string, domainID string, token string) (sdk.Client, errors.SDKError) { + ret := _m.Called(id, domainID, token) + + if len(ret) == 0 { + panic("no return value specified for ClientPermissions") + } + + var r0 sdk.Client + var r1 errors.SDKError + if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Client, errors.SDKError)); ok { + return rf(id, domainID, token) + } + if rf, ok := ret.Get(0).(func(string, string, string) sdk.Client); ok { + r0 = rf(id, domainID, token) + } else { + r0 = ret.Get(0).(sdk.Client) + } + + if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { + r1 = rf(id, domainID, token) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.SDKError) + } + } + + return r0, r1 +} + +// Clients provides a mock function with given fields: pm, domainID, token +func (_m *SDK) Clients(pm sdk.PageMetadata, domainID string, token string) (sdk.ClientsPage, errors.SDKError) { + ret := _m.Called(pm, domainID, token) + + if len(ret) == 0 { + panic("no return value specified for Clients") + } + + var r0 sdk.ClientsPage + var r1 errors.SDKError + if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) (sdk.ClientsPage, errors.SDKError)); ok { + return rf(pm, domainID, token) + } + if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) sdk.ClientsPage); ok { + r0 = rf(pm, domainID, token) + } else { + r0 = ret.Get(0).(sdk.ClientsPage) + } + + if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string, string) errors.SDKError); ok { + r1 = rf(pm, domainID, token) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.SDKError) + } + } + + return r0, r1 +} + +// ClientsByChannel provides a mock function with given fields: chanID, pm, domainID, token +func (_m *SDK) ClientsByChannel(chanID string, pm sdk.PageMetadata, domainID string, token string) (sdk.ClientsPage, errors.SDKError) { + ret := _m.Called(chanID, pm, domainID, token) + + if len(ret) == 0 { + panic("no return value specified for ClientsByChannel") + } + + var r0 sdk.ClientsPage + var r1 errors.SDKError + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.ClientsPage, errors.SDKError)); ok { + return rf(chanID, pm, domainID, token) + } + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.ClientsPage); ok { + r0 = rf(chanID, pm, domainID, token) + } else { + r0 = ret.Get(0).(sdk.ClientsPage) + } + + if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string, string) errors.SDKError); ok { + r1 = rf(chanID, pm, domainID, token) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.SDKError) + } + } + + return r0, r1 +} + // Connect provides a mock function with given fields: conns, domainID, token func (_m *SDK) Connect(conns sdk.Connection, domainID string, token string) errors.SDKError { ret := _m.Called(conns, domainID, token) @@ -406,17 +526,17 @@ func (_m *SDK) Connect(conns sdk.Connection, domainID string, token string) erro return r0 } -// ConnectThing provides a mock function with given fields: thingID, chanID, domainID, token -func (_m *SDK) ConnectThing(thingID string, chanID string, domainID string, token string) errors.SDKError { - ret := _m.Called(thingID, chanID, domainID, token) +// ConnectClient provides a mock function with given fields: clientID, chanID, domainID, token +func (_m *SDK) ConnectClient(clientID string, chanID string, domainID string, token string) errors.SDKError { + ret := _m.Called(clientID, chanID, domainID, token) if len(ret) == 0 { - panic("no return value specified for ConnectThing") + panic("no return value specified for ConnectClient") } var r0 errors.SDKError if rf, ok := ret.Get(0).(func(string, string, string, string) errors.SDKError); ok { - r0 = rf(thingID, chanID, domainID, token) + r0 = rf(clientID, chanID, domainID, token) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(errors.SDKError) @@ -456,27 +576,27 @@ func (_m *SDK) CreateChannel(channel sdk.Channel, domainID string, token string) return r0, r1 } -// CreateDomain provides a mock function with given fields: d, token -func (_m *SDK) CreateDomain(d sdk.Domain, token string) (sdk.Domain, errors.SDKError) { - ret := _m.Called(d, token) +// CreateClient provides a mock function with given fields: client, domainID, token +func (_m *SDK) CreateClient(client sdk.Client, domainID string, token string) (sdk.Client, errors.SDKError) { + ret := _m.Called(client, domainID, token) if len(ret) == 0 { - panic("no return value specified for CreateDomain") + panic("no return value specified for CreateClient") } - var r0 sdk.Domain + var r0 sdk.Client var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Domain, string) (sdk.Domain, errors.SDKError)); ok { - return rf(d, token) + if rf, ok := ret.Get(0).(func(sdk.Client, string, string) (sdk.Client, errors.SDKError)); ok { + return rf(client, domainID, token) } - if rf, ok := ret.Get(0).(func(sdk.Domain, string) sdk.Domain); ok { - r0 = rf(d, token) + if rf, ok := ret.Get(0).(func(sdk.Client, string, string) sdk.Client); ok { + r0 = rf(client, domainID, token) } else { - r0 = ret.Get(0).(sdk.Domain) + r0 = ret.Get(0).(sdk.Client) } - if rf, ok := ret.Get(1).(func(sdk.Domain, string) errors.SDKError); ok { - r1 = rf(d, token) + if rf, ok := ret.Get(1).(func(sdk.Client, string, string) errors.SDKError); ok { + r1 = rf(client, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -486,27 +606,29 @@ func (_m *SDK) CreateDomain(d sdk.Domain, token string) (sdk.Domain, errors.SDKE return r0, r1 } -// CreateGroup provides a mock function with given fields: group, domainID, token -func (_m *SDK) CreateGroup(group sdk.Group, domainID string, token string) (sdk.Group, errors.SDKError) { - ret := _m.Called(group, domainID, token) +// CreateClients provides a mock function with given fields: client, domainID, token +func (_m *SDK) CreateClients(client []sdk.Client, domainID string, token string) ([]sdk.Client, errors.SDKError) { + ret := _m.Called(client, domainID, token) if len(ret) == 0 { - panic("no return value specified for CreateGroup") + panic("no return value specified for CreateClients") } - var r0 sdk.Group + var r0 []sdk.Client var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Group, string, string) (sdk.Group, errors.SDKError)); ok { - return rf(group, domainID, token) + if rf, ok := ret.Get(0).(func([]sdk.Client, string, string) ([]sdk.Client, errors.SDKError)); ok { + return rf(client, domainID, token) } - if rf, ok := ret.Get(0).(func(sdk.Group, string, string) sdk.Group); ok { - r0 = rf(group, domainID, token) + if rf, ok := ret.Get(0).(func([]sdk.Client, string, string) []sdk.Client); ok { + r0 = rf(client, domainID, token) } else { - r0 = ret.Get(0).(sdk.Group) + if ret.Get(0) != nil { + r0 = ret.Get(0).([]sdk.Client) + } } - if rf, ok := ret.Get(1).(func(sdk.Group, string, string) errors.SDKError); ok { - r1 = rf(group, domainID, token) + if rf, ok := ret.Get(1).(func([]sdk.Client, string, string) errors.SDKError); ok { + r1 = rf(client, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -516,27 +638,27 @@ func (_m *SDK) CreateGroup(group sdk.Group, domainID string, token string) (sdk. return r0, r1 } -// CreateSubscription provides a mock function with given fields: topic, contact, token -func (_m *SDK) CreateSubscription(topic string, contact string, token string) (string, errors.SDKError) { - ret := _m.Called(topic, contact, token) +// CreateDomain provides a mock function with given fields: d, token +func (_m *SDK) CreateDomain(d sdk.Domain, token string) (sdk.Domain, errors.SDKError) { + ret := _m.Called(d, token) if len(ret) == 0 { - panic("no return value specified for CreateSubscription") + panic("no return value specified for CreateDomain") } - var r0 string + var r0 sdk.Domain var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (string, errors.SDKError)); ok { - return rf(topic, contact, token) + if rf, ok := ret.Get(0).(func(sdk.Domain, string) (sdk.Domain, errors.SDKError)); ok { + return rf(d, token) } - if rf, ok := ret.Get(0).(func(string, string, string) string); ok { - r0 = rf(topic, contact, token) + if rf, ok := ret.Get(0).(func(sdk.Domain, string) sdk.Domain); ok { + r0 = rf(d, token) } else { - r0 = ret.Get(0).(string) + r0 = ret.Get(0).(sdk.Domain) } - if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(topic, contact, token) + if rf, ok := ret.Get(1).(func(sdk.Domain, string) errors.SDKError); ok { + r1 = rf(d, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -546,27 +668,27 @@ func (_m *SDK) CreateSubscription(topic string, contact string, token string) (s return r0, r1 } -// CreateThing provides a mock function with given fields: thing, domainID, token -func (_m *SDK) CreateThing(thing sdk.Thing, domainID string, token string) (sdk.Thing, errors.SDKError) { - ret := _m.Called(thing, domainID, token) +// CreateGroup provides a mock function with given fields: group, domainID, token +func (_m *SDK) CreateGroup(group sdk.Group, domainID string, token string) (sdk.Group, errors.SDKError) { + ret := _m.Called(group, domainID, token) if len(ret) == 0 { - panic("no return value specified for CreateThing") + panic("no return value specified for CreateGroup") } - var r0 sdk.Thing + var r0 sdk.Group var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Thing, string, string) (sdk.Thing, errors.SDKError)); ok { - return rf(thing, domainID, token) + if rf, ok := ret.Get(0).(func(sdk.Group, string, string) (sdk.Group, errors.SDKError)); ok { + return rf(group, domainID, token) } - if rf, ok := ret.Get(0).(func(sdk.Thing, string, string) sdk.Thing); ok { - r0 = rf(thing, domainID, token) + if rf, ok := ret.Get(0).(func(sdk.Group, string, string) sdk.Group); ok { + r0 = rf(group, domainID, token) } else { - r0 = ret.Get(0).(sdk.Thing) + r0 = ret.Get(0).(sdk.Group) } - if rf, ok := ret.Get(1).(func(sdk.Thing, string, string) errors.SDKError); ok { - r1 = rf(thing, domainID, token) + if rf, ok := ret.Get(1).(func(sdk.Group, string, string) errors.SDKError); ok { + r1 = rf(group, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -576,29 +698,27 @@ func (_m *SDK) CreateThing(thing sdk.Thing, domainID string, token string) (sdk. return r0, r1 } -// CreateThings provides a mock function with given fields: things, domainID, token -func (_m *SDK) CreateThings(things []sdk.Thing, domainID string, token string) ([]sdk.Thing, errors.SDKError) { - ret := _m.Called(things, domainID, token) +// CreateSubscription provides a mock function with given fields: topic, contact, token +func (_m *SDK) CreateSubscription(topic string, contact string, token string) (string, errors.SDKError) { + ret := _m.Called(topic, contact, token) if len(ret) == 0 { - panic("no return value specified for CreateThings") + panic("no return value specified for CreateSubscription") } - var r0 []sdk.Thing + var r0 string var r1 errors.SDKError - if rf, ok := ret.Get(0).(func([]sdk.Thing, string, string) ([]sdk.Thing, errors.SDKError)); ok { - return rf(things, domainID, token) + if rf, ok := ret.Get(0).(func(string, string, string) (string, errors.SDKError)); ok { + return rf(topic, contact, token) } - if rf, ok := ret.Get(0).(func([]sdk.Thing, string, string) []sdk.Thing); ok { - r0 = rf(things, domainID, token) + if rf, ok := ret.Get(0).(func(string, string, string) string); ok { + r0 = rf(topic, contact, token) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]sdk.Thing) - } + r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func([]sdk.Thing, string, string) errors.SDKError); ok { - r1 = rf(things, domainID, token) + if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { + r1 = rf(topic, contact, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -688,6 +808,26 @@ func (_m *SDK) DeleteChannel(id string, domainID string, token string) errors.SD return r0 } +// DeleteClient provides a mock function with given fields: id, domainID, token +func (_m *SDK) DeleteClient(id string, domainID string, token string) errors.SDKError { + ret := _m.Called(id, domainID, token) + + if len(ret) == 0 { + panic("no return value specified for DeleteClient") + } + + var r0 errors.SDKError + if rf, ok := ret.Get(0).(func(string, string, string) errors.SDKError); ok { + r0 = rf(id, domainID, token) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(errors.SDKError) + } + } + + return r0 +} + // DeleteGroup provides a mock function with given fields: id, domainID, token func (_m *SDK) DeleteGroup(id string, domainID string, token string) errors.SDKError { ret := _m.Called(id, domainID, token) @@ -746,26 +886,6 @@ func (_m *SDK) DeleteSubscription(id string, token string) errors.SDKError { return r0 } -// DeleteThing provides a mock function with given fields: id, domainID, token -func (_m *SDK) DeleteThing(id string, domainID string, token string) errors.SDKError { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for DeleteThing") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) errors.SDKError); ok { - r0 = rf(id, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - // DeleteUser provides a mock function with given fields: id, token func (_m *SDK) DeleteUser(id string, token string) errors.SDKError { ret := _m.Called(id, token) @@ -816,6 +936,36 @@ func (_m *SDK) DisableChannel(id string, domainID string, token string) (sdk.Cha return r0, r1 } +// DisableClient provides a mock function with given fields: id, domainID, token +func (_m *SDK) DisableClient(id string, domainID string, token string) (sdk.Client, errors.SDKError) { + ret := _m.Called(id, domainID, token) + + if len(ret) == 0 { + panic("no return value specified for DisableClient") + } + + var r0 sdk.Client + var r1 errors.SDKError + if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Client, errors.SDKError)); ok { + return rf(id, domainID, token) + } + if rf, ok := ret.Get(0).(func(string, string, string) sdk.Client); ok { + r0 = rf(id, domainID, token) + } else { + r0 = ret.Get(0).(sdk.Client) + } + + if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { + r1 = rf(id, domainID, token) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.SDKError) + } + } + + return r0, r1 +} + // DisableDomain provides a mock function with given fields: domainID, token func (_m *SDK) DisableDomain(domainID string, token string) errors.SDKError { ret := _m.Called(domainID, token) @@ -866,36 +1016,6 @@ func (_m *SDK) DisableGroup(id string, domainID string, token string) (sdk.Group return r0, r1 } -// DisableThing provides a mock function with given fields: id, domainID, token -func (_m *SDK) DisableThing(id string, domainID string, token string) (sdk.Thing, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for DisableThing") - } - - var r0 sdk.Thing - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Thing, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Thing); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Thing) - } - - if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(id, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - // DisableUser provides a mock function with given fields: id, token func (_m *SDK) DisableUser(id string, token string) (sdk.User, errors.SDKError) { ret := _m.Called(id, token) @@ -946,17 +1066,17 @@ func (_m *SDK) Disconnect(connIDs sdk.Connection, domainID string, token string) return r0 } -// DisconnectThing provides a mock function with given fields: thingID, chanID, domainID, token -func (_m *SDK) DisconnectThing(thingID string, chanID string, domainID string, token string) errors.SDKError { - ret := _m.Called(thingID, chanID, domainID, token) +// DisconnectClient provides a mock function with given fields: clientID, chanID, domainID, token +func (_m *SDK) DisconnectClient(clientID string, chanID string, domainID string, token string) errors.SDKError { + ret := _m.Called(clientID, chanID, domainID, token) if len(ret) == 0 { - panic("no return value specified for DisconnectThing") + panic("no return value specified for DisconnectClient") } var r0 errors.SDKError if rf, ok := ret.Get(0).(func(string, string, string, string) errors.SDKError); ok { - r0 = rf(thingID, chanID, domainID, token) + r0 = rf(clientID, chanID, domainID, token) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(errors.SDKError) @@ -1086,6 +1206,36 @@ func (_m *SDK) EnableChannel(id string, domainID string, token string) (sdk.Chan return r0, r1 } +// EnableClient provides a mock function with given fields: id, domainID, token +func (_m *SDK) EnableClient(id string, domainID string, token string) (sdk.Client, errors.SDKError) { + ret := _m.Called(id, domainID, token) + + if len(ret) == 0 { + panic("no return value specified for EnableClient") + } + + var r0 sdk.Client + var r1 errors.SDKError + if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Client, errors.SDKError)); ok { + return rf(id, domainID, token) + } + if rf, ok := ret.Get(0).(func(string, string, string) sdk.Client); ok { + r0 = rf(id, domainID, token) + } else { + r0 = ret.Get(0).(sdk.Client) + } + + if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { + r1 = rf(id, domainID, token) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.SDKError) + } + } + + return r0, r1 +} + // EnableDomain provides a mock function with given fields: domainID, token func (_m *SDK) EnableDomain(domainID string, token string) errors.SDKError { ret := _m.Called(domainID, token) @@ -1136,36 +1286,6 @@ func (_m *SDK) EnableGroup(id string, domainID string, token string) (sdk.Group, return r0, r1 } -// EnableThing provides a mock function with given fields: id, domainID, token -func (_m *SDK) EnableThing(id string, domainID string, token string) (sdk.Thing, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for EnableThing") - } - - var r0 sdk.Thing - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Thing, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Thing); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Thing) - } - - if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(id, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - // EnableUser provides a mock function with given fields: id, token func (_m *SDK) EnableUser(id string, token string) (sdk.User, errors.SDKError) { ret := _m.Called(id, token) @@ -1372,9 +1492,9 @@ func (_m *SDK) Invitations(pm sdk.PageMetadata, token string) (sdk.InvitationPag return r0, r1 } -// IssueCert provides a mock function with given fields: thingID, validity, domainID, token -func (_m *SDK) IssueCert(thingID string, validity string, domainID string, token string) (sdk.Cert, errors.SDKError) { - ret := _m.Called(thingID, validity, domainID, token) +// IssueCert provides a mock function with given fields: clientID, validity, domainID, token +func (_m *SDK) IssueCert(clientID string, validity string, domainID string, token string) (sdk.Cert, errors.SDKError) { + ret := _m.Called(clientID, validity, domainID, token) if len(ret) == 0 { panic("no return value specified for IssueCert") @@ -1383,16 +1503,16 @@ func (_m *SDK) IssueCert(thingID string, validity string, domainID string, token var r0 sdk.Cert var r1 errors.SDKError if rf, ok := ret.Get(0).(func(string, string, string, string) (sdk.Cert, errors.SDKError)); ok { - return rf(thingID, validity, domainID, token) + return rf(clientID, validity, domainID, token) } if rf, ok := ret.Get(0).(func(string, string, string, string) sdk.Cert); ok { - r0 = rf(thingID, validity, domainID, token) + r0 = rf(clientID, validity, domainID, token) } else { r0 = ret.Get(0).(sdk.Cert) } if rf, ok := ret.Get(1).(func(string, string, string, string) errors.SDKError); ok { - r1 = rf(thingID, validity, domainID, token) + r1 = rf(clientID, validity, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -1460,27 +1580,57 @@ func (_m *SDK) ListChannelUserGroups(channelID string, pm sdk.PageMetadata, doma return r0, r1 } -// ListChannelUsers provides a mock function with given fields: channelID, pm, domainID, token -func (_m *SDK) ListChannelUsers(channelID string, pm sdk.PageMetadata, domainID string, token string) (sdk.UsersPage, errors.SDKError) { - ret := _m.Called(channelID, pm, domainID, token) +// ListChannelUsers provides a mock function with given fields: channelID, pm, domainID, token +func (_m *SDK) ListChannelUsers(channelID string, pm sdk.PageMetadata, domainID string, token string) (sdk.UsersPage, errors.SDKError) { + ret := _m.Called(channelID, pm, domainID, token) + + if len(ret) == 0 { + panic("no return value specified for ListChannelUsers") + } + + var r0 sdk.UsersPage + var r1 errors.SDKError + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.UsersPage, errors.SDKError)); ok { + return rf(channelID, pm, domainID, token) + } + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.UsersPage); ok { + r0 = rf(channelID, pm, domainID, token) + } else { + r0 = ret.Get(0).(sdk.UsersPage) + } + + if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string, string) errors.SDKError); ok { + r1 = rf(channelID, pm, domainID, token) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.SDKError) + } + } + + return r0, r1 +} + +// ListClientUsers provides a mock function with given fields: id, pm, domainID, token +func (_m *SDK) ListClientUsers(id string, pm sdk.PageMetadata, domainID string, token string) (sdk.UsersPage, errors.SDKError) { + ret := _m.Called(id, pm, domainID, token) if len(ret) == 0 { - panic("no return value specified for ListChannelUsers") + panic("no return value specified for ListClientUsers") } var r0 sdk.UsersPage var r1 errors.SDKError if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.UsersPage, errors.SDKError)); ok { - return rf(channelID, pm, domainID, token) + return rf(id, pm, domainID, token) } if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.UsersPage); ok { - r0 = rf(channelID, pm, domainID, token) + r0 = rf(id, pm, domainID, token) } else { r0 = ret.Get(0).(sdk.UsersPage) } if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string, string) errors.SDKError); ok { - r1 = rf(channelID, pm, domainID, token) + r1 = rf(id, pm, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -1610,27 +1760,27 @@ func (_m *SDK) ListSubscriptions(pm sdk.PageMetadata, token string) (sdk.Subscri return r0, r1 } -// ListThingUsers provides a mock function with given fields: thingID, pm, domainID, token -func (_m *SDK) ListThingUsers(thingID string, pm sdk.PageMetadata, domainID string, token string) (sdk.UsersPage, errors.SDKError) { - ret := _m.Called(thingID, pm, domainID, token) +// ListUserChannels provides a mock function with given fields: userID, pm, token +func (_m *SDK) ListUserChannels(userID string, pm sdk.PageMetadata, token string) (sdk.ChannelsPage, errors.SDKError) { + ret := _m.Called(userID, pm, token) if len(ret) == 0 { - panic("no return value specified for ListThingUsers") + panic("no return value specified for ListUserChannels") } - var r0 sdk.UsersPage + var r0 sdk.ChannelsPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.UsersPage, errors.SDKError)); ok { - return rf(thingID, pm, domainID, token) + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.ChannelsPage, errors.SDKError)); ok { + return rf(userID, pm, token) } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.UsersPage); ok { - r0 = rf(thingID, pm, domainID, token) + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.ChannelsPage); ok { + r0 = rf(userID, pm, token) } else { - r0 = ret.Get(0).(sdk.UsersPage) + r0 = ret.Get(0).(sdk.ChannelsPage) } - if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string, string) errors.SDKError); ok { - r1 = rf(thingID, pm, domainID, token) + if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string) errors.SDKError); ok { + r1 = rf(userID, pm, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -1640,23 +1790,23 @@ func (_m *SDK) ListThingUsers(thingID string, pm sdk.PageMetadata, domainID stri return r0, r1 } -// ListUserChannels provides a mock function with given fields: userID, pm, token -func (_m *SDK) ListUserChannels(userID string, pm sdk.PageMetadata, token string) (sdk.ChannelsPage, errors.SDKError) { +// ListUserClients provides a mock function with given fields: userID, pm, token +func (_m *SDK) ListUserClients(userID string, pm sdk.PageMetadata, token string) (sdk.ClientsPage, errors.SDKError) { ret := _m.Called(userID, pm, token) if len(ret) == 0 { - panic("no return value specified for ListUserChannels") + panic("no return value specified for ListUserClients") } - var r0 sdk.ChannelsPage + var r0 sdk.ClientsPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.ChannelsPage, errors.SDKError)); ok { + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.ClientsPage, errors.SDKError)); ok { return rf(userID, pm, token) } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.ChannelsPage); ok { + if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.ClientsPage); ok { r0 = rf(userID, pm, token) } else { - r0 = ret.Get(0).(sdk.ChannelsPage) + r0 = ret.Get(0).(sdk.ClientsPage) } if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string) errors.SDKError); ok { @@ -1730,36 +1880,6 @@ func (_m *SDK) ListUserGroups(userID string, pm sdk.PageMetadata, token string) return r0, r1 } -// ListUserThings provides a mock function with given fields: userID, pm, token -func (_m *SDK) ListUserThings(userID string, pm sdk.PageMetadata, token string) (sdk.ThingsPage, errors.SDKError) { - ret := _m.Called(userID, pm, token) - - if len(ret) == 0 { - panic("no return value specified for ListUserThings") - } - - var r0 sdk.ThingsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.ThingsPage, errors.SDKError)); ok { - return rf(userID, pm, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.ThingsPage); ok { - r0 = rf(userID, pm, token) - } else { - r0 = ret.Get(0).(sdk.ThingsPage) - } - - if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string) errors.SDKError); ok { - r1 = rf(userID, pm, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - // Members provides a mock function with given fields: groupID, meta, token func (_m *SDK) Members(groupID string, meta sdk.PageMetadata, token string) (sdk.UsersPage, errors.SDKError) { ret := _m.Called(groupID, meta, token) @@ -2038,9 +2158,9 @@ func (_m *SDK) ResetPasswordRequest(email string) errors.SDKError { return r0 } -// RevokeCert provides a mock function with given fields: thingID, domainID, token -func (_m *SDK) RevokeCert(thingID string, domainID string, token string) (time.Time, errors.SDKError) { - ret := _m.Called(thingID, domainID, token) +// RevokeCert provides a mock function with given fields: clientID, domainID, token +func (_m *SDK) RevokeCert(clientID string, domainID string, token string) (time.Time, errors.SDKError) { + ret := _m.Called(clientID, domainID, token) if len(ret) == 0 { panic("no return value specified for RevokeCert") @@ -2049,16 +2169,16 @@ func (_m *SDK) RevokeCert(thingID string, domainID string, token string) (time.T var r0 time.Time var r1 errors.SDKError if rf, ok := ret.Get(0).(func(string, string, string) (time.Time, errors.SDKError)); ok { - return rf(thingID, domainID, token) + return rf(clientID, domainID, token) } if rf, ok := ret.Get(0).(func(string, string, string) time.Time); ok { - r0 = rf(thingID, domainID, token) + r0 = rf(clientID, domainID, token) } else { r0 = ret.Get(0).(time.Time) } if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(thingID, domainID, token) + r1 = rf(clientID, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2156,17 +2276,17 @@ func (_m *SDK) SetContentType(ct sdk.ContentType) errors.SDKError { return r0 } -// ShareThing provides a mock function with given fields: thingID, req, domainID, token -func (_m *SDK) ShareThing(thingID string, req sdk.UsersRelationRequest, domainID string, token string) errors.SDKError { - ret := _m.Called(thingID, req, domainID, token) +// ShareClient provides a mock function with given fields: id, req, domainID, token +func (_m *SDK) ShareClient(id string, req sdk.UsersRelationRequest, domainID string, token string) errors.SDKError { + ret := _m.Called(id, req, domainID, token) if len(ret) == 0 { - panic("no return value specified for ShareThing") + panic("no return value specified for ShareClient") } var r0 errors.SDKError if rf, ok := ret.Get(0).(func(string, sdk.UsersRelationRequest, string, string) errors.SDKError); ok { - r0 = rf(thingID, req, domainID, token) + r0 = rf(id, req, domainID, token) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(errors.SDKError) @@ -2176,137 +2296,17 @@ func (_m *SDK) ShareThing(thingID string, req sdk.UsersRelationRequest, domainID return r0 } -// Thing provides a mock function with given fields: id, domainID, token -func (_m *SDK) Thing(id string, domainID string, token string) (sdk.Thing, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Thing") - } - - var r0 sdk.Thing - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Thing, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Thing); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Thing) - } - - if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(id, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// ThingPermissions provides a mock function with given fields: id, domainID, token -func (_m *SDK) ThingPermissions(id string, domainID string, token string) (sdk.Thing, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ThingPermissions") - } - - var r0 sdk.Thing - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Thing, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Thing); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Thing) - } - - if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(id, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Things provides a mock function with given fields: pm, domainID, token -func (_m *SDK) Things(pm sdk.PageMetadata, domainID string, token string) (sdk.ThingsPage, errors.SDKError) { - ret := _m.Called(pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Things") - } - - var r0 sdk.ThingsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) (sdk.ThingsPage, errors.SDKError)); ok { - return rf(pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) sdk.ThingsPage); ok { - r0 = rf(pm, domainID, token) - } else { - r0 = ret.Get(0).(sdk.ThingsPage) - } - - if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string, string) errors.SDKError); ok { - r1 = rf(pm, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// ThingsByChannel provides a mock function with given fields: chanID, pm, domainID, token -func (_m *SDK) ThingsByChannel(chanID string, pm sdk.PageMetadata, domainID string, token string) (sdk.ThingsPage, errors.SDKError) { - ret := _m.Called(chanID, pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ThingsByChannel") - } - - var r0 sdk.ThingsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.ThingsPage, errors.SDKError)); ok { - return rf(chanID, pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.ThingsPage); ok { - r0 = rf(chanID, pm, domainID, token) - } else { - r0 = ret.Get(0).(sdk.ThingsPage) - } - - if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string, string) errors.SDKError); ok { - r1 = rf(chanID, pm, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// UnshareThing provides a mock function with given fields: thingID, req, domainID, token -func (_m *SDK) UnshareThing(thingID string, req sdk.UsersRelationRequest, domainID string, token string) errors.SDKError { - ret := _m.Called(thingID, req, domainID, token) +// UnshareClient provides a mock function with given fields: id, req, domainID, token +func (_m *SDK) UnshareClient(id string, req sdk.UsersRelationRequest, domainID string, token string) errors.SDKError { + ret := _m.Called(id, req, domainID, token) if len(ret) == 0 { - panic("no return value specified for UnshareThing") + panic("no return value specified for UnshareClient") } var r0 errors.SDKError if rf, ok := ret.Get(0).(func(string, sdk.UsersRelationRequest, string, string) errors.SDKError); ok { - r0 = rf(thingID, req, domainID, token) + r0 = rf(id, req, domainID, token) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(errors.SDKError) @@ -2416,27 +2416,27 @@ func (_m *SDK) UpdateChannel(channel sdk.Channel, domainID string, token string) return r0, r1 } -// UpdateDomain provides a mock function with given fields: d, token -func (_m *SDK) UpdateDomain(d sdk.Domain, token string) (sdk.Domain, errors.SDKError) { - ret := _m.Called(d, token) +// UpdateClient provides a mock function with given fields: client, domainID, token +func (_m *SDK) UpdateClient(client sdk.Client, domainID string, token string) (sdk.Client, errors.SDKError) { + ret := _m.Called(client, domainID, token) if len(ret) == 0 { - panic("no return value specified for UpdateDomain") + panic("no return value specified for UpdateClient") } - var r0 sdk.Domain + var r0 sdk.Client var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Domain, string) (sdk.Domain, errors.SDKError)); ok { - return rf(d, token) + if rf, ok := ret.Get(0).(func(sdk.Client, string, string) (sdk.Client, errors.SDKError)); ok { + return rf(client, domainID, token) } - if rf, ok := ret.Get(0).(func(sdk.Domain, string) sdk.Domain); ok { - r0 = rf(d, token) + if rf, ok := ret.Get(0).(func(sdk.Client, string, string) sdk.Client); ok { + r0 = rf(client, domainID, token) } else { - r0 = ret.Get(0).(sdk.Domain) + r0 = ret.Get(0).(sdk.Client) } - if rf, ok := ret.Get(1).(func(sdk.Domain, string) errors.SDKError); ok { - r1 = rf(d, token) + if rf, ok := ret.Get(1).(func(sdk.Client, string, string) errors.SDKError); ok { + r1 = rf(client, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2446,27 +2446,27 @@ func (_m *SDK) UpdateDomain(d sdk.Domain, token string) (sdk.Domain, errors.SDKE return r0, r1 } -// UpdateGroup provides a mock function with given fields: group, domainID, token -func (_m *SDK) UpdateGroup(group sdk.Group, domainID string, token string) (sdk.Group, errors.SDKError) { - ret := _m.Called(group, domainID, token) +// UpdateClientSecret provides a mock function with given fields: id, secret, domainID, token +func (_m *SDK) UpdateClientSecret(id string, secret string, domainID string, token string) (sdk.Client, errors.SDKError) { + ret := _m.Called(id, secret, domainID, token) if len(ret) == 0 { - panic("no return value specified for UpdateGroup") + panic("no return value specified for UpdateClientSecret") } - var r0 sdk.Group + var r0 sdk.Client var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Group, string, string) (sdk.Group, errors.SDKError)); ok { - return rf(group, domainID, token) + if rf, ok := ret.Get(0).(func(string, string, string, string) (sdk.Client, errors.SDKError)); ok { + return rf(id, secret, domainID, token) } - if rf, ok := ret.Get(0).(func(sdk.Group, string, string) sdk.Group); ok { - r0 = rf(group, domainID, token) + if rf, ok := ret.Get(0).(func(string, string, string, string) sdk.Client); ok { + r0 = rf(id, secret, domainID, token) } else { - r0 = ret.Get(0).(sdk.Group) + r0 = ret.Get(0).(sdk.Client) } - if rf, ok := ret.Get(1).(func(sdk.Group, string, string) errors.SDKError); ok { - r1 = rf(group, domainID, token) + if rf, ok := ret.Get(1).(func(string, string, string, string) errors.SDKError); ok { + r1 = rf(id, secret, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2476,27 +2476,27 @@ func (_m *SDK) UpdateGroup(group sdk.Group, domainID string, token string) (sdk. return r0, r1 } -// UpdatePassword provides a mock function with given fields: oldPass, newPass, token -func (_m *SDK) UpdatePassword(oldPass string, newPass string, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(oldPass, newPass, token) +// UpdateClientTags provides a mock function with given fields: client, domainID, token +func (_m *SDK) UpdateClientTags(client sdk.Client, domainID string, token string) (sdk.Client, errors.SDKError) { + ret := _m.Called(client, domainID, token) if len(ret) == 0 { - panic("no return value specified for UpdatePassword") + panic("no return value specified for UpdateClientTags") } - var r0 sdk.User + var r0 sdk.Client var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.User, errors.SDKError)); ok { - return rf(oldPass, newPass, token) + if rf, ok := ret.Get(0).(func(sdk.Client, string, string) (sdk.Client, errors.SDKError)); ok { + return rf(client, domainID, token) } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.User); ok { - r0 = rf(oldPass, newPass, token) + if rf, ok := ret.Get(0).(func(sdk.Client, string, string) sdk.Client); ok { + r0 = rf(client, domainID, token) } else { - r0 = ret.Get(0).(sdk.User) + r0 = ret.Get(0).(sdk.Client) } - if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(oldPass, newPass, token) + if rf, ok := ret.Get(1).(func(sdk.Client, string, string) errors.SDKError); ok { + r1 = rf(client, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2506,27 +2506,27 @@ func (_m *SDK) UpdatePassword(oldPass string, newPass string, token string) (sdk return r0, r1 } -// UpdateProfilePicture provides a mock function with given fields: user, token -func (_m *SDK) UpdateProfilePicture(user sdk.User, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(user, token) +// UpdateDomain provides a mock function with given fields: d, token +func (_m *SDK) UpdateDomain(d sdk.Domain, token string) (sdk.Domain, errors.SDKError) { + ret := _m.Called(d, token) if len(ret) == 0 { - panic("no return value specified for UpdateProfilePicture") + panic("no return value specified for UpdateDomain") } - var r0 sdk.User + var r0 sdk.Domain var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.User, string) (sdk.User, errors.SDKError)); ok { - return rf(user, token) + if rf, ok := ret.Get(0).(func(sdk.Domain, string) (sdk.Domain, errors.SDKError)); ok { + return rf(d, token) } - if rf, ok := ret.Get(0).(func(sdk.User, string) sdk.User); ok { - r0 = rf(user, token) + if rf, ok := ret.Get(0).(func(sdk.Domain, string) sdk.Domain); ok { + r0 = rf(d, token) } else { - r0 = ret.Get(0).(sdk.User) + r0 = ret.Get(0).(sdk.Domain) } - if rf, ok := ret.Get(1).(func(sdk.User, string) errors.SDKError); ok { - r1 = rf(user, token) + if rf, ok := ret.Get(1).(func(sdk.Domain, string) errors.SDKError); ok { + r1 = rf(d, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2536,27 +2536,27 @@ func (_m *SDK) UpdateProfilePicture(user sdk.User, token string) (sdk.User, erro return r0, r1 } -// UpdateThing provides a mock function with given fields: thing, domainID, token -func (_m *SDK) UpdateThing(thing sdk.Thing, domainID string, token string) (sdk.Thing, errors.SDKError) { - ret := _m.Called(thing, domainID, token) +// UpdateGroup provides a mock function with given fields: group, domainID, token +func (_m *SDK) UpdateGroup(group sdk.Group, domainID string, token string) (sdk.Group, errors.SDKError) { + ret := _m.Called(group, domainID, token) if len(ret) == 0 { - panic("no return value specified for UpdateThing") + panic("no return value specified for UpdateGroup") } - var r0 sdk.Thing + var r0 sdk.Group var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Thing, string, string) (sdk.Thing, errors.SDKError)); ok { - return rf(thing, domainID, token) + if rf, ok := ret.Get(0).(func(sdk.Group, string, string) (sdk.Group, errors.SDKError)); ok { + return rf(group, domainID, token) } - if rf, ok := ret.Get(0).(func(sdk.Thing, string, string) sdk.Thing); ok { - r0 = rf(thing, domainID, token) + if rf, ok := ret.Get(0).(func(sdk.Group, string, string) sdk.Group); ok { + r0 = rf(group, domainID, token) } else { - r0 = ret.Get(0).(sdk.Thing) + r0 = ret.Get(0).(sdk.Group) } - if rf, ok := ret.Get(1).(func(sdk.Thing, string, string) errors.SDKError); ok { - r1 = rf(thing, domainID, token) + if rf, ok := ret.Get(1).(func(sdk.Group, string, string) errors.SDKError); ok { + r1 = rf(group, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2566,27 +2566,27 @@ func (_m *SDK) UpdateThing(thing sdk.Thing, domainID string, token string) (sdk. return r0, r1 } -// UpdateThingSecret provides a mock function with given fields: id, secret, domainID, token -func (_m *SDK) UpdateThingSecret(id string, secret string, domainID string, token string) (sdk.Thing, errors.SDKError) { - ret := _m.Called(id, secret, domainID, token) +// UpdatePassword provides a mock function with given fields: oldPass, newPass, token +func (_m *SDK) UpdatePassword(oldPass string, newPass string, token string) (sdk.User, errors.SDKError) { + ret := _m.Called(oldPass, newPass, token) if len(ret) == 0 { - panic("no return value specified for UpdateThingSecret") + panic("no return value specified for UpdatePassword") } - var r0 sdk.Thing + var r0 sdk.User var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string, string) (sdk.Thing, errors.SDKError)); ok { - return rf(id, secret, domainID, token) + if rf, ok := ret.Get(0).(func(string, string, string) (sdk.User, errors.SDKError)); ok { + return rf(oldPass, newPass, token) } - if rf, ok := ret.Get(0).(func(string, string, string, string) sdk.Thing); ok { - r0 = rf(id, secret, domainID, token) + if rf, ok := ret.Get(0).(func(string, string, string) sdk.User); ok { + r0 = rf(oldPass, newPass, token) } else { - r0 = ret.Get(0).(sdk.Thing) + r0 = ret.Get(0).(sdk.User) } - if rf, ok := ret.Get(1).(func(string, string, string, string) errors.SDKError); ok { - r1 = rf(id, secret, domainID, token) + if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { + r1 = rf(oldPass, newPass, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2596,27 +2596,27 @@ func (_m *SDK) UpdateThingSecret(id string, secret string, domainID string, toke return r0, r1 } -// UpdateThingTags provides a mock function with given fields: thing, domainID, token -func (_m *SDK) UpdateThingTags(thing sdk.Thing, domainID string, token string) (sdk.Thing, errors.SDKError) { - ret := _m.Called(thing, domainID, token) +// UpdateProfilePicture provides a mock function with given fields: user, token +func (_m *SDK) UpdateProfilePicture(user sdk.User, token string) (sdk.User, errors.SDKError) { + ret := _m.Called(user, token) if len(ret) == 0 { - panic("no return value specified for UpdateThingTags") + panic("no return value specified for UpdateProfilePicture") } - var r0 sdk.Thing + var r0 sdk.User var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Thing, string, string) (sdk.Thing, errors.SDKError)); ok { - return rf(thing, domainID, token) + if rf, ok := ret.Get(0).(func(sdk.User, string) (sdk.User, errors.SDKError)); ok { + return rf(user, token) } - if rf, ok := ret.Get(0).(func(sdk.Thing, string, string) sdk.Thing); ok { - r0 = rf(thing, domainID, token) + if rf, ok := ret.Get(0).(func(sdk.User, string) sdk.User); ok { + r0 = rf(user, token) } else { - r0 = ret.Get(0).(sdk.Thing) + r0 = ret.Get(0).(sdk.User) } - if rf, ok := ret.Get(1).(func(sdk.Thing, string, string) errors.SDKError); ok { - r1 = rf(thing, domainID, token) + if rf, ok := ret.Get(1).(func(sdk.User, string) errors.SDKError); ok { + r1 = rf(user, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2926,27 +2926,27 @@ func (_m *SDK) ViewCert(certID string, domainID string, token string) (sdk.Cert, return r0, r1 } -// ViewCertByThing provides a mock function with given fields: thingID, domainID, token -func (_m *SDK) ViewCertByThing(thingID string, domainID string, token string) (sdk.CertSerials, errors.SDKError) { - ret := _m.Called(thingID, domainID, token) +// ViewCertByClient provides a mock function with given fields: clientID, domainID, token +func (_m *SDK) ViewCertByClient(clientID string, domainID string, token string) (sdk.CertSerials, errors.SDKError) { + ret := _m.Called(clientID, domainID, token) if len(ret) == 0 { - panic("no return value specified for ViewCertByThing") + panic("no return value specified for ViewCertByClient") } var r0 sdk.CertSerials var r1 errors.SDKError if rf, ok := ret.Get(0).(func(string, string, string) (sdk.CertSerials, errors.SDKError)); ok { - return rf(thingID, domainID, token) + return rf(clientID, domainID, token) } if rf, ok := ret.Get(0).(func(string, string, string) sdk.CertSerials); ok { - r0 = rf(thingID, domainID, token) + r0 = rf(clientID, domainID, token) } else { r0 = ret.Get(0).(sdk.CertSerials) } if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(thingID, domainID, token) + r1 = rf(clientID, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) @@ -2986,9 +2986,9 @@ func (_m *SDK) ViewSubscription(id string, token string) (sdk.Subscription, erro return r0, r1 } -// Whitelist provides a mock function with given fields: thingID, state, domainID, token -func (_m *SDK) Whitelist(thingID string, state int, domainID string, token string) errors.SDKError { - ret := _m.Called(thingID, state, domainID, token) +// Whitelist provides a mock function with given fields: clientID, state, domainID, token +func (_m *SDK) Whitelist(clientID string, state int, domainID string, token string) errors.SDKError { + ret := _m.Called(clientID, state, domainID, token) if len(ret) == 0 { panic("no return value specified for Whitelist") @@ -2996,7 +2996,7 @@ func (_m *SDK) Whitelist(thingID string, state int, domainID string, token strin var r0 errors.SDKError if rf, ok := ret.Get(0).(func(string, int, string, string) errors.SDKError); ok { - r0 = rf(thingID, state, domainID, token) + r0 = rf(clientID, state, domainID, token) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(errors.SDKError) diff --git a/pkg/sid/README.md b/pkg/sid/README.md new file mode 100644 index 0000000000..1e95d1045b --- /dev/null +++ b/pkg/sid/README.md @@ -0,0 +1,2 @@ +# Short identity provider + diff --git a/pkg/sid/doc.go b/pkg/sid/doc.go new file mode 100644 index 0000000000..a3b28fdfde --- /dev/null +++ b/pkg/sid/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package ulid contains ULID generator. +package sid diff --git a/pkg/sid/mock.go b/pkg/sid/mock.go new file mode 100644 index 0000000000..5211b34606 --- /dev/null +++ b/pkg/sid/mock.go @@ -0,0 +1,35 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package sid + +import ( + "fmt" + "sync" + + "github.com/absmach/magistrala" +) + +// Prefix represents the prefix used to generate UUID mocks. +const Prefix = "123e4567-e89b-12d3-a456-" + +var _ magistrala.IDProvider = (*sidProviderMock)(nil) + +type sidProviderMock struct { + mu sync.Mutex + counter int +} + +func (up *sidProviderMock) ID() (string, error) { + up.mu.Lock() + defer up.mu.Unlock() + + up.counter++ + return fmt.Sprintf("%s%012d", Prefix, up.counter), nil +} + +// NewMock creates "mirror" uuid provider, i.e. generated +// token will hold value provided by the caller. +func NewMock() magistrala.IDProvider { + return &sidProviderMock{} +} diff --git a/pkg/sid/sid.go b/pkg/sid/sid.go new file mode 100644 index 0000000000..1e8cbfd468 --- /dev/null +++ b/pkg/sid/sid.go @@ -0,0 +1,55 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package ulid provides a ULID identity provider. +package sid + +import ( + "encoding/binary" + + "github.com/absmach/magistrala" + "github.com/absmach/magistrala/pkg/errors" + "github.com/gofrs/uuid/v5" + "github.com/sqids/sqids-go" +) + +// ErrGeneratingID indicates error in generating ULID. +var ( + ErrInitializingShortID = errors.New("failed to initialize short id provider") + ErrGeneratingID = errors.New("generating id failed") + ErrEncodeID = errors.New("encoding id failed") +) +var _ magistrala.IDProvider = (*sidProvider)(nil) + +type sidProvider struct { + sidEncoder *sqids.Sqids +} + +// New instantiates a short ID provider. +func New() (magistrala.IDProvider, error) { + sidEncoder, err := sqids.New(sqids.Options{ + Alphabet: "FxnXM1kBN6cuhsAvjW3Co7l2RePyY8DwaU04Tzt9fHQrqSVKdpimLGIJOgb5ZE", + }) + if err != nil { + return nil, errors.Wrap(ErrInitializingShortID, err) + } + return &sidProvider{sidEncoder}, nil +} + +func (s *sidProvider) ID() (string, error) { + id, err := uuid.NewV4() + if err != nil { + return "", errors.Wrap(ErrGeneratingID, err) + } + idBytes := id.Bytes() + + sid, err := s.sidEncoder.Encode([]uint64{ + binary.BigEndian.Uint64(idBytes[:8]), + binary.BigEndian.Uint64(idBytes[8:]), + }) + if err != nil { + return "", errors.Wrap(ErrEncodeID, err) + } + + return sid, nil +} diff --git a/pkg/svcutil/externaloperationperm.go b/pkg/svcutil/externaloperationperm.go new file mode 100644 index 0000000000..aabb59a604 --- /dev/null +++ b/pkg/svcutil/externaloperationperm.go @@ -0,0 +1,77 @@ +package svcutil + +import "fmt" + +type ExternalOperation int + +func (op ExternalOperation) String(operations []string) string { + if (int(op) < 0) || (int(op) == len(operations)) { + return fmt.Sprintf("UnknownOperation(%d)", op) + } + return operations[op] +} + +type ExternalOperationPerm struct { + opPerm map[ExternalOperation]Permission + expectedOps []ExternalOperation + opNames []string +} + +func NewExternalOperationPerm(expectedOps []ExternalOperation, opNames []string) ExternalOperationPerm { + return ExternalOperationPerm{ + opPerm: make(map[ExternalOperation]Permission), + expectedOps: expectedOps, + opNames: opNames, + } +} + +func (eopp ExternalOperationPerm) isKeyRequired(eop ExternalOperation) bool { + for _, key := range eopp.expectedOps { + if key == eop { + return true + } + } + return false +} + +func (eopp ExternalOperationPerm) AddOperationPermissionMap(eopMap map[ExternalOperation]Permission) error { + // First iteration check all the keys are valid, If any one key is invalid then no key should be added. + for eop := range eopMap { + if !eopp.isKeyRequired(eop) { + return fmt.Errorf("%v is not a valid external operation", eop.String(eopp.opNames)) + } + } + for eop, perm := range eopMap { + eopp.opPerm[eop] = perm + } + return nil +} + +func (eopp ExternalOperationPerm) AddOperationPermission(eop ExternalOperation, perm Permission) error { + if !eopp.isKeyRequired(eop) { + return fmt.Errorf("%v is not a valid external operation", eop.String(eopp.opNames)) + } + eopp.opPerm[eop] = perm + return nil +} + +func (eopp ExternalOperationPerm) Validate() error { + for eop := range eopp.opPerm { + if !eopp.isKeyRequired(eop) { + return fmt.Errorf("ExternalOperationPerm: \"%s\" is not a valid external operation", eop.String(eopp.opNames)) + } + } + for _, eeo := range eopp.expectedOps { + if _, ok := eopp.opPerm[eeo]; !ok { + return fmt.Errorf("ExternalOperationPerm: \"%s\" external operation is missing", eeo.String(eopp.opNames)) + } + } + return nil +} + +func (eopp ExternalOperationPerm) GetPermission(eop ExternalOperation) (Permission, error) { + if perm, ok := eopp.opPerm[eop]; ok { + return perm, nil + } + return "", fmt.Errorf("external operation \"%s\" doesn't have any permissions", eop.String(eopp.opNames)) +} diff --git a/pkg/svcutil/operationperm.go b/pkg/svcutil/operationperm.go new file mode 100644 index 0000000000..916bca6112 --- /dev/null +++ b/pkg/svcutil/operationperm.go @@ -0,0 +1,83 @@ +package svcutil + +import "fmt" + +type Permission string + +func (p Permission) String() string { + return string(p) +} + +type Operation int + +func (op Operation) String(operations []string) string { + if (int(op) < 0) || (int(op) == len(operations)) { + return fmt.Sprintf("UnknownOperation(%d)", op) + } + return operations[op] +} + +type OperationPerm struct { + opPerm map[Operation]Permission + expectedOps []Operation + opNames []string +} + +func NewOperationPerm(expectedOps []Operation, opNames []string) OperationPerm { + return OperationPerm{ + opPerm: make(map[Operation]Permission), + expectedOps: expectedOps, + opNames: opNames, + } +} + +func (opp OperationPerm) isKeyRequired(op Operation) bool { + for _, key := range opp.expectedOps { + if key == op { + return true + } + } + return false +} + +func (opp OperationPerm) AddOperationPermissionMap(opMap map[Operation]Permission) error { + // First iteration check all the keys are valid, If any one key is invalid then no key should be added. + for op := range opMap { + if !opp.isKeyRequired(op) { + return fmt.Errorf("%v is not a valid operation", op.String(opp.opNames)) + } + } + for op, perm := range opMap { + opp.opPerm[op] = perm + } + return nil +} + +func (opp OperationPerm) AddOperationPermission(op Operation, perm Permission) error { + if !opp.isKeyRequired(op) { + return fmt.Errorf("%v is not a valid operation", op.String(opp.opNames)) + } + opp.opPerm[op] = perm + return nil +} + +func (opp OperationPerm) Validate() error { + for op := range opp.opPerm { + if !opp.isKeyRequired(op) { + return fmt.Errorf("OperationPerm: \"%s\" is not a valid operation", op.String(opp.opNames)) + } + } + for _, eeo := range opp.expectedOps { + if _, ok := opp.opPerm[eeo]; !ok { + return fmt.Errorf("OperationPerm: \"%s\" operation is missing", eeo.String(opp.opNames)) + } + } + return nil +} + +func (opp OperationPerm) GetPermission(op Operation) (Permission, error) { + if perm, ok := opp.opPerm[op]; ok { + return perm, nil + } + return "", fmt.Errorf("operation \"%s\" doesn't have any permissions", op.String(opp.opNames)) +} diff --git a/provision/README.md b/provision/README.md index 73f6c86392..2980b32643 100644 --- a/provision/README.md +++ b/provision/README.md @@ -1,13 +1,13 @@ # Provision service Provision service provides an HTTP API to interact with [Magistrala][magistrala]. -Provision service is used to setup initial applications configuration i.e. things, channels, connections and certificates that will be required for the specific use case especially useful for gateway provision. +Provision service is used to setup initial applications configuration i.e. clients, channels, connections and certificates that will be required for the specific use case especially useful for gateway provision. -For gateways to communicate with [Magistrala][magistrala] configuration is required (mqtt host, thing, channels, certificates...). To get the configuration gateway will send a request to [Bootstrap][bootstrap] service providing `` and `` in request. To make a request to [Bootstrap][bootstrap] service you can use [Agent][agent] service on a gateway. +For gateways to communicate with [Magistrala][magistrala] configuration is required (mqtt host, client, channels, certificates...). To get the configuration gateway will send a request to [Bootstrap][bootstrap] service providing `` and `` in request. To make a request to [Bootstrap][bootstrap] service you can use [Agent][agent] service on a gateway. -To create bootstrap configuration you can use [Bootstrap][bootstrap] or `Provision` service. [Magistrala UI][mgxui] uses [Bootstrap][bootstrap] service for creating gateway configurations. `Provision` service should provide an easy way of provisioning your gateways i.e creating bootstrap configuration and as many things and channels that your setup requires. +To create bootstrap configuration you can use [Bootstrap][bootstrap] or `Provision` service. [Magistrala UI][mgxui] uses [Bootstrap][bootstrap] service for creating gateway configurations. `Provision` service should provide an easy way of provisioning your gateways i.e creating bootstrap configuration and as many clients and channels that your setup requires. -Also you may use provision service to create certificates for each thing. Each service running on gateway may require more than one thing and channel for communication. Let's say that you are using services [Agent][agent] and [Export][export] on a gateway you will need two channels for `Agent` (`data` and `control`) and one for `Export` and one thing. Additionally if you enabled mtls each service will need its own thing and certificate for access to [Magistrala][magistrala]. Your setup could require any number of things and channels this kind of setup we can call `provision layout`. +Also you may use provision service to create certificates for each client. Each service running on gateway may require more than one client and channel for communication. Let's say that you are using services [Agent][agent] and [Export][export] on a gateway you will need two channels for `Agent` (`data` and `control`) and one for `Export` and one client. Additionally if you enabled mtls each service will need its own client and certificate for access to [Magistrala][magistrala]. Your setup could require any number of clients and channels this kind of setup we can call `provision layout`. Provision service provides a way of specifying this `provision layout` and creating a setup according to that layout by serving requests on `/mapping` endpoint. Provision layout is configured in [config.toml](configs/config.toml). @@ -17,48 +17,48 @@ The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ----------------------------------- | ------------------------------------------------- | ------------------------------------ | -| MG_PROVISION_LOG_LEVEL | Service log level | debug | -| MG_PROVISION_USER | User (email) for accessing Magistrala | | -| MG_PROVISION_PASS | Magistrala password | user123 | -| MG_PROVISION_API_KEY | Magistrala authentication token | | -| MG_PROVISION_CONFIG_FILE | Provision config file | config.toml | -| MG_PROVISION_HTTP_PORT | Provision service listening port | 9016 | -| MG_PROVISION_ENV_CLIENTS_TLS | Magistrala SDK TLS verification | false | -| MG_PROVISION_SERVER_CERT | Magistrala gRPC secure server cert | | -| MG_PROVISION_SERVER_KEY | Magistrala gRPC secure server key | | -| MG_PROVISION_USERS_LOCATION | Users service URL | | -| MG_PROVISION_THINGS_LOCATION | Things service URL | | -| MG_PROVISION_BS_SVC_URL | Magistrala Bootstrap service URL | | -| MG_PROVISION_CERTS_SVC_URL | Certificates service URL | | -| MG_PROVISION_X509_PROVISIONING | Should X509 client cert be provisioned | false | -| MG_PROVISION_BS_CONFIG_PROVISIONING | Should thing config be saved in Bootstrap service | true | -| MG_PROVISION_BS_AUTO_WHITELIST | Should thing be auto whitelisted | true | -| MG_PROVISION_BS_CONTENT | Bootstrap service configs content, JSON format | {} | -| MG_PROVISION_CERTS_RSA_BITS | Certificate RSA bits parameter | 4096 | -| MG_PROVISION_CERTS_HOURS_VALID | Number of hours that certificate is valid | "2400h" | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | - -By default, call to `/mapping` endpoint will create one thing and two channels (`control` and `data`) and connect it. If there is a requirement for different provision layout we can use [config](docker/configs/config.toml) file in addition to environment variables. +| Variable | Description | Default | +| ----------------------------------- | -------------------------------------------------- | ----------------------- | +| MG_PROVISION_LOG_LEVEL | Service log level | debug | +| MG_PROVISION_USER | User (email) for accessing Magistrala | | +| MG_PROVISION_PASS | Magistrala password | user123 | +| MG_PROVISION_API_KEY | Magistrala authentication token | | +| MG_PROVISION_CONFIG_FILE | Provision config file | config.toml | +| MG_PROVISION_HTTP_PORT | Provision service listening port | 9016 | +| MG_PROVISION_ENV_CLIENTS_TLS | Magistrala SDK TLS verification | false | +| MG_PROVISION_SERVER_CERT | Magistrala gRPC secure server cert | | +| MG_PROVISION_SERVER_KEY | Magistrala gRPC secure server key | | +| MG_PROVISION_USERS_LOCATION | Users service URL | | +| MG_PROVISION_CLIENTS_LOCATION | Clients service URL | | +| MG_PROVISION_BS_SVC_URL | Magistrala Bootstrap service URL | | +| MG_PROVISION_CERTS_SVC_URL | Certificates service URL | | +| MG_PROVISION_X509_PROVISIONING | Should X509 client cert be provisioned | false | +| MG_PROVISION_BS_CONFIG_PROVISIONING | Should client config be saved in Bootstrap service | true | +| MG_PROVISION_BS_AUTO_WHITELIST | Should client be auto whitelisted | true | +| MG_PROVISION_BS_CONTENT | Bootstrap service configs content, JSON format | {} | +| MG_PROVISION_CERTS_RSA_BITS | Certificate RSA bits parameter | 4096 | +| MG_PROVISION_CERTS_HOURS_VALID | Number of hours that certificate is valid | "2400h" | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | + +By default, call to `/mapping` endpoint will create one client and two channels (`control` and `data`) and connect it. If there is a requirement for different provision layout we can use [config](docker/configs/config.toml) file in addition to environment variables. For the purposes of running provision as an add-on in docker composition environment variables seems more suitable. Environment variables are set in [.env](.env). Configuration can be specified in [config.toml](configs/config.toml). Config file can specify all the settings that environment variables can configure and in addition `/mapping` endpoint provision layout can be configured. -In `config.toml` we can enlist array of things and channels that we want to create and make connections between them which we call provision layout. +In `config.toml` we can enlist array of clients and channels that we want to create and make connections between them which we call provision layout. -Metadata can be whatever suits your needs except that at least one thing needs to have `external_id` (which is populated with value from [request](#example)). Thing that has `external_id` will be used for creating bootstrap configuration which can be fetched with [Agent][agent]. +Metadata can be whatever suits your needs except that at least one client needs to have `external_id` (which is populated with value from [request](#example)). Client that has `external_id` will be used for creating bootstrap configuration which can be fetched with [Agent][agent]. For channels metadata `type` is reserved for `control` and `data` which we use with [Agent][agent]. Example of provision layout below ```toml -[[things]] - name = "thing" +[[clients]] + name = "client" - [things.metadata] + [clients.metadata] external_id = "xxxxxx" @@ -99,7 +99,7 @@ Standalone: ```bash MG_PROVISION_BS_SVC_URL=http://localhost:9013 \ -MG_PROVISION_THINGS_LOCATION=http://localhost:9000 \ +MG_PROVISION_CLIENTS_LOCATION=http://localhost:9000 \ MG_PROVISION_USERS_LOCATION=http://localhost:9002 \ MG_PROVISION_CONFIG_FILE=docker/addons/provision/configs/config.toml \ build/magistrala-provision @@ -123,7 +123,7 @@ In the case that provision service is not deployed with credentials or API key o curl -s -S -X POST http://localhost:/mapping -H "Authorization: Bearer " -H 'Content-Type: application/json' -d '{"external_id": "", "external_key": ""}' ``` -Or if you want to specify a name for thing different than in `config.toml` you can specify post data as: +Or if you want to specify a name for client different than in `config.toml` you can specify post data as: ```json { @@ -133,14 +133,14 @@ Or if you want to specify a name for thing different than in `config.toml` you c } ``` -Response contains created things, channels and certificates if any: +Response contains created clients, channels and certificates if any: ```json { - "things": [ + "clients": [ { "id": "c22b0c0f-8c03-40da-a06b-37ed3a72c8d1", - "name": "thing", + "name": "client", "key": "007cce56-e0eb-40d6-b2b9-ed348a97d1eb", "metadata": { "external_id": "33:52:79:C3:43" @@ -171,19 +171,19 @@ Response contains created things, channels and certificates if any: ## Certificates -Provision service has `/certs` endpoint that can be used to generate certificates for things when mTLS is required: +Provision service has `/certs` endpoint that can be used to generate certificates for clients when mTLS is required: - `users_token` - users authentication token or API token -- `thing_id` - id of the thing for which certificate is going to be generated +- `client_id` - id of the client for which certificate is going to be generated ```bash -curl -s -X POST http://localhost:8190/certs -H "Authorization: Bearer " -H 'Content-Type: application/json' -d '{"thing_id": "", "ttl":"2400h" }' +curl -s -X POST http://localhost:8190/certs -H "Authorization: Bearer " -H 'Content-Type: application/json' -d '{"client_id": "", "ttl":"2400h" }' ``` ```json { - "thing_cert": "-----BEGIN CERTIFICATE-----\nMIIEmDCCA4CgAwIBAgIQCZ0NOq2oKLo+XftbAu0TfzANBgkqhkiG9w0BAQsFADBX\nMRIwEAYDVQQDDAlsb2NhbGhvc3QxETAPBgNVBAoMCE1haW5mbHV4MQwwCgYDVQQL\nDANJb1QxIDAeBgkqhkiG9w0BCQEWEWluZm9AbWFpbmZsdXguY29tMB4XDTIwMDYw\nNTEyMzc1M1oXDTIwMDkxMzEyMzc1M1owVTERMA8GA1UEChMITWFpbmZsdXgxETAP\nBgNVBAsTCG1haW5mbHV4MS0wKwYDVQQDEyQyYmZlYmZmMC05ODZhLTQ3ZTAtOGQ3\nYS00YTRiN2UyYjU3OGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCn\nWvTuOIdhqOLEREcEJqfQAtDoYu3rUDijOffXuWFZgNqfZTGmoD5ZqJXxwbZ4tCST\npdSteHtyr7JXnPJQN1dsslU+q3haKjFoZRc39/7u4/8XCTwlqbMl9YVcwqS+FLkM\niLSyyqzryP7Y8H8cidTKg56p5JALaEKfzZS6Km3G+CCinR6hNNW9ckWsy29a0/9E\nMAUtM+Lsk5OjsHzOnWruuqHsCx4ODI5aJQaMC1qntkbXkht0WDiwAt9SDQ3uLWru\nAoSJDK9a6EgR3a0Jf7ZiVPiwlZNjrB/I5OQyFDGqcmSAl2rdJqPkmaDXKKFyL1cG\nMIyHv62QzJoMdRoXu20lxyGxAvEjQNVHux4LA3dbf/85nEVTI2uP8crMf2Jnzbg5\n9zF+iTMJGpUlatCyK2RJS/mvHbbUIf5Ro3VbcPHbgFroJ7qMFz0Fc5kYY8IdwXjG\nlyG9MobKEO2CfBGRjPmCuTQq2HcuOy7F6KfQf3HToI8MmC5hBtCmTNbV8I3GIjWA\n/xJQLm2pVZ41QhrnNGtuqAYoe3Zt6OldxGRcoAj7KlIpYcPZ55PJ6mWcV6dB9Fnl\n5mYOwQL8jtfybbGWvqJldhTxUqm7/EbAaF0Qjmh4oOHMl2xADrmYzJHvf0llwr6g\noRQuzqxPi0aW3tkFNsm63NX1Ab5BXFQhMSj5+82blwIDAQABo2IwYDAOBgNVHQ8B\nAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDgQH\nBAUBAgMEBjAfBgNVHSMEGDAWgBRs4xR91qEjNRGmw391xS7x6Tc+8jANBgkqhkiG\n9w0BAQsFAAOCAQEAphLT8PjawRRWswU1B5oWnnqeTllnvGB88sjDPLAG0UiBlDLX\nwoPiBVPWuYV+MMJuaREgheYF1Ahx4Jrfy9stFDU7B99ON1T58oM1aKEq4rKc+/Ke\nyxrAFTonclC0LNaaOvpZZjsPFWr2muTQO8XHiS8icw3BLxEzoF+5aJ8ihtxRtfKL\nUvtHDqC6IPAbSUcvqyjrFh3RrTUAyGOzW12IEWSXP9DLwoiLPwJ6kCVoXdG/asjz\nUpk/jj7AUn9oJNF8nUbyhdOnmeJ2z0x1ylgYrIAxvGzm8zs+NEVN67CrBYKwstlN\nvw7DRQsCvGJjZzWj28VV3FGLtXFgu52bFZNBww==\n-----END CERTIFICATE-----\n", - "thing_cert_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIJJwIBAAKCAgEAp1r07jiHYajixERHBCan0ALQ6GLt61A4ozn317lhWYDan2Ux\npqA+WaiV8cG2eLQkk6XUrXh7cq+yV5zyUDdXbLJVPqt4WioxaGUXN/f+7uP/Fwk8\nJamzJfWFXMKkvhS5DIi0ssqs68j+2PB/HInUyoOeqeSQC2hCn82Uuiptxvggop0e\noTTVvXJFrMtvWtP/RDAFLTPi7JOTo7B8zp1q7rqh7AseDgyOWiUGjAtap7ZG15Ib\ndFg4sALfUg0N7i1q7gKEiQyvWuhIEd2tCX+2YlT4sJWTY6wfyOTkMhQxqnJkgJdq\n3Saj5Jmg1yihci9XBjCMh7+tkMyaDHUaF7ttJcchsQLxI0DVR7seCwN3W3//OZxF\nUyNrj/HKzH9iZ824OfcxfokzCRqVJWrQsitkSUv5rx221CH+UaN1W3Dx24Ba6Ce6\njBc9BXOZGGPCHcF4xpchvTKGyhDtgnwRkYz5grk0Kth3Ljsuxein0H9x06CPDJgu\nYQbQpkzW1fCNxiI1gP8SUC5tqVWeNUIa5zRrbqgGKHt2bejpXcRkXKAI+ypSKWHD\n2eeTyeplnFenQfRZ5eZmDsEC/I7X8m2xlr6iZXYU8VKpu/xGwGhdEI5oeKDhzJds\nQA65mMyR739JZcK+oKEULs6sT4tGlt7ZBTbJutzV9QG+QVxUITEo+fvNm5cCAwEA\nAQKCAgAmCIfNc89gpG8Ux6eUC+zrWxh7F7CWX97fSZdH0XuMSbplqyvDgHtrCOM6\n1BlSCS6e13skCVOU1tUjECoJjOoza7vvyCxL4XblEMRcFeI8DFi2tYST0qNCJzAt\nypaCFFeRv6fBUkpGM6GnT9Czfad8drkiRy1tSj6J7sC0JlxYcZ+JFUgWvtksesHW\n6UzfSXqj1n32reoOdeOBueRDWIcqxgNyj3w/GR9o4S1BunrZzpT+/Nd8c2g+qAh0\nrz7ROEUq3iucseNQN6XZWZWvqPScGE+EYhni9wUqNMqfjvNSlzi7+K1yoQtyMm/Z\nNgSq3JNcdsAZQbiCRd1ko2BQsGm3ZBnbsAJ1Dxcn+i9nF5DT/ddWjUWin6LYWuUM\n/0Bqfv3etlrFuP6yxc8bPEMX0ucJg4yVxdkDrm1tYlJ+ANEQoOlZqhngvjz0f8uO\nOtEcDLmiG5VG6Yl72UtWIw+ALnKc5U7ib43Qve0bDAKR5zlHODcRetN9BCMvpekY\nOA4hohkllTP25xmMzLokBqY9n38zEt74kJOp67VKMvhoF7QkrLOfKWCRJjFL7/9I\nHDa6jb31INA9Wu+p/2LIa6I1SUYnMvCUqISgF2hBG9Q9S9TZvKnYUvfurhFS9jZv\n18sxW7IFYWmQyioo+gsAmfKLolJtLl9hCmTfYi7oqCh/EtZdIQKCAQEA0Umkp0Uu\nimVilLjgYGTWLcg8T3NWaELQzb2HYRXSzEq/M8GOtEr7TR7noJBm8fcgl55HEnPl\ni4cEJrr+VprzGbdMtXjHbCD+I945GA6vv3khg7mbqS9a1Uw6gjrQEZgZQU+/IVCu\n9Pbvx8Af32xaBWuN2cFzC7Z6iB815LPc2O5qyZ3+3nEUPah+Z+a9WEeTR6M0hy5c\nkkaRqhehugHDgqMRWGt8GfsFOmaR13kvfFfKadPRPkaGkftCSKBMWjrU4uX7aulm\nD7k4VDbnXIBMhI039+0znSkhZdcV1zk6qwBYn9TtZ11PTlspFPjtPxqS5M6IGflw\nsXkZGv4rZ5CkiQKCAQEAzLVdw2qw/8rWGsCV39EKp7hXLvp7+FuodPvX1L55lWB0\nvmSOldGcNvb2ZsK3RNvgteb8VfKRgaY6waeN5Qm1UXazsOX4F+GThPGHstdNuzkt\nJofRQQHQVR3npZbCngSkSZdahQ9SjiLIDKn8baPN8I8HfpJ4oHLUvkayavbch1kJ\nYWUfGtVKxHGX5m/nnxLdgbJEx9Q+3Qa7DDHuxTqsEqhkk0R0Ganred34HjpDNMs6\nV95HFNolW3yKfuHETKA1bLhej+XdMa11Ts5hBVGCMnnT07WcGhxtyK2dSa656SyT\ngT9+Hd1VWZ/KPpAkQmH9boOr2ihE+oAXiZ4D1t53HwKCAQAD0cA7fTu4Mtl1tVoC\n6FQwSbMwD/7HsFB3MLpDv041hDexDhs4lxW29pVrjLcUO1pQ6gaKA6twvGoK+uah\nVfqRwZKYzTd2dbOtm+SW183FRMSjzsNUdxTFR7rZnZEmgQwU8Quf5AUNW2RM1Oi/\n/w41gxz3mFwtHotl6IvnPJEPNGqme0enb5Da/zQvWTqjXcsGR6gxv1rZIIiP/hZp\nepbCz48FehCtuLMDudN3hzKipkd/Xuo2pLrX9ynigWpjSyePbHsGHHRMXSj2AHqA\naab71EftMlr6x0FgxmgToWu8qyjy4cPjWwSTfX5mb5SEzktX+ZzqPG8eDgOzRmgs\nX6thAoIBADL3kQG/hZQaL1Z3zpjsFggOKH7E1KrQP0/pCCKqzeC4JDjnFm0MxCUX\nNd/96N1XFUqU2QyZGUs7VPO0QOrekOtYb4LCrxNbEXyPGicX3f2YTbqDJEFYL0OR\n74PV1ly7cR/1dA8e8oH6/O3SQMwXdYXIRqhn1Wq1TGyXc4KYNe3o6CH8qFLo+fWR\nBq3T/MopS0coWGGcYY5sR5PQts8aPY9jp67W40UkfkFYV5dHEEaLttn7uJzjd1ug\n1Waj1VjypnqMKNcQ9xKQSl21mohVc+IXXPsgA16o51iIiVm4DAeXFp6ebUsIOWDY\nHOWYw75XYV7rn5TwY8Qusi2MTw5nUycCggEAB/45U0LW7ZGpks/aF/BeGaSWiLIG\nodBWUjRQ4w+Le/pTC8Ci9fiidxuCDH6TQbsUTGKOk7GsfncWHTQJogaMyO26IJ1N\nmYGgK2JJvs7PKyIkocPDVD/Yh0gIzQIE92ZdyXUT21pIYKDUB9e3p0fy/+E0pyeI\nsmsV8oaLr4tZRY1cMogI+pvtUUferbLQmZHhFd9X3m3RslR43Dl1qpYQyzE3x/a3\nWA2NJZbJhh+LiAKzqk7swXOqrTrmXuzLcjMG+T/3lizrbLLuKjQrf+eehlpw0db0\nHVVvkMLOP5ZH/ImkmvOZJY7xxup89VV7LD7TfMKwXafOrjMDdvTAYPtgxw==\n-----END RSA PRIVATE KEY-----\n" + "client_cert": "-----BEGIN CERTIFICATE-----\nMIIEmDCCA4CgAwIBAgIQCZ0NOq2oKLo+XftbAu0TfzANBgkqhkiG9w0BAQsFADBX\nMRIwEAYDVQQDDAlsb2NhbGhvc3QxETAPBgNVBAoMCE1haW5mbHV4MQwwCgYDVQQL\nDANJb1QxIDAeBgkqhkiG9w0BCQEWEWluZm9AbWFpbmZsdXguY29tMB4XDTIwMDYw\nNTEyMzc1M1oXDTIwMDkxMzEyMzc1M1owVTERMA8GA1UEChMITWFpbmZsdXgxETAP\nBgNVBAsTCG1haW5mbHV4MS0wKwYDVQQDEyQyYmZlYmZmMC05ODZhLTQ3ZTAtOGQ3\nYS00YTRiN2UyYjU3OGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCn\nWvTuOIdhqOLEREcEJqfQAtDoYu3rUDijOffXuWFZgNqfZTGmoD5ZqJXxwbZ4tCST\npdSteHtyr7JXnPJQN1dsslU+q3haKjFoZRc39/7u4/8XCTwlqbMl9YVcwqS+FLkM\niLSyyqzryP7Y8H8cidTKg56p5JALaEKfzZS6Km3G+CCinR6hNNW9ckWsy29a0/9E\nMAUtM+Lsk5OjsHzOnWruuqHsCx4ODI5aJQaMC1qntkbXkht0WDiwAt9SDQ3uLWru\nAoSJDK9a6EgR3a0Jf7ZiVPiwlZNjrB/I5OQyFDGqcmSAl2rdJqPkmaDXKKFyL1cG\nMIyHv62QzJoMdRoXu20lxyGxAvEjQNVHux4LA3dbf/85nEVTI2uP8crMf2Jnzbg5\n9zF+iTMJGpUlatCyK2RJS/mvHbbUIf5Ro3VbcPHbgFroJ7qMFz0Fc5kYY8IdwXjG\nlyG9MobKEO2CfBGRjPmCuTQq2HcuOy7F6KfQf3HToI8MmC5hBtCmTNbV8I3GIjWA\n/xJQLm2pVZ41QhrnNGtuqAYoe3Zt6OldxGRcoAj7KlIpYcPZ55PJ6mWcV6dB9Fnl\n5mYOwQL8jtfybbGWvqJldhTxUqm7/EbAaF0Qjmh4oOHMl2xADrmYzJHvf0llwr6g\noRQuzqxPi0aW3tkFNsm63NX1Ab5BXFQhMSj5+82blwIDAQABo2IwYDAOBgNVHQ8B\nAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDgQH\nBAUBAgMEBjAfBgNVHSMEGDAWgBRs4xR91qEjNRGmw391xS7x6Tc+8jANBgkqhkiG\n9w0BAQsFAAOCAQEAphLT8PjawRRWswU1B5oWnnqeTllnvGB88sjDPLAG0UiBlDLX\nwoPiBVPWuYV+MMJuaREgheYF1Ahx4Jrfy9stFDU7B99ON1T58oM1aKEq4rKc+/Ke\nyxrAFTonclC0LNaaOvpZZjsPFWr2muTQO8XHiS8icw3BLxEzoF+5aJ8ihtxRtfKL\nUvtHDqC6IPAbSUcvqyjrFh3RrTUAyGOzW12IEWSXP9DLwoiLPwJ6kCVoXdG/asjz\nUpk/jj7AUn9oJNF8nUbyhdOnmeJ2z0x1ylgYrIAxvGzm8zs+NEVN67CrBYKwstlN\nvw7DRQsCvGJjZzWj28VV3FGLtXFgu52bFZNBww==\n-----END CERTIFICATE-----\n", + "client_cert_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIJJwIBAAKCAgEAp1r07jiHYajixERHBCan0ALQ6GLt61A4ozn317lhWYDan2Ux\npqA+WaiV8cG2eLQkk6XUrXh7cq+yV5zyUDdXbLJVPqt4WioxaGUXN/f+7uP/Fwk8\nJamzJfWFXMKkvhS5DIi0ssqs68j+2PB/HInUyoOeqeSQC2hCn82Uuiptxvggop0e\noTTVvXJFrMtvWtP/RDAFLTPi7JOTo7B8zp1q7rqh7AseDgyOWiUGjAtap7ZG15Ib\ndFg4sALfUg0N7i1q7gKEiQyvWuhIEd2tCX+2YlT4sJWTY6wfyOTkMhQxqnJkgJdq\n3Saj5Jmg1yihci9XBjCMh7+tkMyaDHUaF7ttJcchsQLxI0DVR7seCwN3W3//OZxF\nUyNrj/HKzH9iZ824OfcxfokzCRqVJWrQsitkSUv5rx221CH+UaN1W3Dx24Ba6Ce6\njBc9BXOZGGPCHcF4xpchvTKGyhDtgnwRkYz5grk0Kth3Ljsuxein0H9x06CPDJgu\nYQbQpkzW1fCNxiI1gP8SUC5tqVWeNUIa5zRrbqgGKHt2bejpXcRkXKAI+ypSKWHD\n2eeTyeplnFenQfRZ5eZmDsEC/I7X8m2xlr6iZXYU8VKpu/xGwGhdEI5oeKDhzJds\nQA65mMyR739JZcK+oKEULs6sT4tGlt7ZBTbJutzV9QG+QVxUITEo+fvNm5cCAwEA\nAQKCAgAmCIfNc89gpG8Ux6eUC+zrWxh7F7CWX97fSZdH0XuMSbplqyvDgHtrCOM6\n1BlSCS6e13skCVOU1tUjECoJjOoza7vvyCxL4XblEMRcFeI8DFi2tYST0qNCJzAt\nypaCFFeRv6fBUkpGM6GnT9Czfad8drkiRy1tSj6J7sC0JlxYcZ+JFUgWvtksesHW\n6UzfSXqj1n32reoOdeOBueRDWIcqxgNyj3w/GR9o4S1BunrZzpT+/Nd8c2g+qAh0\nrz7ROEUq3iucseNQN6XZWZWvqPScGE+EYhni9wUqNMqfjvNSlzi7+K1yoQtyMm/Z\nNgSq3JNcdsAZQbiCRd1ko2BQsGm3ZBnbsAJ1Dxcn+i9nF5DT/ddWjUWin6LYWuUM\n/0Bqfv3etlrFuP6yxc8bPEMX0ucJg4yVxdkDrm1tYlJ+ANEQoOlZqhngvjz0f8uO\nOtEcDLmiG5VG6Yl72UtWIw+ALnKc5U7ib43Qve0bDAKR5zlHODcRetN9BCMvpekY\nOA4hohkllTP25xmMzLokBqY9n38zEt74kJOp67VKMvhoF7QkrLOfKWCRJjFL7/9I\nHDa6jb31INA9Wu+p/2LIa6I1SUYnMvCUqISgF2hBG9Q9S9TZvKnYUvfurhFS9jZv\n18sxW7IFYWmQyioo+gsAmfKLolJtLl9hCmTfYi7oqCh/EtZdIQKCAQEA0Umkp0Uu\nimVilLjgYGTWLcg8T3NWaELQzb2HYRXSzEq/M8GOtEr7TR7noJBm8fcgl55HEnPl\ni4cEJrr+VprzGbdMtXjHbCD+I945GA6vv3khg7mbqS9a1Uw6gjrQEZgZQU+/IVCu\n9Pbvx8Af32xaBWuN2cFzC7Z6iB815LPc2O5qyZ3+3nEUPah+Z+a9WEeTR6M0hy5c\nkkaRqhehugHDgqMRWGt8GfsFOmaR13kvfFfKadPRPkaGkftCSKBMWjrU4uX7aulm\nD7k4VDbnXIBMhI039+0znSkhZdcV1zk6qwBYn9TtZ11PTlspFPjtPxqS5M6IGflw\nsXkZGv4rZ5CkiQKCAQEAzLVdw2qw/8rWGsCV39EKp7hXLvp7+FuodPvX1L55lWB0\nvmSOldGcNvb2ZsK3RNvgteb8VfKRgaY6waeN5Qm1UXazsOX4F+GThPGHstdNuzkt\nJofRQQHQVR3npZbCngSkSZdahQ9SjiLIDKn8baPN8I8HfpJ4oHLUvkayavbch1kJ\nYWUfGtVKxHGX5m/nnxLdgbJEx9Q+3Qa7DDHuxTqsEqhkk0R0Ganred34HjpDNMs6\nV95HFNolW3yKfuHETKA1bLhej+XdMa11Ts5hBVGCMnnT07WcGhxtyK2dSa656SyT\ngT9+Hd1VWZ/KPpAkQmH9boOr2ihE+oAXiZ4D1t53HwKCAQAD0cA7fTu4Mtl1tVoC\n6FQwSbMwD/7HsFB3MLpDv041hDexDhs4lxW29pVrjLcUO1pQ6gaKA6twvGoK+uah\nVfqRwZKYzTd2dbOtm+SW183FRMSjzsNUdxTFR7rZnZEmgQwU8Quf5AUNW2RM1Oi/\n/w41gxz3mFwtHotl6IvnPJEPNGqme0enb5Da/zQvWTqjXcsGR6gxv1rZIIiP/hZp\nepbCz48FehCtuLMDudN3hzKipkd/Xuo2pLrX9ynigWpjSyePbHsGHHRMXSj2AHqA\naab71EftMlr6x0FgxmgToWu8qyjy4cPjWwSTfX5mb5SEzktX+ZzqPG8eDgOzRmgs\nX6thAoIBADL3kQG/hZQaL1Z3zpjsFggOKH7E1KrQP0/pCCKqzeC4JDjnFm0MxCUX\nNd/96N1XFUqU2QyZGUs7VPO0QOrekOtYb4LCrxNbEXyPGicX3f2YTbqDJEFYL0OR\n74PV1ly7cR/1dA8e8oH6/O3SQMwXdYXIRqhn1Wq1TGyXc4KYNe3o6CH8qFLo+fWR\nBq3T/MopS0coWGGcYY5sR5PQts8aPY9jp67W40UkfkFYV5dHEEaLttn7uJzjd1ug\n1Waj1VjypnqMKNcQ9xKQSl21mohVc+IXXPsgA16o51iIiVm4DAeXFp6ebUsIOWDY\nHOWYw75XYV7rn5TwY8Qusi2MTw5nUycCggEAB/45U0LW7ZGpks/aF/BeGaSWiLIG\nodBWUjRQ4w+Le/pTC8Ci9fiidxuCDH6TQbsUTGKOk7GsfncWHTQJogaMyO26IJ1N\nmYGgK2JJvs7PKyIkocPDVD/Yh0gIzQIE92ZdyXUT21pIYKDUB9e3p0fy/+E0pyeI\nsmsV8oaLr4tZRY1cMogI+pvtUUferbLQmZHhFd9X3m3RslR43Dl1qpYQyzE3x/a3\nWA2NJZbJhh+LiAKzqk7swXOqrTrmXuzLcjMG+T/3lizrbLLuKjQrf+eehlpw0db0\nHVVvkMLOP5ZH/ImkmvOZJY7xxup89VV7LD7TfMKwXafOrjMDdvTAYPtgxw==\n-----END RSA PRIVATE KEY-----\n" } ``` diff --git a/provision/api/endpoint.go b/provision/api/endpoint.go index ec21527a5e..fbd132268d 100644 --- a/provision/api/endpoint.go +++ b/provision/api/endpoint.go @@ -25,7 +25,7 @@ func doProvision(svc provision.Service) endpoint.Endpoint { } provisionResponse := provisionRes{ - Things: res.Things, + Clients: res.Clients, Channels: res.Channels, ClientCert: res.ClientCert, ClientKey: res.ClientKey, diff --git a/provision/api/logging.go b/provision/api/logging.go index 4d19af3c07..2156c722f9 100644 --- a/provision/api/logging.go +++ b/provision/api/logging.go @@ -42,22 +42,22 @@ func (lm *loggingMiddleware) Provision(domainID, token, name, externalID, extern return lm.svc.Provision(domainID, token, name, externalID, externalKey) } -func (lm *loggingMiddleware) Cert(domainID, token, thingID, duration string) (cert, key string, err error) { +func (lm *loggingMiddleware) Cert(domainID, token, clientID, duration string) (cert, key string, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", thingID), + slog.String("client_id", clientID), slog.String("ttl", duration), } if err != nil { args = append(args, slog.Any("error", err)) - lm.logger.Warn("Thing certificate failed to create successfully", args...) + lm.logger.Warn("Client certificate failed to create successfully", args...) return } - lm.logger.Info("Thing certificate created successfully", args...) + lm.logger.Info("Client certificate created successfully", args...) }(time.Now()) - return lm.svc.Cert(domainID, token, thingID, duration) + return lm.svc.Cert(domainID, token, clientID, duration) } func (lm *loggingMiddleware) Mapping(token string) (res map[string]interface{}, err error) { diff --git a/provision/api/responses.go b/provision/api/responses.go index 87c105225c..6dcaa77774 100644 --- a/provision/api/responses.go +++ b/provision/api/responses.go @@ -14,7 +14,7 @@ import ( var _ magistrala.Response = (*provisionRes)(nil) type provisionRes struct { - Things []sdk.Thing `json:"things"` + Clients []sdk.Client `json:"clients"` Channels []sdk.Channel `json:"channels"` ClientCert map[string]string `json:"client_cert,omitempty"` ClientKey map[string]string `json:"client_key,omitempty"` diff --git a/provision/config.go b/provision/config.go index 7540e44015..1078e85a3d 100644 --- a/provision/config.go +++ b/provision/config.go @@ -7,9 +7,9 @@ import ( "fmt" "os" + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/things" "github.com/pelletier/go-toml" ) @@ -22,7 +22,7 @@ type ServiceConf struct { TLS bool `toml:"tls" env:"MG_PROVISION_ENV_CLIENTS_TLS" envDefault:"false"` ServerCert string `toml:"server_cert" env:"MG_PROVISION_SERVER_CERT" envDefault:""` ServerKey string `toml:"server_key" env:"MG_PROVISION_SERVER_KEY" envDefault:""` - ThingsURL string `toml:"things_url" env:"MG_PROVISION_THINGS_LOCATION" envDefault:"http://localhost"` + ClientsURL string `toml:"clients_url" env:"MG_PROVISION_CLIENTS_LOCATION" envDefault:"http://localhost"` UsersURL string `toml:"users_url" env:"MG_PROVISION_USERS_LOCATION" envDefault:"http://localhost"` HTTPPort string `toml:"http_port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"` MgEmail string `toml:"mg_email" env:"MG_PROVISION_EMAIL" envDefault:"test@example.com"` @@ -60,15 +60,15 @@ type Cert struct { // Config struct of Provision. type Config struct { - File string `toml:"file" env:"MG_PROVISION_CONFIG_FILE" envDefault:"config.toml"` - Server ServiceConf `toml:"server" mapstructure:"server"` - Bootstrap Bootstrap `toml:"bootstrap" mapstructure:"bootstrap"` - Things []things.Client `toml:"things" mapstructure:"things"` - Channels []groups.Group `toml:"channels" mapstructure:"channels"` - Cert Cert `toml:"cert" mapstructure:"cert"` - BSContent string `env:"MG_PROVISION_BS_CONTENT" envDefault:""` - SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"MG_MQTT_ADAPTER_INSTANCE_ID" envDefault:""` + File string `toml:"file" env:"MG_PROVISION_CONFIG_FILE" envDefault:"config.toml"` + Server ServiceConf `toml:"server" mapstructure:"server"` + Bootstrap Bootstrap `toml:"bootstrap" mapstructure:"bootstrap"` + Clients []clients.Client `toml:"clients" mapstructure:"clients"` + Channels []channels.Channel `toml:"channels" mapstructure:"channels"` + Cert Cert `toml:"cert" mapstructure:"cert"` + BSContent string `env:"MG_PROVISION_BS_CONTENT" envDefault:""` + SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"MG_MQTT_ADAPTER_INSTANCE_ID" envDefault:""` } // Save - store config in a file. diff --git a/provision/config_test.go b/provision/config_test.go index 6857b82653..13b6404cd1 100644 --- a/provision/config_test.go +++ b/provision/config_test.go @@ -8,10 +8,10 @@ import ( "os" "testing" + "github.com/absmach/magistrala/channels" + "github.com/absmach/magistrala/clients" "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/provision" - "github.com/absmach/magistrala/things" "github.com/pelletier/go-toml" "github.com/stretchr/testify/assert" ) @@ -31,7 +31,7 @@ var ( "test": "test", }, }, - Things: []things.Client{ + Clients: []clients.Client{ { ID: "1234567890", Name: "test", @@ -42,7 +42,7 @@ var ( Permissions: []string{"test"}, }, }, - Channels: []groups.Group{ + Channels: []channels.Channel{ { ID: "1234567890", Name: "test", diff --git a/provision/configs/config.toml b/provision/configs/config.toml index 38455eb239..650ed35186 100644 --- a/provision/configs/config.toml +++ b/provision/configs/config.toml @@ -23,14 +23,14 @@ file = "config.toml" port = "" server_cert = "" server_key = "" - things_location = "http://localhost:9000" + clients_location = "http://localhost:9006" tls = true users_location = "" -[[things]] - name = "thing" +[[clients]] + name = "client" - [things.metadata] + [client.metadata] external_id = "xxxxxx" diff --git a/provision/mocks/service.go b/provision/mocks/service.go index ff45e5faca..1ee4a20065 100644 --- a/provision/mocks/service.go +++ b/provision/mocks/service.go @@ -14,9 +14,9 @@ type Service struct { mock.Mock } -// Cert provides a mock function with given fields: domainID, token, thingID, duration -func (_m *Service) Cert(domainID string, token string, thingID string, duration string) (string, string, error) { - ret := _m.Called(domainID, token, thingID, duration) +// Cert provides a mock function with given fields: domainID, token, clientID, duration +func (_m *Service) Cert(domainID string, token string, clientID string, duration string) (string, string, error) { + ret := _m.Called(domainID, token, clientID, duration) if len(ret) == 0 { panic("no return value specified for Cert") @@ -26,22 +26,22 @@ func (_m *Service) Cert(domainID string, token string, thingID string, duration var r1 string var r2 error if rf, ok := ret.Get(0).(func(string, string, string, string) (string, string, error)); ok { - return rf(domainID, token, thingID, duration) + return rf(domainID, token, clientID, duration) } if rf, ok := ret.Get(0).(func(string, string, string, string) string); ok { - r0 = rf(domainID, token, thingID, duration) + r0 = rf(domainID, token, clientID, duration) } else { r0 = ret.Get(0).(string) } if rf, ok := ret.Get(1).(func(string, string, string, string) string); ok { - r1 = rf(domainID, token, thingID, duration) + r1 = rf(domainID, token, clientID, duration) } else { r1 = ret.Get(1).(string) } if rf, ok := ret.Get(2).(func(string, string, string, string) error); ok { - r2 = rf(domainID, token, thingID, duration) + r2 = rf(domainID, token, clientID, duration) } else { r2 = ret.Error(2) } diff --git a/provision/service.go b/provision/service.go index 228586aa7b..be0ec763cc 100644 --- a/provision/service.go +++ b/provision/service.go @@ -25,13 +25,13 @@ const ( var ( ErrUnauthorized = errors.New("unauthorized access") ErrFailedToCreateToken = errors.New("failed to create access token") - ErrEmptyThingsList = errors.New("things list in configuration empty") - ErrThingUpdate = errors.New("failed to update thing") + ErrEmptyClientsList = errors.New("clients list in configuration empty") + ErrClientUpdate = errors.New("failed to update client") ErrEmptyChannelsList = errors.New("channels list in configuration is empty") ErrFailedChannelCreation = errors.New("failed to create channel") ErrFailedChannelRetrieval = errors.New("failed to retrieve channel") - ErrFailedThingCreation = errors.New("failed to create thing") - ErrFailedThingRetrieval = errors.New("failed to retrieve thing") + ErrFailedClientCreation = errors.New("failed to create client") + ErrFailedClientRetrieval = errors.New("failed to retrieve client") ErrMissingCredentials = errors.New("missing credentials") ErrFailedBootstrapRetrieval = errors.New("failed to retrieve bootstrap") ErrFailedCertCreation = errors.New("failed to create certificates") @@ -52,10 +52,10 @@ var _ Service = (*provisionService)(nil) type Service interface { // Provision is the only method this API specifies. Depending on the configuration, // the following actions will can be executed: - // - create a Thing based on external_id (eg. MAC address) + // - create a Client based on external_id (eg. MAC address) // - create multiple Channels // - create Bootstrap configuration - // - whitelist Thing in Bootstrap configuration == connect Thing to Channels + // - whitelist Client in Bootstrap configuration == connect Client to Channels Provision(domainID, token, name, externalID, externalKey string) (Result, error) // Mapping returns current configuration used for provision @@ -63,11 +63,11 @@ type Service interface { // one created with Provision method. Mapping(token string) (map[string]interface{}, error) - // Certs creates certificate for things that communicate over mTLS + // Certs creates certificate for clients that communicate over mTLS // A duration string is a possibly signed sequence of decimal numbers, // each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". - Cert(domainID, token, thingID, duration string) (string, string, error) + Cert(domainID, token, clientID, duration string) (string, string, error) } type provisionService struct { @@ -78,7 +78,7 @@ type provisionService struct { // Result represent what is created with additional info. type Result struct { - Things []sdk.Thing `json:"things,omitempty"` + Clients []sdk.Client `json:"clients,omitempty"` Channels []sdk.Channel `json:"channels,omitempty"` ClientCert map[string]string `json:"client_cert,omitempty"` ClientKey map[string]string `json:"client_key,omitempty"` @@ -114,47 +114,47 @@ func (ps *provisionService) Mapping(token string) (map[string]interface{}, error // provision layout specified in config.toml. func (ps *provisionService) Provision(domainID, token, name, externalID, externalKey string) (res Result, err error) { var channels []sdk.Channel - var things []sdk.Thing - defer ps.recover(&err, &things, &channels, &domainID, &token) + var clients []sdk.Client + defer ps.recover(&err, &clients, &channels, &domainID, &token) token, err = ps.createTokenIfEmpty(token) if err != nil { return res, errors.Wrap(ErrFailedToCreateToken, err) } - if len(ps.conf.Things) == 0 { - return res, ErrEmptyThingsList + if len(ps.conf.Clients) == 0 { + return res, ErrEmptyClientsList } if len(ps.conf.Channels) == 0 { return res, ErrEmptyChannelsList } - for _, thing := range ps.conf.Things { - // If thing in configs contains metadata with external_id + for _, c := range ps.conf.Clients { + // If client in configs contains metadata with external_id // set value for it from the provision request - if _, ok := thing.Metadata[externalIDKey]; ok { - thing.Metadata[externalIDKey] = externalID + if _, ok := c.Metadata[externalIDKey]; ok { + c.Metadata[externalIDKey] = externalID } - th := sdk.Thing{ - Metadata: thing.Metadata, + cli := sdk.Client{ + Metadata: c.Metadata, } if name == "" { - name = thing.Name + name = c.Name } - th.Name = name - th, err := ps.sdk.CreateThing(th, domainID, token) + cli.Name = name + cli, err := ps.sdk.CreateClient(cli, domainID, token) if err != nil { res.Error = err.Error() - return res, errors.Wrap(ErrFailedThingCreation, err) + return res, errors.Wrap(ErrFailedClientCreation, err) } - // Get newly created thing (in order to get the key). - th, err = ps.sdk.Thing(th.ID, domainID, token) + // Get newly created client (in order to get the key). + cli, err = ps.sdk.Client(cli.ID, domainID, token) if err != nil { - e := errors.Wrap(err, fmt.Errorf("thing id: %s", th.ID)) - return res, errors.Wrap(ErrFailedThingRetrieval, e) + e := errors.Wrap(err, fmt.Errorf("client id: %s", cli.ID)) + return res, errors.Wrap(ErrFailedClientRetrieval, e) } - things = append(things, th) + clients = append(clients, cli) } for _, channel := range ps.conf.Channels { @@ -175,7 +175,7 @@ func (ps *provisionService) Provision(domainID, token, name, externalID, externa } res = Result{ - Things: things, + Clients: clients, Channels: channels, Whitelisted: map[string]bool{}, ClientCert: map[string]string{}, @@ -184,7 +184,7 @@ func (ps *provisionService) Provision(domainID, token, name, externalID, externa var cert sdk.Cert var bsConfig sdk.BootstrapConfig - for _, thing := range things { + for _, c := range clients { var chanIDs []string for _, ch := range channels { @@ -195,9 +195,9 @@ func (ps *provisionService) Provision(domainID, token, name, externalID, externa return Result{}, errors.Wrap(ErrFailedBootstrap, err) } - if ps.conf.Bootstrap.Provision && needsBootstrap(thing) { + if ps.conf.Bootstrap.Provision && needsBootstrap(c) { bsReq := sdk.BootstrapConfig{ - ThingID: thing.ID, + ClientID: c.ID, ExternalID: externalID, ExternalKey: externalKey, Channels: chanIDs, @@ -220,9 +220,9 @@ func (ps *provisionService) Provision(domainID, token, name, externalID, externa if ps.conf.Bootstrap.X509Provision { var cert sdk.Cert - cert, err = ps.sdk.IssueCert(thing.ID, ps.conf.Cert.TTL, domainID, token) + cert, err = ps.sdk.IssueCert(c.ID, ps.conf.Cert.TTL, domainID, token) if err != nil { - e := errors.Wrap(err, fmt.Errorf("thing id: %s", thing.ID)) + e := errors.Wrap(err, fmt.Errorf("client id: %s", c.ID)) return res, errors.Wrap(ErrFailedCertCreation, e) } cert, err := ps.sdk.ViewCert(cert.SerialNumber, domainID, token) @@ -230,23 +230,23 @@ func (ps *provisionService) Provision(domainID, token, name, externalID, externa return res, errors.Wrap(ErrFailedCertView, err) } - res.ClientCert[thing.ID] = cert.Certificate - res.ClientKey[thing.ID] = cert.Key + res.ClientCert[c.ID] = cert.Certificate + res.ClientKey[c.ID] = cert.Key res.CACert = "" - if needsBootstrap(thing) { - if _, err = ps.sdk.UpdateBootstrapCerts(bsConfig.ThingID, cert.Certificate, cert.Key, "", domainID, token); err != nil { + if needsBootstrap(c) { + if _, err = ps.sdk.UpdateBootstrapCerts(bsConfig.ClientID, cert.Certificate, cert.Key, "", domainID, token); err != nil { return Result{}, errors.Wrap(ErrFailedCertCreation, err) } } } if ps.conf.Bootstrap.AutoWhiteList { - if err := ps.sdk.Whitelist(thing.ID, Active, domainID, token); err != nil { + if err := ps.sdk.Whitelist(c.ID, Active, domainID, token); err != nil { res.Error = err.Error() - return res, ErrThingUpdate + return res, ErrClientUpdate } - res.Whitelisted[thing.ID] = true + res.Whitelisted[c.ID] = true } } @@ -256,13 +256,13 @@ func (ps *provisionService) Provision(domainID, token, name, externalID, externa return res, nil } -func (ps *provisionService) Cert(domainID, token, thingID, ttl string) (string, string, error) { +func (ps *provisionService) Cert(domainID, token, clientID, ttl string) (string, string, error) { token, err := ps.createTokenIfEmpty(token) if err != nil { return "", "", errors.Wrap(ErrFailedToCreateToken, err) } - th, err := ps.sdk.Thing(thingID, domainID, token) + th, err := ps.sdk.Client(clientID, domainID, token) if err != nil { return "", "", errors.Wrap(ErrUnauthorized, err) } @@ -319,10 +319,10 @@ func (ps *provisionService) updateGateway(domainID, token string, bs sdk.Bootstr } gw.ExternalID = bs.ExternalID gw.ExternalKey = bs.ExternalKey - gw.CfgID = bs.ThingID + gw.CfgID = bs.ClientID gw.Type = gateway - th, sdkerr := ps.sdk.Thing(bs.ThingID, domainID, token) + c, sdkerr := ps.sdk.Client(bs.ClientID, domainID, token) if sdkerr != nil { return errors.Wrap(ErrGatewayUpdate, sdkerr) } @@ -330,10 +330,10 @@ func (ps *provisionService) updateGateway(domainID, token string, bs sdk.Bootstr if err != nil { return errors.Wrap(ErrGatewayUpdate, err) } - if err := json.Unmarshal(b, &th.Metadata); err != nil { + if err := json.Unmarshal(b, &c.Metadata); err != nil { return errors.Wrap(ErrGatewayUpdate, err) } - if _, err := ps.sdk.UpdateThing(th, domainID, token); err != nil { + if _, err := ps.sdk.UpdateClient(c, domainID, token); err != nil { return errors.Wrap(ErrGatewayUpdate, err) } return nil @@ -345,9 +345,9 @@ func (ps *provisionService) errLog(err error) { } } -func clean(ps *provisionService, things []sdk.Thing, channels []sdk.Channel, domainID, token string) { - for _, t := range things { - err := ps.sdk.DeleteThing(t.ID, domainID, token) +func clean(ps *provisionService, clients []sdk.Client, channels []sdk.Channel, domainID, token string) { + for _, t := range clients { + err := ps.sdk.DeleteClient(t.ID, domainID, token) ps.errLog(err) } for _, c := range channels { @@ -356,28 +356,28 @@ func clean(ps *provisionService, things []sdk.Thing, channels []sdk.Channel, dom } } -func (ps *provisionService) recover(e *error, ths *[]sdk.Thing, chs *[]sdk.Channel, dm, tkn *string) { +func (ps *provisionService) recover(e *error, ths *[]sdk.Client, chs *[]sdk.Channel, dm, tkn *string) { if e == nil { return } - things, channels, domainID, token, err := *ths, *chs, *dm, *tkn, *e + clients, channels, domainID, token, err := *ths, *chs, *dm, *tkn, *e - if errors.Contains(err, ErrFailedThingRetrieval) || errors.Contains(err, ErrFailedChannelCreation) { - for _, th := range things { - err := ps.sdk.DeleteThing(th.ID, domainID, token) + if errors.Contains(err, ErrFailedClientRetrieval) || errors.Contains(err, ErrFailedChannelCreation) { + for _, c := range clients { + err := ps.sdk.DeleteClient(c.ID, domainID, token) ps.errLog(err) } return } if errors.Contains(err, ErrFailedBootstrap) || errors.Contains(err, ErrFailedChannelRetrieval) { - clean(ps, things, channels, domainID, token) + clean(ps, clients, channels, domainID, token) return } if errors.Contains(err, ErrFailedBootstrapValidate) || errors.Contains(err, ErrFailedCertCreation) { - clean(ps, things, channels, domainID, token) - for _, th := range things { + clean(ps, clients, channels, domainID, token) + for _, th := range clients { if needsBootstrap(th) { ps.errLog(ps.sdk.RemoveBootstrap(th.ID, domainID, token)) } @@ -386,19 +386,19 @@ func (ps *provisionService) recover(e *error, ths *[]sdk.Thing, chs *[]sdk.Chann } if errors.Contains(err, ErrFailedBootstrapValidate) || errors.Contains(err, ErrFailedCertCreation) { - clean(ps, things, channels, domainID, token) - for _, th := range things { + clean(ps, clients, channels, domainID, token) + for _, th := range clients { if needsBootstrap(th) { bs, err := ps.sdk.ViewBootstrap(th.ID, domainID, token) ps.errLog(errors.Wrap(ErrFailedBootstrapRetrieval, err)) - ps.errLog(ps.sdk.RemoveBootstrap(bs.ThingID, domainID, token)) + ps.errLog(ps.sdk.RemoveBootstrap(bs.ClientID, domainID, token)) } } } - if errors.Contains(err, ErrThingUpdate) || errors.Contains(err, ErrGatewayUpdate) { - clean(ps, things, channels, domainID, token) - for _, th := range things { + if errors.Contains(err, ErrClientUpdate) || errors.Contains(err, ErrGatewayUpdate) { + clean(ps, clients, channels, domainID, token) + for _, th := range clients { if ps.conf.Bootstrap.X509Provision && needsBootstrap(th) { _, err := ps.sdk.RevokeCert(th.ID, domainID, token) ps.errLog(err) @@ -406,14 +406,14 @@ func (ps *provisionService) recover(e *error, ths *[]sdk.Thing, chs *[]sdk.Chann if needsBootstrap(th) { bs, err := ps.sdk.ViewBootstrap(th.ID, domainID, token) ps.errLog(errors.Wrap(ErrFailedBootstrapRetrieval, err)) - ps.errLog(ps.sdk.RemoveBootstrap(bs.ThingID, domainID, token)) + ps.errLog(ps.sdk.RemoveBootstrap(bs.ClientID, domainID, token)) } } return } } -func needsBootstrap(th sdk.Thing) bool { +func needsBootstrap(th sdk.Client) bool { if th.Metadata == nil { return false } diff --git a/provision/service_test.go b/provision/service_test.go index 4e3fd314bf..70c8f46524 100644 --- a/provision/service_test.go +++ b/provision/service_test.go @@ -62,33 +62,33 @@ func TestMapping(t *testing.T) { func TestCert(t *testing.T) { cases := []struct { - desc string - config provision.Config - domainID string - token string - thingID string - ttl string - serial string - cert string - key string - sdkThingErr error - sdkCertErr error - sdkTokenErr error - err error + desc string + config provision.Config + domainID string + token string + clientID string + ttl string + serial string + cert string + key string + sdkClientErr error + sdkCertErr error + sdkTokenErr error + err error }{ { - desc: "valid", - config: validConfig, - domainID: testsutil.GenerateUUID(t), - token: validToken, - thingID: testsutil.GenerateUUID(t), - ttl: "1h", - cert: "cert", - key: "key", - sdkThingErr: nil, - sdkCertErr: nil, - sdkTokenErr: nil, - err: nil, + desc: "valid", + config: validConfig, + domainID: testsutil.GenerateUUID(t), + token: validToken, + clientID: testsutil.GenerateUUID(t), + ttl: "1h", + cert: "cert", + key: "key", + sdkClientErr: nil, + sdkCertErr: nil, + sdkTokenErr: nil, + err: nil, }, { desc: "empty token with config API key", @@ -96,16 +96,16 @@ func TestCert(t *testing.T) { Server: provision.ServiceConf{MgAPIKey: "key"}, Cert: provision.Cert{TTL: "1h"}, }, - domainID: testsutil.GenerateUUID(t), - token: "", - thingID: testsutil.GenerateUUID(t), - ttl: "1h", - cert: "cert", - key: "key", - sdkThingErr: nil, - sdkCertErr: nil, - sdkTokenErr: nil, - err: nil, + domainID: testsutil.GenerateUUID(t), + token: "", + clientID: testsutil.GenerateUUID(t), + ttl: "1h", + cert: "cert", + key: "key", + sdkClientErr: nil, + sdkCertErr: nil, + sdkTokenErr: nil, + err: nil, }, { desc: "empty token with username and password", @@ -117,16 +117,16 @@ func TestCert(t *testing.T) { }, Cert: provision.Cert{TTL: "1h"}, }, - domainID: testsutil.GenerateUUID(t), - token: "", - thingID: testsutil.GenerateUUID(t), - ttl: "1h", - cert: "cert", - key: "key", - sdkThingErr: nil, - sdkCertErr: nil, - sdkTokenErr: nil, - err: nil, + domainID: testsutil.GenerateUUID(t), + token: "", + clientID: testsutil.GenerateUUID(t), + ttl: "1h", + cert: "cert", + key: "key", + sdkClientErr: nil, + sdkCertErr: nil, + sdkTokenErr: nil, + err: nil, }, { desc: "empty token with username and invalid password", @@ -138,16 +138,16 @@ func TestCert(t *testing.T) { }, Cert: provision.Cert{TTL: "1h"}, }, - domainID: testsutil.GenerateUUID(t), - token: "", - thingID: testsutil.GenerateUUID(t), - ttl: "1h", - cert: "", - key: "", - sdkThingErr: nil, - sdkCertErr: nil, - sdkTokenErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401), - err: provision.ErrFailedToCreateToken, + domainID: testsutil.GenerateUUID(t), + token: "", + clientID: testsutil.GenerateUUID(t), + ttl: "1h", + cert: "", + key: "", + sdkClientErr: nil, + sdkCertErr: nil, + sdkTokenErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401), + err: provision.ErrFailedToCreateToken, }, { desc: "empty token with empty username and password", @@ -155,58 +155,58 @@ func TestCert(t *testing.T) { Server: provision.ServiceConf{}, Cert: provision.Cert{TTL: "1h"}, }, - domainID: testsutil.GenerateUUID(t), - token: "", - thingID: testsutil.GenerateUUID(t), - ttl: "1h", - cert: "", - key: "", - sdkThingErr: nil, - sdkCertErr: nil, - sdkTokenErr: nil, - err: provision.ErrMissingCredentials, + domainID: testsutil.GenerateUUID(t), + token: "", + clientID: testsutil.GenerateUUID(t), + ttl: "1h", + cert: "", + key: "", + sdkClientErr: nil, + sdkCertErr: nil, + sdkTokenErr: nil, + err: provision.ErrMissingCredentials, }, { - desc: "invalid thingID", - config: validConfig, - domainID: testsutil.GenerateUUID(t), - token: "invalid", - thingID: testsutil.GenerateUUID(t), - ttl: "1h", - cert: "", - key: "", - sdkThingErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401), - sdkCertErr: nil, - sdkTokenErr: nil, - err: provision.ErrUnauthorized, + desc: "invalid clientID", + config: validConfig, + domainID: testsutil.GenerateUUID(t), + token: "invalid", + clientID: testsutil.GenerateUUID(t), + ttl: "1h", + cert: "", + key: "", + sdkClientErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401), + sdkCertErr: nil, + sdkTokenErr: nil, + err: provision.ErrUnauthorized, }, { - desc: "invalid thingID", - config: validConfig, - domainID: testsutil.GenerateUUID(t), - token: validToken, - thingID: "invalid", - ttl: "1h", - cert: "", - key: "", - sdkThingErr: errors.NewSDKErrorWithStatus(repoerr.ErrNotFound, 404), - sdkCertErr: nil, - sdkTokenErr: nil, - err: provision.ErrUnauthorized, + desc: "invalid clientID", + config: validConfig, + domainID: testsutil.GenerateUUID(t), + token: validToken, + clientID: "invalid", + ttl: "1h", + cert: "", + key: "", + sdkClientErr: errors.NewSDKErrorWithStatus(repoerr.ErrNotFound, 404), + sdkCertErr: nil, + sdkTokenErr: nil, + err: provision.ErrUnauthorized, }, { - desc: "failed to issue cert", - config: validConfig, - domainID: testsutil.GenerateUUID(t), - token: validToken, - thingID: testsutil.GenerateUUID(t), - ttl: "1h", - cert: "", - key: "", - sdkThingErr: nil, - sdkTokenErr: nil, - sdkCertErr: errors.NewSDKError(repoerr.ErrCreateEntity), - err: repoerr.ErrCreateEntity, + desc: "failed to issue cert", + config: validConfig, + domainID: testsutil.GenerateUUID(t), + token: validToken, + clientID: testsutil.GenerateUUID(t), + ttl: "1h", + cert: "", + key: "", + sdkClientErr: nil, + sdkTokenErr: nil, + sdkCertErr: errors.NewSDKError(repoerr.ErrCreateEntity), + err: repoerr.ErrCreateEntity, }, } @@ -215,15 +215,15 @@ func TestCert(t *testing.T) { mgsdk := new(sdkmocks.SDK) svc := provision.New(c.config, mgsdk, mglog.NewMock()) - mgsdk.On("Thing", c.thingID, c.domainID, mock.Anything).Return(sdk.Thing{ID: c.thingID}, c.sdkThingErr) - mgsdk.On("IssueCert", c.thingID, c.config.Cert.TTL, c.domainID, mock.Anything).Return(sdk.Cert{SerialNumber: c.serial}, c.sdkCertErr) + mgsdk.On("Client", c.clientID, c.domainID, mock.Anything).Return(sdk.Client{ID: c.clientID}, c.sdkClientErr) + mgsdk.On("IssueCert", c.clientID, c.config.Cert.TTL, c.domainID, mock.Anything).Return(sdk.Cert{SerialNumber: c.serial}, c.sdkCertErr) mgsdk.On("ViewCert", c.serial, mock.Anything, mock.Anything).Return(sdk.Cert{Certificate: c.cert, Key: c.key}, c.sdkCertErr) login := sdk.Login{ Identity: c.config.Server.MgUsername, Secret: c.config.Server.MgPass, } mgsdk.On("CreateToken", login).Return(sdk.Token{AccessToken: validToken}, c.sdkTokenErr) - cert, key, err := svc.Cert(c.domainID, c.token, c.thingID, c.ttl) + cert, key, err := svc.Cert(c.domainID, c.token, c.clientID, c.ttl) assert.Equal(t, c.cert, cert) assert.Equal(t, c.key, key) assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected error %v, got %v", c.err, err)) diff --git a/readers/api/endpoint.go b/readers/api/endpoint.go index 794063f79d..477cbe7705 100644 --- a/readers/api/endpoint.go +++ b/readers/api/endpoint.go @@ -6,24 +6,24 @@ package api import ( "context" - "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" "github.com/absmach/magistrala/pkg/apiutil" mgauthn "github.com/absmach/magistrala/pkg/authn" - mgauthz "github.com/absmach/magistrala/pkg/authz" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/readers" "github.com/go-kit/kit/endpoint" ) -func listMessagesEndpoint(svc readers.MessageRepository, authn mgauthn.Authentication, authz mgauthz.Authorization, thingsClient magistrala.ThingsServiceClient) endpoint.Endpoint { +func listMessagesEndpoint(svc readers.MessageRepository, authn mgauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(listMessagesReq) if err := req.validate(); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - if err := authorize(ctx, req, authn, authz, thingsClient); err != nil { + if err := authnAuthz(ctx, req, authn, clients, channels); err != nil { return nil, errors.Wrap(svcerr.ErrAuthorization, err) } diff --git a/readers/api/endpoint_test.go b/readers/api/endpoint_test.go index 156e79ec79..51d53b731f 100644 --- a/readers/api/endpoint_test.go +++ b/readers/api/endpoint_test.go @@ -11,25 +11,22 @@ import ( "testing" "time" - "github.com/absmach/magistrala" + chmocks "github.com/absmach/magistrala/channels/mocks" + climocks "github.com/absmach/magistrala/clients/mocks" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" - authzmocks "github.com/absmach/magistrala/pkg/authz/mocks" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/transformers/senml" "github.com/absmach/magistrala/readers" "github.com/absmach/magistrala/readers/api" "github.com/absmach/magistrala/readers/mocks" - thmocks "github.com/absmach/magistrala/things/mocks" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) const ( svcName = "test-service" - thingToken = "1" + clientToken = "1" userToken = "token" invalidToken = "invalid" email = "user@example.com" @@ -53,8 +50,8 @@ var ( validSession = mgauthn.Session{UserID: testsutil.GenerateUUID(&testing.T{})} ) -func newServer(repo *mocks.MessageRepository, authn *authnmocks.Authentication, authz *authzmocks.Authorization, thingsAuthzClient *thmocks.ThingsServiceClient) *httptest.Server { - mux := api.MakeHandler(repo, authn, authz, thingsAuthzClient, svcName, instanceID) +func newServer(repo *mocks.MessageRepository, authn *authnmocks.Authentication, clients *climocks.ClientsServiceClient, channels *chmocks.ChannelsServiceClient) *httptest.Server { + mux := api.MakeHandler(repo, authn, clients, channels, svcName, instanceID) return httptest.NewServer(mux) } @@ -75,7 +72,7 @@ func (tr testRequest) make() (*http.Response, error) { req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) } if tr.key != "" { - req.Header.Set("Authorization", apiutil.ThingPrefix+tr.key) + req.Header.Set("Authorization", apiutil.ClientPrefix+tr.key) } return tr.client.Do(req) @@ -132,10 +129,10 @@ func TestReadAll(t *testing.T) { } repo := new(mocks.MessageRepository) - authz := new(authzmocks.Authorization) authn := new(authnmocks.Authentication) - things := new(thmocks.ThingsServiceClient) - ts := newServer(repo, authn, authz, things) + clients := new(climocks.ClientsServiceClient) + channels := new(chmocks.ChannelsServiceClient) + ts := newServer(repo, authn, clients, channels) defer ts.Close() cases := []struct { @@ -181,64 +178,64 @@ func TestReadAll(t *testing.T) { status: http.StatusBadRequest, }, { - desc: "read page with negative offset as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=-1&limit=10", ts.URL, "", chanID), - key: thingToken, + desc: "read page with negative offset as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=-1&limit=10", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with negative limit as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=-10", ts.URL, "", chanID), - key: thingToken, + desc: "read page with negative limit as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=-10", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with zero limit as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=0", ts.URL, "", chanID), - key: thingToken, + desc: "read page with zero limit as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=0", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with non-integer offset as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=abc&limit=10", ts.URL, "", chanID), - key: thingToken, + desc: "read page with non-integer offset as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=abc&limit=10", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with non-integer limit as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=abc", ts.URL, "", chanID), - key: thingToken, + desc: "read page with non-integer limit as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=abc", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with invalid channel id as thing", - url: fmt.Sprintf("%s/%s/channels//messages?offset=0&limit=10", ts.URL, ""), - key: thingToken, + desc: "read page with invalid channel id as client", + url: fmt.Sprintf("%s/channels//messages?offset=0&limit=10", ts.URL), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with multiple offset as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, "", chanID), - key: thingToken, + desc: "read page with multiple offset as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with multiple limit as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, "", chanID), - key: thingToken, + desc: "read page with multiple limit as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with empty token as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, "", chanID), + desc: "read page with empty token as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), token: "", authResponse: false, authnErr: svcerr.ErrAuthentication, @@ -246,9 +243,9 @@ func TestReadAll(t *testing.T) { err: svcerr.ErrAuthentication, }, { - desc: "read page with default offset as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?limit=10", ts.URL, "", chanID), - key: thingToken, + desc: "read page with default offset as client", + url: fmt.Sprintf("%s/channels/%s/messages?limit=10", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -258,9 +255,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with default limit as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0", ts.URL, "", chanID), - key: thingToken, + desc: "read page with default limit as client", + url: fmt.Sprintf("%s/channels/%s/messages?offset=0", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -270,9 +267,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with senml format as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?format=messages", ts.URL, "", chanID), - key: thingToken, + desc: "read page with senml format as client", + url: fmt.Sprintf("%s/channels/%s/messages?format=messages", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -282,9 +279,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with subtopic as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, "", chanID, subtopic, httpProt), - key: thingToken, + desc: "read page with subtopic as client", + url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -294,9 +291,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with subtopic and protocol as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, "", chanID, subtopic, httpProt), - key: thingToken, + desc: "read page with subtopic and protocol as client", + url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -306,9 +303,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with publisher as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?publisher=%s", ts.URL, "", chanID, pubID2), - key: thingToken, + desc: "read page with publisher as client", + url: fmt.Sprintf("%s/channels/%s/messages?publisher=%s", ts.URL, chanID, pubID2), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -318,9 +315,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with protocol as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?protocol=http", ts.URL, "", chanID), - key: thingToken, + desc: "read page with protocol as client", + url: fmt.Sprintf("%s/channels/%s/messages?protocol=http", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -330,9 +327,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with name as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?name=%s", ts.URL, "", chanID, msgName), - key: thingToken, + desc: "read page with name as client", + url: fmt.Sprintf("%s/channels/%s/messages?name=%s", ts.URL, chanID, msgName), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -342,9 +339,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f", ts.URL, "", chanID, v), - key: thingToken, + desc: "read page with value as client", + url: fmt.Sprintf("%s/channels/%s/messages?v=%f", ts.URL, chanID, v), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -354,9 +351,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with value and equal comparator as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v, readers.EqualKey), - key: thingToken, + desc: "read page with value and equal comparator as client", + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v, readers.EqualKey), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -366,9 +363,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with value and lower-than comparator as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v+1, readers.LowerThanKey), - key: thingToken, + desc: "read page with value and lower-than comparator as client", + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanKey), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -378,9 +375,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with value and lower-than-or-equal comparator as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v+1, readers.LowerThanEqualKey), - key: thingToken, + desc: "read page with value and lower-than-or-equal comparator as client", + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanEqualKey), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -390,9 +387,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with value and greater-than comparator as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v-1, readers.GreaterThanKey), - key: thingToken, + desc: "read page with value and greater-than comparator as client", + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanKey), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -402,9 +399,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with value and greater-than-or-equal comparator as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v-1, readers.GreaterThanEqualKey), - key: thingToken, + desc: "read page with value and greater-than-or-equal comparator as client", + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanEqualKey), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -414,23 +411,23 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with non-float value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=ab01", ts.URL, "", chanID), - key: thingToken, + desc: "read page with non-float value as client", + url: fmt.Sprintf("%s/channels/%s/messages?v=ab01", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with value and wrong comparator as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, "", chanID, v-1), - key: thingToken, + desc: "read page with value and wrong comparator as client", + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, chanID, v-1), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with boolean value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=true", ts.URL, "", chanID), - key: thingToken, + desc: "read page with boolean value as client", + url: fmt.Sprintf("%s/channels/%s/messages?vb=true", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -440,16 +437,16 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with non-boolean value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=yes", ts.URL, "", chanID), - key: thingToken, + desc: "read page with non-boolean value as client", + url: fmt.Sprintf("%s/channels/%s/messages?vb=yes", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with string value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vs=%s", ts.URL, "", chanID, vs), - key: thingToken, + desc: "read page with string value as client", + url: fmt.Sprintf("%s/channels/%s/messages?vs=%s", ts.URL, chanID, vs), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -459,9 +456,9 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with data value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vd=%s", ts.URL, "", chanID, vd), - key: thingToken, + desc: "read page with data value as client", + url: fmt.Sprintf("%s/channels/%s/messages?vd=%s", ts.URL, chanID, vd), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -471,23 +468,23 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with non-float from as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?from=ABCD", ts.URL, "", chanID), - key: thingToken, + desc: "read page with non-float from as client", + url: fmt.Sprintf("%s/channels/%s/messages?from=ABCD", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with non-float to as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?to=ABCD", ts.URL, "", chanID), - key: thingToken, + desc: "read page with non-float to as client", + url: fmt.Sprintf("%s/channels/%s/messages?to=ABCD", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with from/to as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?from=%f&to=%f", ts.URL, "", chanID, messages[19].Time, messages[4].Time), - key: thingToken, + desc: "read page with from/to as client", + url: fmt.Sprintf("%s/channels/%s/messages?from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -497,16 +494,16 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with aggregation as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX", ts.URL, "", chanID), - key: thingToken, + desc: "read page with aggregation as client", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with interval as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?interval=10h", ts.URL, "", chanID), - key: thingToken, + desc: "read page with interval as client", + url: fmt.Sprintf("%s/channels/%s/messages?interval=10h", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -516,16 +513,16 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with aggregation and interval as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, "", chanID), - key: thingToken, + desc: "read page with aggregation and interval as client", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, chanID), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with aggregation, interval, to and from as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=%f", ts.URL, "", chanID, messages[19].Time, messages[4].Time), - key: thingToken, + desc: "read page with aggregation, interval, to and from as client", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + key: clientToken, authResponse: true, status: http.StatusOK, res: pageRes{ @@ -535,37 +532,37 @@ func TestReadAll(t *testing.T) { }, }, { - desc: "read page with invalid aggregation and valid interval, to and from as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=invalid&interval=10h&from=%f&to=%f", ts.URL, "", chanID, messages[19].Time, messages[4].Time), - key: thingToken, + desc: "read page with invalid aggregation and valid interval, to and from as client", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=invalid&interval=10h&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with invalid interval and valid aggregation, to and from as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10hrs&from=%f&to=%f", ts.URL, "", chanID, messages[19].Time, messages[4].Time), - key: thingToken, + desc: "read page with invalid interval and valid aggregation, to and from as client", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10hrs&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with aggregation, interval and to with missing from as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&to=%f", ts.URL, "", chanID, messages[4].Time), - key: thingToken, + desc: "read page with aggregation, interval and to with missing from as client", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&to=%f", ts.URL, chanID, messages[4].Time), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with aggregation, interval and to with invalid from as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&to=ABCD&from=%f", ts.URL, "", chanID, messages[4].Time), - key: thingToken, + desc: "read page with aggregation, interval and to with invalid from as client", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&to=ABCD&from=%f", ts.URL, chanID, messages[4].Time), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, { - desc: "read page with aggregation, interval and to with invalid to as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=ABCD", ts.URL, "", chanID, messages[4].Time), - key: thingToken, + desc: "read page with aggregation, interval and to with invalid to as client", + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=ABCD", ts.URL, chanID, messages[4].Time), + key: clientToken, authResponse: true, status: http.StatusBadRequest, }, @@ -980,12 +977,7 @@ func TestReadAll(t *testing.T) { } for _, tc := range cases { - authCall := authz.On("Authorize", mock.Anything, mock.Anything).Return(tc.err) - authCall1 := authn.On("Authenticate", mock.Anything, tc.token).Return(validSession, tc.authnErr) repo.On("ReadAll", chanID, tc.res.PageMetadata).Return(readers.MessagesPage{Total: tc.res.Total, Messages: fromSenml(tc.res.Messages)}, nil) - if tc.key != "" { - authCall = things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.ThingsAuthzRes{Authorized: tc.authResponse}, tc.err) - } req := testRequest{ client: ts.Client(), method: http.MethodGet, @@ -1004,8 +996,6 @@ func TestReadAll(t *testing.T) { assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected %d got %d", tc.desc, tc.status, res.StatusCode)) assert.Equal(t, tc.res.Total, page.Total, fmt.Sprintf("%s: expected %d got %d", tc.desc, tc.res.Total, page.Total)) assert.ElementsMatch(t, tc.res.Messages, page.Messages, fmt.Sprintf("%s: got incorrect body from response", tc.desc)) - authCall.Unset() - authCall1.Unset() } } diff --git a/readers/api/transport.go b/readers/api/transport.go index 194da47ff1..eb6285562d 100644 --- a/readers/api/transport.go +++ b/readers/api/transport.go @@ -9,9 +9,11 @@ import ( "net/http" "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" "github.com/absmach/magistrala/pkg/apiutil" mgauthn "github.com/absmach/magistrala/pkg/authn" - mgauthz "github.com/absmach/magistrala/pkg/authz" + "github.com/absmach/magistrala/pkg/connections" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/policies" @@ -19,8 +21,6 @@ import ( "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" "github.com/prometheus/client_golang/prometheus/promhttp" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) const ( @@ -45,19 +45,26 @@ const ( defLimit = 10 defOffset = 0 defFormat = "messages" + + tokenKind = "token" + clientType = "client" + userType = "user" + subscribePermission = "subscribe" + viewPermission = "view" + groupType = "group" ) var errUserAccess = errors.New("user has no permission") // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc readers.MessageRepository, authn mgauthn.Authentication, authz mgauthz.Authorization, things magistrala.ThingsServiceClient, svcName, instanceID string) http.Handler { +func MakeHandler(svc readers.MessageRepository, authn mgauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient, svcName, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(encodeError), } mux := chi.NewRouter() - mux.Get("/{domainID}/channels/{chanID}/messages", kithttp.NewServer( - listMessagesEndpoint(svc, authn, authz, things), + mux.Get("/channels/{chanID}/messages", kithttp.NewServer( + listMessagesEndpoint(svc, authn, clients, channels), decodeList, encodeResponse, opts..., @@ -154,10 +161,9 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) { } req := listMessagesReq{ - chanID: chi.URLParam(r, "chanID"), - token: apiutil.ExtractBearerToken(r), - key: apiutil.ExtractThingKey(r), - domainID: chi.URLParam(r, "domainID"), + chanID: chi.URLParam(r, "chanID"), + token: apiutil.ExtractBearerToken(r), + key: apiutil.ExtractClientSecret(r), pageMeta: readers.PageMetadata{ Offset: offset, Limit: limit, @@ -239,43 +245,54 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) { } } -func authorize(ctx context.Context, req listMessagesReq, authn mgauthn.Authentication, authz mgauthz.Authorization, things magistrala.ThingsServiceClient) (err error) { +func authnAuthz(ctx context.Context, req listMessagesReq, authn mgauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) error { + clientID, clientType, err := authenticate(ctx, req, authn, clients) + if err != nil { + return nil + } + if err := authorize(ctx, clientID, clientType, req.chanID, channels); err != nil { + return err + } + return nil +} + +func authenticate(ctx context.Context, req listMessagesReq, authn mgauthn.Authentication, clients grpcClientsV1.ClientsServiceClient) (clientID string, clientType string, err error) { switch { case req.token != "": session, err := authn.Authenticate(ctx, req.token) if err != nil { - return errors.Wrap(svcerr.ErrAuthentication, err) + return "", "", err } - if err = authz.Authorize(ctx, mgauthz.PolicyReq{ - Domain: req.domainID, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Subject: req.domainID + "_" + session.UserID, - Permission: policies.ViewPermission, - ObjectType: policies.GroupType, - Object: req.chanID, - }); err != nil { - e, ok := status.FromError(err) - if ok && e.Code() == codes.PermissionDenied { - return errors.Wrap(errUserAccess, err) - } - return err - } - return nil + + return session.DomainUserID, policies.UserType, nil case req.key != "": - if _, err = things.Authorize(ctx, &magistrala.ThingsAuthzReq{ - ThingKey: req.key, - ChannelId: req.chanID, - Permission: policies.SubscribePermission, - }); err != nil { - e, ok := status.FromError(err) - if ok && e.Code() == codes.PermissionDenied { - return errors.Wrap(errUserAccess, err) - } - return err + res, err := clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ + ClientSecret: req.key, + }) + if err != nil { + return "", "", err } - return nil + if !res.GetAuthenticated() { + return "", "", svcerr.ErrAuthentication + } + return res.GetId(), policies.ClientType, nil default: + return "", "", svcerr.ErrAuthentication + } +} + +func authorize(ctx context.Context, clientID, clientType, chanID string, channels grpcChannelsV1.ChannelsServiceClient) (err error) { + res, err := channels.Authorize(ctx, &grpcChannelsV1.AuthzReq{ + ClientId: clientID, + ClientType: clientType, + Type: uint32(connections.Subscribe), + ChannelId: chanID, + }) + if err != nil { + return errors.Wrap(svcerr.ErrAuthorization, err) + } + if !res.GetAuthorized() { return svcerr.ErrAuthorization } + return nil } diff --git a/readers/postgres/README.md b/readers/postgres/README.md index 66e289d442..6ba5e6ba06 100644 --- a/readers/postgres/README.md +++ b/readers/postgres/README.md @@ -8,33 +8,33 @@ The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ----------------------------------- | --------------------------------------------- | ----------------------------- | -| MG_POSTGRES_READER_LOG_LEVEL | Service log level | info | -| MG_POSTGRES_READER_HTTP_HOST | Service HTTP host | localhost | -| MG_POSTGRES_READER_HTTP_PORT | Service HTTP port | 9009 | -| MG_POSTGRES_READER_HTTP_SERVER_CERT | Service HTTP server cert | "" | -| MG_POSTGRES_READER_HTTP_SERVER_KEY | Service HTTP server key | "" | -| MG_POSTGRES_HOST | Postgres DB host | localhost | -| MG_POSTGRES_PORT | Postgres DB port | 5432 | -| MG_POSTGRES_USER | Postgres user | magistrala | -| MG_POSTGRES_PASS | Postgres password | magistrala | -| MG_POSTGRES_NAME | Postgres database name | messages | -| MG_POSTGRES_SSL_MODE | Postgres SSL mode | disabled | -| MG_POSTGRES_SSL_CERT | Postgres SSL certificate path | "" | -| MG_POSTGRES_SSL_KEY | Postgres SSL key | "" | -| MG_POSTGRES_SSL_ROOT_CERT | Postgres SSL root certificate path | "" | -| MG_THINGS_AUTH_GRPC_URL | Things service Auth gRPC URL | localhost:7000 | -| MG_THINGS_AUTH_GRPC_TIMEOUT | Things service Auth gRPC timeout in seconds | 1s | -| MG_THINGS_AUTH_GRPC_CLIENT_TLS | Things service Auth gRPC TLS mode flag | false | -| MG_THINGS_AUTH_GRPC_CA_CERTS | Things service Auth gRPC CA certificates | "" | -| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 | -| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | -| MG_AUTH_GRPC_CLIENT_TLS | Auth service gRPC TLS mode flag | false | -| MG_AUTH_GRPC_CA_CERTS | Auth service gRPC CA certificates | "" | +| Variable | Description | Default | +| ----------------------------------- | --------------------------------------------- | ---------------------------- | +| MG_POSTGRES_READER_LOG_LEVEL | Service log level | info | +| MG_POSTGRES_READER_HTTP_HOST | Service HTTP host | localhost | +| MG_POSTGRES_READER_HTTP_PORT | Service HTTP port | 9009 | +| MG_POSTGRES_READER_HTTP_SERVER_CERT | Service HTTP server cert | "" | +| MG_POSTGRES_READER_HTTP_SERVER_KEY | Service HTTP server key | "" | +| MG_POSTGRES_HOST | Postgres DB host | localhost | +| MG_POSTGRES_PORT | Postgres DB port | 5432 | +| MG_POSTGRES_USER | Postgres user | magistrala | +| MG_POSTGRES_PASS | Postgres password | magistrala | +| MG_POSTGRES_NAME | Postgres database name | messages | +| MG_POSTGRES_SSL_MODE | Postgres SSL mode | disabled | +| MG_POSTGRES_SSL_CERT | Postgres SSL certificate path | "" | +| MG_POSTGRES_SSL_KEY | Postgres SSL key | "" | +| MG_POSTGRES_SSL_ROOT_CERT | Postgres SSL root certificate path | "" | +| MG_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | localhost:7000 | +| MG_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC timeout in seconds | 1s | +| MG_CLIENTS_AUTH_GRPC_CLIENT_TLS | Clients service Auth gRPC TLS mode flag | false | +| MG_CLIENTS_AUTH_GRPC_CA_CERTS | Clients service Auth gRPC CA certificates | "" | +| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 | +| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | +| MG_AUTH_GRPC_CLIENT_TLS | Auth service gRPC TLS mode flag | false | +| MG_AUTH_GRPC_CA_CERTS | Auth service gRPC CA certificates | "" | | MG_JAEGER_URL | Jaeger server URL | http://jaeger:4318/v1/traces | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_POSTGRES_READER_INSTANCE_ID | Postgres reader instance ID | | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_POSTGRES_READER_INSTANCE_ID | Postgres reader instance ID | | ## Deployment @@ -70,10 +70,10 @@ MG_POSTGRES_SSL_MODE=[Postgres SSL mode] \ MG_POSTGRES_SSL_CERT=[Postgres SSL cert] \ MG_POSTGRES_SSL_KEY=[Postgres SSL key] \ MG_POSTGRES_SSL_ROOT_CERT=[Postgres SSL Root cert] \ -MG_THINGS_AUTH_GRPC_URL=[Things service Auth GRPC URL] \ -MG_THINGS_AUTH_GRPC_TIMEOUT=[Things service Auth gRPC request timeout in seconds] \ -MG_THINGS_AUTH_GRPC_CLIENT_TLS=[Things service Auth gRPC TLS mode flag] \ -MG_THINGS_AUTH_GRPC_CA_CERTS=[Things service Auth gRPC CA certificates] \ +MG_CLIENTS_AUTH_GRPC_URL=[Clients service Auth GRPC URL] \ +MG_CLIENTS_AUTH_GRPC_TIMEOUT=[Clients service Auth gRPC request timeout in seconds] \ +MG_CLIENTS_AUTH_GRPC_CLIENT_TLS=[Clients service Auth gRPC TLS mode flag] \ +MG_CLIENTS_AUTH_GRPC_CA_CERTS=[Clients service Auth gRPC CA certificates] \ MG_AUTH_GRPC_URL=[Auth service gRPC URL] \ MG_AUTH_GRPC_TIMEOUT=[Auth service gRPC request timeout in seconds] \ MG_AUTH_GRPC_CLIENT_TLS=[Auth service gRPC TLS mode flag] \ diff --git a/readers/timescale/README.md b/readers/timescale/README.md index 7ce7db3b35..df5025ca51 100644 --- a/readers/timescale/README.md +++ b/readers/timescale/README.md @@ -8,33 +8,33 @@ The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ------------------------------------ | --------------------------------------------- | ----------------------------- | -| MG_TIMESCALE_READER_LOG_LEVEL | Service log level | info | -| MG_TIMESCALE_READER_HTTP_HOST | Service HTTP host | localhost | -| MG_TIMESCALE_READER_HTTP_PORT | Service HTTP port | 8180 | -| MG_TIMESCALE_READER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" | -| MG_TIMESCALE_READER_HTTP_SERVER_KEY | Service HTTP server key path | "" | -| MG_TIMESCALE_HOST | Timescale DB host | localhost | -| MG_TIMESCALE_PORT | Timescale DB port | 5432 | -| MG_TIMESCALE_USER | Timescale user | magistrala | -| MG_TIMESCALE_PASS | Timescale password | magistrala | -| MG_TIMESCALE_NAME | Timescale database name | messages | -| MG_TIMESCALE_SSL_MODE | Timescale SSL mode | disabled | -| MG_TIMESCALE_SSL_CERT | Timescale SSL certificate path | "" | -| MG_TIMESCALE_SSL_KEY | Timescale SSL key | "" | -| MG_TIMESCALE_SSL_ROOT_CERT | Timescale SSL root certificate path | "" | -| MG_THINGS_AUTH_GRPC_URL | Things service Auth gRPC URL | localhost:7000 | -| MG_THINGS_AUTH_GRPC_TIMEOUT | Things service Auth gRPC timeout in seconds | 1s | -| MG_THINGS_AUTH_GRPC_CLIENT_TLS | Things service Auth gRPC TLS enabled flag | false | -| MG_THINGS_AUTH_GRPC_CA_CERTS | Things service Auth gRPC CA certificates | "" | -| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 | -| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC timeout in seconds | 1s | -| MG_AUTH_GRPC_CLIENT_TLS | Auth service gRPC TLS enabled flag | false | -| MG_AUTH_GRPC_CA_CERT | Auth service gRPC CA certificate | "" | +| Variable | Description | Default | +| ------------------------------------ | --------------------------------------------- | ---------------------------- | +| MG_TIMESCALE_READER_LOG_LEVEL | Service log level | info | +| MG_TIMESCALE_READER_HTTP_HOST | Service HTTP host | localhost | +| MG_TIMESCALE_READER_HTTP_PORT | Service HTTP port | 8180 | +| MG_TIMESCALE_READER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" | +| MG_TIMESCALE_READER_HTTP_SERVER_KEY | Service HTTP server key path | "" | +| MG_TIMESCALE_HOST | Timescale DB host | localhost | +| MG_TIMESCALE_PORT | Timescale DB port | 5432 | +| MG_TIMESCALE_USER | Timescale user | magistrala | +| MG_TIMESCALE_PASS | Timescale password | magistrala | +| MG_TIMESCALE_NAME | Timescale database name | messages | +| MG_TIMESCALE_SSL_MODE | Timescale SSL mode | disabled | +| MG_TIMESCALE_SSL_CERT | Timescale SSL certificate path | "" | +| MG_TIMESCALE_SSL_KEY | Timescale SSL key | "" | +| MG_TIMESCALE_SSL_ROOT_CERT | Timescale SSL root certificate path | "" | +| MG_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | localhost:7000 | +| MG_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC timeout in seconds | 1s | +| MG_CLIENTS_AUTH_GRPC_CLIENT_TLS | Clients service Auth gRPC TLS enabled flag | false | +| MG_CLIENTS_AUTH_GRPC_CA_CERTS | Clients service Auth gRPC CA certificates | "" | +| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 | +| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC timeout in seconds | 1s | +| MG_AUTH_GRPC_CLIENT_TLS | Auth service gRPC TLS enabled flag | false | +| MG_AUTH_GRPC_CA_CERT | Auth service gRPC CA certificate | "" | | MG_JAEGER_URL | Jaeger server URL | http://jaeger:4318/v1/traces | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_TIMESCALE_READER_INSTANCE_ID | Timescale reader instance ID | "" | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_TIMESCALE_READER_INSTANCE_ID | Timescale reader instance ID | "" | ## Deployment @@ -69,10 +69,10 @@ MG_TIMESCALE_SSL_MODE=[Timescale SSL mode] \ MG_TIMESCALE_SSL_CERT=[Timescale SSL cert] \ MG_TIMESCALE_SSL_KEY=[Timescale SSL key] \ MG_TIMESCALE_SSL_ROOT_CERT=[Timescale SSL Root cert] \ -MG_THINGS_AUTH_GRPC_URL=[Things service Auth GRPC URL] \ -MG_THINGS_AUTH_GRPC_TIMEOUT=[Things service Auth gRPC request timeout in seconds] \ -MG_THINGS_AUTH_GRPC_CLIENT_TLS=[Things service Auth gRPC TLS enabled flag] \ -MG_THINGS_AUTH_GRPC_CA_CERTS=[Things service Auth gRPC CA certificates] \ +MG_CLIENTS_AUTH_GRPC_URL=[Clients service Auth GRPC URL] \ +MG_CLIENTS_AUTH_GRPC_TIMEOUT=[Clients service Auth gRPC request timeout in seconds] \ +MG_CLIENTS_AUTH_GRPC_CLIENT_TLS=[Clients service Auth gRPC TLS enabled flag] \ +MG_CLIENTS_AUTH_GRPC_CA_CERTS=[Clients service Auth gRPC CA certificates] \ MG_AUTH_GRPC_URL=[Auth service Auth gRPC URL] \ MG_AUTH_GRPC_TIMEOUT=[Auth service Auth gRPC request timeout in seconds] \ MG_AUTH_GRPC_CLIENT_TLS=[Auth service Auth gRPC TLS enabled flag] \ @@ -88,12 +88,12 @@ $GOBIN/magistrala-timescale-reader Starting service will start consuming normalized messages in SenML format. Comparator Usage Guide: -| Comparator | Usage | Example | -|----------------------|-----------------------------------------------------------------------------|------------------------------------| -| eq | Return values that are equal to the query | eq["active"] -> "active" | -| ge | Return values that are substrings of the query | ge["tiv"] -> "active" and "tiv" | -| gt | Return values that are substrings of the query and not equal to the query | gt["tiv"] -> "active" | -| le | Return values that are superstrings of the query | le["active"] -> "tiv" | -| lt | Return values that are superstrings of the query and not equal to the query | lt["active"] -> "active" and "tiv" | +| Comparator | Usage | Example | +| ---------- | --------------------------------------------------------------------------- | ---------------------------------- | +| eq | Return values that are equal to the query | eq["active"] -> "active" | +| ge | Return values that are substrings of the query | ge["tiv"] -> "active" and "tiv" | +| gt | Return values that are substrings of the query and not equal to the query | gt["tiv"] -> "active" | +| le | Return values that are superstrings of the query | le["active"] -> "tiv" | +| lt | Return values that are superstrings of the query and not equal to the query | lt["active"] -> "active" and "tiv" | Official docs can be found [here](https://docs.magistrala.abstractmachines.fr). diff --git a/scripts/csv/clients.csv b/scripts/csv/clients.csv new file mode 100644 index 0000000000..b5bc8e7fbc --- /dev/null +++ b/scripts/csv/clients.csv @@ -0,0 +1,10 @@ +client_1 +client_2 +client_3 +client_4 +client_5 +client_6 +client_7 +client_8 +client_9 +client_10 diff --git a/scripts/csv/things.csv b/scripts/csv/things.csv deleted file mode 100644 index 4636a47627..0000000000 --- a/scripts/csv/things.csv +++ /dev/null @@ -1,10 +0,0 @@ -thing_1 -thing_2 -thing_3 -thing_4 -thing_5 -thing_6 -thing_7 -thing_8 -thing_9 -thing_10 diff --git a/scripts/provision-dev.sh b/scripts/provision-dev.sh index 49b5080882..a4d44edd75 100755 --- a/scripts/provision-dev.sh +++ b/scripts/provision-dev.sh @@ -5,7 +5,7 @@ # ### -# Provisions example user, thing and channel on a clean Magistrala installation. +# Provisions example user, client and channel on a clean Magistrala installation. # # Expects a running Magistrala installation. # @@ -31,13 +31,13 @@ curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H JWTTOKEN=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H "Content-Type: application/json" https://localhost/users/tokens/issue -d '{"identity":"'"$EMAIL"'", "secret":"'"$PASSWORD"'"}' | grep -oP '"access_token":"\K[^"]+' ) printf "JWT TOKEN for user is $JWTTOKEN \n" -#provision thing -printf "Provisioning thing with name $DEVICE \n" -DEVICEID=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/things -d '{"name":"'"$DEVICE"'", "status": "enabled"}' | grep -oP '"id":"\K[^"]+' ) -curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/things/$DEVICEID +#provision client +printf "Provisioning client with name $DEVICE \n" +DEVICEID=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/clients -d '{"name":"'"$DEVICE"'", "status": "enabled"}' | grep -oP '"id":"\K[^"]+' ) +curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/clients/$DEVICEID -#get thing token -DEVICETOKEN=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -H "Authorization: Bearer $JWTTOKEN" https://localhost/things/$DEVICEID | grep -oP '"secret":"\K[^"]+' ) +#get client token +DEVICETOKEN=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -H "Authorization: Bearer $JWTTOKEN" https://localhost/clients/$DEVICEID | grep -oP '"secret":"\K[^"]+' ) printf "Device token is $DEVICETOKEN \n" #provision channel @@ -45,6 +45,6 @@ printf "Provisioning channel with name $CHANNEL \n" CHANNELID=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/channels -d '{"name":"'"$CHANNEL"'", "status": "enabled"}' | grep -oP '"id":"\K[^"]+' ) curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/channels/$CHANNELID -#connect thing to channel -printf "Connecting thing of id $DEVICEID to channel of id $CHANNELID \n" -curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X PUT -H "Authorization: Bearer $JWTTOKEN" https://localhost/channels/$CHANNELID/things/$DEVICEID +#connect client to channel +printf "Connecting client of id $DEVICEID to channel of id $CHANNELID \n" +curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X PUT -H "Authorization: Bearer $JWTTOKEN" https://localhost/channels/$CHANNELID/clients/$DEVICEID diff --git a/scripts/run.sh b/scripts/run.sh index 0cdd52ca6a..b2a1280ce8 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -41,29 +41,29 @@ done MG_USERS_LOG_LEVEL=info MG_USERS_HTTP_PORT=9002 MG_USERS_GRPC_PORT=7001 MG_USERS_ADMIN_EMAIL=admin@magistrala.com MG_USERS_ADMIN_PASSWORD=12345678 MG_USERS_ADMIN_USERNAME=admin MG_EMAIL_TEMPLATE=../docker/templates/users.tmpl $BUILD_DIR/magistrala-users & ### -# Things +# Clients ### -MG_THINGS_LOG_LEVEL=info MG_THINGS_HTTP_PORT=9000 MG_THINGS_AUTH_GRPC_PORT=7000 MG_THINGS_AUTH_HTTP_PORT=9002 $BUILD_DIR/magistrala-things & +MG_CLIENTS_LOG_LEVEL=info MG_CLIENTS_HTTP_PORT=9000 MG_CLIENTS_AUTH_GRPC_PORT=7000 MG_CLIENTS_AUTH_HTTP_PORT=9002 $BUILD_DIR/magistrala-clients & ### # HTTP ### -MG_HTTP_ADAPTER_LOG_LEVEL=info MG_HTTP_ADAPTER_PORT=8008 MG_THINGS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-http & +MG_HTTP_ADAPTER_LOG_LEVEL=info MG_HTTP_ADAPTER_PORT=8008 MG_CLIENTS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-http & ### # WS ### -MG_WS_ADAPTER_LOG_LEVEL=info MG_WS_ADAPTER_HTTP_PORT=8190 MG_THINGS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-ws & +MG_WS_ADAPTER_LOG_LEVEL=info MG_WS_ADAPTER_HTTP_PORT=8190 MG_CLIENTS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-ws & ### # MQTT ### -MG_MQTT_ADAPTER_LOG_LEVEL=info MG_THINGS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-mqtt & +MG_MQTT_ADAPTER_LOG_LEVEL=info MG_CLIENTS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-mqtt & ### # CoAP ### -MG_COAP_ADAPTER_LOG_LEVEL=info MG_COAP_ADAPTER_PORT=5683 MG_THINGS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-coap & +MG_COAP_ADAPTER_LOG_LEVEL=info MG_COAP_ADAPTER_PORT=5683 MG_CLIENTS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-coap & trap cleanup EXIT diff --git a/things/README.md b/things/README.md deleted file mode 100644 index f570b0ff5f..0000000000 --- a/things/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Things - -Things service provides an HTTP API for managing platform resources: things and channels. -Through this API clients are able to do the following actions: - -- provision new things -- create new channels -- "connect" things into the channels - -For an in-depth explanation of the aforementioned scenarios, as well as thorough -understanding of Magistrala, please check out the [official documentation][doc]. - -## Configuration - -The service is configured using the environment variables presented in the -following table. Note that any unset variables will be replaced with their -default values. - -| Variable | Description | Default | -| ------------------------------- | ----------------------------------------------------------------------- | ------------------------------- | -| MG_THINGS_LOG_LEVEL | Log level for Things (debug, info, warn, error) | info | -| MG_THINGS_HTTP_HOST | Things service HTTP host | localhost | -| MG_THINGS_HTTP_PORT | Things service HTTP port | 9000 | -| MG_THINGS_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| MG_THINGS_SERVER_KEY | Path to the PEM encoded server key file | "" | -| MG_THINGS_AUTH_GRPC_HOST | Things service gRPC host | localhost | -| MG_THINGS_AUTH_GRPC_PORT | Things service gRPC port | 7000 | -| MG_THINGS_AUTH_GRPC_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| MG_THINGS_AUTH_GRPC_SERVER_KEY | Path to the PEM encoded server key file | "" | -| MG_THINGS_DB_HOST | Database host address | localhost | -| MG_THINGS_DB_PORT | Database host port | 5432 | -| MG_THINGS_DB_USER | Database user | magistrala | -| MG_THINGS_DB_PASS | Database password | magistrala | -| MG_THINGS_DB_NAME | Name of the database used by the service | things | -| MG_THINGS_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | -| MG_THINGS_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | -| MG_THINGS_DB_SSL_KEY | Path to the PEM encoded key file | "" | -| MG_THINGS_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | -| MG_THINGS_CACHE_URL | Cache database URL | | -| MG_THINGS_CACHE_KEY_DURATION | Cache key duration in seconds | 3600 | -| MG_THINGS_ES_URL | Event store URL | | -| MG_THINGS_ES_PASS | Event store password | "" | -| MG_THINGS_ES_DB | Event store instance name | 0 | -| MG_THINGS_STANDALONE_ID | User ID for standalone mode (no gRPC communication with Auth) | "" | -| MG_THINGS_STANDALONE_TOKEN | User token for standalone mode that should be passed in auth header | "" | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 | -| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | -| MG_AUTH_GRPC_CLIENT_TLS | Enable TLS for gRPC client | false | -| MG_AUTH_GRPC_CA_CERT | Path to the CA certificate file | "" | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server. | true | -| MG_THINGS_INSTANCE_ID | Things instance ID | "" | - -**Note** that if you want `things` service to have only one user locally, you should use `MG_THINGS_STANDALONE` env vars. By specifying these, you don't need `auth` service in your deployment for users' authorization. - -## Deployment - -The service itself is distributed as Docker container. Check the [`things `](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yml#L167-L194) service section in -docker-compose file to see how service is deployed. - -To start the service outside of the container, execute the following shell script: - -```bash -# download the latest version of the service -git clone https://github.com/absmach/magistrala - -cd magistrala - -# compile the things -make things - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_THINGS_LOG_LEVEL=[Things log level] \ -MG_THINGS_STANDALONE_ID=[User ID for standalone mode (no gRPC communication with auth)] \ -MG_THINGS_STANDALONE_TOKEN=[User token for standalone mode that should be passed in auth header] \ -MG_THINGS_CACHE_KEY_DURATION=[Cache key duration in seconds] \ -MG_THINGS_HTTP_HOST=[Things service HTTP host] \ -MG_THINGS_HTTP_PORT=[Things service HTTP port] \ -MG_THINGS_HTTP_SERVER_CERT=[Path to server certificate in pem format] \ -MG_THINGS_HTTP_SERVER_KEY=[Path to server key in pem format] \ -MG_THINGS_AUTH_GRPC_HOST=[Things service gRPC host] \ -MG_THINGS_AUTH_GRPC_PORT=[Things service gRPC port] \ -MG_THINGS_AUTH_GRPC_SERVER_CERT=[Path to server certificate in pem format] \ -MG_THINGS_AUTH_GRPC_SERVER_KEY=[Path to server key in pem format] \ -MG_THINGS_DB_HOST=[Database host address] \ -MG_THINGS_DB_PORT=[Database host port] \ -MG_THINGS_DB_USER=[Database user] \ -MG_THINGS_DB_PASS=[Database password] \ -MG_THINGS_DB_NAME=[Name of the database used by the service] \ -MG_THINGS_DB_SSL_MODE=[SSL mode to connect to the database with] \ -MG_THINGS_DB_SSL_CERT=[Path to the PEM encoded certificate file] \ -MG_THINGS_DB_SSL_KEY=[Path to the PEM encoded key file] \ -MG_THINGS_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] \ -MG_THINGS_CACHE_URL=[Cache database URL] \ -MG_THINGS_ES_URL=[Event store URL] \ -MG_THINGS_ES_PASS=[Event store password] \ -MG_THINGS_ES_DB=[Event store instance name] \ -MG_AUTH_GRPC_URL=[Auth service gRPC URL] \ -MG_AUTH_GRPC_TIMEOUT=[Auth service gRPC request timeout in seconds] \ -MG_AUTH_GRPC_CLIENT_TLS=[Enable TLS for gRPC client] \ -MG_AUTH_GRPC_CA_CERT=[Path to trusted CA certificate file] \ -MG_JAEGER_URL=[Jaeger server URL] \ -MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \ -MG_THINGS_INSTANCE_ID=[Things instance ID] \ -$GOBIN/magistrala-things -``` - -Setting `MG_THINGS_CA_CERTS` expects a file in PEM format of trusted CAs. This will enable TLS against the Auth gRPC endpoint trusting only those CAs that are provided. - -In constrained environments, sometimes it makes sense to run Things service as a standalone to reduce network traffic and simplify deployment. This means that Things service -operates only using a single user and is able to authorize it without gRPC communication with Auth service. -To run service in a standalone mode, set `MG_THINGS_STANDALONE_EMAIL` and `MG_THINGS_STANDALONE_TOKEN`. - -## Usage - -For more information about service capabilities and its usage, please check out -the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=things-openapi.yml). - -[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/things/api/grpc/endpoint.go b/things/api/grpc/endpoint.go deleted file mode 100644 index 0c00c38a7b..0000000000 --- a/things/api/grpc/endpoint.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpc - -import ( - "context" - - "github.com/absmach/magistrala/things" - "github.com/go-kit/kit/endpoint" -) - -func authorizeEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(authorizeReq) - - thingID, err := svc.Authorize(ctx, things.AuthzReq{ - ChannelID: req.ChannelID, - ClientID: req.ThingID, - ClientKey: req.ThingKey, - Permission: req.Permission, - }) - if err != nil { - return authorizeRes{}, err - } - return authorizeRes{ - authorized: true, - id: thingID, - }, err - } -} diff --git a/things/api/grpc/endpoint_test.go b/things/api/grpc/endpoint_test.go deleted file mode 100644 index 5feb894384..0000000000 --- a/things/api/grpc/endpoint_test.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpc_test - -import ( - "context" - "fmt" - "net" - "testing" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/policies" - "github.com/absmach/magistrala/things" - grpcapi "github.com/absmach/magistrala/things/api/grpc" - "github.com/absmach/magistrala/things/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials/insecure" -) - -const port = 7000 - -var ( - thingID = "testID" - clientKey = "testKey" - channelID = "testID" - invalid = "invalid" -) - -func startGRPCServer(svc *mocks.Service, port int) { - listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - panic(fmt.Sprintf("failed to obtain port: %s", err)) - } - server := grpc.NewServer() - magistrala.RegisterThingsServiceServer(server, grpcapi.NewServer(svc)) - go func() { - if err := server.Serve(listener); err != nil { - panic(fmt.Sprintf("failed to serve: %s", err)) - } - }() -} - -func TestAuthorize(t *testing.T) { - svc := new(mocks.Service) - startGRPCServer(svc, port) - authAddr := fmt.Sprintf("localhost:%d", port) - conn, _ := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - client := grpcapi.NewClient(conn, time.Second) - - cases := []struct { - desc string - req *magistrala.ThingsAuthzReq - res *magistrala.ThingsAuthzRes - thingID string - identifyKey string - authorizeReq things.AuthzReq - authorizeRes string - authorizeErr error - identifyErr error - err error - code codes.Code - }{ - { - desc: "authorize successfully", - thingID: thingID, - req: &magistrala.ThingsAuthzReq{ - ThingKey: clientKey, - ChannelId: channelID, - Permission: policies.PublishPermission, - }, - authorizeReq: things.AuthzReq{ - ClientKey: clientKey, - ChannelID: channelID, - Permission: policies.PublishPermission, - }, - authorizeRes: thingID, - identifyKey: clientKey, - res: &magistrala.ThingsAuthzRes{Authorized: true, Id: thingID}, - err: nil, - }, - { - desc: "authorize with invalid key", - req: &magistrala.ThingsAuthzReq{ - ThingKey: invalid, - ChannelId: channelID, - Permission: policies.PublishPermission, - }, - authorizeReq: things.AuthzReq{ - ClientKey: invalid, - ChannelID: channelID, - Permission: policies.PublishPermission, - }, - authorizeErr: svcerr.ErrAuthentication, - identifyKey: invalid, - identifyErr: svcerr.ErrAuthentication, - res: &magistrala.ThingsAuthzRes{}, - err: svcerr.ErrAuthentication, - }, - { - desc: "authorize with failed authorization", - thingID: thingID, - req: &magistrala.ThingsAuthzReq{ - ThingKey: clientKey, - ChannelId: channelID, - Permission: policies.PublishPermission, - }, - authorizeReq: things.AuthzReq{ - ClientKey: clientKey, - ChannelID: channelID, - Permission: policies.PublishPermission, - }, - authorizeErr: svcerr.ErrAuthorization, - identifyKey: clientKey, - res: &magistrala.ThingsAuthzRes{Authorized: false}, - err: svcerr.ErrAuthorization, - }, - - { - desc: "authorize with invalid permission", - thingID: thingID, - req: &magistrala.ThingsAuthzReq{ - ThingKey: clientKey, - ChannelId: channelID, - Permission: invalid, - }, - authorizeReq: things.AuthzReq{ - ChannelID: channelID, - ClientKey: clientKey, - Permission: invalid, - }, - identifyKey: clientKey, - authorizeErr: svcerr.ErrAuthorization, - res: &magistrala.ThingsAuthzRes{Authorized: false}, - err: svcerr.ErrAuthorization, - }, - { - desc: "authorize with invalid channel ID", - thingID: thingID, - req: &magistrala.ThingsAuthzReq{ - ThingKey: clientKey, - ChannelId: invalid, - Permission: policies.PublishPermission, - }, - authorizeReq: things.AuthzReq{ - ChannelID: invalid, - ClientKey: clientKey, - Permission: policies.PublishPermission, - }, - identifyKey: clientKey, - authorizeErr: svcerr.ErrAuthorization, - res: &magistrala.ThingsAuthzRes{Authorized: false}, - err: svcerr.ErrAuthorization, - }, - { - desc: "authorize with empty channel ID", - thingID: thingID, - req: &magistrala.ThingsAuthzReq{ - ThingKey: clientKey, - ChannelId: "", - Permission: policies.PublishPermission, - }, - authorizeReq: things.AuthzReq{ - ClientKey: clientKey, - ChannelID: "", - Permission: policies.PublishPermission, - }, - authorizeErr: svcerr.ErrAuthorization, - identifyKey: clientKey, - res: &magistrala.ThingsAuthzRes{Authorized: false}, - err: svcerr.ErrAuthorization, - }, - { - desc: "authorize with empty permission", - thingID: thingID, - req: &magistrala.ThingsAuthzReq{ - ThingKey: clientKey, - ChannelId: channelID, - Permission: "", - }, - authorizeReq: things.AuthzReq{ - ChannelID: channelID, - Permission: "", - ClientKey: clientKey, - }, - identifyKey: clientKey, - authorizeErr: svcerr.ErrAuthorization, - res: &magistrala.ThingsAuthzRes{Authorized: false}, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - svcCall1 := svc.On("Identify", mock.Anything, tc.identifyKey).Return(tc.thingID, tc.identifyErr) - svcCall2 := svc.On("Authorize", mock.Anything, tc.authorizeReq).Return(tc.thingID, tc.authorizeErr) - res, err := client.Authorize(context.Background(), tc.req) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) - assert.Equal(t, tc.res, res, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.res, res)) - svcCall1.Unset() - svcCall2.Unset() - } -} diff --git a/things/api/grpc/request.go b/things/api/grpc/request.go deleted file mode 100644 index 890335ecff..0000000000 --- a/things/api/grpc/request.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpc - -type authorizeReq struct { - ThingID string - ThingKey string - ChannelID string - Permission string -} diff --git a/things/api/http/channels.go b/things/api/http/channels.go deleted file mode 100644 index 7efd4685f2..0000000000 --- a/things/api/http/channels.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "context" - "encoding/json" - "log/slog" - "net/http" - "strings" - - "github.com/absmach/magistrala/internal/api" - gapi "github.com/absmach/magistrala/internal/groups/api" - "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/policies" - "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -func groupsHandler(svc groups.Service, authn mgauthn.Authentication, r *chi.Mux, logger *slog.Logger) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, true)) - - r.Route("/{domainID}/channels", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - gapi.CreateGroupEndpoint(svc, policies.NewChannelKind), - gapi.DecodeGroupCreate, - api.EncodeResponse, - opts..., - ), "create_channel").ServeHTTP) - - r.Get("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( - gapi.ViewGroupEndpoint(svc), - gapi.DecodeGroupRequest, - api.EncodeResponse, - opts..., - ), "view_channel").ServeHTTP) - - r.Delete("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( - gapi.DeleteGroupEndpoint(svc), - gapi.DecodeGroupRequest, - api.EncodeResponse, - opts..., - ), "delete_channel").ServeHTTP) - - r.Get("/{groupID}/permissions", otelhttp.NewHandler(kithttp.NewServer( - gapi.ViewGroupPermsEndpoint(svc), - gapi.DecodeGroupPermsRequest, - api.EncodeResponse, - opts..., - ), "view_channel_permissions").ServeHTTP) - - r.Put("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( - gapi.UpdateGroupEndpoint(svc), - gapi.DecodeGroupUpdate, - api.EncodeResponse, - opts..., - ), "update_channel").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "channels", "users"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_channels").ServeHTTP) - - r.Post("/{groupID}/enable", otelhttp.NewHandler(kithttp.NewServer( - gapi.EnableGroupEndpoint(svc), - gapi.DecodeChangeGroupStatus, - api.EncodeResponse, - opts..., - ), "enable_channel").ServeHTTP) - - r.Post("/{groupID}/disable", otelhttp.NewHandler(kithttp.NewServer( - gapi.DisableGroupEndpoint(svc), - gapi.DecodeChangeGroupStatus, - api.EncodeResponse, - opts..., - ), "disable_channel").ServeHTTP) - - // Request to add users to a channel - // This endpoint can be used alternative to /channels/{groupID}/members - r.Post("/{groupID}/users/assign", otelhttp.NewHandler(kithttp.NewServer( - assignUsersEndpoint(svc), - decodeAssignUsersRequest, - api.EncodeResponse, - opts..., - ), "assign_users").ServeHTTP) - - // Request to remove users from a channel - // This endpoint can be used alternative to /channels/{groupID}/members - r.Post("/{groupID}/users/unassign", otelhttp.NewHandler(kithttp.NewServer( - unassignUsersEndpoint(svc), - decodeUnassignUsersRequest, - api.EncodeResponse, - opts..., - ), "unassign_users").ServeHTTP) - - // Request to add user_groups to a channel - // This endpoint can be used alternative to /channels/{groupID}/members - r.Post("/{groupID}/groups/assign", otelhttp.NewHandler(kithttp.NewServer( - assignUserGroupsEndpoint(svc), - decodeAssignUserGroupsRequest, - api.EncodeResponse, - opts..., - ), "assign_groups").ServeHTTP) - - // Request to remove user_groups from a channel - // This endpoint can be used alternative to /channels/{groupID}/members - r.Post("/{groupID}/groups/unassign", otelhttp.NewHandler(kithttp.NewServer( - unassignUserGroupsEndpoint(svc), - decodeUnassignUserGroupsRequest, - api.EncodeResponse, - opts..., - ), "unassign_groups").ServeHTTP) - - r.Post("/{groupID}/things/{thingID}/connect", otelhttp.NewHandler(kithttp.NewServer( - connectChannelThingEndpoint(svc), - decodeConnectChannelThingRequest, - api.EncodeResponse, - opts..., - ), "connect_channel_thing").ServeHTTP) - - r.Post("/{groupID}/things/{thingID}/disconnect", otelhttp.NewHandler(kithttp.NewServer( - disconnectChannelThingEndpoint(svc), - decodeDisconnectChannelThingRequest, - api.EncodeResponse, - opts..., - ), "disconnect_channel_thing").ServeHTTP) - }) - - // Ideal location: things service, things endpoint - // Reason for placing here : - // SpiceDB provides list of channel ids to which thing id attached - // and channel service can access spiceDB and get this channel ids list with given thing id. - // Request to get list of channels to which thingID ({memberID}) belongs - r.Get("/{domainID}/things/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "channels", "things"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_channel_by_thing_id").ServeHTTP) - - // Ideal location: users service, users endpoint - // Reason for placing here : - // SpiceDB provides list of channel ids attached to given user id - // and channel service can access spiceDB and get this user ids list with given thing id. - // Request to get list of channels to which userID ({memberID}) have permission. - r.Get("/{domainID}/users/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "channels", "users"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_channel_by_user_id").ServeHTTP) - - // Ideal location: users service, groups endpoint - // SpiceDB provides list of channel ids attached to given user_group id - // and channel service can access spiceDB and get this user ids list with given user_group id. - // Request to get list of channels to which user_group_id ({memberID}) attached. - r.Get("/{domainID}/groups/{memberID}/channels", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "channels", "groups"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_channel_by_user_group_id").ServeHTTP) - - // Connect channel and thing - r.Post("/{domainID}/connect", otelhttp.NewHandler(kithttp.NewServer( - connectEndpoint(svc), - decodeConnectRequest, - api.EncodeResponse, - opts..., - ), "connect").ServeHTTP) - - // Disconnect channel and thing - r.Post("/{domainID}/disconnect", otelhttp.NewHandler(kithttp.NewServer( - disconnectEndpoint(svc), - decodeDisconnectRequest, - api.EncodeResponse, - opts..., - ), "disconnect").ServeHTTP) - }) - - return r -} - -func decodeAssignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := assignUsersRequest{ - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - -func decodeUnassignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := assignUsersRequest{ - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - -func decodeAssignUserGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := assignUserGroupsRequest{ - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - -func decodeUnassignUserGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := assignUserGroupsRequest{ - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - return req, nil -} - -func decodeConnectChannelThingRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := connectChannelThingRequest{ - ThingID: chi.URLParam(r, "thingID"), - ChannelID: chi.URLParam(r, "groupID"), - } - - return req, nil -} - -func decodeDisconnectChannelThingRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := connectChannelThingRequest{ - ThingID: chi.URLParam(r, "thingID"), - ChannelID: chi.URLParam(r, "groupID"), - } - - return req, nil -} - -func decodeConnectRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := connectChannelThingRequest{} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} - -func decodeDisconnectRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := connectChannelThingRequest{} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return req, nil -} diff --git a/things/api/http/endpoints.go b/things/api/http/endpoints.go deleted file mode 100644 index 10b9abc697..0000000000 --- a/things/api/http/endpoints.go +++ /dev/null @@ -1,530 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "context" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/policies" - "github.com/absmach/magistrala/things" - "github.com/go-kit/kit/endpoint" -) - -func createClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createClientReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - thing, err := svc.CreateClients(ctx, session, req.thing) - if err != nil { - return nil, err - } - - return createClientRes{ - Client: thing[0], - created: true, - }, nil - } -} - -func createClientsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createClientsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - page, err := svc.CreateClients(ctx, session, req.Things...) - if err != nil { - return nil, err - } - - res := clientsPageRes{ - pageRes: pageRes{ - Total: uint64(len(page)), - }, - Clients: []viewClientRes{}, - } - for _, c := range page { - res.Clients = append(res.Clients, viewClientRes{Client: c}) - } - - return res, nil - } -} - -func viewClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(viewClientReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - c, err := svc.View(ctx, session, req.id) - if err != nil { - return nil, err - } - - return viewClientRes{Client: c}, nil - } -} - -func viewClientPermsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(viewClientPermsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - p, err := svc.ViewPerms(ctx, session, req.id) - if err != nil { - return nil, err - } - - return viewClientPermsRes{Permissions: p}, nil - } -} - -func listClientsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listClientsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - pm := things.Page{ - Status: req.status, - Offset: req.offset, - Limit: req.limit, - Name: req.name, - Tag: req.tag, - Permission: req.permission, - Metadata: req.metadata, - ListPerms: req.listPerms, - Id: req.id, - } - page, err := svc.ListClients(ctx, session, req.userID, pm) - if err != nil { - return nil, 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 things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - page, err := svc.ListClientsByGroup(ctx, session, req.groupID, req.Page) - if err != nil { - return nil, err - } - - return buildClientsResponse(page), nil - } -} - -func updateClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - cli := things.Client{ - ID: req.id, - Name: req.Name, - Metadata: req.Metadata, - } - client, err := svc.Update(ctx, session, cli) - if err != nil { - return nil, err - } - - return updateClientRes{Client: client}, nil - } -} - -func updateClientTagsEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientTagsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - cli := things.Client{ - ID: req.id, - Tags: req.Tags, - } - client, err := svc.UpdateTags(ctx, session, cli) - if err != nil { - return nil, err - } - - return updateClientRes{Client: client}, nil - } -} - -func updateClientSecretEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateClientCredentialsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - client, err := svc.UpdateSecret(ctx, session, req.id, req.Secret) - if err != nil { - return nil, err - } - - return updateClientRes{Client: client}, nil - } -} - -func enableClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeClientStatusReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - client, err := svc.Enable(ctx, session, req.id) - if err != nil { - return nil, err - } - - return changeClientStatusRes{Client: client}, nil - } -} - -func disableClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeClientStatusReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - client, err := svc.Disable(ctx, session, req.id) - if err != nil { - return nil, err - } - - return changeClientStatusRes{Client: client}, nil - } -} - -func buildClientsResponse(cp things.MembersPage) clientsPageRes { - res := clientsPageRes{ - pageRes: pageRes{ - Total: cp.Total, - Offset: cp.Offset, - Limit: cp.Limit, - }, - Clients: []viewClientRes{}, - } - for _, c := range cp.Members { - res.Clients = append(res.Clients, viewClientRes{Client: c}) - } - - return res -} - -func assignUsersEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUsersRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Assign(ctx, session, req.groupID, req.Relation, policies.UsersKind, req.UserIDs...); err != nil { - return nil, err - } - - return assignUsersRes{}, nil - } -} - -func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUsersRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Unassign(ctx, session, req.groupID, req.Relation, policies.UsersKind, req.UserIDs...); err != nil { - return nil, err - } - - return unassignUsersRes{}, nil - } -} - -func assignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUserGroupsRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Assign(ctx, session, req.groupID, policies.ParentGroupRelation, policies.ChannelsKind, req.UserGroupIDs...); err != nil { - return nil, err - } - - return assignUserGroupsRes{}, nil - } -} - -func unassignUserGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUserGroupsRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Unassign(ctx, session, req.groupID, policies.ParentGroupRelation, policies.ChannelsKind, req.UserGroupIDs...); err != nil { - return nil, err - } - - return unassignUserGroupsRes{}, nil - } -} - -func connectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connectChannelThingRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Assign(ctx, session, req.ChannelID, policies.GroupRelation, policies.ThingsKind, req.ThingID); err != nil { - return nil, err - } - - return connectChannelThingRes{}, nil - } -} - -func disconnectChannelThingEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connectChannelThingRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Unassign(ctx, session, req.ChannelID, policies.GroupRelation, policies.ThingsKind, req.ThingID); err != nil { - return nil, err - } - - return disconnectChannelThingRes{}, nil - } -} - -func connectEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connectChannelThingRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Assign(ctx, session, req.ChannelID, policies.GroupRelation, policies.ThingsKind, req.ThingID); err != nil { - return nil, err - } - - return connectChannelThingRes{}, nil - } -} - -func disconnectEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(connectChannelThingRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Unassign(ctx, session, req.ChannelID, policies.GroupRelation, policies.ThingsKind, req.ThingID); err != nil { - return nil, err - } - - return disconnectChannelThingRes{}, nil - } -} - -func thingShareEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(thingShareRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Share(ctx, session, req.thingID, req.Relation, req.UserIDs...); err != nil { - return nil, err - } - - return thingShareRes{}, nil - } -} - -func thingUnshareEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(thingShareRequest) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Unshare(ctx, session, req.thingID, req.Relation, req.UserIDs...); err != nil { - return nil, err - } - - return thingUnshareRes{}, nil - } -} - -func deleteClientEndpoint(svc things.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(deleteClientReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Delete(ctx, session, req.id); err != nil { - return nil, err - } - - return deleteClientRes{}, nil - } -} diff --git a/things/api/http/responses.go b/things/api/http/responses.go deleted file mode 100644 index c998bb0585..0000000000 --- a/things/api/http/responses.go +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "fmt" - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/things" -) - -var ( - _ magistrala.Response = (*viewClientRes)(nil) - _ magistrala.Response = (*viewClientPermsRes)(nil) - _ magistrala.Response = (*createClientRes)(nil) - _ magistrala.Response = (*deleteClientRes)(nil) - _ magistrala.Response = (*clientsPageRes)(nil) - _ magistrala.Response = (*viewMembersRes)(nil) - _ magistrala.Response = (*assignUsersGroupsRes)(nil) - _ magistrala.Response = (*unassignUsersGroupsRes)(nil) - _ magistrala.Response = (*connectChannelThingRes)(nil) - _ magistrala.Response = (*disconnectChannelThingRes)(nil) - _ magistrala.Response = (*changeClientStatusRes)(nil) -) - -type pageRes struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset"` - Total uint64 `json:"total"` -} - -type createClientRes struct { - things.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("/things/%s", res.ID), - } - } - - return map[string]string{} -} - -func (res createClientRes) Empty() bool { - return false -} - -type updateClientRes struct { - things.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 { - things.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 viewClientPermsRes struct { - Permissions []string `json:"permissions"` -} - -func (res viewClientPermsRes) Code() int { - return http.StatusOK -} - -func (res viewClientPermsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewClientPermsRes) Empty() bool { - return false -} - -type clientsPageRes struct { - pageRes - Clients []viewClientRes `json:"things"` -} - -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 { - things.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 changeClientStatusRes struct { - things.Client -} - -func (res changeClientStatusRes) Code() int { - return http.StatusOK -} - -func (res changeClientStatusRes) Headers() map[string]string { - return map[string]string{} -} - -func (res changeClientStatusRes) Empty() bool { - return false -} - -type deleteClientRes struct{} - -func (res deleteClientRes) Code() int { - return http.StatusNoContent -} - -func (res deleteClientRes) Headers() map[string]string { - return map[string]string{} -} - -func (res deleteClientRes) Empty() bool { - return true -} - -type assignUsersGroupsRes struct{} - -func (res assignUsersGroupsRes) Code() int { - return http.StatusCreated -} - -func (res assignUsersGroupsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res assignUsersGroupsRes) Empty() bool { - return true -} - -type unassignUsersGroupsRes struct{} - -func (res unassignUsersGroupsRes) Code() int { - return http.StatusNoContent -} - -func (res unassignUsersGroupsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res unassignUsersGroupsRes) Empty() bool { - return true -} - -type assignUsersRes struct{} - -func (res assignUsersRes) Code() int { - return http.StatusCreated -} - -func (res assignUsersRes) Headers() map[string]string { - return map[string]string{} -} - -func (res assignUsersRes) Empty() bool { - return true -} - -type unassignUsersRes struct{} - -func (res unassignUsersRes) Code() int { - return http.StatusNoContent -} - -func (res unassignUsersRes) Headers() map[string]string { - return map[string]string{} -} - -func (res unassignUsersRes) Empty() bool { - return true -} - -type assignUserGroupsRes struct{} - -func (res assignUserGroupsRes) Code() int { - return http.StatusCreated -} - -func (res assignUserGroupsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res assignUserGroupsRes) Empty() bool { - return true -} - -type unassignUserGroupsRes struct{} - -func (res unassignUserGroupsRes) Code() int { - return http.StatusNoContent -} - -func (res unassignUserGroupsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res unassignUserGroupsRes) Empty() bool { - return true -} - -type connectChannelThingRes struct{} - -func (res connectChannelThingRes) Code() int { - return http.StatusCreated -} - -func (res connectChannelThingRes) Headers() map[string]string { - return map[string]string{} -} - -func (res connectChannelThingRes) Empty() bool { - return true -} - -type disconnectChannelThingRes struct{} - -func (res disconnectChannelThingRes) Code() int { - return http.StatusNoContent -} - -func (res disconnectChannelThingRes) Headers() map[string]string { - return map[string]string{} -} - -func (res disconnectChannelThingRes) Empty() bool { - return true -} - -type thingShareRes struct{} - -func (res thingShareRes) Code() int { - return http.StatusCreated -} - -func (res thingShareRes) Headers() map[string]string { - return map[string]string{} -} - -func (res thingShareRes) Empty() bool { - return true -} - -type thingUnshareRes struct{} - -func (res thingUnshareRes) Code() int { - return http.StatusNoContent -} - -func (res thingUnshareRes) Headers() map[string]string { - return map[string]string{} -} - -func (res thingUnshareRes) Empty() bool { - return true -} diff --git a/things/api/http/transport.go b/things/api/http/transport.go deleted file mode 100644 index 415e463d02..0000000000 --- a/things/api/http/transport.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "log/slog" - "net/http" - - "github.com/absmach/magistrala" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/things" - "github.com/go-chi/chi/v5" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// MakeHandler returns a HTTP handler for Things and Groups API endpoints. -func MakeHandler(tsvc things.Service, grps groups.Service, authn mgauthn.Authentication, mux *chi.Mux, logger *slog.Logger, instanceID string) http.Handler { - clientsHandler(tsvc, mux, authn, logger) - groupsHandler(grps, authn, mux, logger) - - mux.Get("/health", magistrala.Health("things", instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} diff --git a/things/cache/things.go b/things/cache/things.go deleted file mode 100644 index b09aa6efcb..0000000000 --- a/things/cache/things.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cache - -import ( - "context" - "fmt" - "time" - - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/things" - "github.com/redis/go-redis/v9" -) - -const ( - keyPrefix = "thing_key" - idPrefix = "thing_id" -) - -var _ things.Cache = (*thingCache)(nil) - -type thingCache struct { - client *redis.Client - keyDuration time.Duration -} - -// NewCache returns redis thing cache implementation. -func NewCache(client *redis.Client, duration time.Duration) things.Cache { - return &thingCache{ - client: client, - keyDuration: duration, - } -} - -func (tc *thingCache) Save(ctx context.Context, thingKey, thingID string) error { - if thingKey == "" || thingID == "" { - return errors.Wrap(repoerr.ErrCreateEntity, errors.New("thing key or thing id is empty")) - } - tkey := fmt.Sprintf("%s:%s", keyPrefix, thingKey) - if err := tc.client.Set(ctx, tkey, thingID, tc.keyDuration).Err(); err != nil { - return errors.Wrap(repoerr.ErrCreateEntity, err) - } - - tid := fmt.Sprintf("%s:%s", idPrefix, thingID) - if err := tc.client.Set(ctx, tid, thingKey, tc.keyDuration).Err(); err != nil { - return errors.Wrap(repoerr.ErrCreateEntity, err) - } - - return nil -} - -func (tc *thingCache) ID(ctx context.Context, thingKey string) (string, error) { - if thingKey == "" { - return "", repoerr.ErrNotFound - } - - tkey := fmt.Sprintf("%s:%s", keyPrefix, thingKey) - thingID, err := tc.client.Get(ctx, tkey).Result() - if err != nil { - return "", errors.Wrap(repoerr.ErrNotFound, err) - } - - return thingID, nil -} - -func (tc *thingCache) Remove(ctx context.Context, thingID string) error { - tid := fmt.Sprintf("%s:%s", idPrefix, thingID) - key, err := tc.client.Get(ctx, tid).Result() - // Redis returns Nil Reply when key does not exist. - if err == redis.Nil { - return nil - } - if err != nil { - return errors.Wrap(repoerr.ErrRemoveEntity, err) - } - - tkey := fmt.Sprintf("%s:%s", keyPrefix, key) - if err := tc.client.Del(ctx, tkey, tid).Err(); err != nil { - return errors.Wrap(repoerr.ErrRemoveEntity, err) - } - - return nil -} diff --git a/things/doc.go b/things/doc.go deleted file mode 100644 index c22b930318..0000000000 --- a/things/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package things contains the domain concept definitions needed to -// support Magistrala things service functionality. -// -// This package defines the core domain concepts and types necessary to -// handle things in the context of a Magistrala things service. It abstracts -// the underlying complexities of user management and provides a structured -// approach to working with things. -package things diff --git a/things/errors.go b/things/errors.go deleted file mode 100644 index 901dcfa7d4..0000000000 --- a/things/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package things - -import "errors" - -var ( - // 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") -) diff --git a/things/events/streams.go b/things/events/streams.go deleted file mode 100644 index 295fb37bc5..0000000000 --- a/things/events/streams.go +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "context" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/store" - "github.com/absmach/magistrala/things" -) - -const streamID = "magistrala.things" - -var _ things.Service = (*eventStore)(nil) - -type eventStore struct { - events.Publisher - svc things.Service -} - -// NewEventStoreMiddleware returns wrapper around things service that sends -// events to event store. -func NewEventStoreMiddleware(ctx context.Context, svc things.Service, url string) (things.Service, error) { - publisher, err := store.NewPublisher(ctx, url, streamID) - if err != nil { - return nil, err - } - - return &eventStore{ - svc: svc, - Publisher: publisher, - }, nil -} - -func (es *eventStore) CreateClients(ctx context.Context, session authn.Session, thing ...things.Client) ([]things.Client, error) { - sths, err := es.svc.CreateClients(ctx, session, thing...) - if err != nil { - return sths, err - } - - for _, th := range sths { - event := createClientEvent{ - th, - } - if err := es.Publish(ctx, event); err != nil { - return sths, err - } - } - - return sths, nil -} - -func (es *eventStore) Update(ctx context.Context, session authn.Session, thing things.Client) (things.Client, error) { - cli, err := es.svc.Update(ctx, session, thing) - if err != nil { - return cli, err - } - - return es.update(ctx, "", cli) -} - -func (es *eventStore) UpdateTags(ctx context.Context, session authn.Session, thing things.Client) (things.Client, error) { - cli, err := es.svc.UpdateTags(ctx, session, thing) - if err != nil { - return cli, err - } - - return es.update(ctx, "tags", cli) -} - -func (es *eventStore) UpdateSecret(ctx context.Context, session authn.Session, id, key string) (things.Client, error) { - cli, err := es.svc.UpdateSecret(ctx, session, id, key) - if err != nil { - return cli, err - } - - return es.update(ctx, "secret", cli) -} - -func (es *eventStore) update(ctx context.Context, operation string, thing things.Client) (things.Client, error) { - event := updateClientEvent{ - thing, operation, - } - - if err := es.Publish(ctx, event); err != nil { - return thing, err - } - - return thing, nil -} - -func (es *eventStore) View(ctx context.Context, session authn.Session, id string) (things.Client, error) { - thi, err := es.svc.View(ctx, session, id) - if err != nil { - return thi, err - } - - event := viewClientEvent{ - thi, - } - if err := es.Publish(ctx, event); err != nil { - return thi, err - } - - return thi, nil -} - -func (es *eventStore) ViewPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - permissions, err := es.svc.ViewPerms(ctx, session, id) - if err != nil { - return permissions, err - } - - event := viewClientPermsEvent{ - permissions, - } - if err := es.Publish(ctx, event); err != nil { - return permissions, err - } - - return permissions, nil -} - -func (es *eventStore) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm things.Page) (things.ClientsPage, error) { - cp, err := es.svc.ListClients(ctx, session, reqUserID, pm) - if err != nil { - return cp, err - } - event := listClientEvent{ - reqUserID, - pm, - } - if err := es.Publish(ctx, event); err != nil { - return cp, err - } - - return cp, nil -} - -func (es *eventStore) ListClientsByGroup(ctx context.Context, session authn.Session, chID string, pm things.Page) (things.MembersPage, error) { - mp, err := es.svc.ListClientsByGroup(ctx, session, chID, pm) - if err != nil { - return mp, err - } - event := listClientByGroupEvent{ - pm, chID, - } - if err := es.Publish(ctx, event); err != nil { - return mp, err - } - - return mp, nil -} - -func (es *eventStore) Enable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - thi, err := es.svc.Enable(ctx, session, id) - if err != nil { - return thi, err - } - - return es.changeStatus(ctx, thi) -} - -func (es *eventStore) Disable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - thi, err := es.svc.Disable(ctx, session, id) - if err != nil { - return thi, err - } - - return es.changeStatus(ctx, thi) -} - -func (es *eventStore) changeStatus(ctx context.Context, thi things.Client) (things.Client, error) { - event := changeStatusClientEvent{ - id: thi.ID, - updatedAt: thi.UpdatedAt, - updatedBy: thi.UpdatedBy, - status: thi.Status.String(), - } - if err := es.Publish(ctx, event); err != nil { - return thi, err - } - - return thi, nil -} - -func (es *eventStore) Identify(ctx context.Context, key string) (string, error) { - thingID, err := es.svc.Identify(ctx, key) - if err != nil { - return thingID, err - } - event := identifyClientEvent{ - thingID: thingID, - } - - if err := es.Publish(ctx, event); err != nil { - return thingID, err - } - return thingID, nil -} - -func (es *eventStore) Authorize(ctx context.Context, req things.AuthzReq) (string, error) { - thingID, err := es.svc.Authorize(ctx, req) - if err != nil { - return thingID, err - } - - event := authorizeClientEvent{ - thingID: thingID, - channelID: req.ChannelID, - permission: req.Permission, - } - - if err := es.Publish(ctx, event); err != nil { - return thingID, err - } - - return thingID, nil -} - -func (es *eventStore) Share(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { - if err := es.svc.Share(ctx, session, id, relation, userids...); err != nil { - return err - } - - event := shareClientEvent{ - action: "share", - id: id, - relation: relation, - userIDs: userids, - } - - return es.Publish(ctx, event) -} - -func (es *eventStore) Unshare(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { - if err := es.svc.Unshare(ctx, session, id, relation, userids...); err != nil { - return err - } - - event := shareClientEvent{ - action: "unshare", - id: id, - relation: relation, - userIDs: userids, - } - - return es.Publish(ctx, event) -} - -func (es *eventStore) Delete(ctx context.Context, session authn.Session, id string) error { - if err := es.svc.Delete(ctx, session, id); err != nil { - return err - } - - event := removeClientEvent{id} - - if err := es.Publish(ctx, event); err != nil { - return err - } - - return nil -} diff --git a/things/middleware/authorization.go b/things/middleware/authorization.go deleted file mode 100644 index 85a3af5d2b..0000000000 --- a/things/middleware/authorization.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - - "github.com/absmach/magistrala/pkg/authn" - mgauthz "github.com/absmach/magistrala/pkg/authz" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/policies" - "github.com/absmach/magistrala/things" -) - -var _ things.Service = (*authorizationMiddleware)(nil) - -type authorizationMiddleware struct { - svc things.Service - authz mgauthz.Authorization -} - -// AuthorizationMiddleware adds authorization to the clients service. -func AuthorizationMiddleware(svc things.Service, authz mgauthz.Authorization) things.Service { - return &authorizationMiddleware{ - svc: svc, - authz: authz, - } -} - -func (am *authorizationMiddleware) CreateClients(ctx context.Context, session authn.Session, client ...things.Client) ([]things.Client, error) { - if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.CreatePermission, policies.DomainType, session.DomainID); err != nil { - return nil, err - } - - return am.svc.CreateClients(ctx, session, client...) -} - -func (am *authorizationMiddleware) View(ctx context.Context, session authn.Session, id string) (things.Client, error) { - if session.DomainUserID == "" { - return things.Client{}, svcerr.ErrDomainAuthorization - } - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.ViewPermission, policies.ThingType, id); err != nil { - return things.Client{}, err - } - - return am.svc.View(ctx, session, id) -} - -func (am *authorizationMiddleware) ViewPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - return am.svc.ViewPerms(ctx, session, id) -} - -func (am *authorizationMiddleware) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm things.Page) (things.ClientsPage, error) { - if session.DomainUserID == "" { - return things.ClientsPage{}, svcerr.ErrDomainAuthorization - } - switch { - case reqUserID != "" && reqUserID != session.UserID: - if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.AdminPermission, policies.DomainType, session.DomainID); err != nil { - return things.ClientsPage{}, err - } - default: - err := am.checkSuperAdmin(ctx, session.UserID) - switch { - case err == nil: - session.SuperAdmin = true - default: - if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID); err != nil { - return things.ClientsPage{}, err - } - } - } - - return am.svc.ListClients(ctx, session, reqUserID, pm) -} - -func (am *authorizationMiddleware) ListClientsByGroup(ctx context.Context, session authn.Session, groupID string, pm things.Page) (things.MembersPage, error) { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, pm.Permission, policies.GroupType, groupID); err != nil { - return things.MembersPage{}, err - } - - return am.svc.ListClientsByGroup(ctx, session, groupID, pm) -} - -func (am *authorizationMiddleware) Update(ctx context.Context, session authn.Session, client things.Client) (things.Client, error) { - if session.DomainUserID == "" { - return things.Client{}, svcerr.ErrDomainAuthorization - } - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ThingType, client.ID); err != nil { - return things.Client{}, err - } - - return am.svc.Update(ctx, session, client) -} - -func (am *authorizationMiddleware) UpdateTags(ctx context.Context, session authn.Session, client things.Client) (things.Client, error) { - if session.DomainUserID == "" { - return things.Client{}, svcerr.ErrDomainAuthorization - } - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ThingType, client.ID); err != nil { - return things.Client{}, err - } - - return am.svc.UpdateTags(ctx, session, client) -} - -func (am *authorizationMiddleware) UpdateSecret(ctx context.Context, session authn.Session, id, key string) (things.Client, error) { - if session.DomainUserID == "" { - return things.Client{}, svcerr.ErrDomainAuthorization - } - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ThingType, id); err != nil { - return things.Client{}, err - } - - return am.svc.UpdateSecret(ctx, session, id, key) -} - -func (am *authorizationMiddleware) Enable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - if session.DomainUserID == "" { - return things.Client{}, svcerr.ErrDomainAuthorization - } - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.ThingType, id); err != nil { - return things.Client{}, err - } - - return am.svc.Enable(ctx, session, id) -} - -func (am *authorizationMiddleware) Disable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - if session.DomainUserID == "" { - return things.Client{}, svcerr.ErrDomainAuthorization - } - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.ThingType, id); err != nil { - return things.Client{}, err - } - - return am.svc.Disable(ctx, session, id) -} - -func (am *authorizationMiddleware) Share(ctx context.Context, session authn.Session, id string, relation string, userids ...string) error { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.ThingType, id); err != nil { - return err - } - - return am.svc.Share(ctx, session, id, relation, userids...) -} - -func (am *authorizationMiddleware) Unshare(ctx context.Context, session authn.Session, id string, relation string, userids ...string) error { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.ThingType, id); err != nil { - return err - } - - return am.svc.Unshare(ctx, session, id, relation, userids...) -} - -func (am *authorizationMiddleware) Identify(ctx context.Context, key string) (string, error) { - return am.svc.Identify(ctx, key) -} - -func (am *authorizationMiddleware) Authorize(ctx context.Context, req things.AuthzReq) (string, error) { - return am.svc.Authorize(ctx, req) -} - -func (am *authorizationMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.ThingType, id); err != nil { - return err - } - - return am.svc.Delete(ctx, session, id) -} - -func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID string) error { - if err := am.authz.Authorize(ctx, mgauthz.PolicyReq{ - SubjectType: policies.UserType, - Subject: adminID, - Permission: policies.AdminPermission, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }); err != nil { - return err - } - return nil -} - -func (am *authorizationMiddleware) authorize(ctx context.Context, domain, subjType, subjKind, subj, perm, objType, obj string) error { - req := mgauthz.PolicyReq{ - Domain: domain, - SubjectType: subjType, - SubjectKind: subjKind, - Subject: subj, - Permission: perm, - ObjectType: objType, - Object: obj, - } - if err := am.authz.Authorize(ctx, req); err != nil { - return err - } - return nil -} diff --git a/things/middleware/logging.go b/things/middleware/logging.go deleted file mode 100644 index a176159c8e..0000000000 --- a/things/middleware/logging.go +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "fmt" - "log/slog" - "time" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/things" -) - -var _ things.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc things.Service -} - -func LoggingMiddleware(svc things.Service, logger *slog.Logger) things.Service { - return &loggingMiddleware{logger, svc} -} - -func (lm *loggingMiddleware) CreateClients(ctx context.Context, session authn.Session, clients ...things.Client) (cs []things.Client, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn(fmt.Sprintf("Create %d things failed", len(clients)), args...) - return - } - lm.logger.Info(fmt.Sprintf("Create %d things completed successfully", len(clients)), args...) - }(time.Now()) - return lm.svc.CreateClients(ctx, session, clients...) -} - -func (lm *loggingMiddleware) View(ctx context.Context, session authn.Session, id string) (c things.Client, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("thing", - slog.String("id", c.ID), - slog.String("name", c.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View thing failed", args...) - return - } - lm.logger.Info("View thing completed successfully", args...) - }(time.Now()) - return lm.svc.View(ctx, session, id) -} - -func (lm *loggingMiddleware) ViewPerms(ctx context.Context, session authn.Session, id string) (p []string, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View thing permissions failed", args...) - return - } - lm.logger.Info("View thing permissions completed successfully", args...) - }(time.Now()) - return lm.svc.ViewPerms(ctx, session, id) -} - -func (lm *loggingMiddleware) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm things.Page) (cp things.ClientsPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("user_id", reqUserID), - slog.Group("page", - slog.Uint64("limit", pm.Limit), - slog.Uint64("offset", pm.Offset), - slog.Uint64("total", cp.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List things failed", args...) - return - } - lm.logger.Info("List things completed successfully", args...) - }(time.Now()) - return lm.svc.ListClients(ctx, session, reqUserID, pm) -} - -func (lm *loggingMiddleware) Update(ctx context.Context, session authn.Session, client things.Client) (c things.Client, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("thing", - slog.String("id", client.ID), - slog.String("name", client.Name), - slog.Any("metadata", client.Metadata), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update thing failed", args...) - return - } - lm.logger.Info("Update thing completed successfully", args...) - }(time.Now()) - return lm.svc.Update(ctx, session, client) -} - -func (lm *loggingMiddleware) UpdateTags(ctx context.Context, session authn.Session, client things.Client) (c things.Client, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("thing", - slog.String("id", c.ID), - slog.String("name", c.Name), - slog.Any("tags", c.Tags), - ), - } - if err != nil { - args := append(args, slog.String("error", err.Error())) - lm.logger.Warn("Update thing tags failed", args...) - return - } - lm.logger.Info("Update thing tags completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateTags(ctx, session, client) -} - -func (lm *loggingMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (c things.Client, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("thing", - slog.String("id", c.ID), - slog.String("name", c.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update thing secret failed", args...) - return - } - lm.logger.Info("Update thing secret completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateSecret(ctx, session, oldSecret, newSecret) -} - -func (lm *loggingMiddleware) Enable(ctx context.Context, session authn.Session, id string) (c things.Client, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("thing", - slog.String("id", id), - slog.String("name", c.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Enable thing failed", args...) - return - } - lm.logger.Info("Enable thing completed successfully", args...) - }(time.Now()) - return lm.svc.Enable(ctx, session, id) -} - -func (lm *loggingMiddleware) Disable(ctx context.Context, session authn.Session, id string) (c things.Client, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("thing", - slog.String("id", id), - slog.String("name", c.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Disable thing failed", args...) - return - } - lm.logger.Info("Disable thing completed successfully", args...) - }(time.Now()) - return lm.svc.Disable(ctx, session, id) -} - -func (lm *loggingMiddleware) ListClientsByGroup(ctx context.Context, session authn.Session, channelID string, cp things.Page) (mp things.MembersPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("channel_id", channelID), - slog.Group("page", - slog.Uint64("offset", cp.Offset), - slog.Uint64("limit", cp.Limit), - slog.Uint64("total", mp.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List things by group failed", args...) - return - } - lm.logger.Info("List things by group completed successfully", args...) - }(time.Now()) - return lm.svc.ListClientsByGroup(ctx, session, channelID, cp) -} - -func (lm *loggingMiddleware) Identify(ctx context.Context, key string) (id string, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Identify thing failed", args...) - return - } - lm.logger.Info("Identify thing completed successfully", args...) - }(time.Now()) - return lm.svc.Identify(ctx, key) -} - -func (lm *loggingMiddleware) Authorize(ctx context.Context, req things.AuthzReq) (id string, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("clientID", req.ClientID), - slog.String("clientKey", req.ClientKey), - slog.String("channelID", req.ChannelID), - slog.String("permission", req.Permission), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Authorize failed", args...) - return - } - lm.logger.Info("Authorize completed successfully", args...) - }(time.Now()) - return lm.svc.Authorize(ctx, req) -} - -func (lm *loggingMiddleware) Share(ctx context.Context, session authn.Session, id, relation string, userids ...string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("client_id", id), - slog.Any("user_ids", userids), - slog.String("relation", relation), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Share client failed", args...) - return - } - lm.logger.Info("Share client completed successfully", args...) - }(time.Now()) - return lm.svc.Share(ctx, session, id, relation, userids...) -} - -func (lm *loggingMiddleware) Unshare(ctx context.Context, session authn.Session, id, relation string, userids ...string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("client_id", id), - slog.Any("user_ids", userids), - slog.String("relation", relation), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Unshare client failed", args...) - return - } - lm.logger.Info("Unshare client completed successfully", args...) - }(time.Now()) - return lm.svc.Unshare(ctx, session, id, relation, userids...) -} - -func (lm *loggingMiddleware) Delete(ctx context.Context, session authn.Session, id string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("client_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Delete client failed", args...) - return - } - lm.logger.Info("Delete client completed successfully", args...) - }(time.Now()) - return lm.svc.Delete(ctx, session, id) -} diff --git a/things/mocks/repository.go b/things/mocks/repository.go deleted file mode 100644 index 2917461ba0..0000000000 --- a/things/mocks/repository.go +++ /dev/null @@ -1,366 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - things "github.com/absmach/magistrala/things" - mock "github.com/stretchr/testify/mock" -) - -// Repository is an autogenerated mock type for the Repository type -type Repository struct { - mock.Mock -} - -// ChangeStatus provides a mock function with given fields: ctx, client -func (_m *Repository) ChangeStatus(ctx context.Context, client things.Client) (things.Client, error) { - ret := _m.Called(ctx, client) - - if len(ret) == 0 { - panic("no return value specified for ChangeStatus") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.Client) (things.Client, error)); ok { - return rf(ctx, client) - } - if rf, ok := ret.Get(0).(func(context.Context, things.Client) things.Client); ok { - r0 = rf(ctx, client) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.Client) error); ok { - r1 = rf(ctx, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Delete provides a mock function with given fields: ctx, id -func (_m *Repository) Delete(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// RetrieveAll provides a mock function with given fields: ctx, pm -func (_m *Repository) RetrieveAll(ctx context.Context, pm things.Page) (things.ClientsPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAll") - } - - var r0 things.ClientsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.Page) (things.ClientsPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, things.Page) things.ClientsPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(things.ClientsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.Page) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveAllByIDs provides a mock function with given fields: ctx, pm -func (_m *Repository) RetrieveAllByIDs(ctx context.Context, pm things.Page) (things.ClientsPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAllByIDs") - } - - var r0 things.ClientsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.Page) (things.ClientsPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, things.Page) things.ClientsPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(things.ClientsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.Page) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveByID provides a mock function with given fields: ctx, id -func (_m *Repository) RetrieveByID(ctx context.Context, id string) (things.Client, error) { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByID") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (things.Client, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string) things.Client); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveBySecret provides a mock function with given fields: ctx, key -func (_m *Repository) RetrieveBySecret(ctx context.Context, key string) (things.Client, error) { - ret := _m.Called(ctx, key) - - if len(ret) == 0 { - panic("no return value specified for RetrieveBySecret") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (things.Client, error)); ok { - return rf(ctx, key) - } - if rf, ok := ret.Get(0).(func(context.Context, string) things.Client); ok { - r0 = rf(ctx, key) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, key) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, client -func (_m *Repository) Save(ctx context.Context, client ...things.Client) ([]things.Client, error) { - _va := make([]interface{}, len(client)) - for _i := range client { - _va[_i] = client[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 []things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, ...things.Client) ([]things.Client, error)); ok { - return rf(ctx, client...) - } - if rf, ok := ret.Get(0).(func(context.Context, ...things.Client) []things.Client); ok { - r0 = rf(ctx, client...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]things.Client) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, ...things.Client) error); ok { - r1 = rf(ctx, client...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// SearchClients provides a mock function with given fields: ctx, pm -func (_m *Repository) SearchClients(ctx context.Context, pm things.Page) (things.ClientsPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for SearchClients") - } - - var r0 things.ClientsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.Page) (things.ClientsPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, things.Page) things.ClientsPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(things.ClientsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.Page) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Update provides a mock function with given fields: ctx, client -func (_m *Repository) Update(ctx context.Context, client things.Client) (things.Client, error) { - ret := _m.Called(ctx, client) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.Client) (things.Client, error)); ok { - return rf(ctx, client) - } - if rf, ok := ret.Get(0).(func(context.Context, things.Client) things.Client); ok { - r0 = rf(ctx, client) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.Client) error); ok { - r1 = rf(ctx, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateIdentity provides a mock function with given fields: ctx, client -func (_m *Repository) UpdateIdentity(ctx context.Context, client things.Client) (things.Client, error) { - ret := _m.Called(ctx, client) - - if len(ret) == 0 { - panic("no return value specified for UpdateIdentity") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.Client) (things.Client, error)); ok { - return rf(ctx, client) - } - if rf, ok := ret.Get(0).(func(context.Context, things.Client) things.Client); ok { - r0 = rf(ctx, client) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.Client) error); ok { - r1 = rf(ctx, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateSecret provides a mock function with given fields: ctx, client -func (_m *Repository) UpdateSecret(ctx context.Context, client things.Client) (things.Client, error) { - ret := _m.Called(ctx, client) - - if len(ret) == 0 { - panic("no return value specified for UpdateSecret") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.Client) (things.Client, error)); ok { - return rf(ctx, client) - } - if rf, ok := ret.Get(0).(func(context.Context, things.Client) things.Client); ok { - r0 = rf(ctx, client) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.Client) error); ok { - r1 = rf(ctx, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateTags provides a mock function with given fields: ctx, client -func (_m *Repository) UpdateTags(ctx context.Context, client things.Client) (things.Client, error) { - ret := _m.Called(ctx, client) - - if len(ret) == 0 { - panic("no return value specified for UpdateTags") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.Client) (things.Client, error)); ok { - return rf(ctx, client) - } - if rf, ok := ret.Get(0).(func(context.Context, things.Client) things.Client); ok { - r0 = rf(ctx, client) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.Client) error); ok { - r1 = rf(ctx, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *Repository { - mock := &Repository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/things/mocks/service.go b/things/mocks/service.go deleted file mode 100644 index 9719334dca..0000000000 --- a/things/mocks/service.go +++ /dev/null @@ -1,449 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - authn "github.com/absmach/magistrala/pkg/authn" - - mock "github.com/stretchr/testify/mock" - - things "github.com/absmach/magistrala/things" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// Authorize provides a mock function with given fields: ctx, req -func (_m *Service) Authorize(ctx context.Context, req things.AuthzReq) (string, error) { - ret := _m.Called(ctx, req) - - if len(ret) == 0 { - panic("no return value specified for Authorize") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, things.AuthzReq) (string, error)); ok { - return rf(ctx, req) - } - if rf, ok := ret.Get(0).(func(context.Context, things.AuthzReq) string); ok { - r0 = rf(ctx, req) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, things.AuthzReq) error); ok { - r1 = rf(ctx, req) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CreateClients provides a mock function with given fields: ctx, session, client -func (_m *Service) CreateClients(ctx context.Context, session authn.Session, client ...things.Client) ([]things.Client, error) { - _va := make([]interface{}, len(client)) - for _i := range client { - _va[_i] = client[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, session) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for CreateClients") - } - - var r0 []things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, ...things.Client) ([]things.Client, error)); ok { - return rf(ctx, session, client...) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, ...things.Client) []things.Client); ok { - r0 = rf(ctx, session, client...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]things.Client) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, ...things.Client) error); ok { - r1 = rf(ctx, session, client...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Delete provides a mock function with given fields: ctx, session, id -func (_m *Service) Delete(ctx context.Context, session authn.Session, id string) error { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Disable provides a mock function with given fields: ctx, session, id -func (_m *Service) Disable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for Disable") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (things.Client, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) things.Client); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Enable provides a mock function with given fields: ctx, session, id -func (_m *Service) Enable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for Enable") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (things.Client, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) things.Client); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Identify provides a mock function with given fields: ctx, key -func (_m *Service) Identify(ctx context.Context, key string) (string, error) { - ret := _m.Called(ctx, key) - - if len(ret) == 0 { - panic("no return value specified for Identify") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { - return rf(ctx, key) - } - if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { - r0 = rf(ctx, key) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, key) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListClients provides a mock function with given fields: ctx, session, reqUserID, pm -func (_m *Service) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm things.Page) (things.ClientsPage, error) { - ret := _m.Called(ctx, session, reqUserID, pm) - - if len(ret) == 0 { - panic("no return value specified for ListClients") - } - - var r0 things.ClientsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, things.Page) (things.ClientsPage, error)); ok { - return rf(ctx, session, reqUserID, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, things.Page) things.ClientsPage); ok { - r0 = rf(ctx, session, reqUserID, pm) - } else { - r0 = ret.Get(0).(things.ClientsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, things.Page) error); ok { - r1 = rf(ctx, session, reqUserID, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListClientsByGroup provides a mock function with given fields: ctx, session, groupID, pm -func (_m *Service) ListClientsByGroup(ctx context.Context, session authn.Session, groupID string, pm things.Page) (things.MembersPage, error) { - ret := _m.Called(ctx, session, groupID, pm) - - if len(ret) == 0 { - panic("no return value specified for ListClientsByGroup") - } - - var r0 things.MembersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, things.Page) (things.MembersPage, error)); ok { - return rf(ctx, session, groupID, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, things.Page) things.MembersPage); ok { - r0 = rf(ctx, session, groupID, pm) - } else { - r0 = ret.Get(0).(things.MembersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, things.Page) error); ok { - r1 = rf(ctx, session, groupID, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Share provides a mock function with given fields: ctx, session, id, relation, userids -func (_m *Service) Share(ctx context.Context, session authn.Session, id string, relation string, userids ...string) error { - _va := make([]interface{}, len(userids)) - for _i := range userids { - _va[_i] = userids[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, session, id, relation) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Share") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, ...string) error); ok { - r0 = rf(ctx, session, id, relation, userids...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Unshare provides a mock function with given fields: ctx, session, id, relation, userids -func (_m *Service) Unshare(ctx context.Context, session authn.Session, id string, relation string, userids ...string) error { - _va := make([]interface{}, len(userids)) - for _i := range userids { - _va[_i] = userids[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, session, id, relation) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Unshare") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, ...string) error); ok { - r0 = rf(ctx, session, id, relation, userids...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Update provides a mock function with given fields: ctx, session, client -func (_m *Service) Update(ctx context.Context, session authn.Session, client things.Client) (things.Client, error) { - ret := _m.Called(ctx, session, client) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, things.Client) (things.Client, error)); ok { - return rf(ctx, session, client) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, things.Client) things.Client); ok { - r0 = rf(ctx, session, client) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, things.Client) error); ok { - r1 = rf(ctx, session, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateSecret provides a mock function with given fields: ctx, session, id, key -func (_m *Service) UpdateSecret(ctx context.Context, session authn.Session, id string, key string) (things.Client, error) { - ret := _m.Called(ctx, session, id, key) - - if len(ret) == 0 { - panic("no return value specified for UpdateSecret") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (things.Client, error)); ok { - return rf(ctx, session, id, key) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) things.Client); ok { - r0 = rf(ctx, session, id, key) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { - r1 = rf(ctx, session, id, key) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateTags provides a mock function with given fields: ctx, session, client -func (_m *Service) UpdateTags(ctx context.Context, session authn.Session, client things.Client) (things.Client, error) { - ret := _m.Called(ctx, session, client) - - if len(ret) == 0 { - panic("no return value specified for UpdateTags") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, things.Client) (things.Client, error)); ok { - return rf(ctx, session, client) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, things.Client) things.Client); ok { - r0 = rf(ctx, session, client) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, things.Client) error); ok { - r1 = rf(ctx, session, client) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// View provides a mock function with given fields: ctx, session, id -func (_m *Service) View(ctx context.Context, session authn.Session, id string) (things.Client, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for View") - } - - var r0 things.Client - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (things.Client, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) things.Client); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(things.Client) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ViewPerms provides a mock function with given fields: ctx, session, id -func (_m *Service) ViewPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for ViewPerms") - } - - var r0 []string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) ([]string, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) []string); ok { - r0 = rf(ctx, session, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]string) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewService(t interface { - mock.TestingT - Cleanup(func()) -}) *Service { - mock := &Service{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/things/mocks/things_client.go b/things/mocks/things_client.go deleted file mode 100644 index 136280a869..0000000000 --- a/things/mocks/things_client.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Abstract Machines - -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by mockery v2.43.2. DO NOT EDIT. - -package mocks - -import ( - context "context" - - grpc "google.golang.org/grpc" - - magistrala "github.com/absmach/magistrala" - - mock "github.com/stretchr/testify/mock" -) - -// ThingsServiceClient is an autogenerated mock type for the ThingsServiceClient type -type ThingsServiceClient struct { - mock.Mock -} - -type ThingsServiceClient_Expecter struct { - mock *mock.Mock -} - -func (_m *ThingsServiceClient) EXPECT() *ThingsServiceClient_Expecter { - return &ThingsServiceClient_Expecter{mock: &_m.Mock} -} - -// Authorize provides a mock function with given fields: ctx, in, opts -func (_m *ThingsServiceClient) Authorize(ctx context.Context, in *magistrala.ThingsAuthzReq, opts ...grpc.CallOption) (*magistrala.ThingsAuthzRes, error) { - _va := make([]interface{}, len(opts)) - for _i := range opts { - _va[_i] = opts[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, in) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Authorize") - } - - var r0 *magistrala.ThingsAuthzRes - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.ThingsAuthzReq, ...grpc.CallOption) (*magistrala.ThingsAuthzRes, error)); ok { - return rf(ctx, in, opts...) - } - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.ThingsAuthzReq, ...grpc.CallOption) *magistrala.ThingsAuthzRes); ok { - r0 = rf(ctx, in, opts...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.ThingsAuthzRes) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *magistrala.ThingsAuthzReq, ...grpc.CallOption) error); ok { - r1 = rf(ctx, in, opts...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ThingsServiceClient_Authorize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Authorize' -type ThingsServiceClient_Authorize_Call struct { - *mock.Call -} - -// Authorize is a helper method to define mock.On call -// - ctx context.Context -// - in *magistrala.ThingsAuthzReq -// - opts ...grpc.CallOption -func (_e *ThingsServiceClient_Expecter) Authorize(ctx interface{}, in interface{}, opts ...interface{}) *ThingsServiceClient_Authorize_Call { - return &ThingsServiceClient_Authorize_Call{Call: _e.mock.On("Authorize", - append([]interface{}{ctx, in}, opts...)...)} -} - -func (_c *ThingsServiceClient_Authorize_Call) Run(run func(ctx context.Context, in *magistrala.ThingsAuthzReq, opts ...grpc.CallOption)) *ThingsServiceClient_Authorize_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]grpc.CallOption, len(args)-2) - for i, a := range args[2:] { - if a != nil { - variadicArgs[i] = a.(grpc.CallOption) - } - } - run(args[0].(context.Context), args[1].(*magistrala.ThingsAuthzReq), variadicArgs...) - }) - return _c -} - -func (_c *ThingsServiceClient_Authorize_Call) Return(_a0 *magistrala.ThingsAuthzRes, _a1 error) *ThingsServiceClient_Authorize_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *ThingsServiceClient_Authorize_Call) RunAndReturn(run func(context.Context, *magistrala.ThingsAuthzReq, ...grpc.CallOption) (*magistrala.ThingsAuthzRes, error)) *ThingsServiceClient_Authorize_Call { - _c.Call.Return(run) - return _c -} - -// NewThingsServiceClient creates a new instance of ThingsServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewThingsServiceClient(t interface { - mock.TestingT - Cleanup(func()) -}) *ThingsServiceClient { - mock := &ThingsServiceClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/things/postgres/clients.go b/things/postgres/clients.go deleted file mode 100644 index bc9fe3f7aa..0000000000 --- a/things/postgres/clients.go +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/postgres" - "github.com/absmach/magistrala/things" - "github.com/jackc/pgtype" -) - -type clientRepo struct { - Repository things.ClientRepository -} - -// NewRepository instantiates a PostgreSQL -// implementation of Clients repository. -func NewRepository(db postgres.Database) things.Repository { - return &clientRepo{ - Repository: things.ClientRepository{DB: db}, - } -} - -func (repo *clientRepo) Save(ctx context.Context, th ...things.Client) ([]things.Client, error) { - tx, err := repo.Repository.DB.BeginTxx(ctx, nil) - if err != nil { - return []things.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) - } - var thingsList []things.Client - - for _, thi := range th { - q := `INSERT INTO clients (id, name, tags, domain_id, identity, secret, metadata, created_at, updated_at, updated_by, status) - VALUES (:id, :name, :tags, :domain_id, :identity, :secret, :metadata, :created_at, :updated_at, :updated_by, :status) - RETURNING id, name, tags, identity, secret, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` - - dbthi, err := ToDBClient(thi) - if err != nil { - return []things.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) - } - - row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbthi) - if err != nil { - if err := tx.Rollback(); err != nil { - return []things.Client{}, postgres.HandleError(repoerr.ErrCreateEntity, err) - } - return []things.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) - } - - defer row.Close() - - if row.Next() { - dbthi = DBClient{} - if err := row.StructScan(&dbthi); err != nil { - return []things.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - - thing, err := ToClient(dbthi) - if err != nil { - return []things.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - thingsList = append(thingsList, thing) - } - } - if err = tx.Commit(); err != nil { - return []things.Client{}, errors.Wrap(repoerr.ErrCreateEntity, err) - } - - return thingsList, nil -} - -func (repo *clientRepo) RetrieveBySecret(ctx context.Context, key string) (things.Client, error) { - q := fmt.Sprintf(`SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, identity, secret, metadata, created_at, updated_at, updated_by, status - FROM clients - WHERE secret = :secret AND status = %d`, things.EnabledStatus) - - dbt := DBClient{ - Secret: key, - } - - rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbt) - if err != nil { - return things.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - dbt = DBClient{} - if rows.Next() { - if err = rows.StructScan(&dbt); err != nil { - return things.Client{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - - thing, err := ToClient(dbt) - if err != nil { - return things.Client{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - - return thing, nil - } - - return things.Client{}, repoerr.ErrNotFound -} - -func (repo *clientRepo) Update(ctx context.Context, thing things.Client) (things.Client, error) { - var query []string - var upq string - if thing.Name != "" { - query = append(query, "name = :name,") - } - if thing.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, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, secret, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by`, - upq) - thing.Status = things.EnabledStatus - return repo.update(ctx, thing, q) -} - -func (repo *clientRepo) UpdateTags(ctx context.Context, thing things.Client) (things.Client, error) { - q := `UPDATE clients SET tags = :tags, updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` - thing.Status = things.EnabledStatus - return repo.update(ctx, thing, q) -} - -func (repo *clientRepo) UpdateIdentity(ctx context.Context, thing things.Client) (things.Client, error) { - q := `UPDATE clients SET identity = :identity, updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` - thing.Status = things.EnabledStatus - return repo.update(ctx, thing, q) -} - -func (repo *clientRepo) UpdateSecret(ctx context.Context, thing things.Client) (things.Client, error) { - q := `UPDATE clients SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` - thing.Status = things.EnabledStatus - return repo.update(ctx, thing, q) -} - -func (repo *clientRepo) ChangeStatus(ctx context.Context, thing things.Client) (things.Client, error) { - q := `UPDATE clients SET status = :status, updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id - RETURNING id, name, tags, identity, metadata, COALESCE(domain_id, '') AS domain_id, status, created_at, updated_at, updated_by` - - return repo.update(ctx, thing, q) -} - -func (repo *clientRepo) RetrieveByID(ctx context.Context, id string) (things.Client, error) { - q := `SELECT id, name, tags, COALESCE(domain_id, '') AS domain_id, identity, secret, metadata, created_at, updated_at, updated_by, status - FROM clients WHERE id = :id` - - dbt := DBClient{ - ID: id, - } - - row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbt) - if err != nil { - return things.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - defer row.Close() - - dbt = DBClient{} - if row.Next() { - if err := row.StructScan(&dbt); err != nil { - return things.Client{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - return ToClient(dbt) - } - - return things.Client{}, repoerr.ErrNotFound -} - -func (repo *clientRepo) RetrieveAll(ctx context.Context, pm things.Page) (things.ClientsPage, error) { - query, err := PageQuery(pm) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - query = applyOrdering(query, pm) - - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.domain_id, '') AS domain_id, c.status, - c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query) - - dbPage, err := ToDBClientsPage(pm) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - var items []things.Client - for rows.Next() { - dbt := DBClient{} - if err := rows.StructScan(&dbt); err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - c, err := ToClient(dbt) - if err != nil { - return things.ClientsPage{}, err - } - - items = append(items, c) - } - cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, query) - - total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - page := things.ClientsPage{ - Clients: items, - Page: things.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - } - - return page, nil -} - -func (repo *clientRepo) SearchClients(ctx context.Context, pm things.Page) (things.ClientsPage, error) { - query, err := PageQuery(pm) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - tq := query - query = applyOrdering(query, pm) - - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.created_at, c.updated_at, c.status FROM clients c %s LIMIT :limit OFFSET :offset;`, query) - - dbPage, err := ToDBClientsPage(pm) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - var items []things.Client - for rows.Next() { - dbt := DBClient{} - if err := rows.StructScan(&dbt); err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - c, err := ToClient(dbt) - if err != nil { - return things.ClientsPage{}, err - } - - items = append(items, c) - } - - cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, tq) - total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - page := things.ClientsPage{ - Clients: items, - Page: things.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - } - - return page, nil -} - -func (repo *clientRepo) RetrieveAllByIDs(ctx context.Context, pm things.Page) (things.ClientsPage, error) { - if (len(pm.IDs) == 0) && (pm.Domain == "") { - return things.ClientsPage{ - Page: things.Page{Total: pm.Total, Offset: pm.Offset, Limit: pm.Limit}, - }, nil - } - query, err := PageQuery(pm) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - query = applyOrdering(query, pm) - - q := fmt.Sprintf(`SELECT c.id, c.name, c.tags, c.identity, c.metadata, COALESCE(c.domain_id, '') AS domain_id, c.status, - c.created_at, c.updated_at, COALESCE(c.updated_by, '') AS updated_by FROM clients c %s ORDER BY c.created_at LIMIT :limit OFFSET :offset;`, query) - - dbPage, err := ToDBClientsPage(pm) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - var items []things.Client - for rows.Next() { - dbt := DBClient{} - if err := rows.StructScan(&dbt); err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - c, err := ToClient(dbt) - if err != nil { - return things.ClientsPage{}, err - } - - items = append(items, c) - } - cq := fmt.Sprintf(`SELECT COUNT(*) FROM clients c %s;`, query) - - total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage) - if err != nil { - return things.ClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - page := things.ClientsPage{ - Clients: items, - Page: things.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - } - - return page, nil -} - -func (repo *clientRepo) update(ctx context.Context, thing things.Client, query string) (things.Client, error) { - dbc, err := ToDBClient(thing) - if err != nil { - return things.Client{}, errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - row, err := repo.Repository.DB.NamedQueryContext(ctx, query, dbc) - if err != nil { - return things.Client{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - defer row.Close() - - dbc = DBClient{} - if row.Next() { - if err := row.StructScan(&dbc); err != nil { - return things.Client{}, errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - return ToClient(dbc) - } - - return things.Client{}, repoerr.ErrNotFound -} - -func (repo *clientRepo) Delete(ctx context.Context, id string) error { - q := "DELETE FROM clients AS c WHERE c.id = $1 ;" - - result, err := repo.Repository.DB.ExecContext(ctx, q, id) - if err != nil { - return postgres.HandleError(repoerr.ErrRemoveEntity, err) - } - if rows, _ := result.RowsAffected(); rows == 0 { - return repoerr.ErrNotFound - } - - return nil -} - -type DBClient struct { - ID string `db:"id"` - Name string `db:"name,omitempty"` - Tags pgtype.TextArray `db:"tags,omitempty"` - Identity string `db:"identity"` - Domain string `db:"domain_id"` - Secret string `db:"secret"` - Metadata []byte `db:"metadata,omitempty"` - CreatedAt time.Time `db:"created_at,omitempty"` - UpdatedAt sql.NullTime `db:"updated_at,omitempty"` - UpdatedBy *string `db:"updated_by,omitempty"` - Groups []groups.Group `db:"groups,omitempty"` - Status things.Status `db:"status,omitempty"` -} - -func ToDBClient(c things.Client) (DBClient, error) { - data := []byte("{}") - if len(c.Metadata) > 0 { - b, err := json.Marshal(c.Metadata) - if err != nil { - return DBClient{}, errors.Wrap(repoerr.ErrMalformedEntity, err) - } - data = b - } - var tags pgtype.TextArray - if err := tags.Set(c.Tags); err != nil { - return DBClient{}, err - } - var updatedBy *string - if c.UpdatedBy != "" { - updatedBy = &c.UpdatedBy - } - var updatedAt sql.NullTime - if c.UpdatedAt != (time.Time{}) { - updatedAt = sql.NullTime{Time: c.UpdatedAt, Valid: true} - } - - return DBClient{ - ID: c.ID, - Name: c.Name, - Tags: tags, - Domain: c.Domain, - Identity: c.Credentials.Identity, - Secret: c.Credentials.Secret, - Metadata: data, - CreatedAt: c.CreatedAt, - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - Status: c.Status, - }, nil -} - -func ToClient(t DBClient) (things.Client, error) { - var metadata things.Metadata - if t.Metadata != nil { - if err := json.Unmarshal([]byte(t.Metadata), &metadata); err != nil { - return things.Client{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - } - var tags []string - for _, e := range t.Tags.Elements { - tags = append(tags, e.String) - } - var updatedBy string - if t.UpdatedBy != nil { - updatedBy = *t.UpdatedBy - } - var updatedAt time.Time - if t.UpdatedAt.Valid { - updatedAt = t.UpdatedAt.Time - } - - thg := things.Client{ - ID: t.ID, - Name: t.Name, - Tags: tags, - Domain: t.Domain, - Credentials: things.Credentials{ - Identity: t.Identity, - Secret: t.Secret, - }, - Metadata: metadata, - CreatedAt: t.CreatedAt, - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - Status: t.Status, - } - return thg, nil -} - -func ToDBClientsPage(pm things.Page) (dbClientsPage, error) { - _, data, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return dbClientsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - return dbClientsPage{ - Name: pm.Name, - Identity: pm.Identity, - Id: pm.Id, - Metadata: data, - Domain: pm.Domain, - Total: pm.Total, - Offset: pm.Offset, - Limit: pm.Limit, - Status: pm.Status, - Tag: pm.Tag, - }, nil -} - -type dbClientsPage struct { - Total uint64 `db:"total"` - Limit uint64 `db:"limit"` - Offset uint64 `db:"offset"` - Name string `db:"name"` - Id string `db:"id"` - Domain string `db:"domain_id"` - Identity string `db:"identity"` - Metadata []byte `db:"metadata"` - Tag string `db:"tag"` - Status things.Status `db:"status"` - GroupID string `db:"group_id"` -} - -func PageQuery(pm things.Page) (string, error) { - mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return "", errors.Wrap(errors.ErrMalformedEntity, err) - } - - var query []string - if pm.Name != "" { - query = append(query, "name ILIKE '%' || :name || '%'") - } - if pm.Identity != "" { - query = append(query, "identity ILIKE '%' || :identity || '%'") - } - if pm.Id != "" { - query = append(query, "id ILIKE '%' || :id || '%'") - } - if pm.Tag != "" { - query = append(query, "EXISTS (SELECT 1 FROM unnest(tags) AS tag WHERE tag ILIKE '%' || :tag || '%')") - } - // If there are search params presents, use search and ignore other options. - // Always combine role with search params, so len(query) > 1. - if len(query) > 1 { - return fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")), nil - } - - if mq != "" { - query = append(query, mq) - } - - if len(pm.IDs) != 0 { - query = append(query, fmt.Sprintf("id IN ('%s')", strings.Join(pm.IDs, "','"))) - } - if pm.Status != things.AllStatus { - query = append(query, "c.status = :status") - } - if pm.Domain != "" { - query = append(query, "c.domain_id = :domain_id") - } - var emq string - if len(query) > 0 { - emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")) - } - return emq, nil -} - -func applyOrdering(emq string, pm things.Page) string { - switch pm.Order { - case "name", "identity", "created_at", "updated_at": - emq = fmt.Sprintf("%s ORDER BY %s", emq, pm.Order) - if pm.Dir == api.AscDir || pm.Dir == api.DescDir { - emq = fmt.Sprintf("%s %s", emq, pm.Dir) - } - } - return emq -} diff --git a/things/postgres/init.go b/things/postgres/init.go deleted file mode 100644 index 28e07a2cc6..0000000000 --- a/things/postgres/init.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access - migrate "github.com/rubenv/sql-migrate" -) - -func Migration() *migrate.MemoryMigrationSource { - return &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "clients_01", - // VARCHAR(36) for colums with IDs as UUIDS have a maximum of 36 characters - // STATUS 0 to imply enabled and 1 to imply disabled - Up: []string{ - `CREATE TABLE IF NOT EXISTS clients ( - id VARCHAR(36) PRIMARY KEY, - name VARCHAR(1024), - domain_id VARCHAR(36) NOT NULL, - identity VARCHAR(254), - secret VARCHAR(4096) NOT NULL, - tags TEXT[], - metadata JSONB, - created_at TIMESTAMP, - updated_at TIMESTAMP, - updated_by VARCHAR(254), - status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), - UNIQUE (domain_id, secret), - UNIQUE (domain_id, name) - )`, - }, - Down: []string{ - `DROP TABLE IF EXISTS clients`, - }, - }, - }, - } -} diff --git a/things/service_test.go b/things/service_test.go deleted file mode 100644 index 79aa727ed3..0000000000 --- a/things/service_test.go +++ /dev/null @@ -1,1393 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package things_test - -import ( - "context" - "fmt" - "testing" - - "github.com/absmach/magistrala/internal/testsutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/policies" - policysvc "github.com/absmach/magistrala/pkg/policies" - policymocks "github.com/absmach/magistrala/pkg/policies/mocks" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/absmach/magistrala/things" - "github.com/absmach/magistrala/things/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - secret = "strongsecret" - validTMetadata = things.Metadata{"role": "thing"} - ID = "6e5e10b3-d4df-4758-b426-4929d55ad740" - thing = things.Client{ - ID: ID, - Name: "thingname", - Tags: []string{"tag1", "tag2"}, - Credentials: things.Credentials{Identity: "thingidentity", Secret: secret}, - Metadata: validTMetadata, - Status: things.EnabledStatus, - } - validToken = "token" - valid = "valid" - invalid = "invalid" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - wrongID = testsutil.GenerateUUID(&testing.T{}) - errRemovePolicies = errors.New("failed to delete policies") -) - -var ( - pService *policymocks.Service - pEvaluator *policymocks.Evaluator - cache *mocks.Cache - cRepo *mocks.Repository -) - -func newService() things.Service { - pService = new(policymocks.Service) - pEvaluator = new(policymocks.Evaluator) - cache = new(mocks.Cache) - idProvider := uuid.NewMock() - cRepo = new(mocks.Repository) - - return things.NewService(pEvaluator, pService, cRepo, cache, idProvider) -} - -func TestCreateClients(t *testing.T) { - svc := newService() - - cases := []struct { - desc string - thing things.Client - token string - addPolicyErr error - deletePolicyErr error - saveErr error - err error - }{ - { - desc: "create a new thing successfully", - thing: thing, - token: validToken, - err: nil, - }, - { - desc: "create an existing thing", - thing: thing, - token: validToken, - saveErr: repoerr.ErrConflict, - err: repoerr.ErrConflict, - }, - { - desc: "create a new thing without secret", - thing: things.Client{ - Name: "thingWithoutSecret", - Credentials: things.Credentials{ - Identity: "newthingwithoutsecret@example.com", - }, - Status: things.EnabledStatus, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new thing without identity", - thing: things.Client{ - Name: "thingWithoutIdentity", - Credentials: things.Credentials{ - Identity: "newthingwithoutsecret@example.com", - }, - Status: things.EnabledStatus, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new enabled thing with name", - thing: things.Client{ - Name: "thingWithName", - Credentials: things.Credentials{ - Identity: "newthingwithname@example.com", - Secret: secret, - }, - Status: things.EnabledStatus, - }, - token: validToken, - err: nil, - }, - - { - desc: "create a new disabled thing with name", - thing: things.Client{ - Name: "thingWithName", - Credentials: things.Credentials{ - Identity: "newthingwithname@example.com", - Secret: secret, - }, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new enabled thing with tags", - thing: things.Client{ - Tags: []string{"tag1", "tag2"}, - Credentials: things.Credentials{ - Identity: "newthingwithtags@example.com", - Secret: secret, - }, - Status: things.EnabledStatus, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new disabled thing with tags", - thing: things.Client{ - Tags: []string{"tag1", "tag2"}, - Credentials: things.Credentials{ - Identity: "newthingwithtags@example.com", - Secret: secret, - }, - Status: things.DisabledStatus, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new enabled thing with metadata", - thing: things.Client{ - Credentials: things.Credentials{ - Identity: "newthingwithmetadata@example.com", - Secret: secret, - }, - Metadata: validTMetadata, - Status: things.EnabledStatus, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new disabled thing with metadata", - thing: things.Client{ - Credentials: things.Credentials{ - Identity: "newthingwithmetadata@example.com", - Secret: secret, - }, - Metadata: validTMetadata, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new disabled thing", - thing: things.Client{ - Credentials: things.Credentials{ - Identity: "newthingwithvalidstatus@example.com", - Secret: secret, - }, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new thing with valid disabled status", - thing: things.Client{ - Credentials: things.Credentials{ - Identity: "newthingwithvalidstatus@example.com", - Secret: secret, - }, - Status: things.DisabledStatus, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new thing with all fields", - thing: things.Client{ - Name: "newthingwithallfields", - Tags: []string{"tag1", "tag2"}, - Credentials: things.Credentials{ - Identity: "newthingwithallfields@example.com", - Secret: secret, - }, - Metadata: things.Metadata{ - "name": "newthingwithallfields", - }, - Status: things.EnabledStatus, - }, - token: validToken, - err: nil, - }, - { - desc: "create a new thing with invalid status", - thing: things.Client{ - Credentials: things.Credentials{ - Identity: "newthingwithinvalidstatus@example.com", - Secret: secret, - }, - Status: things.AllStatus, - }, - token: validToken, - err: svcerr.ErrInvalidStatus, - }, - { - desc: "create a new thing with failed add policies response", - thing: things.Client{ - Credentials: things.Credentials{ - Identity: "newthingwithfailedpolicy@example.com", - Secret: secret, - }, - Status: things.EnabledStatus, - }, - token: validToken, - addPolicyErr: svcerr.ErrInvalidPolicy, - err: svcerr.ErrInvalidPolicy, - }, - { - desc: "create a new thing with failed delete policies response", - thing: things.Client{ - Credentials: things.Credentials{ - Identity: "newthingwithfailedpolicy@example.com", - Secret: secret, - }, - Status: things.EnabledStatus, - }, - token: validToken, - saveErr: repoerr.ErrConflict, - deletePolicyErr: svcerr.ErrInvalidPolicy, - err: repoerr.ErrConflict, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("Save", context.Background(), mock.Anything).Return([]things.Client{tc.thing}, tc.saveErr) - policyCall := pService.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPolicyErr) - policyCall1 := pService.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePolicyErr) - expected, err := svc.CreateClients(context.Background(), mgauthn.Session{}, tc.thing) - 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.thing.ID = expected[0].ID - tc.thing.CreatedAt = expected[0].CreatedAt - tc.thing.UpdatedAt = expected[0].UpdatedAt - tc.thing.Credentials.Secret = expected[0].Credentials.Secret - tc.thing.Domain = expected[0].Domain - tc.thing.UpdatedBy = expected[0].UpdatedBy - assert.Equal(t, tc.thing, expected[0], fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.thing, expected[0])) - } - repoCall.Unset() - policyCall.Unset() - policyCall1.Unset() - } -} - -func TestViewClient(t *testing.T) { - svc := newService() - - cases := []struct { - desc string - clientID string - response things.Client - retrieveErr error - err error - }{ - { - desc: "view thing successfully", - response: thing, - clientID: thing.ID, - err: nil, - }, - { - desc: "view thing with an invalid token", - response: things.Client{}, - clientID: "", - err: svcerr.ErrAuthorization, - }, - { - desc: "view thing with valid token and invalid thing id", - response: things.Client{}, - clientID: wrongID, - retrieveErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "view thing with an invalid token and invalid thing id", - response: things.Client{}, - clientID: wrongID, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - repoCall1 := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.response, tc.err) - rThing, err := svc.View(context.Background(), mgauthn.Session{}, 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, rThing, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rThing)) - repoCall1.Unset() - } -} - -func TestListClients(t *testing.T) { - svc := newService() - - adminID := testsutil.GenerateUUID(t) - domainID := testsutil.GenerateUUID(t) - nonAdminID := testsutil.GenerateUUID(t) - thing.Permissions = []string{"read", "write"} - - cases := []struct { - desc string - userKind string - session mgauthn.Session - page things.Page - listObjectsResponse policysvc.PolicyPage - retrieveAllResponse things.ClientsPage - listPermissionsResponse policysvc.Permissions - response things.ClientsPage - id string - size uint64 - listObjectsErr error - retrieveAllErr error - listPermissionsErr error - err error - }{ - { - desc: "list all things successfully as non admin", - userKind: "non-admin", - session: mgauthn.Session{UserID: nonAdminID, DomainID: domainID, SuperAdmin: false}, - id: nonAdminID, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - }, - listObjectsResponse: policysvc.PolicyPage{Policies: []string{thing.ID, thing.ID}}, - retrieveAllResponse: things.ClientsPage{ - Page: things.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Clients: []things.Client{thing, thing}, - }, - listPermissionsResponse: []string{"read", "write"}, - response: things.ClientsPage{ - Page: things.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Clients: []things.Client{thing, thing}, - }, - err: nil, - }, - { - desc: "list all things as non admin with failed to retrieve all", - userKind: "non-admin", - session: mgauthn.Session{UserID: nonAdminID, DomainID: domainID, SuperAdmin: false}, - id: nonAdminID, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - }, - listObjectsResponse: policysvc.PolicyPage{Policies: []string{thing.ID, thing.ID}}, - retrieveAllResponse: things.ClientsPage{}, - response: things.ClientsPage{}, - retrieveAllErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "list all things as non admin with failed to list permissions", - userKind: "non-admin", - session: mgauthn.Session{UserID: nonAdminID, DomainID: domainID, SuperAdmin: false}, - id: nonAdminID, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - }, - listObjectsResponse: policysvc.PolicyPage{Policies: []string{thing.ID, thing.ID}}, - retrieveAllResponse: things.ClientsPage{ - Page: things.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Clients: []things.Client{thing, thing}, - }, - listPermissionsResponse: []string{}, - response: things.ClientsPage{}, - listPermissionsErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "list all things as non admin with failed super admin", - userKind: "non-admin", - session: mgauthn.Session{UserID: nonAdminID, DomainID: domainID, SuperAdmin: false}, - id: nonAdminID, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - }, - response: things.ClientsPage{}, - listObjectsResponse: policysvc.PolicyPage{}, - err: nil, - }, - { - desc: "list all things as non admin with failed to list objects", - userKind: "non-admin", - id: nonAdminID, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - }, - response: things.ClientsPage{}, - listObjectsResponse: policysvc.PolicyPage{}, - listObjectsErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - listAllObjectsCall := pService.On("ListAllObjects", mock.Anything, mock.Anything).Return(tc.listObjectsResponse, tc.listObjectsErr) - retrieveAllCall := cRepo.On("SearchClients", mock.Anything, mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) - listPermissionsCall := pService.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionsErr) - page, err := svc.ListClients(context.Background(), tc.session, tc.id, 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)) - listAllObjectsCall.Unset() - retrieveAllCall.Unset() - listPermissionsCall.Unset() - } - - cases2 := []struct { - desc string - userKind string - session mgauthn.Session - page things.Page - listObjectsResponse policysvc.PolicyPage - retrieveAllResponse things.ClientsPage - listPermissionsResponse policysvc.Permissions - response things.ClientsPage - id string - size uint64 - listObjectsErr error - retrieveAllErr error - listPermissionsErr error - err error - }{ - { - desc: "list all things as admin successfully", - userKind: "admin", - id: adminID, - session: mgauthn.Session{UserID: adminID, DomainID: domainID, SuperAdmin: true}, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - Domain: domainID, - }, - listObjectsResponse: policysvc.PolicyPage{Policies: []string{thing.ID, thing.ID}}, - retrieveAllResponse: things.ClientsPage{ - Page: things.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Clients: []things.Client{thing, thing}, - }, - listPermissionsResponse: []string{"read", "write"}, - response: things.ClientsPage{ - Page: things.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Clients: []things.Client{thing, thing}, - }, - err: nil, - }, - { - desc: "list all things as admin with failed to retrieve all", - userKind: "admin", - id: adminID, - session: mgauthn.Session{UserID: adminID, DomainID: domainID, SuperAdmin: true}, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - Domain: domainID, - }, - listObjectsResponse: policysvc.PolicyPage{}, - retrieveAllResponse: things.ClientsPage{}, - retrieveAllErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "list all things as admin with failed to list permissions", - userKind: "admin", - id: adminID, - session: mgauthn.Session{UserID: adminID, DomainID: domainID, SuperAdmin: true}, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - Domain: domainID, - }, - listObjectsResponse: policysvc.PolicyPage{}, - retrieveAllResponse: things.ClientsPage{ - Page: things.Page{ - Total: 2, - Offset: 0, - Limit: 100, - }, - Clients: []things.Client{thing, thing}, - }, - listPermissionsResponse: []string{}, - listPermissionsErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "list all things as admin with failed to list things", - userKind: "admin", - id: adminID, - session: mgauthn.Session{UserID: adminID, DomainID: domainID, SuperAdmin: true}, - page: things.Page{ - Offset: 0, - Limit: 100, - ListPerms: true, - Domain: domainID, - }, - retrieveAllResponse: things.ClientsPage{}, - retrieveAllErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases2 { - listAllObjectsCall := pService.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: tc.session.DomainID + "_" + adminID, - Permission: "", - ObjectType: policysvc.ThingType, - }).Return(tc.listObjectsResponse, tc.listObjectsErr) - listAllObjectsCall2 := pService.On("ListAllObjects", context.Background(), policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: tc.session.UserID, - Permission: "", - ObjectType: policysvc.ThingType, - }).Return(tc.listObjectsResponse, tc.listObjectsErr) - retrieveAllCall := cRepo.On("SearchClients", mock.Anything, mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) - listPermissionsCall := pService.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionsErr) - page, err := svc.ListClients(context.Background(), tc.session, tc.id, 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)) - listAllObjectsCall.Unset() - listAllObjectsCall2.Unset() - retrieveAllCall.Unset() - listPermissionsCall.Unset() - } -} - -func TestUpdateClient(t *testing.T) { - svc := newService() - - thing1 := thing - thing2 := thing - thing1.Name = "Updated thing" - thing2.Metadata = things.Metadata{"role": "test"} - - cases := []struct { - desc string - thing things.Client - session mgauthn.Session - updateResponse things.Client - updateErr error - err error - }{ - { - desc: "update thing name successfully", - thing: thing1, - session: mgauthn.Session{UserID: validID}, - updateResponse: thing1, - err: nil, - }, - { - desc: "update thing metadata with valid token", - thing: thing2, - updateResponse: thing2, - session: mgauthn.Session{UserID: validID}, - err: nil, - }, - { - desc: "update thing with failed to update repo", - thing: thing1, - updateResponse: things.Client{}, - session: mgauthn.Session{UserID: validID}, - updateErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall1 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateResponse, tc.updateErr) - updatedThing, err := svc.Update(context.Background(), tc.session, tc.thing) - 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.updateResponse, updatedThing, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateResponse, updatedThing)) - repoCall1.Unset() - } -} - -func TestUpdateTags(t *testing.T) { - svc := newService() - - thing.Tags = []string{"updated"} - - cases := []struct { - desc string - thing things.Client - session mgauthn.Session - updateResponse things.Client - updateErr error - err error - }{ - { - desc: "update thing tags successfully", - thing: thing, - session: mgauthn.Session{UserID: validID}, - updateResponse: thing, - err: nil, - }, - { - desc: "update thing tags with failed to update repo", - thing: thing, - updateResponse: things.Client{}, - session: mgauthn.Session{UserID: validID}, - updateErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall1 := cRepo.On("UpdateTags", context.Background(), mock.Anything).Return(tc.updateResponse, tc.updateErr) - updatedThing, err := svc.UpdateTags(context.Background(), tc.session, tc.thing) - 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.updateResponse, updatedThing, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateResponse, updatedThing)) - repoCall1.Unset() - } -} - -func TestUpdateSecret(t *testing.T) { - svc := newService() - - cases := []struct { - desc string - thing things.Client - newSecret string - updateSecretResponse things.Client - session mgauthn.Session - updateErr error - err error - }{ - { - desc: "update thing secret successfully", - thing: thing, - newSecret: "newSecret", - session: mgauthn.Session{UserID: validID}, - updateSecretResponse: things.Client{ - ID: thing.ID, - Credentials: things.Credentials{ - Identity: thing.Credentials.Identity, - Secret: "newSecret", - }, - }, - err: nil, - }, - { - desc: "update thing secret with failed to update repo", - thing: thing, - newSecret: "newSecret", - session: mgauthn.Session{UserID: validID}, - updateSecretResponse: things.Client{}, - updateErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.updateSecretResponse, tc.updateErr) - updatedThing, err := svc.UpdateSecret(context.Background(), tc.session, tc.thing.ID, 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.updateSecretResponse, updatedThing, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateSecretResponse, updatedThing)) - repoCall.Unset() - } -} - -func TestEnable(t *testing.T) { - svc := newService() - - enabledThing1 := things.Client{ID: ID, Credentials: things.Credentials{Identity: "thing1@example.com", Secret: "password"}, Status: things.EnabledStatus} - disabledThing1 := things.Client{ID: ID, Credentials: things.Credentials{Identity: "thing3@example.com", Secret: "password"}, Status: things.DisabledStatus} - endisabledThing1 := disabledThing1 - endisabledThing1.Status = things.EnabledStatus - - cases := []struct { - desc string - id string - session mgauthn.Session - thing things.Client - changeStatusResponse things.Client - retrieveByIDResponse things.Client - changeStatusErr error - retrieveIDErr error - err error - }{ - { - desc: "enable disabled thing", - id: disabledThing1.ID, - session: mgauthn.Session{UserID: validID}, - thing: disabledThing1, - changeStatusResponse: endisabledThing1, - retrieveByIDResponse: disabledThing1, - err: nil, - }, - { - desc: "enable disabled thing with failed to update repo", - id: disabledThing1.ID, - session: mgauthn.Session{UserID: validID}, - thing: disabledThing1, - changeStatusResponse: things.Client{}, - retrieveByIDResponse: disabledThing1, - changeStatusErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - { - desc: "enable enabled thing", - id: enabledThing1.ID, - session: mgauthn.Session{UserID: validID}, - thing: enabledThing1, - changeStatusResponse: enabledThing1, - retrieveByIDResponse: enabledThing1, - changeStatusErr: errors.ErrStatusAlreadyAssigned, - err: errors.ErrStatusAlreadyAssigned, - }, - { - desc: "enable non-existing thing", - id: wrongID, - session: mgauthn.Session{UserID: validID}, - thing: things.Client{}, - changeStatusResponse: things.Client{}, - retrieveByIDResponse: things.Client{}, - retrieveIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveIDErr) - repoCall1 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) - _, err := svc.Enable(context.Background(), tc.session, 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() - } -} - -func TestDisable(t *testing.T) { - svc := newService() - - enabledThing1 := things.Client{ID: ID, Credentials: things.Credentials{Identity: "thing1@example.com", Secret: "password"}, Status: things.EnabledStatus} - disabledThing1 := things.Client{ID: ID, Credentials: things.Credentials{Identity: "thing3@example.com", Secret: "password"}, Status: things.DisabledStatus} - disenabledClient1 := enabledThing1 - disenabledClient1.Status = things.DisabledStatus - - cases := []struct { - desc string - id string - session mgauthn.Session - thing things.Client - changeStatusResponse things.Client - retrieveByIDResponse things.Client - changeStatusErr error - retrieveIDErr error - removeErr error - err error - }{ - { - desc: "disable enabled thing", - id: enabledThing1.ID, - session: mgauthn.Session{UserID: validID}, - thing: enabledThing1, - changeStatusResponse: disenabledClient1, - retrieveByIDResponse: enabledThing1, - err: nil, - }, - { - desc: "disable thing with failed to update repo", - id: enabledThing1.ID, - session: mgauthn.Session{UserID: validID}, - thing: enabledThing1, - changeStatusResponse: things.Client{}, - retrieveByIDResponse: enabledThing1, - changeStatusErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - { - desc: "disable disabled thing", - id: disabledThing1.ID, - session: mgauthn.Session{UserID: validID}, - thing: disabledThing1, - changeStatusResponse: things.Client{}, - retrieveByIDResponse: disabledThing1, - changeStatusErr: errors.ErrStatusAlreadyAssigned, - err: errors.ErrStatusAlreadyAssigned, - }, - { - desc: "disable non-existing thing", - id: wrongID, - thing: things.Client{}, - session: mgauthn.Session{UserID: validID}, - changeStatusResponse: things.Client{}, - retrieveByIDResponse: things.Client{}, - retrieveIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "disable thing with failed to remove from cache", - id: enabledThing1.ID, - session: mgauthn.Session{UserID: validID}, - thing: disabledThing1, - changeStatusResponse: disenabledClient1, - retrieveByIDResponse: enabledThing1, - removeErr: svcerr.ErrRemoveEntity, - err: svcerr.ErrRemoveEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveIDErr) - repoCall1 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) - repoCall2 := cache.On("Remove", mock.Anything, mock.Anything).Return(tc.removeErr) - _, err := svc.Disable(context.Background(), tc.session, 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() - } -} - -func TestListMembers(t *testing.T) { - svc := newService() - - nThings := uint64(10) - aThings := []things.Client{} - domainID := testsutil.GenerateUUID(t) - for i := uint64(0); i < nThings; i++ { - identity := fmt.Sprintf("member_%d@example.com", i) - thing := things.Client{ - ID: testsutil.GenerateUUID(t), - Domain: domainID, - Name: identity, - Credentials: things.Credentials{ - Identity: identity, - Secret: "password", - }, - Tags: []string{"tag1", "tag2"}, - Metadata: things.Metadata{"role": "thing"}, - } - aThings = append(aThings, thing) - } - aThings[0].Permissions = []string{"admin"} - - cases := []struct { - desc string - groupID string - page things.Page - session mgauthn.Session - listObjectsResponse policysvc.PolicyPage - listPermissionsResponse policysvc.Permissions - retreiveAllByIDsResponse things.ClientsPage - response things.MembersPage - identifyErr error - authorizeErr error - listObjectsErr error - listPermissionsErr error - retreiveAllByIDsErr error - err error - }{ - { - desc: "list members with authorized token", - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - groupID: testsutil.GenerateUUID(t), - listObjectsResponse: policysvc.PolicyPage{}, - listPermissionsResponse: []string{}, - retreiveAllByIDsResponse: things.ClientsPage{ - Page: things.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Clients: []things.Client{}, - }, - response: things.MembersPage{ - Page: things.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - Members: []things.Client{}, - }, - err: nil, - }, - { - desc: "list members with offset and limit", - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - groupID: testsutil.GenerateUUID(t), - page: things.Page{ - Offset: 6, - Limit: nThings, - Status: things.AllStatus, - }, - listObjectsResponse: policysvc.PolicyPage{}, - listPermissionsResponse: []string{}, - retreiveAllByIDsResponse: things.ClientsPage{ - Page: things.Page{ - Total: nThings - 6 - 1, - }, - Clients: aThings[6 : nThings-1], - }, - response: things.MembersPage{ - Page: things.Page{ - Total: nThings - 6 - 1, - }, - Members: aThings[6 : nThings-1], - }, - err: nil, - }, - { - desc: "list members with an invalid id", - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - groupID: wrongID, - listObjectsResponse: policysvc.PolicyPage{}, - listPermissionsResponse: []string{}, - retreiveAllByIDsResponse: things.ClientsPage{}, - response: things.MembersPage{ - Page: things.Page{ - Total: 0, - Offset: 0, - Limit: 0, - }, - }, - retreiveAllByIDsErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "list members with permissions", - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - groupID: testsutil.GenerateUUID(t), - page: things.Page{ - ListPerms: true, - }, - listObjectsResponse: policysvc.PolicyPage{}, - listPermissionsResponse: []string{"admin"}, - retreiveAllByIDsResponse: things.ClientsPage{ - Page: things.Page{ - Total: 1, - }, - Clients: []things.Client{aThings[0]}, - }, - response: things.MembersPage{ - Page: things.Page{ - Total: 1, - }, - Members: []things.Client{aThings[0]}, - }, - err: nil, - }, - { - desc: "list members with failed to list objects", - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - groupID: testsutil.GenerateUUID(t), - page: things.Page{ - ListPerms: true, - }, - listObjectsResponse: policysvc.PolicyPage{}, - listObjectsErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "list members with failed to list permissions", - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - groupID: testsutil.GenerateUUID(t), - page: things.Page{ - ListPerms: true, - }, - retreiveAllByIDsResponse: things.ClientsPage{ - Page: things.Page{ - Total: 1, - }, - Clients: []things.Client{aThings[0]}, - }, - response: things.MembersPage{}, - listObjectsResponse: policysvc.PolicyPage{}, - listPermissionsResponse: []string{}, - listPermissionsErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - policyCall := pService.On("ListAllObjects", mock.Anything, mock.Anything).Return(tc.listObjectsResponse, tc.listObjectsErr) - repoCall := cRepo.On("RetrieveAllByIDs", context.Background(), mock.Anything).Return(tc.retreiveAllByIDsResponse, tc.retreiveAllByIDsErr) - repoCall1 := pService.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionsErr) - page, err := svc.ListClientsByGroup(context.Background(), tc.session, 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)) - policyCall.Unset() - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestDelete(t *testing.T) { - svc := newService() - - client := things.Client{ - ID: testsutil.GenerateUUID(t), - } - - cases := []struct { - desc string - clientID string - removeErr error - deleteErr error - deletePolicyErr error - err error - }{ - { - desc: "Delete client successfully", - clientID: client.ID, - err: nil, - }, - { - desc: "Delete non-existing client", - clientID: wrongID, - deleteErr: repoerr.ErrNotFound, - err: svcerr.ErrRemoveEntity, - }, - { - desc: "Delete client with repo error ", - clientID: client.ID, - deleteErr: repoerr.ErrRemoveEntity, - err: repoerr.ErrRemoveEntity, - }, - { - desc: "Delete client with cache error ", - clientID: client.ID, - removeErr: svcerr.ErrRemoveEntity, - err: repoerr.ErrRemoveEntity, - }, - { - desc: "Delete client with failed to delete policies", - clientID: client.ID, - deletePolicyErr: errRemovePolicies, - err: errRemovePolicies, - }, - } - - for _, tc := range cases { - repoCall := cache.On("Remove", mock.Anything, tc.clientID).Return(tc.removeErr) - policyCall := pService.On("DeletePolicyFilter", context.Background(), mock.Anything).Return(tc.deletePolicyErr) - repoCall1 := cRepo.On("Delete", context.Background(), tc.clientID).Return(tc.deleteErr) - err := svc.Delete(context.Background(), mgauthn.Session{}, tc.clientID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - policyCall.Unset() - repoCall1.Unset() - } -} - -func TestShare(t *testing.T) { - svc := newService() - - clientID := "clientID" - - cases := []struct { - desc string - session mgauthn.Session - clientID string - relation string - userID string - addPoliciesErr error - err error - }{ - { - desc: "share client successfully", - session: mgauthn.Session{UserID: validID, DomainID: validID}, - clientID: clientID, - err: nil, - }, - { - desc: "share client with failed to add policies", - session: mgauthn.Session{UserID: validID, DomainID: validID}, - clientID: clientID, - addPoliciesErr: svcerr.ErrInvalidPolicy, - err: svcerr.ErrInvalidPolicy, - }, - } - - for _, tc := range cases { - policyCall := pService.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesErr) - err := svc.Share(context.Background(), tc.session, tc.clientID, tc.relation, tc.userID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - policyCall.Unset() - } -} - -func TestUnShare(t *testing.T) { - svc := newService() - - clientID := "clientID" - - cases := []struct { - desc string - session mgauthn.Session - clientID string - relation string - userID string - deletePoliciesErr error - err error - }{ - { - desc: "unshare client successfully", - session: mgauthn.Session{UserID: validID, DomainID: validID}, - clientID: clientID, - err: nil, - }, - { - desc: "share client with failed to delete policies", - session: mgauthn.Session{UserID: validID, DomainID: validID}, - clientID: clientID, - deletePoliciesErr: svcerr.ErrInvalidPolicy, - err: svcerr.ErrInvalidPolicy, - }, - } - - for _, tc := range cases { - policyCall := pService.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) - err := svc.Unshare(context.Background(), tc.session, tc.clientID, tc.relation, tc.userID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - policyCall.Unset() - } -} - -func TestViewClientPerms(t *testing.T) { - svc := newService() - - validID := valid - - cases := []struct { - desc string - session mgauthn.Session - clientID string - listPermResponse policysvc.Permissions - listPermErr error - err error - }{ - { - desc: "view client permissions successfully", - session: mgauthn.Session{UserID: validID, DomainID: validID}, - clientID: validID, - listPermResponse: policysvc.Permissions{"admin"}, - err: nil, - }, - { - desc: "view permissions with failed retrieve list permissions response", - session: mgauthn.Session{UserID: validID, DomainID: validID}, - clientID: validID, - listPermResponse: []string{}, - listPermErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - policyCall := pService.On("ListPermissions", mock.Anything, mock.Anything, []string{}).Return(tc.listPermResponse, tc.listPermErr) - res, err := svc.ViewPerms(context.Background(), tc.session, tc.clientID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if tc.err == nil { - assert.ElementsMatch(t, tc.listPermResponse, res, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.listPermResponse, res)) - } - policyCall.Unset() - } -} - -func TestIdentify(t *testing.T) { - svc := newService() - - valid := valid - - cases := []struct { - desc string - key string - cacheIDResponse string - cacheIDErr error - repoIDResponse things.Client - retrieveBySecretErr error - saveErr error - err error - }{ - { - desc: "identify client with valid key from cache", - key: valid, - cacheIDResponse: thing.ID, - err: nil, - }, - { - desc: "identify client with valid key from repo", - key: valid, - cacheIDResponse: "", - cacheIDErr: repoerr.ErrNotFound, - repoIDResponse: thing, - err: nil, - }, - { - desc: "identify client with invalid key", - key: invalid, - cacheIDResponse: "", - cacheIDErr: repoerr.ErrNotFound, - repoIDResponse: things.Client{}, - retrieveBySecretErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "identify client with failed to save to cache", - key: valid, - cacheIDResponse: "", - cacheIDErr: repoerr.ErrNotFound, - repoIDResponse: thing, - saveErr: errors.ErrMalformedEntity, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - repoCall := cache.On("ID", mock.Anything, tc.key).Return(tc.cacheIDResponse, tc.cacheIDErr) - repoCall1 := cRepo.On("RetrieveBySecret", mock.Anything, mock.Anything).Return(tc.repoIDResponse, tc.retrieveBySecretErr) - repoCall2 := cache.On("Save", mock.Anything, mock.Anything, mock.Anything).Return(tc.saveErr) - _, err := svc.Identify(context.Background(), tc.key) - 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() - } -} - -func TestAuthorize(t *testing.T) { - svc := newService() - - cases := []struct { - desc string - request things.AuthzReq - cacheIDRes string - cacheIDErr error - retrieveBySecretRes things.Client - retrieveBySecretErr error - cacheSaveErr error - checkPolicyErr error - id string - err error - }{ - { - desc: "authorize client with valid key not in cache", - request: things.AuthzReq{ClientKey: valid, ChannelID: valid, Permission: policies.PublishPermission}, - cacheIDRes: "", - cacheIDErr: repoerr.ErrNotFound, - retrieveBySecretRes: things.Client{ID: valid}, - retrieveBySecretErr: nil, - cacheSaveErr: nil, - checkPolicyErr: nil, - id: valid, - err: nil, - }, - { - desc: "authorize thing with valid key in cache", - request: things.AuthzReq{ClientKey: valid, ChannelID: valid, Permission: policies.PublishPermission}, - cacheIDRes: valid, - checkPolicyErr: nil, - id: valid, - }, - { - desc: "authorize thing with invalid key not in cache for non existing thing", - request: things.AuthzReq{ClientKey: valid, ChannelID: valid, Permission: policies.PublishPermission}, - cacheIDRes: "", - cacheIDErr: repoerr.ErrNotFound, - retrieveBySecretRes: things.Client{}, - retrieveBySecretErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "authorize thing with valid key not in cache with failed to save to cache", - request: things.AuthzReq{ClientKey: valid, ChannelID: valid, Permission: policies.PublishPermission}, - cacheIDRes: "", - cacheIDErr: repoerr.ErrNotFound, - retrieveBySecretRes: things.Client{ID: valid}, - cacheSaveErr: errors.ErrMalformedEntity, - err: svcerr.ErrAuthorization, - }, - { - desc: "authorize thing with valid key not in cache and failed to authorize", - request: things.AuthzReq{ClientKey: valid, ChannelID: valid, Permission: policies.PublishPermission}, - cacheIDRes: "", - cacheIDErr: repoerr.ErrNotFound, - retrieveBySecretRes: things.Client{ID: valid}, - retrieveBySecretErr: nil, - cacheSaveErr: nil, - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "authorize thing with valid key not in cache and not authorize", - request: things.AuthzReq{ClientKey: valid, ChannelID: valid, Permission: policies.PublishPermission}, - cacheIDRes: "", - cacheIDErr: repoerr.ErrNotFound, - retrieveBySecretRes: things.Client{ID: valid}, - retrieveBySecretErr: nil, - cacheSaveErr: nil, - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - cacheCall := cache.On("ID", context.Background(), tc.request.ClientKey).Return(tc.cacheIDRes, tc.cacheIDErr) - repoCall := cRepo.On("RetrieveBySecret", context.Background(), tc.request.ClientKey).Return(tc.retrieveBySecretRes, tc.retrieveBySecretErr) - cacheCall1 := cache.On("Save", context.Background(), tc.request.ClientKey, tc.retrieveBySecretRes.ID).Return(tc.cacheSaveErr) - policyCall := pEvaluator.On("CheckPolicy", context.Background(), policies.Policy{ - SubjectType: policies.GroupType, - Subject: tc.request.ChannelID, - ObjectType: policies.ThingType, - Object: valid, - Permission: tc.request.Permission, - }).Return(tc.checkPolicyErr) - id, err := svc.Authorize(context.Background(), tc.request) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if tc.err == nil { - assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.id, id)) - } - cacheCall.Unset() - cacheCall1.Unset() - repoCall.Unset() - policyCall.Unset() - } -} diff --git a/things/tracing/tracing.go b/things/tracing/tracing.go deleted file mode 100644 index 20fe07b5fc..0000000000 --- a/things/tracing/tracing.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/things" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ things.Service = (*tracingMiddleware)(nil) - -type tracingMiddleware struct { - tracer trace.Tracer - svc things.Service -} - -// New returns a new group service with tracing capabilities. -func New(svc things.Service, tracer trace.Tracer) things.Service { - return &tracingMiddleware{tracer, svc} -} - -// CreateClients traces the "CreateClients" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) CreateClients(ctx context.Context, session authn.Session, cli ...things.Client) ([]things.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_create_client") - defer span.End() - - return tm.svc.CreateClients(ctx, session, cli...) -} - -// View traces the "View" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) View(ctx context.Context, session authn.Session, id string) (things.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_view_client", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - return tm.svc.View(ctx, session, id) -} - -// ViewPerms traces the "ViewPerms" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) ViewPerms(ctx context.Context, session authn.Session, id string) ([]string, error) { - ctx, span := tm.tracer.Start(ctx, "svc_view_client_permissions", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - return tm.svc.ViewPerms(ctx, session, id) -} - -// ListClients traces the "ListClients" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) ListClients(ctx context.Context, session authn.Session, reqUserID string, pm things.Page) (things.ClientsPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_clients") - defer span.End() - return tm.svc.ListClients(ctx, session, reqUserID, pm) -} - -// Update traces the "Update" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) Update(ctx context.Context, session authn.Session, cli things.Client) (things.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_client", trace.WithAttributes(attribute.String("id", cli.ID))) - defer span.End() - - return tm.svc.Update(ctx, session, cli) -} - -// UpdateTags traces the "UpdateTags" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) UpdateTags(ctx context.Context, session authn.Session, cli things.Client) (things.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_client_tags", trace.WithAttributes( - attribute.String("id", cli.ID), - attribute.StringSlice("tags", cli.Tags), - )) - defer span.End() - - return tm.svc.UpdateTags(ctx, session, cli) -} - -// UpdateSecret traces the "UpdateSecret" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (things.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_client_secret") - defer span.End() - - return tm.svc.UpdateSecret(ctx, session, oldSecret, newSecret) -} - -// Enable traces the "Enable" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) Enable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_enable_client", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return tm.svc.Enable(ctx, session, id) -} - -// Disable traces the "Disable" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) Disable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - ctx, span := tm.tracer.Start(ctx, "svc_disable_client", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return tm.svc.Disable(ctx, session, id) -} - -// ListClientsByGroup traces the "ListClientsByGroup" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) ListClientsByGroup(ctx context.Context, session authn.Session, groupID string, pm things.Page) (things.MembersPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_clients_by_channel", trace.WithAttributes(attribute.String("groupID", groupID))) - defer span.End() - - return tm.svc.ListClientsByGroup(ctx, session, groupID, pm) -} - -// ListMemberships traces the "ListMemberships" operation of the wrapped policies.Service. -func (tm *tracingMiddleware) Identify(ctx context.Context, key string) (string, error) { - ctx, span := tm.tracer.Start(ctx, "svc_identify", trace.WithAttributes(attribute.String("key", key))) - defer span.End() - - return tm.svc.Identify(ctx, key) -} - -// Authorize traces the "Authorize" operation of the wrapped things.Service. -func (tm *tracingMiddleware) Authorize(ctx context.Context, req things.AuthzReq) (string, error) { - ctx, span := tm.tracer.Start(ctx, "connect", trace.WithAttributes(attribute.String("thingKey", req.ClientKey), attribute.String("channelID", req.ChannelID))) - defer span.End() - - return tm.svc.Authorize(ctx, req) -} - -// Share traces the "Share" operation of the wrapped things.Service. -func (tm *tracingMiddleware) Share(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { - ctx, span := tm.tracer.Start(ctx, "share", trace.WithAttributes(attribute.String("id", id), attribute.String("relation", relation), attribute.StringSlice("user_ids", userids))) - defer span.End() - return tm.svc.Share(ctx, session, id, relation, userids...) -} - -// Unshare traces the "Unshare" operation of the wrapped things.Service. -func (tm *tracingMiddleware) Unshare(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { - ctx, span := tm.tracer.Start(ctx, "unshare", trace.WithAttributes(attribute.String("id", id), attribute.String("relation", relation), attribute.StringSlice("user_ids", userids))) - defer span.End() - return tm.svc.Unshare(ctx, session, id, relation, userids...) -} - -// Delete traces the "Delete" operation of the wrapped things.Service. -func (tm *tracingMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { - ctx, span := tm.tracer.Start(ctx, "delete_client", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - return tm.svc.Delete(ctx, session, id) -} diff --git a/tools/config/mockery.yaml b/tools/config/mockery.yaml index 69e231658a..cc31d2947e 100644 --- a/tools/config/mockery.yaml +++ b/tools/config/mockery.yaml @@ -6,23 +6,41 @@ filename: "{{.InterfaceName}}.go" outpkg: "mocks" boilerplate-file: "./tools/config/boilerplate.txt" packages: - github.com/absmach/magistrala: + github.com/absmach/magistrala/internal/grpc/clients/v1: interfaces: - ThingsServiceClient: + ClientsServiceClient: config: - dir: "./things/mocks" - mockname: "ThingsServiceClient" - filename: "things_client.go" + dir: "./clients/mocks" + mockname: "ClientsServiceClient" + filename: "clients_client.go" + github.com/absmach/magistrala/internal/grpc/domains/v1: + interfaces: DomainsServiceClient: config: - dir: "./auth/mocks" + dir: "./domains/mocks" mockname: "DomainsServiceClient" filename: "domains_client.go" + github.com/absmach/magistrala/internal/grpc/token/v1: + interfaces: TokenServiceClient: config: dir: "./auth/mocks" mockname: "TokenServiceClient" filename: "token_client.go" + github.com/absmach/magistrala/internal/grpc/channels/v1: + interfaces: + ChannelsServiceClient: + config: + dir: "./channels/mocks" + mockname: "ChannelsServiceClient" + filename: "channels_client.go" + github.com/absmach/magistrala/internal/grpc/groups/v1: + interfaces: + GroupsServiceClient: + config: + dir: "./groups/mocks" + mockname: "GroupsServiceClient" + filename: "groups_client.go" github.com/absmach/magistrala/certs/pki/amcerts: interfaces: diff --git a/tools/e2e/README.md b/tools/e2e/README.md index 6e35845144..dcbe2788f4 100644 --- a/tools/e2e/README.md +++ b/tools/e2e/README.md @@ -1,6 +1,6 @@ -# Magistrala Users Groups Things and Channels E2E Testing Tool +# Magistrala Users Groups Clients and Channels E2E Testing Tool -A simple utility to create a list of groups and users connected to these groups and channels and things connected to these channels. +A simple utility to create a list of groups and users connected to these groups and channels and clients connected to these channels. ## Installation @@ -14,9 +14,9 @@ make ```bash ./e2e --help Tool for testing end-to-end flow of Magistrala by doing a couple of operations namely: -1. Creating, viewing, updating and changing status of users, groups, things and channels. -2. Connecting users and groups to each other and things and channels to each other. -3. Sending messages from things to channels on all 4 protocol adapters (HTTP, WS, CoAP and MQTT). +1. Creating, viewing, updating and changing status of users, groups, clients and channels. +2. Connecting users and groups to each other and clients and channels to each other. +3. Sending messages from clients to channels on all 4 protocol adapters (HTTP, WS, CoAP and MQTT). Complete documentation is available at https://docs.magistrala.abstractmachines.fr @@ -39,9 +39,9 @@ Flags: -h, --help help for e2e -H, --host string address for a running Magistrala instance (default "localhost") - -n, --num uint number of users, groups, channels and things to create and connect (default 10) + -n, --num uint number of users, groups, channels and clients to create and connect (default 10) -N, --num_of_messages uint number of messages to send (default 10) - -p, --prefix string name prefix for users, groups, things and channels + -p, --prefix string name prefix for users, groups, clients and channels ``` To use `-H` option, you can specify the address for the Magistrala instance as an argument when running the program. For example, if the Magistrala instance is running on another computer with the IP address 192.168.0.1, you could use the following command: @@ -74,7 +74,7 @@ c8fe4d9d-3ad6-4687-83c0-171356f3e4f6 513f7295-0923-4e21-b41a-3cfd1cb7b9b9 54bd71ea-3c22-401e-89ea-d58162b983c0 ae91b327-4c40-4e68-91fe-cd6223ee4e99 -created things of ids: +created clients of ids: 5909a907-7413-47d4-b793-e1eb36988a5f f9b6bc18-1862-4a24-8973-adde11cb3303 c2bd6eed-6f38-464c-989c-fe8ec8c084ba @@ -86,8 +86,8 @@ d654948d-d6c1-4eae-b69a-29c853282c3d 2c2a5496-89cf-47e6-9d38-5fd5542337bd 7ab3319d-269c-4b07-9dc5-f9906693e894 5d8fa139-10e7-4683-94f3-4e881b4db041 -created policies for users, groups, things and channels -viewed users, groups, things and channels -updated users, groups, things and channels +created policies for users, groups, clients and channels +viewed users, groups, clients and channels +updated users, groups, clients and channels sent messages to channels ``` diff --git a/tools/e2e/cmd/main.go b/tools/e2e/cmd/main.go index 5574382a65..327d1088b0 100644 --- a/tools/e2e/cmd/main.go +++ b/tools/e2e/cmd/main.go @@ -21,9 +21,9 @@ func main() { Use: "e2e", Short: "e2e is end-to-end testing tool for Magistrala", Long: "Tool for testing end-to-end flow of magistrala by doing a couple of operations namely:\n" + - "1. Creating, viewing, updating and changing status of users, groups, things and channels.\n" + - "2. Connecting users and groups to each other and things and channels to each other.\n" + - "3. Sending messages from things to channels on all 4 protocol adapters (HTTP, WS, CoAP and MQTT).\n" + + "1. Creating, viewing, updating and changing status of users, groups, clients and channels.\n" + + "2. Connecting users and groups to each other and clients and channels to each other.\n" + + "3. Sending messages from clients to channels on all 4 protocol adapters (HTTP, WS, CoAP and MQTT).\n" + "Complete documentation is available at https://docs.magistrala.abstractmachines.fr", Example: "Here is a simple example of using e2e tool.\n" + "Use the following commands from the root magistrala directory:\n\n" + @@ -48,8 +48,8 @@ func main() { // Root Flags rootCmd.PersistentFlags().StringVarP(&econf.Host, "host", "H", "localhost", "address for a running magistrala instance") - rootCmd.PersistentFlags().StringVarP(&econf.Prefix, "prefix", "p", "", "name prefix for users, groups, things and channels") - rootCmd.PersistentFlags().Uint64VarP(&econf.Num, "num", "n", defNum, "number of users, groups, channels and things to create and connect") + rootCmd.PersistentFlags().StringVarP(&econf.Prefix, "prefix", "p", "", "name prefix for users, groups, clients and channels") + rootCmd.PersistentFlags().Uint64VarP(&econf.Num, "num", "n", defNum, "number of users, groups, channels and clients to create and connect") rootCmd.PersistentFlags().Uint64VarP(&econf.NumOfMsg, "num_of_messages", "N", defNum, "number of messages to send") if err := rootCmd.Execute(); err != nil { diff --git a/tools/e2e/e2e.go b/tools/e2e/e2e.go index e7bf354031..e86607737b 100644 --- a/tools/e2e/e2e.go +++ b/tools/e2e/e2e.go @@ -26,7 +26,7 @@ const ( numAdapters = 4 batchSize = 99 usersPort = "9002" - thingsPort = "9000" + clientsPort = "9000" domainsPort = "8189" ) @@ -55,17 +55,17 @@ type Config struct { // - Create groups using hierarchy // - Do Read, Update and Change of Status operations on groups. -// - Create things -// - Do Read, Update and Change of Status operations on things. +// - Create clients +// - Do Read, Update and Change of Status operations on clients. // - Create channels // - Do Read, Update and Change of Status operations on channels. -// - Connect thing to channel +// - Connect client to channel // - Publish message from HTTP, MQTT, WS and CoAP Adapters. func Test(conf Config) { sdkConf := sdk.Config{ - ThingsURL: fmt.Sprintf("http://%s:%s", conf.Host, thingsPort), + ClientsURL: fmt.Sprintf("http://%s:%s", conf.Host, clientsPort), UsersURL: fmt.Sprintf("http://%s:%s", conf.Host, usersPort), DomainsURL: fmt.Sprintf("http://%s:%s", conf.Host, domainsPort), HTTPAdapterURL: fmt.Sprintf("http://%s/http", conf.Host), @@ -95,11 +95,11 @@ func Test(conf Config) { } color.Success.Printf("created groups of ids:\n%s\n", magenta(getIDS(groups))) - things, err := createThings(s, conf, domainID, token) + clients, err := createClients(s, conf, domainID, token) if err != nil { - errExit(fmt.Errorf("unable to create things: %w", err)) + errExit(fmt.Errorf("unable to create clients: %w", err)) } - color.Success.Printf("created things of ids:\n%s\n", magenta(getIDS(things))) + color.Success.Printf("created clients of ids:\n%s\n", magenta(getIDS(clients))) channels, err := createChannels(s, conf, domainID, token) if err != nil { @@ -107,20 +107,20 @@ func Test(conf Config) { } color.Success.Printf("created channels of ids:\n%s\n", magenta(getIDS(channels))) - // List users, groups, things and channels - if err := read(s, conf, domainID, token, users, groups, things, channels); err != nil { - errExit(fmt.Errorf("unable to read users, groups, things and channels: %w", err)) + // List users, groups, clients and channels + if err := read(s, conf, domainID, token, users, groups, clients, channels); err != nil { + errExit(fmt.Errorf("unable to read users, groups, clients and channels: %w", err)) } - color.Success.Println("viewed users, groups, things and channels") + color.Success.Println("viewed users, groups, clients and channels") - // Update users, groups, things and channels - if err := update(s, domainID, token, users, groups, things, channels); err != nil { - errExit(fmt.Errorf("unable to update users, groups, things and channels: %w", err)) + // Update users, groups, clients and channels + if err := update(s, domainID, token, users, groups, clients, channels); err != nil { + errExit(fmt.Errorf("unable to update users, groups, clients and channels: %w", err)) } - color.Success.Println("updated users, groups, things and channels") + color.Success.Println("updated users, groups, clients and channels") // Send messages to channels - if err := messaging(s, conf, domainID, token, things, channels); err != nil { + if err := messaging(s, conf, domainID, token, clients, channels); err != nil { errExit(fmt.Errorf("unable to send messages to channels: %w", err)) } color.Success.Println("sent messages to channels") @@ -227,50 +227,50 @@ func createGroups(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Group, return groups, nil } -func createThingsInBatch(s sdk.SDK, conf Config, domainID, token string, num uint64) ([]sdk.Thing, error) { +func createClientsInBatch(s sdk.SDK, conf Config, domainID, token string, num uint64) ([]sdk.Client, error) { var err error - things := make([]sdk.Thing, num) + clients := make([]sdk.Client, num) for i := uint64(0); i < num; i++ { - things[i] = sdk.Thing{ + clients[i] = sdk.Client{ Name: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), } } - things, err = s.CreateThings(things, domainID, token) + clients, err = s.CreateClients(clients, domainID, token) if err != nil { - return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err) + return []sdk.Client{}, fmt.Errorf("failed to create the clients: %w", err) } - return things, nil + return clients, nil } -func createThings(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Thing, error) { - things := []sdk.Thing{} +func createClients(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Client, error) { + clients := []sdk.Client{} if conf.Num > batchSize { batches := int(conf.Num) / batchSize for i := 0; i < batches; i++ { - ths, err := createThingsInBatch(s, conf, domainID, token, batchSize) + ths, err := createClientsInBatch(s, conf, domainID, token, batchSize) if err != nil { - return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err) + return []sdk.Client{}, fmt.Errorf("failed to create the clients: %w", err) } - things = append(things, ths...) + clients = append(clients, ths...) } - ths, err := createThingsInBatch(s, conf, domainID, token, conf.Num%uint64(batchSize)) + ths, err := createClientsInBatch(s, conf, domainID, token, conf.Num%uint64(batchSize)) if err != nil { - return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err) + return []sdk.Client{}, fmt.Errorf("failed to create the clients: %w", err) } - things = append(things, ths...) + clients = append(clients, ths...) } else { - ths, err := createThingsInBatch(s, conf, domainID, token, conf.Num) + ths, err := createClientsInBatch(s, conf, domainID, token, conf.Num) if err != nil { - return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err) + return []sdk.Client{}, fmt.Errorf("failed to create the clients: %w", err) } - things = append(things, ths...) + clients = append(clients, ths...) } - return things, nil + return clients, nil } func createChannelsInBatch(s sdk.SDK, conf Config, domainID, token string, num uint64) ([]sdk.Channel, error) { @@ -318,7 +318,7 @@ func createChannels(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Chann return channels, nil } -func read(s sdk.SDK, conf Config, domainID, token string, users []sdk.User, groups []sdk.Group, things []sdk.Thing, channels []sdk.Channel) error { +func read(s sdk.SDK, conf Config, domainID, token string, users []sdk.User, groups []sdk.Group, clients []sdk.Client, channels []sdk.Channel) error { for _, user := range users { if _, err := s.User(user.ID, token); err != nil { return fmt.Errorf("failed to get user %w", err) @@ -343,17 +343,17 @@ func read(s sdk.SDK, conf Config, domainID, token string, users []sdk.User, grou if gp.Total < conf.Num { return fmt.Errorf("returned groups %d less than created groups %d", gp.Total, conf.Num) } - for _, thing := range things { - if _, err := s.Thing(thing.ID, domainID, token); err != nil { - return fmt.Errorf("failed to get thing %w", err) + for _, c := range clients { + if _, err := s.Client(c.ID, domainID, token); err != nil { + return fmt.Errorf("failed to get client %w", err) } } - tp, err := s.Things(sdk.PageMetadata{}, domainID, token) + tp, err := s.Clients(sdk.PageMetadata{}, domainID, token) if err != nil { - return fmt.Errorf("failed to get things %w", err) + return fmt.Errorf("failed to get clients %w", err) } if tp.Total < conf.Num { - return fmt.Errorf("returned things %d less than created things %d", tp.Total, conf.Num) + return fmt.Errorf("returned clients %d less than created clients %d", tp.Total, conf.Num) } for _, channel := range channels { if _, err := s.Channel(channel.ID, domainID, token); err != nil { @@ -371,7 +371,7 @@ func read(s sdk.SDK, conf Config, domainID, token string, users []sdk.User, grou return nil } -func update(s sdk.SDK, domainID, token string, users []sdk.User, groups []sdk.Group, things []sdk.Thing, channels []sdk.Channel) error { +func update(s sdk.SDK, domainID, token string, users []sdk.User, groups []sdk.Group, clients []sdk.Client, channels []sdk.Channel) error { for _, user := range users { user.FirstName = namesgenerator.Generate() user.Metadata = sdk.Metadata{"Update": namesgenerator.Generate()} @@ -458,48 +458,48 @@ func update(s sdk.SDK, domainID, token string, users []sdk.User, groups []sdk.Gr return fmt.Errorf("failed to enable group before %s after %s", group.Status, rGroup.Status) } } - for _, thing := range things { - thing.Name = namesgenerator.Generate() - thing.Metadata = sdk.Metadata{"Update": namesgenerator.Generate()} - rThing, err := s.UpdateThing(thing, domainID, token) + for _, t := range clients { + t.Name = namesgenerator.Generate() + t.Metadata = sdk.Metadata{"Update": namesgenerator.Generate()} + rClient, err := s.UpdateClient(t, domainID, token) if err != nil { - return fmt.Errorf("failed to update thing %w", err) + return fmt.Errorf("failed to update client %w", err) } - if rThing.Name != thing.Name { - return fmt.Errorf("failed to update thing name before %s after %s", thing.Name, rThing.Name) + if rClient.Name != t.Name { + return fmt.Errorf("failed to update client name before %s after %s", t.Name, rClient.Name) } - if rThing.Metadata["Update"] != thing.Metadata["Update"] { - return fmt.Errorf("failed to update thing metadata before %s after %s", thing.Metadata["Update"], rThing.Metadata["Update"]) + if rClient.Metadata["Update"] != t.Metadata["Update"] { + return fmt.Errorf("failed to update client metadata before %s after %s", t.Metadata["Update"], rClient.Metadata["Update"]) } - thing = rThing - rThing, err = s.UpdateThingSecret(thing.ID, thing.Credentials.Secret, domainID, token) + t = rClient + rClient, err = s.UpdateClientSecret(t.ID, t.Credentials.Secret, domainID, token) if err != nil { - return fmt.Errorf("failed to update thing secret %w", err) + return fmt.Errorf("failed to update client secret %w", err) } - thing = rThing - thing.Tags = []string{namesgenerator.Generate()} - rThing, err = s.UpdateThingTags(thing, domainID, token) + t = rClient + t.Tags = []string{namesgenerator.Generate()} + rClient, err = s.UpdateClientTags(t, domainID, token) if err != nil { - return fmt.Errorf("failed to update thing tags %w", err) + return fmt.Errorf("failed to update client tags %w", err) } - if rThing.Tags[0] != thing.Tags[0] { - return fmt.Errorf("failed to update thing tags before %s after %s", thing.Tags[0], rThing.Tags[0]) + if rClient.Tags[0] != t.Tags[0] { + return fmt.Errorf("failed to update client tags before %s after %s", t.Tags[0], rClient.Tags[0]) } - thing = rThing - rThing, err = s.DisableThing(thing.ID, domainID, token) + t = rClient + rClient, err = s.DisableClient(t.ID, domainID, token) if err != nil { - return fmt.Errorf("failed to disable thing %w", err) + return fmt.Errorf("failed to disable client %w", err) } - if rThing.Status != sdk.DisabledStatus { - return fmt.Errorf("failed to disable thing before %s after %s", thing.Status, rThing.Status) + if rClient.Status != sdk.DisabledStatus { + return fmt.Errorf("failed to disable client before %s after %s", t.Status, rClient.Status) } - thing = rThing - rThing, err = s.EnableThing(thing.ID, domainID, token) + t = rClient + rClient, err = s.EnableClient(t.ID, domainID, token) if err != nil { - return fmt.Errorf("failed to enable thing %w", err) + return fmt.Errorf("failed to enable client %w", err) } - if rThing.Status != sdk.EnabledStatus { - return fmt.Errorf("failed to enable thing before %s after %s", thing.Status, rThing.Status) + if rClient.Status != sdk.EnabledStatus { + return fmt.Errorf("failed to enable client before %s after %s", t.Status, rClient.Status) } } for _, channel := range channels { @@ -536,15 +536,15 @@ func update(s sdk.SDK, domainID, token string, users []sdk.User, groups []sdk.Gr return nil } -func messaging(s sdk.SDK, conf Config, domainID, token string, things []sdk.Thing, channels []sdk.Channel) error { - for _, thing := range things { +func messaging(s sdk.SDK, conf Config, domainID, token string, clients []sdk.Client, channels []sdk.Channel) error { + for _, c := range clients { for _, channel := range channels { conn := sdk.Connection{ - ThingID: thing.ID, + ClientID: c.ID, ChannelID: channel.ID, } if err := s.Connect(conn, domainID, token); err != nil { - return fmt.Errorf("failed to connect thing %s to channel %s", thing.ID, channel.ID) + return fmt.Errorf("failed to connect client %s to channel %s", c.ID, channel.ID) } } } @@ -553,26 +553,26 @@ func messaging(s sdk.SDK, conf Config, domainID, token string, things []sdk.Thin bt := time.Now().Unix() for i := uint64(0); i < conf.NumOfMsg; i++ { - for _, thing := range things { + for _, client := range clients { for _, channel := range channels { - func(num int64, thing sdk.Thing, channel sdk.Channel) { + func(num int64, client sdk.Client, channel sdk.Channel) { g.Go(func() error { msg := fmt.Sprintf(msgFormat, num+1, rand.Int()) - return sendHTTPMessage(s, msg, thing, channel.ID) + return sendHTTPMessage(s, msg, client, channel.ID) }) g.Go(func() error { msg := fmt.Sprintf(msgFormat, num+2, rand.Int()) - return sendCoAPMessage(msg, thing, channel.ID) + return sendCoAPMessage(msg, client, channel.ID) }) g.Go(func() error { msg := fmt.Sprintf(msgFormat, num+3, rand.Int()) - return sendMQTTMessage(msg, thing, channel.ID) + return sendMQTTMessage(msg, client, channel.ID) }) g.Go(func() error { msg := fmt.Sprintf(msgFormat, num+4, rand.Int()) - return sendWSMessage(conf, msg, thing, channel.ID) + return sendWSMessage(conf, msg, client, channel.ID) }) - }(bt, thing, channel) + }(bt, client, channel) bt += numAdapters } } @@ -581,42 +581,42 @@ func messaging(s sdk.SDK, conf Config, domainID, token string, things []sdk.Thin return g.Wait() } -func sendHTTPMessage(s sdk.SDK, msg string, thing sdk.Thing, chanID string) error { - if err := s.SendMessage(chanID, msg, thing.Credentials.Secret); err != nil { - return fmt.Errorf("HTTP failed to send message from thing %s to channel %s: %w", thing.ID, chanID, err) +func sendHTTPMessage(s sdk.SDK, msg string, client sdk.Client, chanID string) error { + if err := s.SendMessage(chanID, msg, client.Credentials.Secret); err != nil { + return fmt.Errorf("HTTP failed to send message from client %s to channel %s: %w", client.ID, chanID, err) } return nil } -func sendCoAPMessage(msg string, thing sdk.Thing, chanID string) error { - cmd := exec.Command("coap-cli", "post", fmt.Sprintf("channels/%s/messages", chanID), "--auth", thing.Credentials.Secret, "-d", msg) +func sendCoAPMessage(msg string, client sdk.Client, chanID string) error { + cmd := exec.Command("coap-cli", "post", fmt.Sprintf("channels/%s/messages", chanID), "--auth", client.Credentials.Secret, "-d", msg) if _, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("CoAP failed to send message from thing %s to channel %s: %w", thing.ID, chanID, err) + return fmt.Errorf("CoAP failed to send message from client %s to channel %s: %w", client.ID, chanID, err) } return nil } -func sendMQTTMessage(msg string, thing sdk.Thing, chanID string) error { - cmd := exec.Command("mosquitto_pub", "--id-prefix", "magistrala", "-u", thing.ID, "-P", thing.Credentials.Secret, "-t", fmt.Sprintf("channels/%s/messages", chanID), "-h", "localhost", "-m", msg) +func sendMQTTMessage(msg string, client sdk.Client, chanID string) error { + cmd := exec.Command("mosquitto_pub", "--id-prefix", "magistrala", "-u", client.ID, "-P", client.Credentials.Secret, "-t", fmt.Sprintf("channels/%s/messages", chanID), "-h", "localhost", "-m", msg) if _, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("MQTT failed to send message from thing %s to channel %s: %w", thing.ID, chanID, err) + return fmt.Errorf("MQTT failed to send message from client %s to channel %s: %w", client.ID, chanID, err) } return nil } -func sendWSMessage(conf Config, msg string, thing sdk.Thing, chanID string) error { +func sendWSMessage(conf Config, msg string, client sdk.Client, chanID string) error { socketURL := fmt.Sprintf("ws://%s:%s/channels/%s/messages", conf.Host, defWSPort, chanID) - header := http.Header{"Authorization": []string{thing.Credentials.Secret}} + header := http.Header{"Authorization": []string{client.Credentials.Secret}} conn, _, err := websocket.DefaultDialer.Dial(socketURL, header) if err != nil { return fmt.Errorf("unable to connect to websocket: %w", err) } defer conn.Close() if err := conn.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil { - return fmt.Errorf("WS failed to send message from thing %s to channel %s: %w", thing.ID, chanID, err) + return fmt.Errorf("WS failed to send message from client %s to channel %s: %w", client.ID, chanID, err) } return nil diff --git a/tools/mqtt-bench/README.md b/tools/mqtt-bench/README.md index f94eb4d216..4510a9dd86 100644 --- a/tools/mqtt-bench/README.md +++ b/tools/mqtt-bench/README.md @@ -2,10 +2,10 @@ A simple MQTT benchmarking tool for Magistrala platform. -It connects Magistrala things as subscribers over a number of channels and -uses other Magistrala things to publish messages and create MQTT load. +It connects Magistrala clients as subscribers over a number of channels and +uses other Magistrala clients to publish messages and create MQTT load. -Magistrala things used must be pre-provisioned first, and Magistrala `provision` tool can be used for this purpose. +Magistrala clients used must be pre-provisioned first, and Magistrala `provision` tool can be used for this purpose. ## Installation @@ -46,7 +46,7 @@ Flags: Two output formats supported: human-readable plain text and JSON. -Before use you need a `mgconn.toml` - a TOML file that describes Magistrala connection data (channels, thingIDs, thingKeys, certs). +Before use you need a `mgconn.toml` - a TOML file that describes Magistrala connection data (channels, clientIDs, clientKeys, certs). You can use `provision` tool (in tools/provision) to create this TOML config file. ```bash diff --git a/tools/mqtt-bench/bench.go b/tools/mqtt-bench/bench.go index b79f7a3d05..f9e8adf236 100644 --- a/tools/mqtt-bench/bench.go +++ b/tools/mqtt-bench/bench.go @@ -72,16 +72,16 @@ func Benchmark(cfg Config) error { go func(i int) { defer wg.Done() mgChan := mg.Channels[i%n] - mgThing := mg.Things[i%n] + mgCli := mg.Clients[i%n] if cfg.MQTT.TLS.MTLS { - cert, err = tls.X509KeyPair([]byte(mgThing.MTLSCert), []byte(mgThing.MTLSKey)) + cert, err = tls.X509KeyPair([]byte(mgCli.MTLSCert), []byte(mgCli.MTLSKey)) if err != nil { errorChan <- err return } } - c, err := makeClient(i, cfg, mgChan, mgThing, startStamp, caByte, cert) + c, err := makeClient(i, cfg, mgChan, mgCli, startStamp, caByte, cert) if err != nil { errorChan <- fmt.Errorf("unable to create message payload %s", err.Error()) return @@ -171,12 +171,12 @@ func getBytePayload(size int, m message) (handler, error) { return ret, nil } -func makeClient(i int, cfg Config, mgChan mgChannel, mgThing mgThing, start time.Time, caCert []byte, clientCert tls.Certificate) (*Client, error) { +func makeClient(i int, cfg Config, mgChan mgChannel, cli mgClient, start time.Time, caCert []byte, clientCert tls.Certificate) (*Client, error) { c := &Client{ ID: strconv.Itoa(i), BrokerURL: cfg.MQTT.Broker.URL, - BrokerUser: mgThing.ThingID, - BrokerPass: mgThing.ThingKey, + BrokerUser: cli.ClientID, + BrokerPass: cli.ClientSecret, MsgTopic: fmt.Sprintf("channels/%s/messages/%d/test", mgChan.ChannelID, start.UnixNano()), MsgSize: cfg.MQTT.Message.Size, MsgCount: cfg.Test.Count, diff --git a/tools/mqtt-bench/config.go b/tools/mqtt-bench/config.go index a67a12c37c..7f71afa5e9 100644 --- a/tools/mqtt-bench/config.go +++ b/tools/mqtt-bench/config.go @@ -43,11 +43,11 @@ type magistralaFile struct { ConnFile string `toml:"connections_file" mapstructure:"connections_file"` } -type mgThing struct { - ThingID string `toml:"thing_id" mapstructure:"thing_id"` - ThingKey string `toml:"thing_key" mapstructure:"thing_key"` - MTLSCert string `toml:"mtls_cert" mapstructure:"mtls_cert"` - MTLSKey string `toml:"mtls_key" mapstructure:"mtls_key"` +type mgClient struct { + ClientID string `toml:"client_id" mapstructure:"client_id"` + ClientSecret string `toml:"client_secret" mapstructure:"client_secret"` + MTLSCert string `toml:"mtls_cert" mapstructure:"mtls_cert"` + MTLSKey string `toml:"mtls_key" mapstructure:"mtls_key"` } type mgChannel struct { @@ -55,7 +55,7 @@ type mgChannel struct { } type magistrala struct { - Things []mgThing `toml:"things" mapstructure:"things"` + Clients []mgClient `toml:"clients" mapstructure:"clients"` Channels []mgChannel `toml:"channels" mapstructure:"channels"` } diff --git a/tools/provision/README.md b/tools/provision/README.md index 77d7068372..5da5f9cf8d 100644 --- a/tools/provision/README.md +++ b/tools/provision/README.md @@ -1,61 +1,63 @@ -# Magistrala Things and Channels Provisioning Tool +# Magistrala Clients and Channels Provisioning Tool -A simple utility to create a list of channels and things connected to these channels with possibility to create certificates for mTLS use case. +A simple utility to create a list of channels and clients connected to these channels with possibility to create certificates for mTLS use case. This tool is useful for testing, and it creates a TOML format output (on stdout, can be redirected into the file as needed) that can be used by Magistrala MQTT benchmarking tool (`mqtt-bench`). ## Installation -``` + +```bash cd tools/provision make ``` ### Usage -``` + +```bash ./provision --help -Tool for provisioning series of Magistrala channels and things and connecting them together. +Tool for provisioning series of Magistrala channels and clients and connecting them together. Complete documentation is available at https://docs.magistrala.abstractmachines.fr Usage: provision [flags] Flags: - --ca string CA for creating and signing things certificate (default "ca.crt") - --cakey string ca.key for creating and signing things certificate (default "ca.key") + --ca string CA for creating and signing clients certificate (default "ca.crt") + --cakey string ca.key for creating and signing clients certificate (default "ca.key") -h, --help help for provision --host string address for magistrala instance (default "https://localhost") - --num int number of channels and things to create and connect (default 10) + --num int number of channels and clients to create and connect (default 10) -p, --password string magistrala users password --ssl create certificates for mTLS access -u, --username string magistrala user - --prefix string name prefix for things and channels + --prefix string name prefix for clients and channels ``` Example: -``` + +```bash go run tools/provision/cmd/main.go -u test@magistrala.com -p test1234 --host https://142.93.118.47 ``` If you want to create a list of channels with certificates: -``` +```bash go run tools/provision/cmd/main.go --host http://localhost --num 10 -u test@magistrala.com -p test1234 --ssl true --ca docker/ssl/certs/ca.crt --cakey docker/ssl/certs/ca.key ``` ->`ca.crt` and `ca.key` are used for creating things certificate and for HTTPS, +> `ca.crt` and `ca.key` are used for creating clients certificate and for HTTPS, > if you are provisioning on remote server you will have to get these files to your local -> directory so that you can create certificates for things - +> directory so that you can create certificates for clients Example of output: -``` -# List of things that can be connected to MQTT broker -[[things]] -thing_id = "0eac601b-6d54-4767-b8b7-594aaf9990d3" -thing_key = "07713103-513f-43c7-b7fe-500c1af23d7d" +```bash +# List of clients that can be connected to MQTT broker +[[clients]] +client_id = "0eac601b-6d54-4767-b8b7-594aaf9990d3" +client_key = "07713103-513f-43c7-b7fe-500c1af23d7d" mtls_cert = """-----BEGIN CERTIFICATE----- MIIEmTCCA4GgAwIBAgIRAO50qOfXsU+cHm/QY2NYu+0wDQYJKoZIhvcNAQELBQAw VzESMBAGA1UEAwwJbG9jYWxob3N0MREwDwYDVQQKDAhNYWluZmx1eDEMMAoGA1UE @@ -137,9 +139,9 @@ uCRt+TFMyEfqilipmNsV7esgbroiyEGXGMI8JdBY9OsnK6ZSlXaMnQ9vq2kK -----END RSA PRIVATE KEY----- """ -# List of channels that things can publish to -# each channel is connected to each thing from things list -# Things connected to channel 1f18afa1-29c4-4634-99d1-68dfa1b74e6a: 0eac601b-6d54-4767-b8b7-594aaf9990d3 +# List of channels that clients can publish to +# each channel is connected to each client from clients list +# Clients connected to channel 1f18afa1-29c4-4634-99d1-68dfa1b74e6a: 0eac601b-6d54-4767-b8b7-594aaf9990d3 [[channels]] channel_id = "1f18afa1-29c4-4634-99d1-68dfa1b74e6a" diff --git a/tools/provision/cmd/main.go b/tools/provision/cmd/main.go index 1b7461e14d..51ab87590b 100644 --- a/tools/provision/cmd/main.go +++ b/tools/provision/cmd/main.go @@ -17,7 +17,7 @@ func main() { rootCmd := &cobra.Command{ Use: "provision", Short: "provision is provisioning tool for Magistrala", - Long: `Tool for provisioning series of Magistrala channels and things and connecting them together. + Long: `Tool for provisioning series of Magistrala channels and clients and connecting them together. Complete documentation is available at https://docs.magistrala.abstractmachines.fr`, Run: func(_ *cobra.Command, _ []string) { if err := provision.Provision(pconf); err != nil { @@ -28,13 +28,13 @@ Complete documentation is available at https://docs.magistrala.abstractmachines. // Root Flags rootCmd.PersistentFlags().StringVarP(&pconf.Host, "host", "", "https://localhost", "address for magistrala instance") - rootCmd.PersistentFlags().StringVarP(&pconf.Prefix, "prefix", "", "", "name prefix for things and channels") + rootCmd.PersistentFlags().StringVarP(&pconf.Prefix, "prefix", "", "", "name prefix for clients and channels") rootCmd.PersistentFlags().StringVarP(&pconf.Username, "username", "u", "", "magistrala user") rootCmd.PersistentFlags().StringVarP(&pconf.Password, "password", "p", "", "magistrala users password") - rootCmd.PersistentFlags().IntVarP(&pconf.Num, "num", "", 10, "number of channels and things to create and connect") + rootCmd.PersistentFlags().IntVarP(&pconf.Num, "num", "", 10, "number of channels and clients to create and connect") rootCmd.PersistentFlags().BoolVarP(&pconf.SSL, "ssl", "", false, "create certificates for mTLS access") - rootCmd.PersistentFlags().StringVarP(&pconf.CAKey, "cakey", "", "ca.key", "ca.key for creating and signing things certificate") - rootCmd.PersistentFlags().StringVarP(&pconf.CA, "ca", "", "ca.crt", "CA for creating and signing things certificate") + rootCmd.PersistentFlags().StringVarP(&pconf.CAKey, "cakey", "", "ca.key", "ca.key for creating and signing clients certificate") + rootCmd.PersistentFlags().StringVarP(&pconf.CA, "ca", "", "ca.crt", "CA for creating and signing clients certificate") if err := rootCmd.Execute(); err != nil { log.Fatal(err) diff --git a/tools/provision/doc.go b/tools/provision/doc.go index 342b0abe95..da596dc217 100644 --- a/tools/provision/doc.go +++ b/tools/provision/doc.go @@ -2,6 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 // Package provision is a simple utility to create -// a list of channels and things connected to these channels +// a list of channels and clients connected to these channels // with possibility to create certificates for mTLS use case. package provision diff --git a/tools/provision/provision.go b/tools/provision/provision.go index d0316a0775..c26617cae4 100644 --- a/tools/provision/provision.go +++ b/tools/provision/provision.go @@ -33,11 +33,11 @@ var namesgenerator = namegenerator.NewGenerator() // MgConn - structure describing Magistrala connection set. type MgConn struct { - ChannelID string - ThingID string - ThingKey string - MTLSCert string - MTLSKey string + ClientID string + ClinetSecret string + ChannelID string + MTLSCert string + MTLSKey string } // Config - provisioning configuration. @@ -62,7 +62,7 @@ func Provision(conf Config) error { msgContentType := string(sdk.CTJSONSenML) sdkConf := sdk.Config{ - ThingsURL: conf.Host, + ClientsURL: conf.Host, UsersURL: conf.Host, ReaderURL: defReaderURL, HTTPAdapterURL: fmt.Sprintf("%s/http", conf.Host), @@ -146,22 +146,22 @@ func Provision(conf Config) error { } } - // Create things and channels - things := make([]sdk.Thing, conf.Num) + // Create clients and channels + clients := make([]sdk.Client, conf.Num) channels := make([]sdk.Channel, conf.Num) cIDs := []string{} tIDs := []string{} - fmt.Println("# List of things that can be connected to MQTT broker") + fmt.Println("# List of clients that can be connected to MQTT broker") for i := 0; i < conf.Num; i++ { - things[i] = sdk.Thing{Name: fmt.Sprintf("%s-thing-%d", conf.Prefix, i)} + clients[i] = sdk.Client{Name: fmt.Sprintf("%s-client-%d", conf.Prefix, i)} channels[i] = sdk.Channel{Name: fmt.Sprintf("%s-channel-%d", conf.Prefix, i)} } - things, err = s.CreateThings(things, domain.ID, token.AccessToken) + clients, err = s.CreateClients(clients, domain.ID, token.AccessToken) if err != nil { - return fmt.Errorf("failed to create the things: %s", err.Error()) + return fmt.Errorf("failed to create the clients: %s", err.Error()) } var chs []sdk.Channel @@ -174,7 +174,7 @@ func Provision(conf Config) error { } channels = chs - for _, t := range things { + for _, t := range clients { tIDs = append(tIDs, t.ID) } @@ -207,7 +207,7 @@ func Provision(conf Config) error { SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"Magistrala"}, - CommonName: things[i].Credentials.Secret, + CommonName: clients[i].Credentials.Secret, OrganizationalUnit: []string{"magistrala"}, }, NotBefore: notBefore, @@ -241,7 +241,7 @@ func Provision(conf Config) error { } // Print output - fmt.Printf("[[things]]\nthing_id = \"%s\"\nthing_key = \"%s\"\n", things[i].ID, things[i].Credentials.Secret) + fmt.Printf("[[clients]]\nclient_id = \"%s\"\nclient_key = \"%s\"\n", clients[i].ID, clients[i].Credentials.Secret) if conf.SSL { fmt.Printf("mtls_cert = \"\"\"%s\"\"\"\n", cert) fmt.Printf("mtls_key = \"\"\"%s\"\"\"\n", key) @@ -249,8 +249,8 @@ func Provision(conf Config) error { fmt.Println("") } - fmt.Printf("# List of channels that things can publish to\n" + - "# each channel is connected to each thing from things list\n") + fmt.Printf("# List of channels that clients can publish to\n" + + "# each channel is connected to each client from clients list\n") for i := 0; i < conf.Num; i++ { fmt.Printf("[[channels]]\nchannel_id = \"%s\"\n\n", cIDs[i]) } @@ -258,11 +258,11 @@ func Provision(conf Config) error { for _, cID := range cIDs { for _, tID := range tIDs { conIDs := sdk.Connection{ - ThingID: tID, + ClientID: tID, ChannelID: cID, } if err := s.Connect(conIDs, domain.ID, token.AccessToken); err != nil { - log.Fatalf("Failed to connect things %s to channels %s: %s", tID, cID, err) + log.Fatalf("Failed to connect clients %s to channels %s: %s", tID, cID, err) } } } diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index 32d219cb1a..b886129b6e 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -13,9 +13,9 @@ import ( "strings" "testing" - "github.com/absmach/magistrala" authmocks "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/internal/api" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/apiutil" @@ -23,7 +23,6 @@ import ( authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" - gmocks "github.com/absmach/magistrala/pkg/groups/mocks" oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/users" httpapi "github.com/absmach/magistrala/users/api" @@ -86,19 +85,17 @@ func (tr testRequest) make() (*http.Response, error) { return tr.user.Do(req) } -func newUsersServer() (*httptest.Server, *mocks.Service, *gmocks.Service, *authnmocks.Authentication) { +func newUsersServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { svc := new(mocks.Service) - gsvc := new(gmocks.Service) - logger := mglog.NewMock() mux := chi.NewRouter() provider := new(oauth2mocks.Provider) provider.On("Name").Return("test") authn := new(authnmocks.Authentication) token := new(authmocks.TokenServiceClient) - httpapi.MakeHandler(svc, authn, token, true, gsvc, mux, logger, "", passRegex, provider) + httpapi.MakeHandler(svc, authn, token, true, mux, logger, "", passRegex, provider) - return httptest.NewServer(mux), svc, gsvc, authn + return httptest.NewServer(mux), svc, authn } func toJSON(data interface{}) string { @@ -110,7 +107,7 @@ func toJSON(data interface{}) string { } func TestRegister(t *testing.T) { - us, svc, _, _ := newUsersServer() + us, svc, _ := newUsersServer() defer us.Close() cases := []struct { @@ -254,7 +251,7 @@ func TestRegister(t *testing.T) { } func TestView(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -340,7 +337,7 @@ func TestView(t *testing.T) { } func TestViewProfile(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -418,7 +415,7 @@ func TestViewProfile(t *testing.T) { } func TestListUsers(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -787,7 +784,7 @@ func TestListUsers(t *testing.T) { } func TestSearchUsers(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -922,7 +919,7 @@ func TestSearchUsers(t *testing.T) { } func TestUpdate(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() newName := "newname" @@ -1061,7 +1058,7 @@ func TestUpdate(t *testing.T) { } func TestUpdateTags(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() defer us.Close() @@ -1202,7 +1199,7 @@ func TestUpdateTags(t *testing.T) { } func TestUpdateEmail(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() newuseremail := "newuseremail@example.com" @@ -1373,7 +1370,7 @@ func TestUpdateEmail(t *testing.T) { } func TestUpdateUsername(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() newusername := "newusername" @@ -1521,7 +1518,7 @@ func TestUpdateUsername(t *testing.T) { } func TestUpdateProfilePicture(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() newprofilepicture := "https://example.com/newprofilepicture" @@ -1647,7 +1644,7 @@ func TestUpdateProfilePicture(t *testing.T) { } func TestPasswordResetRequest(t *testing.T) { - us, svc, _, _ := newUsersServer() + us, svc, _ := newUsersServer() defer us.Close() testemail := "test@example.com" @@ -1745,7 +1742,7 @@ func TestPasswordResetRequest(t *testing.T) { } func TestPasswordReset(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() strongPass := "StrongPassword" @@ -1851,7 +1848,7 @@ func TestPasswordReset(t *testing.T) { } func TestUpdateRole(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -1979,7 +1976,7 @@ func TestUpdateRole(t *testing.T) { } func TestUpdateSecret(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -2118,7 +2115,7 @@ func TestUpdateSecret(t *testing.T) { } func TestIssueToken(t *testing.T) { - us, svc, _, _ := newUsersServer() + us, svc, _ := newUsersServer() defer us.Close() validUsername := "valid" @@ -2184,7 +2181,7 @@ func TestIssueToken(t *testing.T) { body: strings.NewReader(tc.data), } - svcCall := svc.On("IssueToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&magistrala.Token{AccessToken: validToken}, tc.err) + svcCall := svc.On("IssueToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&grpcTokenV1.Token{AccessToken: validToken}, tc.err) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) if tc.err != nil { @@ -2203,7 +2200,7 @@ func TestIssueToken(t *testing.T) { } func TestRefreshToken(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -2273,37 +2270,35 @@ func TestRefreshToken(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/users/tokens/refresh", us.URL), - contentType: tc.contentType, - body: strings.NewReader(tc.data), - token: tc.token, - } - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("RefreshToken", mock.Anything, tc.authnRes, tc.token, mock.Anything).Return(&magistrala.Token{AccessToken: validToken}, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - if tc.err != nil { - var resBody respBody - err = json.NewDecoder(res.Body).Decode(&resBody) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if resBody.Err != "" || resBody.Message != "" { - err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) - } - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + req := testRequest{ + user: us.Client(), + method: http.MethodPost, + url: fmt.Sprintf("%s/users/tokens/refresh", us.URL), + contentType: tc.contentType, + body: strings.NewReader(tc.data), + token: tc.token, + } + authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) + svcCall := svc.On("RefreshToken", mock.Anything, tc.authnRes, tc.token, mock.Anything).Return(&grpcTokenV1.Token{AccessToken: validToken}, tc.err) + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + if tc.err != nil { + var resBody respBody + err = json.NewDecoder(res.Body).Decode(&resBody) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) + if resBody.Err != "" || resBody.Message != "" { + err = errors.Wrap(errors.New(resBody.Err), errors.New(resBody.Message)) } - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) + 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.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + svcCall.Unset() + authnCall.Unset() } } func TestEnable(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { desc string @@ -2364,8 +2359,8 @@ func TestEnable(t *testing.T) { token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, + svcErr: svcerr.ErrEnableUser, + err: svcerr.ErrEnableUser, }, } @@ -2402,7 +2397,7 @@ func TestEnable(t *testing.T) { } func TestDisable(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -2464,8 +2459,8 @@ func TestDisable(t *testing.T) { token: validToken, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, + svcErr: svcerr.ErrDisableUser, + err: svcerr.ErrDisableUser, }, } @@ -2493,7 +2488,7 @@ func TestDisable(t *testing.T) { } func TestDelete(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -2570,7 +2565,7 @@ func TestDelete(t *testing.T) { } func TestListUsersByUserGroupId(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -2897,7 +2892,7 @@ func TestListUsersByUserGroupId(t *testing.T) { } func TestListUsersByChannelID(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -3235,7 +3230,7 @@ func TestListUsersByChannelID(t *testing.T) { } func TestListUsersByDomainID(t *testing.T) { - us, svc, _, authn := newUsersServer() + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { @@ -3578,14 +3573,14 @@ func TestListUsersByDomainID(t *testing.T) { } } -func TestListUsersByThingID(t *testing.T) { - us, svc, _, authn := newUsersServer() +func TestListUsersByClientID(t *testing.T) { + us, svc, authn := newUsersServer() defer us.Close() cases := []struct { desc string token string - thingID string + clientID string page users.Page status int query string @@ -3595,10 +3590,10 @@ func TestListUsersByThingID(t *testing.T) { err error }{ { - desc: "list users with valid token", - token: validToken, - thingID: validID, - status: http.StatusOK, + desc: "list users with valid token", + token: validToken, + clientID: validID, + status: http.StatusOK, listUsersResponse: users.UsersPage{ Page: users.Page{ Total: 1, @@ -3611,7 +3606,7 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with empty token", token: "", - thingID: validID, + clientID: validID, status: http.StatusUnauthorized, authnErr: svcerr.ErrAuthentication, err: apiutil.ErrBearerToken, @@ -3619,15 +3614,15 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with invalid token", token: inValidToken, - thingID: validID, + clientID: validID, status: http.StatusUnauthorized, authnErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, { - desc: "list users with offset", - token: validToken, - thingID: validID, + desc: "list users with offset", + token: validToken, + clientID: validID, listUsersResponse: users.UsersPage{ Page: users.Page{ Offset: 1, @@ -3643,16 +3638,16 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with invalid offset", token: validToken, - thingID: validID, + clientID: validID, query: "offset=invalid", status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, err: apiutil.ErrValidation, }, { - desc: "list users with limit", - token: validToken, - thingID: validID, + desc: "list users with limit", + token: validToken, + clientID: validID, listUsersResponse: users.UsersPage{ Page: users.Page{ Limit: 1, @@ -3668,7 +3663,7 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with invalid limit", token: validToken, - thingID: validID, + clientID: validID, query: "limit=invalid", status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, @@ -3677,16 +3672,16 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with limit greater than max", token: validToken, - thingID: validID, + clientID: validID, query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, err: apiutil.ErrValidation, }, { - desc: "list users with name", - token: validToken, - thingID: validID, + desc: "list users with name", + token: validToken, + clientID: validID, listUsersResponse: users.UsersPage{ Page: users.Page{ Total: 1, @@ -3701,24 +3696,24 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with invalid user name", token: validToken, - thingID: validID, + clientID: validID, query: "username=invalid", status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, err: apiutil.ErrValidation, }, { - desc: "list users with duplicate user name", - token: validToken, - thingID: validID, - query: "username=1&username=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, + desc: "list users with duplicate user name", + token: validToken, + clientID: validID, + query: "username=1&username=2", + status: http.StatusBadRequest, + err: apiutil.ErrInvalidQueryParams, }, { - desc: "list users with status", - token: validToken, - thingID: validID, + desc: "list users with status", + token: validToken, + clientID: validID, listUsersResponse: users.UsersPage{ Page: users.Page{ Total: 1, @@ -3733,7 +3728,7 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with invalid status", token: validToken, - thingID: validID, + clientID: validID, query: "status=invalid", status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, @@ -3747,9 +3742,9 @@ func TestListUsersByThingID(t *testing.T) { err: apiutil.ErrInvalidQueryParams, }, { - desc: "list users with tags", - token: validToken, - thingID: validID, + desc: "list users with tags", + token: validToken, + clientID: validID, listUsersResponse: users.UsersPage{ Page: users.Page{ Total: 1, @@ -3764,7 +3759,7 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with invalid tags", token: validToken, - thingID: validID, + clientID: validID, query: "tag=invalid", status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, @@ -3778,9 +3773,9 @@ func TestListUsersByThingID(t *testing.T) { err: apiutil.ErrInvalidQueryParams, }, { - desc: "list users with metadata", - token: validToken, - thingID: validID, + desc: "list users with metadata", + token: validToken, + clientID: validID, listUsersResponse: users.UsersPage{ Page: users.Page{ Total: 1, @@ -3795,7 +3790,7 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with invalid metadata", token: validToken, - thingID: validID, + clientID: validID, query: "metadata=invalid", status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, @@ -3804,16 +3799,16 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with duplicate metadata", token: validToken, - thingID: validID, + clientID: validID, query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, err: apiutil.ErrInvalidQueryParams, }, { - desc: "list users with permissions", - token: validToken, - thingID: validID, + desc: "list users with permissions", + token: validToken, + clientID: validID, listUsersResponse: users.UsersPage{ Page: users.Page{ Total: 1, @@ -3833,10 +3828,10 @@ func TestListUsersByThingID(t *testing.T) { err: apiutil.ErrInvalidQueryParams, }, { - desc: "list users with email", - token: validToken, - thingID: validID, - query: fmt.Sprintf("email=%s", user.Email), + desc: "list users with email", + token: validToken, + clientID: validID, + query: fmt.Sprintf("email=%s", user.Email), listUsersResponse: users.UsersPage{ Page: users.Page{ Total: 1, @@ -3852,7 +3847,7 @@ func TestListUsersByThingID(t *testing.T) { { desc: "list users with invalid email", token: validToken, - thingID: validID, + clientID: validID, query: "email=invalid", status: http.StatusBadRequest, authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, @@ -3872,7 +3867,7 @@ func TestListUsersByThingID(t *testing.T) { req := testRequest{ user: us.Client(), method: http.MethodGet, - url: fmt.Sprintf("%s/%s/things/%s/users?", us.URL, validID, validID) + tc.query, + url: fmt.Sprintf("%s/%s/clients/%s/users?", us.URL, validID, validID) + tc.query, token: tc.token, } @@ -3892,449 +3887,6 @@ func TestListUsersByThingID(t *testing.T) { } } -func TestAssignUsers(t *testing.T) { - us, _, gsvc, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - domainID string - token string - groupID string - reqBody interface{} - authnRes mgauthn.Session - authnErr error - status int - err error - }{ - { - desc: "assign users to a group successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusCreated, - err: nil, - }, - { - desc: "assign users to a group with invalid token", - domainID: domainID, - token: inValidToken, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "assign users to a group with empty token", - domainID: domainID, - token: "", - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "assign users to a group with empty relation", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "assign users to a group with empty user ids", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{}, - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "assign users to a group with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: map[string]interface{}{ - "relation": make(chan int), - }, - status: http.StatusBadRequest, - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/groups/%s/users/assign", us.URL, tc.domainID, tc.groupID), - token: tc.token, - body: strings.NewReader(data), - } - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Assign", mock.Anything, tc.authnRes, tc.groupID, mock.Anything, "users", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) - } -} - -func TestUnassignUsers(t *testing.T) { - us, _, gsvc, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - domainID string - token string - groupID string - reqBody interface{} - authnRes mgauthn.Session - authnErr error - status int - err error - }{ - { - desc: "unassign users from a group successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusNoContent, - err: nil, - }, - { - desc: "unassign users from a group with invalid token", - domainID: domainID, - token: inValidToken, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "unassign users from a group with empty token", - domainID: domainID, - token: "", - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "unassign users from a group with empty relation", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "", - UserIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "unassign users from a group with empty user ids", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - Relation: "member", - UserIDs: []string{}, - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "unassign users from a group with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: map[string]interface{}{ - "relation": make(chan int), - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/groups/%s/users/unassign", us.URL, tc.domainID, tc.groupID), - token: tc.token, - body: strings.NewReader(data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Unassign", mock.Anything, tc.authnRes, tc.groupID, mock.Anything, "users", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) - } -} - -func TestAssignGroups(t *testing.T) { - us, _, gsvc, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - domainID string - token string - groupID string - reqBody interface{} - authnRes mgauthn.Session - authnErr error - status int - err error - }{ - { - desc: "assign groups to a parent group successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusCreated, - err: nil, - }, - { - desc: "assign groups to a parent group with invalid token", - domainID: domainID, - token: inValidToken, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "assign groups to a parent group with empty token", - domainID: domainID, - token: "", - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "assign groups to a parent group with empty parent group id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: "", - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "assign groups to a parent group with empty group ids", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{}, - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "assign groups to a parent group with invalid request body", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: map[string]interface{}{ - "group_ids": make(chan int), - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/groups/%s/groups/assign", us.URL, tc.domainID, tc.groupID), - token: tc.token, - body: strings.NewReader(data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Assign", mock.Anything, tc.authnRes, tc.groupID, mock.Anything, "groups", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) - } -} - -func TestUnassignGroups(t *testing.T) { - us, _, gsvc, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - domainID string - groupID string - reqBody interface{} - authnRes mgauthn.Session - authnErr error - status int - err error - }{ - { - desc: "unassign groups from a parent group successfully", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusNoContent, - err: nil, - }, - { - desc: "unassign groups from a parent group with invalid token", - domainID: domainID, - token: inValidToken, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "unassign groups from a parent group with empty token", - domainID: domainID, - token: "", - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "unassign groups from a parent group with empty group id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - groupID: "", - reqBody: groupReqBody{ - GroupIDs: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "unassign groups from a parent group with empty group ids", - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - groupID: validID, - reqBody: groupReqBody{ - GroupIDs: []string{}, - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "unassign groups from a parent group with invalid request body", - token: validToken, - groupID: validID, - reqBody: map[string]interface{}{ - "group_ids": make(chan int), - }, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.reqBody) - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/groups/%s/groups/unassign", us.URL, tc.domainID, tc.groupID), - token: tc.token, - body: strings.NewReader(data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := gsvc.On("Unassign", mock.Anything, mock.Anything, tc.groupID, mock.Anything, "groups", mock.Anything).Return(tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - authnCall.Unset() - }) - } -} - type respBody struct { Err string `json:"error"` Message string `json:"message"` diff --git a/users/api/endpoints.go b/users/api/endpoints.go index dcb8986f4f..eb4f79870f 100644 --- a/users/api/endpoints.go +++ b/users/api/endpoints.go @@ -27,7 +27,7 @@ func registrationEndpoint(svc users.Service, selfRegister bool) endpoint.Endpoin if !selfRegister { session, ok = ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } } @@ -52,7 +52,7 @@ func viewEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } user, err := svc.View(ctx, session, req.id) if err != nil { @@ -67,7 +67,7 @@ func viewProfileEndpoint(svc users.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } client, err := svc.ViewProfile(ctx, session) if err != nil { @@ -87,7 +87,7 @@ func listUsersEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } pm := users.Page{ @@ -174,7 +174,7 @@ func listMembersByGroupEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } page, err := svc.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) @@ -197,7 +197,7 @@ func listMembersByChannelEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } page, err := svc.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) @@ -209,17 +209,17 @@ func listMembersByChannelEndpoint(svc users.Service) endpoint.Endpoint { } } -func listMembersByThingEndpoint(svc users.Service) endpoint.Endpoint { +func listMembersByClientEndpoint(svc users.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(listMembersByObjectReq) - req.objectKind = "things" + req.objectKind = "clients" if err := req.validate(); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } page, err := svc.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) @@ -241,7 +241,7 @@ func listMembersByDomainEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } page, err := svc.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) @@ -262,7 +262,7 @@ func updateEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } user := users.User{ @@ -290,7 +290,7 @@ func updateTagsEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } user := users.User{ @@ -316,7 +316,7 @@ func updateEmailEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } user, err := svc.UpdateEmail(ctx, session, req.id, req.Email) @@ -365,7 +365,7 @@ func passwordResetEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } if err := svc.ResetSecret(ctx, session, req.Password); err != nil { return nil, err @@ -384,7 +384,7 @@ func updateSecretEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } user, err := svc.UpdateSecret(ctx, session, req.OldSecret, req.NewSecret) if err != nil { @@ -456,7 +456,7 @@ func updateRoleEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } user, err := svc.UpdateRole(ctx, session, user) @@ -497,7 +497,7 @@ func refreshTokenEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } token, err := svc.RefreshToken(ctx, session, req.RefreshToken) @@ -522,7 +522,7 @@ func enableEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } user, err := svc.Enable(ctx, session, req.id) @@ -543,7 +543,7 @@ func disableEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } user, err := svc.Disable(ctx, session, req.id) @@ -564,7 +564,7 @@ func deleteEndpoint(svc users.Service) endpoint.Endpoint { session, ok := ctx.Value(api.SessionKey).(authn.Session) if !ok { - return nil, svcerr.ErrAuthorization + return nil, svcerr.ErrAuthentication } if err := svc.Delete(ctx, session, req.id); err != nil { diff --git a/users/api/groups.go b/users/api/groups.go deleted file mode 100644 index 72cb478c6f..0000000000 --- a/users/api/groups.go +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "encoding/json" - "log/slog" - "net/http" - - "github.com/absmach/magistrala/internal/api" - gapi "github.com/absmach/magistrala/internal/groups/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/authn" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/pkg/policies" - "github.com/go-chi/chi/v5" - "github.com/go-kit/kit/endpoint" - kithttp "github.com/go-kit/kit/transport/http" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -// MakeHandler returns a HTTP handler for Groups API endpoints. -func groupsHandler(svc groups.Service, authn mgauthn.Authentication, r *chi.Mux, logger *slog.Logger) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, true)) - - r.Route("/{domainID}/groups", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - gapi.CreateGroupEndpoint(svc, policies.NewGroupKind), - gapi.DecodeGroupCreate, - api.EncodeResponse, - opts..., - ), "create_group").ServeHTTP) - - r.Get("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( - gapi.ViewGroupEndpoint(svc), - gapi.DecodeGroupRequest, - api.EncodeResponse, - opts..., - ), "view_group").ServeHTTP) - - r.Delete("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( - gapi.DeleteGroupEndpoint(svc), - gapi.DecodeGroupRequest, - api.EncodeResponse, - opts..., - ), "delete_group").ServeHTTP) - - r.Get("/{groupID}/permissions", otelhttp.NewHandler(kithttp.NewServer( - gapi.ViewGroupPermsEndpoint(svc), - gapi.DecodeGroupPermsRequest, - api.EncodeResponse, - opts..., - ), "view_group_permissions").ServeHTTP) - - r.Put("/{groupID}", otelhttp.NewHandler(kithttp.NewServer( - gapi.UpdateGroupEndpoint(svc), - gapi.DecodeGroupUpdate, - api.EncodeResponse, - opts..., - ), "update_group").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "groups", "users"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_groups").ServeHTTP) - - r.Get("/{groupID}/children", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "groups", "users"), - gapi.DecodeListChildrenRequest, - api.EncodeResponse, - opts..., - ), "list_children").ServeHTTP) - - r.Get("/{groupID}/parents", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "groups", "users"), - gapi.DecodeListParentsRequest, - api.EncodeResponse, - opts..., - ), "list_parents").ServeHTTP) - - r.Post("/{groupID}/enable", otelhttp.NewHandler(kithttp.NewServer( - gapi.EnableGroupEndpoint(svc), - gapi.DecodeChangeGroupStatus, - api.EncodeResponse, - opts..., - ), "enable_group").ServeHTTP) - - r.Post("/{groupID}/disable", otelhttp.NewHandler(kithttp.NewServer( - gapi.DisableGroupEndpoint(svc), - gapi.DecodeChangeGroupStatus, - api.EncodeResponse, - opts..., - ), "disable_group").ServeHTTP) - - r.Post("/{groupID}/users/assign", otelhttp.NewHandler(kithttp.NewServer( - assignUsersEndpoint(svc), - decodeAssignUsersRequest, - api.EncodeResponse, - opts..., - ), "assign_users").ServeHTTP) - - r.Post("/{groupID}/users/unassign", otelhttp.NewHandler(kithttp.NewServer( - unassignUsersEndpoint(svc), - decodeUnassignUsersRequest, - api.EncodeResponse, - opts..., - ), "unassign_users").ServeHTTP) - - r.Post("/{groupID}/groups/assign", otelhttp.NewHandler(kithttp.NewServer( - assignGroupsEndpoint(svc), - decodeAssignGroupsRequest, - api.EncodeResponse, - opts..., - ), "assign_groups").ServeHTTP) - - r.Post("/{groupID}/groups/unassign", otelhttp.NewHandler(kithttp.NewServer( - unassignGroupsEndpoint(svc), - decodeUnassignGroupsRequest, - api.EncodeResponse, - opts..., - ), "unassign_groups").ServeHTTP) - }) - - // The ideal placeholder name should be {channelID}, but gapi.DecodeListGroupsRequest uses {memberID} as a placeholder for the ID. - // So here, we are using {memberID} as the placeholder. - r.Get("/{domainID}/channels/{memberID}/groups", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "groups", "channels"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_groups_by_channel_id").ServeHTTP) - - r.Get("/{domainID}/users/{memberID}/groups", otelhttp.NewHandler(kithttp.NewServer( - gapi.ListGroupsEndpoint(svc, "groups", "users"), - gapi.DecodeListGroupsRequest, - api.EncodeResponse, - opts..., - ), "list_groups_by_user_id").ServeHTTP) - }) - - return r -} - -func decodeAssignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := assignUsersReq{ - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - return req, nil -} - -func decodeUnassignUsersRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := unassignUsersReq{ - groupID: chi.URLParam(r, "groupID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - return req, nil -} - -func assignUsersEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignUsersReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - if err := svc.Assign(ctx, session, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { - return nil, err - } - return assignUsersRes{}, nil - } -} - -func unassignUsersEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignUsersReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Unassign(ctx, session, req.groupID, req.Relation, "users", req.UserIDs...); err != nil { - return nil, err - } - return unassignUsersRes{}, nil - } -} - -func decodeAssignGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := assignGroupsReq{ - groupID: chi.URLParam(r, "groupID"), - domainID: chi.URLParam(r, "domainID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - return req, nil -} - -func decodeUnassignGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := unassignGroupsReq{ - groupID: chi.URLParam(r, "groupID"), - domainID: chi.URLParam(r, "domainID"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - return req, nil -} - -func assignGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignGroupsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - if err := svc.Assign(ctx, session, req.groupID, policies.ParentGroupRelation, policies.GroupsKind, req.GroupIDs...); err != nil { - return nil, err - } - return assignUsersRes{}, nil - } -} - -func unassignGroupsEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignGroupsReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Unassign(ctx, session, req.groupID, policies.ParentGroupRelation, policies.GroupsKind, req.GroupIDs...); err != nil { - return nil, err - } - return unassignUsersRes{}, nil - } -} diff --git a/users/api/transport.go b/users/api/transport.go index e3334b2ab8..5918f3791f 100644 --- a/users/api/transport.go +++ b/users/api/transport.go @@ -9,8 +9,8 @@ import ( "regexp" "github.com/absmach/magistrala" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "github.com/go-chi/chi/v5" @@ -18,9 +18,8 @@ import ( ) // MakeHandler returns a HTTP handler for Users and Groups API endpoints. -func MakeHandler(cls users.Service, authn mgauthn.Authentication, tokenClient magistrala.TokenServiceClient, selfRegister bool, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string, pr *regexp.Regexp, providers ...oauth2.Provider) http.Handler { - usersHandler(cls, authn, tokenClient, selfRegister, mux, logger, pr, providers...) - groupsHandler(grps, authn, mux, logger) +func MakeHandler(cls users.Service, authn mgauthn.Authentication, tokensvc grpcTokenV1.TokenServiceClient, selfRegister bool, mux *chi.Mux, logger *slog.Logger, instanceID string, pr *regexp.Regexp, providers ...oauth2.Provider) http.Handler { + mux = usersHandler(cls, authn, tokensvc, selfRegister, mux, logger, pr, providers...) mux.Get("/health", magistrala.Health("users", instanceID)) mux.Handle("/metrics", promhttp.Handler()) diff --git a/users/api/users.go b/users/api/users.go index c712034d70..66b747676a 100644 --- a/users/api/users.go +++ b/users/api/users.go @@ -11,9 +11,9 @@ import ( "regexp" "strings" - "github.com/absmach/magistrala" mgauth "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/internal/api" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/apiutil" mgauthn "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" @@ -28,7 +28,7 @@ import ( var passRegex = regexp.MustCompile("^.{8,}$") // usersHandler returns a HTTP handler for API endpoints. -func usersHandler(svc users.Service, authn mgauthn.Authentication, tokenClient magistrala.TokenServiceClient, selfRegister bool, r *chi.Mux, logger *slog.Logger, pr *regexp.Regexp, providers ...oauth2.Provider) http.Handler { +func usersHandler(svc users.Service, authn mgauthn.Authentication, tokenClient grpcTokenV1.TokenServiceClient, selfRegister bool, r *chi.Mux, logger *slog.Logger, pr *regexp.Regexp, providers ...oauth2.Provider) *chi.Mux { passRegex = pr opts := []kithttp.ServerOption{ @@ -188,7 +188,7 @@ func usersHandler(svc users.Service, authn mgauthn.Authentication, tokenClient m opts..., ), "list_users_by_user_group_id").ServeHTTP) - // Ideal location: things service, channels endpoint. + // Ideal location: clients service, channels endpoint. // Reason for placing here : // SpiceDB provides list of user ids in given channel_id // and users service can access spiceDB and get the user list with channel_id. @@ -200,12 +200,12 @@ func usersHandler(svc users.Service, authn mgauthn.Authentication, tokenClient m opts..., ), "list_users_by_channel_id").ServeHTTP) - r.Get("/{domainID}/things/{thingID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByThingEndpoint(svc), - decodeListMembersByThing, + r.Get("/{domainID}/clients/{clientID}/users", otelhttp.NewHandler(kithttp.NewServer( + listMembersByClientEndpoint(svc), + decodeListMembersByClient, api.EncodeResponse, opts..., - ), "list_users_by_thing_id").ServeHTTP) + ), "list_users_by_client_id").ServeHTTP) r.Get("/{domainID}/users", otelhttp.NewHandler(kithttp.NewServer( listMembersByDomainEndpoint(svc), @@ -576,14 +576,14 @@ func decodeListMembersByChannel(_ context.Context, r *http.Request) (interface{} return req, nil } -func decodeListMembersByThing(_ context.Context, r *http.Request) (interface{}, error) { +func decodeListMembersByClient(_ context.Context, r *http.Request) (interface{}, error) { page, err := queryPageParams(r, api.DefPermission) if err != nil { return nil, err } req := listMembersByObjectReq{ Page: page, - objectID: chi.URLParam(r, "thingID"), + objectID: chi.URLParam(r, "clientID"), } return req, nil @@ -668,7 +668,7 @@ func queryPageParams(r *http.Request, defPermission string) (users.Page, error) } // oauth2CallbackHandler is a http.HandlerFunc that handles OAuth2 callbacks. -func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service, tokenClient magistrala.TokenServiceClient) http.HandlerFunc { +func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service, tokenClient grpcTokenV1.TokenServiceClient) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !oauth.IsEnabled() { http.Redirect(w, r, oauth.ErrorURL()+"?error=oauth%20provider%20is%20disabled", http.StatusSeeOther) @@ -703,7 +703,7 @@ func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service, tokenClient return } - jwt, err := tokenClient.Issue(r.Context(), &magistrala.IssueReq{ + jwt, err := tokenClient.Issue(r.Context(), &grpcTokenV1.IssueReq{ UserId: user.ID, Type: uint32(mgauth.AccessKey), }) diff --git a/users/delete_handler.go b/users/delete_handler.go index cbe623b684..b98890627f 100644 --- a/users/delete_handler.go +++ b/users/delete_handler.go @@ -14,7 +14,7 @@ import ( "log/slog" "time" - "github.com/absmach/magistrala" + grpcDomainsV1 "github.com/absmach/magistrala/internal/grpc/domains/v1" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/policies" ) @@ -23,14 +23,14 @@ const defLimit = uint64(100) type handler struct { users Repository - domains magistrala.DomainsServiceClient + domains grpcDomainsV1.DomainsServiceClient policies policies.Service checkInterval time.Duration deleteAfter time.Duration logger *slog.Logger } -func NewDeleteHandler(ctx context.Context, users Repository, policyService policies.Service, domainsClient magistrala.DomainsServiceClient, defCheckInterval, deleteAfter time.Duration, logger *slog.Logger) { +func NewDeleteHandler(ctx context.Context, users Repository, policyService policies.Service, domainsClient grpcDomainsV1.DomainsServiceClient, defCheckInterval, deleteAfter time.Duration, logger *slog.Logger) { handler := &handler{ users: users, domains: domainsClient, @@ -73,7 +73,7 @@ func (h *handler) handle(ctx context.Context) { continue } - deletedRes, err := h.domains.DeleteUserFromDomains(ctx, &magistrala.DeleteUserReq{ + deletedRes, err := h.domains.DeleteUserFromDomains(ctx, &grpcDomainsV1.DeleteUserReq{ Id: u.ID, }) if err != nil { diff --git a/users/events/streams.go b/users/events/streams.go index 0820a0e279..8ade66706b 100644 --- a/users/events/streams.go +++ b/users/events/streams.go @@ -6,7 +6,7 @@ package events import ( "context" - "github.com/absmach/magistrala" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/store" @@ -290,7 +290,7 @@ func (es *eventStore) GenerateResetToken(ctx context.Context, email, host string return es.Publish(ctx, event) } -func (es *eventStore) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) { +func (es *eventStore) IssueToken(ctx context.Context, username, secret string) (*grpcTokenV1.Token, error) { token, err := es.svc.IssueToken(ctx, username, secret) if err != nil { return token, err @@ -307,7 +307,7 @@ func (es *eventStore) IssueToken(ctx context.Context, username, secret string) ( return token, nil } -func (es *eventStore) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) { +func (es *eventStore) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*grpcTokenV1.Token, error) { token, err := es.svc.RefreshToken(ctx, session, refreshToken) if err != nil { return token, err diff --git a/users/middleware/authorization.go b/users/middleware/authorization.go index 53c552ff4d..60b7775878 100644 --- a/users/middleware/authorization.go +++ b/users/middleware/authorization.go @@ -6,8 +6,8 @@ package middleware import ( "context" - "github.com/absmach/magistrala" mgauth "github.com/absmach/magistrala/auth" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/authz" mgauthz "github.com/absmach/magistrala/pkg/authz" @@ -76,8 +76,8 @@ func (am *authorizationMiddleware) ListMembers(ctx context.Context, session auth if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, mgauth.SwitchToPermission(pm.Permission), policies.DomainType, objectID); err != nil { return users.MembersPage{}, err } - case policies.ThingsKind: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, mgauth.SwitchToPermission(pm.Permission), policies.ThingType, objectID); err != nil { + case policies.ClientsKind: + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.UserID, mgauth.SwitchToPermission(pm.Permission), policies.ClientType, objectID); err != nil { return users.MembersPage{}, err } default: @@ -147,9 +147,10 @@ func (am *authorizationMiddleware) SendPasswordReset(ctx context.Context, host, } func (am *authorizationMiddleware) UpdateRole(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true + if err := am.checkSuperAdmin(ctx, session.UserID); err != nil { + return users.User{}, err } + session.SuperAdmin = true if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, user.ID, policies.MembershipPermission, policies.PlatformType, policies.MagistralaObject); err != nil { return users.User{}, err } @@ -185,11 +186,11 @@ func (am *authorizationMiddleware) Identify(ctx context.Context, session authn.S return am.svc.Identify(ctx, session) } -func (am *authorizationMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) { +func (am *authorizationMiddleware) IssueToken(ctx context.Context, username, secret string) (*grpcTokenV1.Token, error) { return am.svc.IssueToken(ctx, username, secret) } -func (am *authorizationMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) { +func (am *authorizationMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*grpcTokenV1.Token, error) { return am.svc.RefreshToken(ctx, session, refreshToken) } diff --git a/users/middleware/logging.go b/users/middleware/logging.go index d261b722a1..d89b592884 100644 --- a/users/middleware/logging.go +++ b/users/middleware/logging.go @@ -8,7 +8,7 @@ import ( "log/slog" "time" - "github.com/absmach/magistrala" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/users" ) @@ -50,7 +50,7 @@ func (lm *loggingMiddleware) Register(ctx context.Context, session authn.Session // IssueToken logs the issue_token request. It logs the username type and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) IssueToken(ctx context.Context, username, secret string) (t *magistrala.Token, err error) { +func (lm *loggingMiddleware) IssueToken(ctx context.Context, username, secret string) (t *grpcTokenV1.Token, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), @@ -70,7 +70,7 @@ func (lm *loggingMiddleware) IssueToken(ctx context.Context, username, secret st // RefreshToken logs the refresh_token request. It logs the refreshtoken, token type and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (t *magistrala.Token, err error) { +func (lm *loggingMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (t *grpcTokenV1.Token, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), diff --git a/users/middleware/metrics.go b/users/middleware/metrics.go index ab6321ac97..666c7393cd 100644 --- a/users/middleware/metrics.go +++ b/users/middleware/metrics.go @@ -7,7 +7,7 @@ import ( "context" "time" - "github.com/absmach/magistrala" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/users" "github.com/go-kit/kit/metrics" @@ -40,7 +40,7 @@ func (ms *metricsMiddleware) Register(ctx context.Context, session authn.Session } // IssueToken instruments IssueToken method with metrics. -func (ms *metricsMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) { +func (ms *metricsMiddleware) IssueToken(ctx context.Context, username, secret string) (*grpcTokenV1.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()) @@ -49,7 +49,7 @@ func (ms *metricsMiddleware) IssueToken(ctx context.Context, username, secret st } // RefreshToken instruments RefreshToken method with metrics. -func (ms *metricsMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (token *magistrala.Token, err error) { +func (ms *metricsMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (token *grpcTokenV1.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()) diff --git a/users/mocks/service.go b/users/mocks/service.go index 83dfe9e688..36541f4887 100644 --- a/users/mocks/service.go +++ b/users/mocks/service.go @@ -9,11 +9,11 @@ import ( authn "github.com/absmach/magistrala/pkg/authn" - magistrala "github.com/absmach/magistrala" - mock "github.com/stretchr/testify/mock" users "github.com/absmach/magistrala/users" + + v1 "github.com/absmach/magistrala/internal/grpc/token/v1" ) // Service is an autogenerated mock type for the Service type @@ -142,23 +142,23 @@ func (_m *Service) Identify(ctx context.Context, session authn.Session) (string, } // IssueToken provides a mock function with given fields: ctx, identity, secret -func (_m *Service) IssueToken(ctx context.Context, identity string, secret string) (*magistrala.Token, error) { +func (_m *Service) IssueToken(ctx context.Context, identity string, secret string) (*v1.Token, error) { ret := _m.Called(ctx, identity, secret) if len(ret) == 0 { panic("no return value specified for IssueToken") } - var r0 *magistrala.Token + var r0 *v1.Token var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (*magistrala.Token, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*v1.Token, error)); ok { return rf(ctx, identity, secret) } - if rf, ok := ret.Get(0).(func(context.Context, string, string) *magistrala.Token); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, string) *v1.Token); ok { r0 = rf(ctx, identity, secret) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.Token) + r0 = ret.Get(0).(*v1.Token) } } @@ -274,23 +274,23 @@ func (_m *Service) OAuthCallback(ctx context.Context, user users.User) (users.Us } // RefreshToken provides a mock function with given fields: ctx, session, refreshToken -func (_m *Service) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) { +func (_m *Service) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*v1.Token, error) { ret := _m.Called(ctx, session, refreshToken) if len(ret) == 0 { panic("no return value specified for RefreshToken") } - var r0 *magistrala.Token + var r0 *v1.Token var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (*magistrala.Token, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (*v1.Token, error)); ok { return rf(ctx, session, refreshToken) } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) *magistrala.Token); ok { + if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) *v1.Token); ok { r0 = rf(ctx, session, refreshToken) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.Token) + r0 = ret.Get(0).(*v1.Token) } } diff --git a/users/postgres/users.go b/users/postgres/users.go index f2daae364c..a510a3f2eb 100644 --- a/users/postgres/users.go +++ b/users/postgres/users.go @@ -11,10 +11,10 @@ import ( "strings" "time" + "github.com/absmach/magistrala/groups" "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/postgres" "github.com/absmach/magistrala/users" "github.com/jackc/pgtype" diff --git a/users/service.go b/users/service.go index f6318f8728..0a74095739 100644 --- a/users/service.go +++ b/users/service.go @@ -10,6 +10,8 @@ import ( "github.com/absmach/magistrala" mgauth "github.com/absmach/magistrala/auth" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" + "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" @@ -26,7 +28,7 @@ var ( ) type service struct { - token magistrala.TokenServiceClient + token grpcTokenV1.TokenServiceClient users Repository idProvider magistrala.IDProvider policies policies.Service @@ -35,7 +37,7 @@ type service struct { } // NewService returns a new Users service implementation. -func NewService(token magistrala.TokenServiceClient, urepo Repository, policyService policies.Service, emailer Emailer, hasher Hasher, idp magistrala.IDProvider) Service { +func NewService(token grpcTokenV1.TokenServiceClient, urepo Repository, policyService policies.Service, emailer Emailer, hasher Hasher, idp magistrala.IDProvider) Service { return service{ token: token, users: urepo, @@ -81,7 +83,7 @@ func (svc service) Register(ctx context.Context, session authn.Session, u User, defer func() { if err != nil { if errRollback := svc.addUserPolicyRollback(ctx, u.ID, u.Role); errRollback != nil { - err = errors.Wrap(errors.Wrap(errors.ErrRollbackTx, errRollback), err) + err = errors.Wrap(errors.Wrap(apiutil.ErrRollbackTx, errRollback), err) } } }() @@ -92,7 +94,7 @@ func (svc service) Register(ctx context.Context, session authn.Session, u User, return user, nil } -func (svc service) IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error) { +func (svc service) IssueToken(ctx context.Context, identity, secret string) (*grpcTokenV1.Token, error) { var dbUser User var err error @@ -103,31 +105,31 @@ func (svc service) IssueToken(ctx context.Context, identity, secret string) (*ma } if err != nil { - return &magistrala.Token{}, errors.Wrap(svcerr.ErrAuthentication, err) + return &grpcTokenV1.Token{}, errors.Wrap(svcerr.ErrAuthentication, err) } if err := svc.hasher.Compare(secret, dbUser.Credentials.Secret); err != nil { - return &magistrala.Token{}, errors.Wrap(svcerr.ErrLogin, err) + return &grpcTokenV1.Token{}, errors.Wrap(svcerr.ErrLogin, err) } - token, err := svc.token.Issue(ctx, &magistrala.IssueReq{UserId: dbUser.ID, Type: uint32(mgauth.AccessKey)}) + token, err := svc.token.Issue(ctx, &grpcTokenV1.IssueReq{UserId: dbUser.ID, Type: uint32(mgauth.AccessKey)}) if err != nil { - return &magistrala.Token{}, errors.Wrap(errIssueToken, err) + return &grpcTokenV1.Token{}, errors.Wrap(errIssueToken, err) } return token, nil } -func (svc service) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) { +func (svc service) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*grpcTokenV1.Token, error) { dbUser, err := svc.users.RetrieveByID(ctx, session.UserID) if err != nil { - return &magistrala.Token{}, errors.Wrap(svcerr.ErrAuthentication, err) + return &grpcTokenV1.Token{}, errors.Wrap(svcerr.ErrAuthentication, err) } if dbUser.Status == DisabledStatus { - return &magistrala.Token{}, errors.Wrap(svcerr.ErrAuthentication, errLoginDisableUser) + return &grpcTokenV1.Token{}, errors.Wrap(svcerr.ErrAuthentication, errLoginDisableUser) } - return svc.token.Refresh(ctx, &magistrala.RefreshReq{RefreshToken: refreshToken}) + return svc.token.Refresh(ctx, &grpcTokenV1.RefreshReq{RefreshToken: refreshToken}) } func (svc service) View(ctx context.Context, session authn.Session, id string) (User, error) { @@ -289,7 +291,7 @@ func (svc service) GenerateResetToken(ctx context.Context, email, host string) e if err != nil { return errors.Wrap(svcerr.ErrViewEntity, err) } - issueReq := &magistrala.IssueReq{ + issueReq := &grpcTokenV1.IssueReq{ UserId: user.ID, Type: uint32(mgauth.RecoveryKey), } @@ -411,7 +413,7 @@ func (svc service) Enable(ctx context.Context, session authn.Session, id string) } user, err := svc.changeUserStatus(ctx, session, u) if err != nil { - return User{}, errors.Wrap(ErrEnableClient, err) + return User{}, errors.Wrap(svcerr.ErrEnableUser, err) } return user, nil @@ -425,7 +427,7 @@ func (svc service) Disable(ctx context.Context, session authn.Session, id string } user, err := svc.changeUserStatus(ctx, session, user) if err != nil { - return User{}, err + return User{}, errors.Wrap(svcerr.ErrDisableUser, err) } return user, nil @@ -470,8 +472,8 @@ func (svc service) Delete(ctx context.Context, session authn.Session, id string) func (svc service) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm Page) (MembersPage, error) { var objectType string switch objectKind { - case policies.ThingsKind: - objectType = policies.ThingType + case policies.ClientsKind: + objectType = policies.ClientType case policies.DomainsKind: objectType = policies.DomainType case policies.GroupsKind: diff --git a/users/service_test.go b/users/service_test.go index 8c891afc0f..2b45acf395 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -9,9 +9,9 @@ import ( "strings" "testing" - "github.com/absmach/magistrala" mgauth "github.com/absmach/magistrala/auth" authmocks "github.com/absmach/magistrala/auth/mocks" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" @@ -779,7 +779,7 @@ func TestUpdateSecret(t *testing.T) { retrieveByIDResponse users.User retrieveByEmailResponse users.User updateSecretResponse users.User - issueResponse *magistrala.Token + issueResponse *grpcTokenV1.Token response users.User retrieveByIDErr error retrieveByEmailErr error @@ -795,7 +795,7 @@ func TestUpdateSecret(t *testing.T) { retrieveByEmailResponse: rUser, retrieveByIDResponse: user, updateSecretResponse: responseUser, - issueResponse: &magistrala.Token{AccessToken: validToken}, + issueResponse: &grpcTokenV1.Token{AccessToken: validToken}, response: responseUser, err: nil, }, @@ -1355,9 +1355,9 @@ func TestListMembers(t *testing.T) { err error }{ { - desc: "list members with no policies successfully of the things kind", + desc: "list members with no policies successfully of the clients kind", groupID: validID, - objectKind: policysvc.ThingsKind, + objectKind: policysvc.ClientsKind, objectID: validID, page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, listAllSubjectsResponse: policysvc.PolicyPage{}, @@ -1365,7 +1365,7 @@ func TestListMembers(t *testing.T) { SubjectType: policysvc.UserType, Permission: "read", Object: validID, - ObjectType: policysvc.ThingType, + ObjectType: policysvc.ClientType, }, response: users.MembersPage{ Page: users.Page{ @@ -1377,16 +1377,16 @@ func TestListMembers(t *testing.T) { err: nil, }, { - desc: "list members with policies successsfully of the things kind", + desc: "list members with policies successsfully of the clients kind", groupID: validID, - objectKind: policysvc.ThingsKind, + objectKind: policysvc.ClientsKind, objectID: validID, page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, listAllSubjectsReq: policysvc.Policy{ SubjectType: policysvc.UserType, Permission: "read", Object: validID, - ObjectType: policysvc.ThingType, + ObjectType: policysvc.ClientType, }, listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, retrieveAllResponse: users.UsersPage{ @@ -1408,16 +1408,16 @@ func TestListMembers(t *testing.T) { err: nil, }, { - desc: "list members with policies successsfully of the things kind with permissions", + desc: "list members with policies successsfully of the clients kind with permissions", groupID: validID, - objectKind: policysvc.ThingsKind, + objectKind: policysvc.ClientsKind, objectID: validID, page: users.Page{Offset: 0, Limit: 100, Permission: "read", ListPerms: true}, listAllSubjectsReq: policysvc.Policy{ SubjectType: policysvc.UserType, Permission: "read", Object: validID, - ObjectType: policysvc.ThingType, + ObjectType: policysvc.ClientType, }, listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, retrieveAllResponse: users.UsersPage{ @@ -1440,16 +1440,16 @@ func TestListMembers(t *testing.T) { err: nil, }, { - desc: "list members with policies of the things kind with permissionswith failed list permissions", + desc: "list members with policies of the clients kind with permissionswith failed list permissions", groupID: validID, - objectKind: policysvc.ThingsKind, + objectKind: policysvc.ClientsKind, objectID: validID, page: users.Page{Offset: 0, Limit: 100, Permission: "read", ListPerms: true}, listAllSubjectsReq: policysvc.Policy{ SubjectType: policysvc.UserType, Permission: "read", Object: validID, - ObjectType: policysvc.ThingType, + ObjectType: policysvc.ClientType, }, listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, retrieveAllResponse: users.UsersPage{ @@ -1466,32 +1466,32 @@ func TestListMembers(t *testing.T) { err: svcerr.ErrNotFound, }, { - desc: "list members with of the things kind with failed to list all subjects", + desc: "list members with of the clients kind with failed to list all subjects", groupID: validID, - objectKind: policysvc.ThingsKind, + objectKind: policysvc.ClientsKind, objectID: validID, page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, listAllSubjectsReq: policysvc.Policy{ SubjectType: policysvc.UserType, Permission: "read", Object: validID, - ObjectType: policysvc.ThingType, + ObjectType: policysvc.ClientType, }, listAllSubjectsErr: repoerr.ErrNotFound, listAllSubjectsResponse: policysvc.PolicyPage{}, err: repoerr.ErrNotFound, }, { - desc: "list members with of the things kind with failed to retrieve all", + desc: "list members with of the clients kind with failed to retrieve all", groupID: validID, - objectKind: policysvc.ThingsKind, + objectKind: policysvc.ClientsKind, objectID: validID, page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, listAllSubjectsReq: policysvc.Policy{ SubjectType: policysvc.UserType, Permission: "read", Object: validID, - ObjectType: policysvc.ThingType, + ObjectType: policysvc.ClientType, }, listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, retrieveAllResponse: users.UsersPage{}, @@ -1635,7 +1635,7 @@ func TestIssueToken(t *testing.T) { desc string user users.User retrieveByUsernameResponse users.User - issueResponse *magistrala.Token + issueResponse *grpcTokenV1.Token retrieveByUsernameErr error issueErr error err error @@ -1644,14 +1644,14 @@ func TestIssueToken(t *testing.T) { desc: "issue token for an existing user", user: user, retrieveByUsernameResponse: rUser, - issueResponse: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, + issueResponse: &grpcTokenV1.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, err: nil, }, { desc: "issue token for non-empty domain id", user: user, retrieveByUsernameResponse: rUser, - issueResponse: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, + issueResponse: &grpcTokenV1.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, err: nil, }, { @@ -1671,7 +1671,7 @@ func TestIssueToken(t *testing.T) { desc: "issue token with empty domain id", user: user, retrieveByUsernameResponse: rUser, - issueResponse: &magistrala.Token{}, + issueResponse: &grpcTokenV1.Token{}, issueErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, @@ -1679,29 +1679,27 @@ func TestIssueToken(t *testing.T) { desc: "issue token with grpc error", user: user, retrieveByUsernameResponse: rUser, - issueResponse: &magistrala.Token{}, + issueResponse: &grpcTokenV1.Token{}, issueErr: svcerr.ErrAuthentication, err: svcerr.ErrAuthentication, }, } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := cRepo.On("RetrieveByUsername", context.Background(), tc.user.Credentials.Username).Return(tc.retrieveByUsernameResponse, tc.retrieveByUsernameErr) - authCall := auth.On("Issue", context.Background(), &magistrala.IssueReq{UserId: tc.user.ID, Type: uint32(mgauth.AccessKey)}).Return(tc.issueResponse, tc.issueErr) - token, err := svc.IssueToken(context.Background(), tc.user.Credentials.Username, tc.user.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.GetAccessToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetAccessToken())) - assert.NotEmpty(t, token.GetRefreshToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetRefreshToken())) - ok := repoCall.Parent.AssertCalled(t, "RetrieveByUsername", context.Background(), tc.user.Credentials.Username) - assert.True(t, ok, fmt.Sprintf("RetrieveByUsername was not called on %s", tc.desc)) - ok = authCall.Parent.AssertCalled(t, "Issue", context.Background(), &magistrala.IssueReq{UserId: tc.user.ID, Type: uint32(mgauth.AccessKey)}) - assert.True(t, ok, fmt.Sprintf("Issue was not called on %s", tc.desc)) - } - authCall.Unset() - repoCall.Unset() - }) + repoCall := cRepo.On("RetrieveByUsername", context.Background(), tc.user.Credentials.Username).Return(tc.retrieveByUsernameResponse, tc.retrieveByUsernameErr) + authCall := auth.On("Issue", context.Background(), &grpcTokenV1.IssueReq{UserId: tc.user.ID, Type: uint32(mgauth.AccessKey)}).Return(tc.issueResponse, tc.issueErr) + token, err := svc.IssueToken(context.Background(), tc.user.Credentials.Username, tc.user.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.GetAccessToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetAccessToken())) + assert.NotEmpty(t, token.GetRefreshToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetRefreshToken())) + ok := repoCall.Parent.AssertCalled(t, "RetrieveByUsername", context.Background(), tc.user.Credentials.Username) + assert.True(t, ok, fmt.Sprintf("RetrieveByUsername was not called on %s", tc.desc)) + ok = authCall.Parent.AssertCalled(t, "Issue", context.Background(), &grpcTokenV1.IssueReq{UserId: tc.user.ID, Type: uint32(mgauth.AccessKey)}) + assert.True(t, ok, fmt.Sprintf("Issue was not called on %s", tc.desc)) + } + authCall.Unset() + repoCall.Unset() } } @@ -1714,7 +1712,7 @@ func TestRefreshToken(t *testing.T) { cases := []struct { desc string session authn.Session - refreshResp *magistrala.Token + refreshResp *grpcTokenV1.Token refresErr error repoResp users.User repoErr error @@ -1723,14 +1721,21 @@ func TestRefreshToken(t *testing.T) { { desc: "refresh token with refresh token for an existing user", session: authn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, - refreshResp: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, + refreshResp: &grpcTokenV1.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, + repoResp: rUser, + err: nil, + }, + { + desc: "refresh token with refresh token for empty domain id", + session: authn.Session{UserID: validID}, + refreshResp: &grpcTokenV1.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, repoResp: rUser, err: nil, }, { desc: "refresh token with access token for an existing user", session: authn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, - refreshResp: &magistrala.Token{}, + refreshResp: &grpcTokenV1.Token{}, refresErr: svcerr.ErrAuthentication, repoResp: rUser, err: svcerr.ErrAuthentication, @@ -1750,7 +1755,7 @@ func TestRefreshToken(t *testing.T) { { desc: "refresh token with empty domain id", session: authn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, - refreshResp: &magistrala.Token{}, + refreshResp: &grpcTokenV1.Token{}, refresErr: svcerr.ErrAuthentication, repoResp: rUser, err: svcerr.ErrAuthentication, @@ -1758,22 +1763,20 @@ func TestRefreshToken(t *testing.T) { } for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authCall := authsvc.On("Refresh", context.Background(), &magistrala.RefreshReq{RefreshToken: validToken}).Return(tc.refreshResp, tc.refresErr) - repoCall := crepo.On("RetrieveByID", context.Background(), tc.session.UserID).Return(tc.repoResp, tc.repoErr) - token, err := svc.RefreshToken(context.Background(), tc.session, validToken) - 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.GetAccessToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetAccessToken())) - assert.NotEmpty(t, token.GetRefreshToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetRefreshToken())) - ok := authCall.Parent.AssertCalled(t, "Refresh", context.Background(), &magistrala.RefreshReq{RefreshToken: validToken}) - assert.True(t, ok, fmt.Sprintf("Refresh was not called on %s", tc.desc)) - ok = repoCall.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.session.UserID) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - } - authCall.Unset() - repoCall.Unset() - }) + authCall := authsvc.On("Refresh", context.Background(), &grpcTokenV1.RefreshReq{RefreshToken: validToken}).Return(tc.refreshResp, tc.refresErr) + repoCall := crepo.On("RetrieveByID", context.Background(), tc.session.UserID).Return(tc.repoResp, tc.repoErr) + token, err := svc.RefreshToken(context.Background(), tc.session, validToken) + 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.GetAccessToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetAccessToken())) + assert.NotEmpty(t, token.GetRefreshToken(), fmt.Sprintf("%s: expected %s not to be empty\n", tc.desc, token.GetRefreshToken())) + ok := authCall.Parent.AssertCalled(t, "Refresh", context.Background(), &grpcTokenV1.RefreshReq{RefreshToken: validToken}) + assert.True(t, ok, fmt.Sprintf("Refresh was not called on %s", tc.desc)) + ok = repoCall.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.session.UserID) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + } + authCall.Unset() + repoCall.Unset() } } @@ -1785,7 +1788,7 @@ func TestGenerateResetToken(t *testing.T) { email string host string retrieveByEmailResponse users.User - issueResponse *magistrala.Token + issueResponse *grpcTokenV1.Token retrieveByEmailErr error issueErr error err error @@ -1795,7 +1798,7 @@ func TestGenerateResetToken(t *testing.T) { email: "existingemail@example.com", host: "examplehost", retrieveByEmailResponse: user, - issueResponse: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, + issueResponse: &grpcTokenV1.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, err: nil, }, { @@ -1814,7 +1817,7 @@ func TestGenerateResetToken(t *testing.T) { email: "existingemail@example.com", host: "examplehost", retrieveByEmailResponse: user, - issueResponse: &magistrala.Token{}, + issueResponse: &grpcTokenV1.Token{}, issueErr: svcerr.ErrAuthorization, err: svcerr.ErrAuthorization, }, diff --git a/users/tracing/tracing.go b/users/tracing/tracing.go index 81ad0dcb58..5bdc9c37dc 100644 --- a/users/tracing/tracing.go +++ b/users/tracing/tracing.go @@ -6,7 +6,7 @@ package tracing import ( "context" - "github.com/absmach/magistrala" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/authn" users "github.com/absmach/magistrala/users" "go.opentelemetry.io/otel/attribute" @@ -34,7 +34,7 @@ func (tm *tracingMiddleware) Register(ctx context.Context, session authn.Session } // IssueToken traces the "IssueToken" operation of the wrapped users.Service. -func (tm *tracingMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) { +func (tm *tracingMiddleware) IssueToken(ctx context.Context, username, secret string) (*grpcTokenV1.Token, error) { ctx, span := tm.tracer.Start(ctx, "svc_issue_token", trace.WithAttributes(attribute.String("username", username))) defer span.End() @@ -42,7 +42,7 @@ func (tm *tracingMiddleware) IssueToken(ctx context.Context, username, secret st } // RefreshToken traces the "RefreshToken" operation of the wrapped users.Service. -func (tm *tracingMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) { +func (tm *tracingMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*grpcTokenV1.Token, error) { ctx, span := tm.tracer.Start(ctx, "svc_refresh_token", trace.WithAttributes(attribute.String("refresh_token", refreshToken))) defer span.End() diff --git a/users/users.go b/users/users.go index 8fe96042cf..35e756dcae 100644 --- a/users/users.go +++ b/users/users.go @@ -8,7 +8,7 @@ import ( "net/mail" "time" - "github.com/absmach/magistrala" + grpcTokenV1 "github.com/absmach/magistrala/internal/grpc/token/v1" "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/postgres" @@ -151,7 +151,7 @@ type Service interface { // ListUsers retrieves users list for a valid auth token. ListUsers(ctx context.Context, session authn.Session, pm Page) (UsersPage, error) - // ListMembers retrieves everything that is assigned to a group/thing identified by objectID. + // ListMembers retrieves everything that is assigned to a group/client identified by objectID. ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm Page) (MembersPage, error) // SearchUsers searches for users with provided filters for a valid auth token. @@ -202,12 +202,12 @@ type Service interface { Identify(ctx context.Context, session authn.Session) (string, error) // IssueToken issues a new access and refresh token when provided with either a username or email. - IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error) + IssueToken(ctx context.Context, identity, secret string) (*grpcTokenV1.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, session authn.Session, refreshToken string) (*magistrala.Token, error) + RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*grpcTokenV1.Token, error) // OAuthCallback handles the callback from any supported OAuth provider. // It processes the OAuth tokens and either signs in or signs up the user based on the provided state. diff --git a/ws/README.md b/ws/README.md index 61784314db..dfd3a510ca 100644 --- a/ws/README.md +++ b/ws/README.md @@ -6,29 +6,29 @@ WebSocket adapter provides a [WebSocket](https://en.wikipedia.org/wiki/WebSocket The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| -------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------- | -| MG_WS_ADAPTER_LOG_LEVEL | Log level for the WS Adapter (debug, info, warn, error) | info | -| MG_WS_ADAPTER_HTTP_HOST | Service WS host | "" | -| MG_WS_ADAPTER_HTTP_PORT | Service WS port | 8190 | -| MG_WS_ADAPTER_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| MG_WS_ADAPTER_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | -| MG_THINGS_AUTH_GRPC_URL | Things service Auth gRPC URL | | -| MG_THINGS_AUTH_GRPC_TIMEOUT | Things service Auth gRPC request timeout in seconds | 1s | -| MG_THINGS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded things service Auth gRPC client certificate file | "" | -| MG_THINGS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded things service Auth gRPC client key file | "" | -| MG_THINGS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded things server Auth gRPC server trusted CA certificate file | "" | -| MG_MESSAGE_BROKER_URL | Message broker instance URL | | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_WS_ADAPTER_INSTANCE_ID | Service instance ID | "" | +| Variable | Description | Default | +| --------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | +| MG_WS_ADAPTER_LOG_LEVEL | Log level for the WS Adapter (debug, info, warn, error) | info | +| MG_WS_ADAPTER_HTTP_HOST | Service WS host | "" | +| MG_WS_ADAPTER_HTTP_PORT | Service WS port | 8190 | +| MG_WS_ADAPTER_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| MG_WS_ADAPTER_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | +| MG_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| MG_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| MG_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | +| MG_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | +| MG_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | +| MG_MESSAGE_BROKER_URL | Message broker instance URL | | +| MG_JAEGER_URL | Jaeger server URL | | +| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_WS_ADAPTER_INSTANCE_ID | Service instance ID | "" | ## Deployment The service is distributed as Docker container. Check the [`ws-adapter`](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yml) service section in docker-compose file to see how the service is deployed. -Running this service outside of container requires working instance of the message broker service, things service and Jaeger server. +Running this service outside of container requires working instance of the message broker service, clients service and Jaeger server. To start the service outside of the container, execute the following shell script: ```bash @@ -49,11 +49,11 @@ MG_WS_ADAPTER_HTTP_HOST=localhost \ MG_WS_ADAPTER_HTTP_PORT=8190 \ MG_WS_ADAPTER_HTTP_SERVER_CERT="" \ MG_WS_ADAPTER_HTTP_SERVER_KEY="" \ -MG_THINGS_AUTH_GRPC_URL=localhost:7000 \ -MG_THINGS_AUTH_GRPC_TIMEOUT=1s \ -MG_THINGS_AUTH_GRPC_CLIENT_CERT="" \ -MG_THINGS_AUTH_GRPC_CLIENT_KEY="" \ -MG_THINGS_AUTH_GRPC_SERVER_CERTS="" \ +MG_CLIENTS_AUTH_GRPC_URL=localhost:7000 \ +MG_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ +MG_CLIENTS_AUTH_GRPC_CLIENT_CERT="" \ +MG_CLIENTS_AUTH_GRPC_CLIENT_KEY="" \ +MG_CLIENTS_AUTH_GRPC_SERVER_CERTS="" \ MG_MESSAGE_BROKER_URL=nats://localhost:4222 \ MG_JAEGER_URL=http://localhost:14268/api/traces \ MG_JAEGER_TRACE_RATIO=1.0 \ @@ -64,7 +64,7 @@ $GOBIN/magistrala-ws Setting `MG_WS_ADAPTER_HTTP_SERVER_CERT` and `MG_WS_ADAPTER_HTTP_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key. -Setting `MG_THINGS_AUTH_GRPC_CLIENT_CERT` and `MG_THINGS_AUTH_GRPC_CLIENT_KEY` will enable TLS against the things service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_THINGS_AUTH_GRPC_SERVER_CERTS` will enable TLS against the things service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. +Setting `MG_CLIENTS_AUTH_GRPC_CLIENT_CERT` and `MG_CLIENTS_AUTH_GRPC_CLIENT_KEY` will enable TLS against the clients service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_CLIENTS_AUTH_GRPC_SERVER_CERTS` will enable TLS against the clients service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. ## Usage diff --git a/ws/adapter.go b/ws/adapter.go index e92b041201..ba4e6f604b 100644 --- a/ws/adapter.go +++ b/ws/adapter.go @@ -7,7 +7,9 @@ import ( "context" "fmt" - "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + "github.com/absmach/magistrala/pkg/connections" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" @@ -26,44 +28,46 @@ var ( // errFailedUnsubscribe indicates that client couldn't unsubscribe from specified channel. errFailedUnsubscribe = errors.New("failed to unsubscribe from a channel") - // ErrEmptyTopic indicate absence of thingKey in the request. + // ErrEmptyTopic indicate absence of clientKey in the request. ErrEmptyTopic = errors.New("empty topic") ) // Service specifies web socket service API. type Service interface { - // Subscribe subscribes message from the broker using the thingKey for authorization, + // Subscribe subscribes message from the broker using the clientKey for authorization, // and the channelID for subscription. Subtopic is optional. // If the subscription is successful, nil is returned otherwise error is returned. - Subscribe(ctx context.Context, thingKey, chanID, subtopic string, client *Client) error + Subscribe(ctx context.Context, clientKey, chanID, subtopic string, client *Client) error } var _ Service = (*adapterService)(nil) type adapterService struct { - things magistrala.ThingsServiceClient - pubsub messaging.PubSub + clients grpcClientsV1.ClientsServiceClient + channels grpcChannelsV1.ChannelsServiceClient + pubsub messaging.PubSub } // New instantiates the WS adapter implementation. -func New(thingsClient magistrala.ThingsServiceClient, pubsub messaging.PubSub) Service { +func New(clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient, pubsub messaging.PubSub) Service { return &adapterService{ - things: thingsClient, - pubsub: pubsub, + clients: clients, + channels: channels, + pubsub: pubsub, } } -func (svc *adapterService) Subscribe(ctx context.Context, thingKey, chanID, subtopic string, c *Client) error { - if chanID == "" || thingKey == "" { +func (svc *adapterService) Subscribe(ctx context.Context, clientKey, chanID, subtopic string, c *Client) error { + if chanID == "" || clientKey == "" { return svcerr.ErrAuthentication } - thingID, err := svc.authorize(ctx, thingKey, chanID, policies.SubscribePermission) + clientID, err := svc.authorize(ctx, clientKey, chanID, connections.Subscribe) if err != nil { return svcerr.ErrAuthorization } - c.id = thingID + c.id = clientID subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) if subtopic != "" { @@ -71,7 +75,7 @@ func (svc *adapterService) Subscribe(ctx context.Context, thingKey, chanID, subt } subCfg := messaging.SubscriberConfig{ - ID: thingID, + ID: clientID, Topic: subject, Handler: c, } @@ -82,21 +86,33 @@ func (svc *adapterService) Subscribe(ctx context.Context, thingKey, chanID, subt return nil } -// authorize checks if the thingKey is authorized to access the channel -// and returns the thingID if it is. -func (svc *adapterService) authorize(ctx context.Context, thingKey, chanID, action string) (string, error) { - ar := &magistrala.ThingsAuthzReq{ - Permission: action, - ThingKey: thingKey, +// authorize checks if the clientKey is authorized to access the channel +// and returns the clientID if it is. +func (svc *adapterService) authorize(ctx context.Context, clientKey, chanID string, msgType connections.ConnType) (string, error) { + authnReq := &grpcClientsV1.AuthnReq{ + ClientSecret: clientKey, + } + authnRes, err := svc.clients.Authenticate(ctx, authnReq) + if err != nil { + return "", errors.Wrap(svcerr.ErrAuthentication, err) + } + if !authnRes.GetAuthenticated() { + return "", errors.Wrap(svcerr.ErrAuthentication, err) + } + + authzReq := &grpcChannelsV1.AuthzReq{ + ClientType: policies.ClientType, + ClientId: authnRes.GetId(), + Type: uint32(msgType), ChannelId: chanID, } - res, err := svc.things.Authorize(ctx, ar) + authzRes, err := svc.channels.Authorize(ctx, authzReq) if err != nil { return "", errors.Wrap(svcerr.ErrAuthorization, err) } - if !res.GetAuthorized() { + if !authzRes.GetAuthorized() { return "", errors.Wrap(svcerr.ErrAuthorization, err) } - return res.GetId(), nil + return authnRes.GetId(), nil } diff --git a/ws/adapter_test.go b/ws/adapter_test.go index 40323a2aa9..cbd8e6fcc7 100644 --- a/ws/adapter_test.go +++ b/ws/adapter_test.go @@ -8,12 +8,12 @@ import ( "fmt" "testing" - "github.com/absmach/magistrala" + chmocks "github.com/absmach/magistrala/channels/mocks" + climocks "github.com/absmach/magistrala/clients/mocks" "github.com/absmach/magistrala/internal/testsutil" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" "github.com/absmach/magistrala/pkg/messaging/mocks" - thmocks "github.com/absmach/magistrala/things/mocks" "github.com/absmach/magistrala/ws" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -24,7 +24,7 @@ const ( invalidID = "invalidID" invalidKey = "invalidKey" id = "1" - thingKey = "thing_key" + clientKey = "client_key" subTopic = "subtopic" protocol = "ws" ) @@ -37,70 +37,71 @@ var msg = messaging.Message{ Payload: []byte(`[{"n":"current","t":-5,"v":1.2}]`), } -func newService() (ws.Service, *mocks.PubSub, *thmocks.ThingsServiceClient) { +func newService() (ws.Service, *mocks.PubSub, *climocks.ClientsServiceClient, *chmocks.ChannelsServiceClient) { pubsub := new(mocks.PubSub) - things := new(thmocks.ThingsServiceClient) + clients := new(climocks.ClientsServiceClient) + channels := new(chmocks.ChannelsServiceClient) - return ws.New(things, pubsub), pubsub, things + return ws.New(clients, channels, pubsub), pubsub, clients, channels } func TestSubscribe(t *testing.T) { - svc, pubsub, things := newService() + svc, pubsub, _, _ := newService() c := ws.NewClient(nil) cases := []struct { desc string - thingKey string + clientKey string chanID string subtopic string err error }{ { - desc: "subscribe to channel with valid thingKey, chanID, subtopic", - thingKey: thingKey, + desc: "subscribe to channel with valid clientKey, chanID, subtopic", + clientKey: clientKey, chanID: chanID, subtopic: subTopic, err: nil, }, { - desc: "subscribe again to channel with valid thingKey, chanID, subtopic", - thingKey: thingKey, + desc: "subscribe again to channel with valid clientKey, chanID, subtopic", + clientKey: clientKey, chanID: chanID, subtopic: subTopic, err: nil, }, { desc: "subscribe to channel with subscribe set to fail", - thingKey: thingKey, + clientKey: clientKey, chanID: chanID, subtopic: subTopic, err: ws.ErrFailedSubscription, }, { - desc: "subscribe to channel with invalid chanID and invalid thingKey", - thingKey: invalidKey, + desc: "subscribe to channel with invalid chanID and invalid clientKey", + clientKey: invalidKey, chanID: invalidID, subtopic: subTopic, err: ws.ErrFailedSubscription, }, { desc: "subscribe to channel with empty channel", - thingKey: thingKey, + clientKey: clientKey, chanID: "", subtopic: subTopic, err: svcerr.ErrAuthentication, }, { - desc: "subscribe to channel with empty thingKey", - thingKey: "", + desc: "subscribe to channel with empty clientKey", + clientKey: "", chanID: chanID, subtopic: subTopic, err: svcerr.ErrAuthentication, }, { - desc: "subscribe to channel with empty thingKey and empty channel", - thingKey: "", + desc: "subscribe to channel with empty clientKey and empty channel", + clientKey: "", chanID: "", subtopic: subTopic, err: svcerr.ErrAuthentication, @@ -108,18 +109,15 @@ func TestSubscribe(t *testing.T) { } for _, tc := range cases { - thingID := testsutil.GenerateUUID(t) + clientID := testsutil.GenerateUUID(t) subConfig := messaging.SubscriberConfig{ - ID: thingID, + ID: clientID, Topic: "channels." + tc.chanID + "." + subTopic, Handler: c, } repocall := pubsub.On("Subscribe", mock.Anything, subConfig).Return(tc.err) - repocall1 := things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.ThingsAuthzRes{Authorized: true, Id: thingID}, nil) - err := svc.Subscribe(context.Background(), tc.thingKey, tc.chanID, tc.subtopic, c) + err := svc.Subscribe(context.Background(), tc.clientKey, tc.chanID, tc.subtopic, c) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repocall1.Parent.AssertCalled(t, "Authorize", mock.Anything, mock.Anything) repocall.Unset() - repocall1.Unset() } } diff --git a/ws/api/endpoint_test.go b/ws/api/endpoint_test.go index 1bc1faf13a..4efb797986 100644 --- a/ws/api/endpoint_test.go +++ b/ws/api/endpoint_test.go @@ -12,10 +12,13 @@ import ( "strings" "testing" - "github.com/absmach/magistrala" + chmocks "github.com/absmach/magistrala/channels/mocks" + climocks "github.com/absmach/magistrala/clients/mocks" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" mglog "github.com/absmach/magistrala/logger" + authnMocks "github.com/absmach/magistrala/pkg/authn/mocks" "github.com/absmach/magistrala/pkg/messaging/mocks" - thmocks "github.com/absmach/magistrala/things/mocks" "github.com/absmach/magistrala/ws" "github.com/absmach/magistrala/ws/api" "github.com/absmach/mgate/pkg/session" @@ -29,16 +32,16 @@ import ( const ( chanID = "30315311-56ba-484d-b500-c1e08305511f" id = "1" - thingKey = "c02ff576-ccd5-40f6-ba5f-c85377aad529" + clientKey = "c02ff576-ccd5-40f6-ba5f-c85377aad529" protocol = "ws" instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" ) var msg = []byte(`[{"n":"current","t":-1,"v":1.6}]`) -func newService(things magistrala.ThingsServiceClient) (ws.Service, *mocks.PubSub) { +func newService(clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) (ws.Service, *mocks.PubSub) { pubsub := new(mocks.PubSub) - return ws.New(things, pubsub), pubsub + return ws.New(clients, channels, pubsub), pubsub } func newHTTPServer(svc ws.Service) *httptest.Server { @@ -55,7 +58,7 @@ func newProxyHTPPServer(svc session.Handler, targetServer *httptest.Server) (*ht return httptest.NewServer(http.HandlerFunc(mp.Handler)), nil } -func makeURL(tsURL, chanID, subtopic, thingKey string, header bool) (string, error) { +func makeURL(tsURL, chanID, subtopic, clientKey string, header bool) (string, error) { u, _ := url.Parse(tsURL) u.Scheme = protocol @@ -63,7 +66,7 @@ func makeURL(tsURL, chanID, subtopic, thingKey string, header bool) (string, err if header { return fmt.Sprintf("%s/channels/%s/messages", u, chanID), fmt.Errorf("invalid channel id") } - return fmt.Sprintf("%s/channels/%s/messages?authorization=%s", u, chanID, thingKey), fmt.Errorf("invalid channel id") + return fmt.Sprintf("%s/channels/%s/messages?authorization=%s", u, chanID, clientKey), fmt.Errorf("invalid channel id") } subtopicPart := "" @@ -74,132 +77,131 @@ func makeURL(tsURL, chanID, subtopic, thingKey string, header bool) (string, err return fmt.Sprintf("%s/channels/%s/messages%s", u, chanID, subtopicPart), nil } - return fmt.Sprintf("%s/channels/%s/messages%s?authorization=%s", u, chanID, subtopicPart, thingKey), nil + return fmt.Sprintf("%s/channels/%s/messages%s?authorization=%s", u, chanID, subtopicPart, clientKey), nil } -func handshake(tsURL, chanID, subtopic, thingKey string, addHeader bool) (*websocket.Conn, *http.Response, error) { +func handshake(tsURL, chanID, subtopic, clientKey string, addHeader bool) (*websocket.Conn, *http.Response, error) { header := http.Header{} if addHeader { - header.Add("Authorization", thingKey) + header.Add("Authorization", clientKey) } - turl, _ := makeURL(tsURL, chanID, subtopic, thingKey, addHeader) + turl, _ := makeURL(tsURL, chanID, subtopic, clientKey, addHeader) conn, res, errRet := websocket.DefaultDialer.Dial(turl, header) return conn, res, errRet } func TestHandshake(t *testing.T) { - things := new(thmocks.ThingsServiceClient) - svc, pubsub := newService(things) + clients := new(climocks.ClientsServiceClient) + channels := new(chmocks.ChannelsServiceClient) + authn := new(authnMocks.Authentication) + svc, pubsub := newService(clients, channels) target := newHTTPServer(svc) defer target.Close() - handler := ws.NewHandler(pubsub, mglog.NewMock(), things) + handler := ws.NewHandler(pubsub, mglog.NewMock(), authn, clients, channels) ts, err := newProxyHTPPServer(handler, target) require.Nil(t, err) defer ts.Close() - things.On("Authorize", mock.Anything, &magistrala.ThingsAuthzReq{ThingKey: thingKey, ChannelId: id, Permission: "publish"}).Return(&magistrala.ThingsAuthzRes{Authorized: true, Id: "1"}, nil) - things.On("Authorize", mock.Anything, &magistrala.ThingsAuthzReq{ThingKey: thingKey, ChannelId: id, Permission: "subscribe"}).Return(&magistrala.ThingsAuthzRes{Authorized: true, Id: "2"}, nil) - things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthZRes{Authorized: false, Id: "3"}, nil) pubsub.On("Subscribe", mock.Anything, mock.Anything).Return(nil) pubsub.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) cases := []struct { - desc string - chanID string - subtopic string - header bool - thingKey string - status int - err error - msg []byte + desc string + chanID string + subtopic string + header bool + clientKey string + status int + err error + msg []byte }{ { - desc: "connect and send message", - chanID: id, - subtopic: "", - header: true, - thingKey: thingKey, - status: http.StatusSwitchingProtocols, - msg: msg, + desc: "connect and send message", + chanID: id, + subtopic: "", + header: true, + clientKey: clientKey, + status: http.StatusSwitchingProtocols, + msg: msg, }, { - desc: "connect and send message with thingKey as query parameter", - chanID: id, - subtopic: "", - header: false, - thingKey: thingKey, - status: http.StatusSwitchingProtocols, - msg: msg, + desc: "connect and send message with clientKey as query parameter", + chanID: id, + subtopic: "", + header: false, + clientKey: clientKey, + status: http.StatusSwitchingProtocols, + msg: msg, }, { - desc: "connect and send message that cannot be published", - chanID: id, - subtopic: "", - header: true, - thingKey: thingKey, - status: http.StatusSwitchingProtocols, - msg: []byte{}, + desc: "connect and send message that cannot be published", + chanID: id, + subtopic: "", + header: true, + clientKey: clientKey, + status: http.StatusSwitchingProtocols, + msg: []byte{}, }, { - desc: "connect and send message to subtopic", - chanID: id, - subtopic: "subtopic", - header: true, - thingKey: thingKey, - status: http.StatusSwitchingProtocols, - msg: msg, + desc: "connect and send message to subtopic", + chanID: id, + subtopic: "subtopic", + header: true, + clientKey: clientKey, + status: http.StatusSwitchingProtocols, + msg: msg, }, { - desc: "connect and send message to nested subtopic", - chanID: id, - subtopic: "subtopic/nested", - header: true, - thingKey: thingKey, - status: http.StatusSwitchingProtocols, - msg: msg, + desc: "connect and send message to nested subtopic", + chanID: id, + subtopic: "subtopic/nested", + header: true, + clientKey: clientKey, + status: http.StatusSwitchingProtocols, + msg: msg, }, { - desc: "connect and send message to all subtopics", - chanID: id, - subtopic: ">", - header: true, - thingKey: thingKey, - status: http.StatusSwitchingProtocols, - msg: msg, + desc: "connect and send message to all subtopics", + chanID: id, + subtopic: ">", + header: true, + clientKey: clientKey, + status: http.StatusSwitchingProtocols, + msg: msg, }, { - desc: "connect to empty channel", - chanID: "", - subtopic: "", - header: true, - thingKey: thingKey, - status: http.StatusBadGateway, - msg: []byte{}, + desc: "connect to empty channel", + chanID: "", + subtopic: "", + header: true, + clientKey: clientKey, + status: http.StatusBadGateway, + msg: []byte{}, }, { - desc: "connect with empty thingKey", - chanID: id, - subtopic: "", - header: true, - thingKey: "", - status: http.StatusUnauthorized, - msg: []byte{}, + desc: "connect with empty clientKey", + chanID: id, + subtopic: "", + header: true, + clientKey: "", + status: http.StatusUnauthorized, + msg: []byte{}, }, { - desc: "connect and send message to subtopic with invalid name", - chanID: id, - subtopic: "sub/a*b/topic", - header: true, - thingKey: thingKey, - status: http.StatusBadGateway, - msg: msg, + desc: "connect and send message to subtopic with invalid name", + chanID: id, + subtopic: "sub/a*b/topic", + header: true, + clientKey: clientKey, + status: http.StatusBadGateway, + msg: msg, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - conn, res, err := handshake(ts.URL, tc.chanID, tc.subtopic, tc.thingKey, tc.header) + conn, res, err := handshake(ts.URL, tc.chanID, tc.subtopic, tc.clientKey, tc.header) assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code '%d' got '%d'\n", tc.desc, tc.status, res.StatusCode)) if tc.status == http.StatusSwitchingProtocols { diff --git a/ws/api/endpoints.go b/ws/api/endpoints.go index 040133a9de..eaf4b1d769 100644 --- a/ws/api/endpoints.go +++ b/ws/api/endpoints.go @@ -33,7 +33,7 @@ func handshake(ctx context.Context, svc ws.Service) http.HandlerFunc { req.conn = conn client := ws.NewClient(conn) - if err := svc.Subscribe(ctx, req.thingKey, req.chanID, req.subtopic, client); err != nil { + if err := svc.Subscribe(ctx, req.clientKey, req.chanID, req.subtopic, client); err != nil { req.conn.Close() return } @@ -56,7 +56,7 @@ func decodeRequest(r *http.Request) (connReq, error) { chanID := chi.URLParam(r, "chanID") req := connReq{ - thingKey: authKey, + clientKey: authKey, chanID: chanID, } diff --git a/ws/api/logging.go b/ws/api/logging.go index 5c693a45e2..3af78796e1 100644 --- a/ws/api/logging.go +++ b/ws/api/logging.go @@ -25,7 +25,7 @@ func LoggingMiddleware(svc ws.Service, logger *slog.Logger) ws.Service { // Subscribe logs the subscribe request. It logs the channel and subtopic(if present) and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) Subscribe(ctx context.Context, thingKey, chanID, subtopic string, c *ws.Client) (err error) { +func (lm *loggingMiddleware) Subscribe(ctx context.Context, clientKey, chanID, subtopic string, c *ws.Client) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), @@ -42,5 +42,5 @@ func (lm *loggingMiddleware) Subscribe(ctx context.Context, thingKey, chanID, su lm.logger.Info("Subscribe completed successfully", args...) }(time.Now()) - return lm.svc.Subscribe(ctx, thingKey, chanID, subtopic, c) + return lm.svc.Subscribe(ctx, clientKey, chanID, subtopic, c) } diff --git a/ws/api/metrics.go b/ws/api/metrics.go index a1a8d59322..a04a658cc3 100644 --- a/ws/api/metrics.go +++ b/ws/api/metrics.go @@ -31,11 +31,11 @@ func MetricsMiddleware(svc ws.Service, counter metrics.Counter, latency metrics. } // Subscribe instruments Subscribe method with metrics. -func (mm *metricsMiddleware) Subscribe(ctx context.Context, thingKey, chanID, subtopic string, c *ws.Client) error { +func (mm *metricsMiddleware) Subscribe(ctx context.Context, clientKey, chanID, subtopic string, c *ws.Client) error { defer func(begin time.Time) { mm.counter.With("method", "subscribe").Add(1) mm.latency.With("method", "subscribe").Observe(time.Since(begin).Seconds()) }(time.Now()) - return mm.svc.Subscribe(ctx, thingKey, chanID, subtopic, c) + return mm.svc.Subscribe(ctx, clientKey, chanID, subtopic, c) } diff --git a/ws/api/requests.go b/ws/api/requests.go index cc3f50dcd2..7f7854fb6d 100644 --- a/ws/api/requests.go +++ b/ws/api/requests.go @@ -6,7 +6,7 @@ package api import "github.com/gorilla/websocket" type connReq struct { - thingKey string + clientKey string chanID string subtopic string conn *websocket.Conn diff --git a/ws/handler.go b/ws/handler.go index 56a39da813..b54bb0eb22 100644 --- a/ws/handler.go +++ b/ws/handler.go @@ -12,7 +12,11 @@ import ( "strings" "time" - "github.com/absmach/magistrala" + grpcChannelsV1 "github.com/absmach/magistrala/internal/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/magistrala/internal/grpc/clients/v1" + "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/magistrala/pkg/connections" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/messaging" @@ -50,17 +54,21 @@ var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?] // Event implements events.Event interface. type handler struct { - pubsub messaging.PubSub - things magistrala.ThingsServiceClient - logger *slog.Logger + pubsub messaging.PubSub + clients grpcClientsV1.ClientsServiceClient + channels grpcChannelsV1.ChannelsServiceClient + authn mgauthn.Authentication + logger *slog.Logger } // NewHandler creates new Handler entity. -func NewHandler(pubsub messaging.PubSub, logger *slog.Logger, thingsClient magistrala.ThingsServiceClient) session.Handler { +func NewHandler(pubsub messaging.PubSub, logger *slog.Logger, authn mgauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) session.Handler { return &handler{ - logger: logger, - pubsub: pubsub, - things: thingsClient, + logger: logger, + pubsub: pubsub, + authn: authn, + clients: clients, + channels: channels, } } @@ -83,13 +91,13 @@ func (h *handler) AuthPublish(ctx context.Context, topic *string, payload *[]byt var token string switch { - case strings.HasPrefix(string(s.Password), "Thing"): - token = strings.ReplaceAll(string(s.Password), "Thing ", "") + case strings.HasPrefix(string(s.Password), "Client"): + token = strings.ReplaceAll(string(s.Password), "Client ", "") default: token = string(s.Password) } - return h.authAccess(ctx, token, *topic, policies.PublishPermission) + return h.authAccess(ctx, token, *topic, connections.Publish) } // AuthSubscribe is called on device publish, @@ -105,14 +113,14 @@ func (h *handler) AuthSubscribe(ctx context.Context, topics *[]string) error { var token string switch { - case strings.HasPrefix(string(s.Password), "Thing"): - token = strings.ReplaceAll(string(s.Password), "Thing ", "") + case strings.HasPrefix(string(s.Password), "Client"): + token = strings.ReplaceAll(string(s.Password), "Client ", "") default: token = string(s.Password) } - for _, v := range *topics { - if err := h.authAccess(ctx, token, v, policies.SubscribePermission); err != nil { + for _, topic := range *topics { + if err := h.authAccess(ctx, token, topic, connections.Subscribe); err != nil { return err } } @@ -152,20 +160,36 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e return errors.Wrap(errFailedParseSubtopic, err) } - var token string + var clientID, clientType string switch { - case strings.HasPrefix(string(s.Password), "Thing"): - token = strings.ReplaceAll(string(s.Password), "Thing ", "") + case strings.HasPrefix(string(s.Password), "Client"): + clientKey := extractClientSecret(string(s.Password)) + authnRes, err := h.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ClientSecret: clientKey}) + if err != nil { + return errors.Wrap(svcerr.ErrAuthentication, err) + } + if !authnRes.Authenticated { + return svcerr.ErrAuthentication + } + clientType = policies.ClientType + clientID = authnRes.GetId() default: - token = string(s.Password) + token := string(s.Password) + authnSession, err := h.authn.Authenticate(ctx, extractBearerToken(token)) + if err != nil { + return err + } + clientType = policies.UserType + clientID = authnSession.DomainUserID } - ar := &magistrala.ThingsAuthzReq{ - Permission: policies.PublishPermission, - ThingKey: token, + ar := &grpcChannelsV1.AuthzReq{ + Type: uint32(connections.Publish), + ClientId: clientID, + ClientType: clientType, ChannelId: chanID, } - res, err := h.things.Authorize(ctx, ar) + res, err := h.channels.Authorize(ctx, ar) if err != nil { return err } @@ -174,12 +198,15 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e } msg := messaging.Message{ - Protocol: protocol, - Channel: chanID, - Subtopic: subtopic, - Publisher: res.GetId(), - Payload: *payload, - Created: time.Now().UnixNano(), + Protocol: protocol, + Channel: chanID, + Subtopic: subtopic, + Payload: *payload, + Created: time.Now().UnixNano(), + } + + if clientType == policies.ClientType { + msg.Publisher = clientID } if err := h.pubsub.Publish(ctx, msg.GetChannel(), &msg); err != nil { @@ -215,7 +242,29 @@ func (h *handler) Disconnect(ctx context.Context) error { return nil } -func (h *handler) authAccess(ctx context.Context, password, topic, action string) error { +func (h *handler) authAccess(ctx context.Context, token, topic string, msgType connections.ConnType) error { + var clientID, clientType string + switch { + case strings.HasPrefix(token, "Client"): + clientKey := extractClientSecret(token) + authnRes, err := h.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ClientSecret: clientKey}) + if err != nil { + return errors.Wrap(svcerr.ErrAuthentication, err) + } + if !authnRes.Authenticated { + return svcerr.ErrAuthentication + } + clientType = policies.ClientType + clientID = authnRes.GetId() + default: + authnSession, err := h.authn.Authenticate(ctx, extractBearerToken(token)) + if err != nil { + return err + } + clientType = policies.UserType + clientID = authnSession.DomainUserID + } + // Topics are in the format: // channels//messages//.../ct/ if !channelRegExp.MatchString(topic) { @@ -229,12 +278,13 @@ func (h *handler) authAccess(ctx context.Context, password, topic, action string chanID := channelParts[1] - ar := &magistrala.ThingsAuthzReq{ - Permission: action, - ThingKey: password, + ar := &grpcChannelsV1.AuthzReq{ + Type: uint32(msgType), + ClientId: clientID, + ClientType: clientType, ChannelId: chanID, } - res, err := h.things.Authorize(ctx, ar) + res, err := h.channels.Authorize(ctx, ar) if err != nil { return errors.Wrap(svcerr.ErrAuthorization, err) } @@ -273,3 +323,21 @@ func parseSubtopic(subtopic string) (string, error) { subtopic = strings.Join(filteredElems, ".") return subtopic, nil } + +// extractClientSecret returns value of the client secret. If there is no client key - an empty value is returned. +func extractClientSecret(topic string) string { + if !strings.HasPrefix(topic, apiutil.ClientPrefix) { + return "" + } + + return strings.TrimPrefix(topic, apiutil.ClientPrefix) +} + +// extractBearerToken +func extractBearerToken(token string) string { + if !strings.HasPrefix(token, apiutil.BearerPrefix) { + return "" + } + + return strings.TrimPrefix(token, apiutil.BearerPrefix) +} diff --git a/ws/tracing/tracing.go b/ws/tracing/tracing.go index ed7e62c9ce..0eff5beaed 100644 --- a/ws/tracing/tracing.go +++ b/ws/tracing/tracing.go @@ -32,9 +32,9 @@ func New(tracer trace.Tracer, svc ws.Service) ws.Service { } // Subscribe traces the "Subscribe" operation of the wrapped ws.Service. -func (tm *tracingMiddleware) Subscribe(ctx context.Context, thingKey, chanID, subtopic string, client *ws.Client) error { +func (tm *tracingMiddleware) Subscribe(ctx context.Context, clientKey, chanID, subtopic string, client *ws.Client) error { ctx, span := tm.tracer.Start(ctx, subscribeOP) defer span.End() - return tm.svc.Subscribe(ctx, thingKey, chanID, subtopic, client) + return tm.svc.Subscribe(ctx, clientKey, chanID, subtopic, client) }