diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml deleted file mode 100644 index c5f566c99..000000000 --- a/.github/workflows/api-tests.yml +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: Property Based Tests - -on: - pull_request: - branches: - - main - paths: - - ".github/workflows/api-tests.yml" - - "api/**" - - "auth/api/http/**" - - "bootstrap/api**" - - "certs/api/**" - - "consumers/notifiers/api/**" - - "http/api/**" - - "invitations/api/**" - - "journal/api/**" - - "provision/api/**" - - "readers/api/**" - - "things/api/**" - - "users/api/**" - -env: - TOKENS_URL: http://localhost:9002/users/tokens/issue - DOMAINS_URL: http://localhost:8189/domains - USER_IDENTITY: admin@example.com - USER_SECRET: 12345678 - DOMAIN_NAME: demo-test - USERS_URL: http://localhost:9002 - THINGS_URL: http://localhost:9000 - HTTP_ADAPTER_URL: http://localhost:8008 - INVITATIONS_URL: http://localhost:9020 - AUTH_URL: http://localhost:8189 - BOOTSTRAP_URL: http://localhost:9013 - CERTS_URL: http://localhost:9019 - PROVISION_URL: http://localhost:9016 - POSTGRES_READER_URL: http://localhost:9009 - TIMESCALE_READER_URL: http://localhost:9011 - JOURNAL_URL: http://localhost:9021 - -jobs: - api-test: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache-dependency-path: "go.sum" - - - name: Build images - run: make all -j $(nproc) && make dockers_dev -j $(nproc) - - - name: Start containers - run: make run up args="-d" && make run_addons up args="-d" - - - name: Set access token - run: | - export USER_TOKEN=$(curl -sSX POST $TOKENS_URL -H "Content-Type: application/json" -d "{\"identity\": \"$USER_IDENTITY\",\"secret\": \"$USER_SECRET\"}" | jq -r .access_token) - 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 - - - name: Check for changes in specific paths - uses: dorny/paths-filter@v3 - id: changes - with: - filters: | - journal: - - ".github/workflows/api-tests.yml" - - "api/openapi/journal.yml" - - "journal/api/**" - - auth: - - ".github/workflows/api-tests.yml" - - "api/openapi/auth.yml" - - "auth/api/http/**" - - bootstrap: - - ".github/workflows/api-tests.yml" - - "api/openapi/bootstrap.yml" - - "bootstrap/api/**" - - certs: - - ".github/workflows/api-tests.yml" - - "api/openapi/certs.yml" - - "certs/api/**" - - http: - - ".github/workflows/api-tests.yml" - - "api/openapi/http.yml" - - "http/api/**" - - invitations: - - ".github/workflows/api-tests.yml" - - "api/openapi/invitations.yml" - - "invitations/api/**" - - provision: - - ".github/workflows/api-tests.yml" - - "api/openapi/provision.yml" - - "provision/api/**" - - readers: - - ".github/workflows/api-tests.yml" - - "api/openapi/readers.yml" - - "readers/api/**" - - things: - - ".github/workflows/api-tests.yml" - - "api/openapi/things.yml" - - "things/api/**" - - users: - - ".github/workflows/api-tests.yml" - - "api/openapi/users.yml" - - "users/api/**" - - - name: Run Users API tests - if: steps.changes.outputs.users == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/users.yml - base-url: ${{ env.USERS_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' - - - name: Run Things API tests - if: steps.changes.outputs.things == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/things.yml - base-url: ${{ env.THINGS_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' - - - name: Run HTTP Adapter API tests - if: steps.changes.outputs.http == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/http.yml - 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' - - - name: Run Invitations API tests - if: steps.changes.outputs.invitations == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/invitations.yml - base-url: ${{ env.INVITATIONS_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' - - - name: Run Auth API tests - if: steps.changes.outputs.auth == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/auth.yml - base-url: ${{ env.AUTH_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' - - - name: Run Journal API tests - if: steps.changes.outputs.journal == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/journal.yml - base-url: ${{ env.JOURNAL_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' - - - name: Run Bootstrap API tests - if: steps.changes.outputs.bootstrap == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/bootstrap.yml - base-url: ${{ env.BOOTSTRAP_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' - - - name: Run Certs API tests - if: steps.changes.outputs.certs == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/certs.yml - base-url: ${{ env.CERTS_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' - - - name: Run Provision API tests - if: steps.changes.outputs.provision == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/provision.yml - base-url: ${{ env.PROVISION_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' - - - name: Seed Messages - if: steps.changes.outputs.readers == 'true' - run: | - make cli - ./build/cli provision test - - - name: Run Postgres Reader API tests - if: steps.changes.outputs.readers == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/readers.yml - base-url: ${{ env.POSTGRES_READER_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' - - - name: Run Timescale Reader API tests - if: steps.changes.outputs.readers == 'true' - uses: schemathesis/action@v1 - with: - schema: api/openapi/readers.yml - base-url: ${{ env.TIMESCALE_READER_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' - - - name: Stop containers - if: always() - run: make run down args="-v" && make run_addons down args="-v" diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml deleted file mode 100644 index c0ed4cd18..000000000 --- a/.github/workflows/check-generated-files.yml +++ /dev/null @@ -1,217 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: Check the consistency of generated files - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - check-generated-files: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache-dependency-path: "go.sum" - - - name: Check for changes in go.mod - run: | - go mod tidy - git diff --exit-code - - - name: Check for changes in specific paths - uses: dorny/paths-filter@v3 - id: changes - with: - base: main - filters: | - proto: - - ".github/workflows/check-generated-files.yml" - - "auth.proto" - - "auth/*.pb.go" - - "pkg/messaging/message.proto" - - "pkg/messaging/*.pb.go" - - mocks: - - ".github/workflows/check-generated-files.yml" - - "pkg/sdk/go/sdk.go" - - "users/postgres/clients.go" - - "users/clients.go" - - "pkg/clients/clients.go" - - "pkg/messaging/pubsub.go" - - "things/postgres/clients.go" - - "things/things.go" - - "pkg/authz.go" - - "pkg/authn.go" - - "auth/domains.go" - - "auth/keys.go" - - "auth/service.go" - - "pkg/events/events.go" - - "provision/service.go" - - "pkg/groups/groups.go" - - "bootstrap/service.go" - - "bootstrap/configs.go" - - "invitations/invitations.go" - - "users/emailer.go" - - "users/hasher.go" - - "mqtt/events/streams.go" - - "readers/messages.go" - - "lora/routemap.go" - - "consumers/notifiers/notifier.go" - - "consumers/notifiers/service.go" - - "consumers/notifiers/subscriptions.go" - - "certs/certs.go" - - "certs/pki/vault.go" - - "certs/service.go" - - "journal/journal.go" - - "magistrala/auth_grpc.pb.go" - - - name: Set up protoc - if: steps.changes.outputs.proto == 'true' - run: | - PROTOC_VERSION=27.1 - PROTOC_GEN_VERSION=v1.34.2 - PROTOC_GRPC_VERSION=v1.4.0 - - # Export the variables so they are available in future steps - echo "PROTOC_VERSION=$PROTOC_VERSION" >> $GITHUB_ENV - echo "PROTOC_GEN_VERSION=$PROTOC_GEN_VERSION" >> $GITHUB_ENV - echo "PROTOC_GRPC_VERSION=$PROTOC_GRPC_VERSION" >> $GITHUB_ENV - - # Download and install protoc - PROTOC_ZIP=protoc-$PROTOC_VERSION-linux-x86_64.zip - curl -0L -o $PROTOC_ZIP https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP - unzip -o $PROTOC_ZIP -d protoc3 - sudo mv protoc3/bin/* /usr/local/bin/ - sudo mv protoc3/include/* /usr/local/include/ - rm -rf $PROTOC_ZIP protoc3 - - # Install protoc-gen-go and protoc-gen-go-grpc - go install google.golang.org/protobuf/cmd/protoc-gen-go@$PROTOC_GEN_VERSION - go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@$PROTOC_GRPC_VERSION - - # Add protoc to the PATH - export PATH=$PATH:/usr/local/bin/protoc - - - name: Check Protobuf is up to Date - if: steps.changes.outputs.proto == 'true' - run: | - for p in $(find . -name "*.pb.go"); do - mv $p $p.tmp - done - - make proto - - for p in $(find . -name "*.pb.go"); do - if ! cmp -s $p $p.tmp; then - echo "Error: Proto file and generated Go file $p are out of sync!" - echo "Here is the difference:" - diff $p $p.tmp || true - echo "Please run 'make proto' with protoc version $PROTOC_VERSION, protoc-gen-go version $PROTOC_GEN_VERSION and protoc-gen-go-grpc version $PROTOC_GRPC_VERSION and commit the changes." - exit 1 - fi - done - - - name: Check Mocks are up to Date - if: steps.changes.outputs.mocks == 'true' - run: | - MOCKERY_VERSION=v2.43.2 - go install github.com/vektra/mockery/v2@$MOCKERY_VERSION - - mv ./pkg/sdk/mocks/sdk.go ./pkg/sdk/mocks/sdk.go.tmp - 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 ./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 - mv ./auth/mocks/service.go ./auth/mocks/service.go.tmp - mv ./auth/mocks/token_client.go ./auth/mocks/token_client.go.tmp - mv ./pkg/events/mocks/publisher.go ./pkg/events/mocks/publisher.go.tmp - mv ./pkg/events/mocks/subscriber.go ./pkg/events/mocks/subscriber.go.tmp - mv ./provision/mocks/service.go ./provision/mocks/service.go.tmp - mv ./pkg/groups/mocks/repository.go ./pkg/groups/mocks/repository.go.tmp - mv ./pkg/groups/mocks/service.go ./pkg/groups/mocks/service.go.tmp - mv ./bootstrap/mocks/service.go ./bootstrap/mocks/service.go.tmp - mv ./bootstrap/mocks/configs.go ./bootstrap/mocks/configs.go.tmp - mv ./invitations/mocks/service.go ./invitations/mocks/service.go.tmp - mv ./invitations/mocks/repository.go ./invitations/mocks/repository.go.tmp - mv ./users/mocks/emailer.go ./users/mocks/emailer.go.tmp - mv ./users/mocks/hasher.go ./users/mocks/hasher.go.tmp - mv ./mqtt/mocks/events.go ./mqtt/mocks/events.go.tmp - mv ./readers/mocks/messages.go ./readers/mocks/messages.go.tmp - mv ./consumers/notifiers/mocks/notifier.go ./consumers/notifiers/mocks/notifier.go.tmp - mv ./consumers/notifiers/mocks/service.go ./consumers/notifiers/mocks/service.go.tmp - mv ./consumers/notifiers/mocks/repository.go ./consumers/notifiers/mocks/repository.go.tmp - mv ./certs/mocks/pki.go ./certs/mocks/pki.go.tmp - mv ./certs/mocks/service.go ./certs/mocks/service.go.tmp - 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 ./pkg/authz/mocks/authz.go ./pkg/authz/mocks/authz.go.tmp - mv ./pkg/authn/mocks/authn.go ./pkg/authn/mocks/authn.go.tmp - - make mocks - - check_mock_changes() { - local file_path=$1 - local tmp_file_path=$1.tmp - local entity_name=$2 - - if ! cmp -s "$file_path" "$tmp_file_path"; then - echo "Error: Generated mocks for $entity_name are out of sync!" - echo "Please run 'make mocks' with mockery version $MOCKERY_VERSION and commit the changes." - exit 1 - fi - } - - check_mock_changes ./pkg/sdk/mocks/sdk.go "SDK ./pkg/sdk/mocks/sdk.go" - 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 ./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" - check_mock_changes ./auth/mocks/service.go "Auth Service ./auth/mocks/service.go" - check_mock_changes ./pkg/authn/mocks/authn.go "Authn Service Client .pkg/authn/mocks/authn.go" - check_mock_changes ./pkg/authz/mocks/authz.go "Authz Service Client .pkg/authz/mocks/authz.go" - check_mock_changes ./pkg/events/mocks/publisher.go "ES Publisher ./pkg/events/mocks/publisher.go" - check_mock_changes ./pkg/events/mocks/subscriber.go "EE Subscriber ./pkg/events/mocks/subscriber.go" - check_mock_changes ./provision/mocks/service.go "Provision Service ./provision/mocks/service.go" - check_mock_changes ./pkg/groups/mocks/repository.go "Groups Repository ./pkg/groups/mocks/repository.go" - check_mock_changes ./pkg/groups/mocks/service.go "Groups Service ./pkg/groups/mocks/service.go" - check_mock_changes ./bootstrap/mocks/service.go "Bootstrap Service ./bootstrap/mocks/service.go" - check_mock_changes ./bootstrap/mocks/configs.go "Bootstrap Repository ./bootstrap/mocks/configs.go" - check_mock_changes ./invitations/mocks/service.go "Invitations Service ./invitations/mocks/service.go" - check_mock_changes ./invitations/mocks/repository.go "Invitations Repository ./invitations/mocks/repository.go" - check_mock_changes ./users/mocks/emailer.go "Users Emailer ./users/mocks/emailer.go" - check_mock_changes ./users/mocks/hasher.go "Users Hasher ./users/mocks/hasher.go" - check_mock_changes ./mqtt/mocks/events.go "MQTT Events Store ./mqtt/mocks/events.go" - check_mock_changes ./readers/mocks/messages.go "Message Readers ./readers/mocks/messages.go" - check_mock_changes ./consumers/notifiers/mocks/notifier.go "Notifiers Notifier ./consumers/notifiers/mocks/notifier.go" - check_mock_changes ./consumers/notifiers/mocks/service.go "Notifiers Service ./consumers/notifiers/mocks/service.go" - check_mock_changes ./consumers/notifiers/mocks/repository.go "Notifiers Repository ./consumers/notifiers/mocks/repository.go" - check_mock_changes ./certs/mocks/pki.go "PKI ./certs/mocks/pki.go" - check_mock_changes ./certs/mocks/service.go "Certs Service ./certs/mocks/service.go" - check_mock_changes ./journal/mocks/repository.go "Journal Repository ./journal/mocks/repository.go" - 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" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9d178422a..b7fe0538e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,14 +23,6 @@ jobs: go-version: 1.22.x cache-dependency-path: "go.sum" - - name: Install protolint - run: | - go install github.com/yoheimuta/protolint/cmd/protolint@latest - - - name: Lint Protobuf Files - run: | - protolint . - - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: @@ -41,14 +33,6 @@ jobs: run: | make all -j $(nproc) - - name: Compile check for rabbitmq - run: | - MG_MESSAGE_BROKER_TYPE=rabbitmq make mqtt - - - name: Compile check for redis - run: | - MG_ES_TYPE=redis make mqtt - run-tests: name: Run tests runs-on: ubuntu-latest @@ -75,15 +59,6 @@ jobs: workflow: - ".github/workflows/tests.yml" - auth: - - "auth/**" - - "cmd/auth/**" - - "auth.proto" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "pkg/ulid/**" - - "pkg/uuid/**" - bootstrap: - "bootstrap/**" - "cmd/bootstrap/**" @@ -93,81 +68,16 @@ jobs: - "pkg/sdk/**" - "pkg/events/**" - certs: - - "certs/**" - - "cmd/certs/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "auth/**" - - "pkg/sdk/**" - - cli: - - "cli/**" - - "cmd/cli/**" - - "pkg/sdk/**" - - coap: - - "coap/**" - - "cmd/coap/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "things/**" - - "pkg/messaging/**" - consumers: - "consumers/**" - "cmd/postgres-writer/**" - "cmd/timescale-writer/**" - "cmd/smpp-notifier/**" - "cmd/smtp-notifier/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "auth/**" - - "pkg/ulid/**" - - "pkg/uuid/**" - - "pkg/messaging/**" - - journal: - - "journal/**" - - "cmd/journal/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "auth/**" - - "pkg/events/**" - - http: - - "http/**" - - "cmd/http/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "things/**" - - "pkg/messaging/**" - - "logger/**" internal: - "internal/**" - invitations: - - "invitations/**" - - "cmd/invitations/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "auth/**" - - "pkg/sdk/**" - - logger: - - "logger/**" - - mqtt: - - "mqtt/**" - - "cmd/mqtt/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "things/**" - - "pkg/messaging/**" - - "logger/**" - - "pkg/events/**" - pkg-errors: - "pkg/errors/**" @@ -175,40 +85,6 @@ jobs: - "pkg/events/**" - "pkg/messaging/**" - pkg-grpcclient: - - "pkg/grpcclient/**" - - pkg-messaging: - - "pkg/messaging/**" - - pkg-sdk: - - "pkg/sdk/**" - - "pkg/errors/**" - - "pkg/groups/**" - - "auth/**" - - "bootstrap/**" - - "certs/**" - - "consumers/**" - - "http/**" - - "internal/*" - - "internal/api/**" - - "internal/apiutil/**" - - "internal/groups/**" - - "invitations/**" - - "provision/**" - - "readers/**" - - "things/**" - - "users/**" - - pkg-transformers: - - "pkg/transformers/**" - - pkg-ulid: - - "pkg/ulid/**" - - pkg-uuid: - - "pkg/uuid/**" - provision: - "provision/**" - "cmd/provision/**" @@ -224,138 +100,30 @@ jobs: - "things/**" - "auth/**" - things: - - "things/**" - - "cmd/things/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "auth/**" - - "pkg/ulid/**" - - "pkg/uuid/**" - - "pkg/events/**" - - users: - - "users/**" - - "cmd/users/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "auth/**" - - "pkg/ulid/**" - - "pkg/uuid/**" - - "pkg/events/**" - - ws: - - "ws/**" - - "cmd/ws/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "things/**" - - "pkg/messaging/**" - - name: Create coverage directory run: | mkdir coverage - - name: Run Journal tests - if: steps.changes.outputs.journal == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/journal.out ./journal/... - - - name: Run auth tests - if: steps.changes.outputs.auth == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/auth.out ./auth/... - - name: Run bootstrap tests if: steps.changes.outputs.bootstrap == 'true' || steps.changes.outputs.workflow == 'true' run: | go test --race -v -count=1 -coverprofile=coverage/bootstrap.out ./bootstrap/... - - name: Run certs tests - if: steps.changes.outputs.certs == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/certs.out ./certs/... - - - name: Run cli tests - if: steps.changes.outputs.cli == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/cli.out ./cli/... - - - name: Run CoAP tests - if: steps.changes.outputs.coap == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/coap.out ./coap/... - - name: Run consumers tests if: steps.changes.outputs.consumers == 'true' || steps.changes.outputs.workflow == 'true' run: | go test --race -v -count=1 -coverprofile=coverage/consumers.out ./consumers/... - - name: Run HTTP tests - if: steps.changes.outputs.http == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/http.out ./http/... - - name: Run internal tests if: steps.changes.outputs.internal == 'true' || steps.changes.outputs.workflow == 'true' run: | go test --race -v -count=1 -coverprofile=coverage/internal.out ./internal/... - - name: Run invitations tests - if: steps.changes.outputs.invitations == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/invitations.out ./invitations/... - - - name: Run logger tests - if: steps.changes.outputs.logger == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/logger.out ./logger/... - - - name: Run MQTT tests - if: steps.changes.outputs.mqtt == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/mqtt.out ./mqtt/... - - name: Run pkg errors tests if: steps.changes.outputs.pkg-errors == 'true' || steps.changes.outputs.workflow == 'true' run: | go test --race -v -count=1 -coverprofile=coverage/pkg-errors.out ./pkg/errors/... - - name: Run pkg events tests - if: steps.changes.outputs.pkg-events == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/pkg-events.out ./pkg/events/... - - - name: Run pkg grpcclient tests - if: steps.changes.outputs.pkg-grpcclient == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/pkg-grpcclient.out ./pkg/grpcclient/... - - - name: Run pkg messaging tests - if: steps.changes.outputs.pkg-messaging == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/pkg-messaging.out ./pkg/messaging/... - - - name: Run pkg sdk tests - if: steps.changes.outputs.pkg-sdk == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/pkg-sdk.out ./pkg/sdk/... - - - name: Run pkg transformers tests - if: steps.changes.outputs.pkg-transformers == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/pkg-transformers.out ./pkg/transformers/... - - - name: Run pkg ulid tests - if: steps.changes.outputs.pkg-ulid == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/pkg-ulid.out ./pkg/ulid/... - - - name: Run pkg uuid tests - if: steps.changes.outputs.pkg-uuid == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/pkg-uuid.out ./pkg/uuid/... - - name: Run provision tests if: steps.changes.outputs.provision == 'true' || steps.changes.outputs.workflow == 'true' run: | @@ -366,21 +134,6 @@ 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' - run: | - go test --race -v -count=1 -coverprofile=coverage/things.out ./things/... - - - name: Run users tests - if: steps.changes.outputs.users == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/users.out ./users/... - - - name: Run WebSocket tests - if: steps.changes.outputs.ws == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/ws.out ./ws/... - - name: Upload coverage uses: codecov/codecov-action@v5 with: diff --git a/Makefile b/Makefile index d0ca3cb20..f849fa7c6 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,7 @@ MG_DOCKER_IMAGE_NAME_PREFIX ?= ghcr.io/absmach/magistrala BUILD_DIR = build -SERVICES = auth users things http coap ws postgres-writer postgres-reader timescale-writer \ - timescale-reader cli bootstrap mqtt provision certs invitations journal re -TEST_API_SERVICES = journal auth bootstrap certs http invitations notifiers provision readers things users -TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES)) +SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-writer timescale-reader DOCKERS = $(addprefix docker_,$(SERVICES)) DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES)) CGO_ENABLED ?= 0 @@ -18,7 +15,7 @@ USER_REPO ?= $(shell git remote get-url origin | sed -e 's/.*\/\([^/]*\)\/\([^/] 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_PROJECT ?= test #$(shell echo $(subst $(space),,$(USER_REPO)) | tr -c -s '[:alnum:][=-=]' '_' | tr '[:upper:]' '[:lower:]') DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config DEFAULT_DOCKER_COMPOSE_COMMAND := up GRPC_MTLS_CERT_FILES_EXISTS = 0 diff --git a/api.go b/api.go deleted file mode 100644 index 0250ccd3d..000000000 --- a/api.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package magistrala - -// Response contains HTTP response specific methods. -type Response interface { - // Code returns HTTP response code. - Code() int - - // Headers returns map of HTTP headers with their values. - Headers() map[string]string - - // Empty indicates if HTTP response has content. - Empty() bool -} diff --git a/api/openapi/auth.yml b/api/openapi/auth.yml deleted file mode 100644 index fde5df18e..000000000 --- a/api/openapi/auth.yml +++ /dev/null @@ -1,833 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.3 -info: - title: Magistrala Auth Service - description: | - This is the Auth Server based on the OpenAPI 3.0 specification. It is the HTTP API for managing platform users. 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: - email: info@abstractmachines.fr - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/main/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:8189 - - url: https://localhost:8189 - -tags: - - name: Keys - description: Everything about your Keys. - externalDocs: - description: Find out more about keys - url: https://docs.magistrala.abstractmachines.fr/ - - - name: Domains - description: Everything about your Domains. - externalDocs: - description: Find out more about domains - url: https://docs.magistrala.abstractmachines.fr/ - - - name: Health - description: Service health check endpoint. - externalDocs: - description: Find out more about health check - url: https://docs.magistrala.abstractmachines.fr/ - - -paths: - /domains: - post: - tags: - - Domains - summary: Adds new domain - description: | - Adds new domain. - requestBody: - $ref: "#/components/requestBodies/DomainCreateReq" - responses: - "201": - $ref: "#/components/responses/DomainCreateRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "409": - description: Failed due to using an existing alias. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - get: - summary: Retrieves list of domains. - description: | - Retrieves list of domains that the user have access. - parameters: - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/Status" - - $ref: "#/components/parameters/DomainName" - - $ref: "#/components/parameters/Permission" - tags: - - Domains - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/DomainsPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /domains/{domainID}: - get: - summary: Retrieves domain information - description: | - Retrieves a specific domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/DomainRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - patch: - summary: Updates name, metadata, tags and alias of the domain. - description: | - Updates name, metadata, tags and alias of the domain. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - requestBody: - $ref: "#/components/requestBodies/DomainUpdateReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/DomainRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access to domain id. - "404": - description: Failed due to non existing domain. - "415": - description: Missing or invalid content type. - "500": - $ref: "#/components/responses/ServiceError" - - /domains/{domainID}/permissions: - get: - summary: Retrieves user permissions on domain. - description: | - Retrieves user permissions on domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/DomainPermissionRes" - "400": - description: Malformed entity specification. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed authorization over the domain. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /domains/{domainID}/enable: - post: - summary: Enables a domain - description: | - Enables a specific domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - security: - - bearerAuth: [] - responses: - "200": - description: Successfully enabled domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /domains/{domainID}/disable: - post: - summary: Disable a domain - description: | - Disable a specific domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - security: - - bearerAuth: [] - responses: - "200": - description: Successfully disabled domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /domains/{domainID}/freeze: - post: - summary: Freeze a domain - description: | - Freeze a specific domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - security: - - bearerAuth: [] - responses: - "200": - description: Successfully freezed domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /domains/{domainID}/users/assign: - post: - summary: Assign users to domain - description: | - Assign users to domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - requestBody: - $ref: "#/components/requestBodies/AssignUserReq" - security: - - bearerAuth: [] - responses: - "200": - description: Users successfully assigned to domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "409": - description: Conflict of data. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /domains/{domainID}/users/unassign: - post: - summary: Unassign user from domain - description: | - Unassign user from domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - requestBody: - $ref: "#/components/requestBodies/UnassignUsersReq" - security: - - bearerAuth: [] - responses: - "204": - description: Users successfully unassigned from domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "409": - description: Conflict of data. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /keys: - post: - operationId: issueKey - tags: - - Keys - summary: Issue API key - description: | - Generates a new API key. Thew new API key will - be uniquely identified by its ID. - requestBody: - $ref: "#/components/requestBodies/KeyRequest" - responses: - "201": - description: Issued new key. - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "409": - description: Failed due to using already existing ID. - "415": - description: Missing or invalid content type. - "500": - $ref: "#/components/responses/ServiceError" - - /keys/{keyID}: - get: - operationId: getKey - summary: Gets API key details. - description: | - Gets API key details for the given key. - tags: - - Keys - parameters: - - $ref: "#/components/parameters/ApiKeyId" - responses: - "200": - $ref: "#/components/responses/KeyRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "500": - $ref: "#/components/responses/ServiceError" - - delete: - operationId: revokeKey - summary: Revoke API key - description: | - Revoke API key identified by the given ID. - tags: - - Keys - parameters: - - $ref: "#/components/parameters/ApiKeyId" - responses: - "204": - description: Key revoked. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}/domains: - get: - tags: - - Domains - summary: Lists domains associated with a user. - description: | - Retrieves a list of domains associated with a user. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "users.yml#/components/parameters/UserID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/Status" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/DomainsPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - This endpoint is available only for administrators. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /health: - get: - summary: Retrieves service health check info. - tags: - - Health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - schemas: - DomainReqObj: - type: object - properties: - name: - type: string - example: domainName - description: Domain name. - tags: - type: array - minItems: 0 - items: - type: string - example: ["tag1", "tag2"] - description: domain tags. - metadata: - type: object - example: { "domain": "example.com" } - description: Arbitrary, object-encoded domain's data. - alias: - type: string - example: domain alias - description: Domain alias. - required: - - name - - alias - Domain: - type: object - properties: - id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Domain unique identifier. - name: - type: string - example: domainName - description: Domain name. - tags: - type: array - minItems: 0 - items: - type: string - example: ["tag1", "tag2"] - description: domain tags. - metadata: - type: object - example: { "domain": "example.com" } - description: Arbitrary, object-encoded domain's data. - alias: - type: string - example: domain alias - description: Domain alias. - status: - type: string - description: Domain Status - format: string - example: enabled - created_by: - type: string - format: uuid - example: "0d837f56-3f8a-4e2a-9359-6347d0fc9f06 " - description: User ID of the user who created the domain. - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the domain was created. - updated_by: - type: string - format: uuid - example: "80f66b77-ed74-4e74-9f88-6cce9a0a3049" - description: User ID of the user who last updated the domain. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the domain was last updated. - xml: - name: domain - - DomainsPage: - type: object - properties: - domains: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Domain" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - domains - - total - - offset - DomainUpdate: - type: object - properties: - name: - type: string - example: domainName - description: Domain name. - tags: - type: array - minItems: 0 - items: - type: string - example: ["tag1", "tag2"] - description: domain tags. - metadata: - type: object - example: { "domain": "example.com" } - description: Arbitrary, object-encoded thing's data. - alias: - type: string - example: domain alias - description: Domain alias. - Permissions: - type: object - properties: - permissions: - type: array - minItems: 0 - items: - type: string - description: Permissions - - AssignUserDomainRelationReq: - type: object - properties: - user_ids: - type: array - minItems: 1 - items: - type: string - description: Users IDs - example: - [ - "5dc1ce4b-7cc9-4f12-98a6-9d74cc4980bb", - "c01ed106-e52d-4aa4-bed3-39f360177cfa", - ] - relation: - type: string - enum: ["administrator", "editor", "contributor", "member", "guest"] - example: "administrator" - description: Policy relations. - required: - - user_ids - - relation - UnassignUserDomainRelationReq: - type: object - properties: - user_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User unique identifier. - required: - - user_id - Key: - type: object - properties: - id: - type: string - format: uuid - example: "c5747f2f-2a7c-4fe1-b41a-51a5ae290945" - description: API key unique identifier - issuer_id: - type: string - format: uuid - example: "9118de62-c680-46b7-ad0a-21748a52833a" - description: In ID of the entity that issued the token. - type: - type: integer - example: 0 - description: API key type. Keys of different type are processed differently. - subject: - type: string - format: string - example: "test@example.com" - description: User's email or service identifier of API key subject. - issued_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the key is generated. - expires_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the Key expires. If this field is missing, - that means that Key is valid indefinitely. - - parameters: - DomainID: - name: domainID - description: Unique domain identifier. - in: path - schema: - type: string - format: uuid - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - Status: - name: status - description: Domain status. - in: query - schema: - type: string - default: enabled - required: false - example: enabled - DomainName: - name: name - description: Domain's name. - in: query - schema: - type: string - required: false - example: "domainName" - Permission: - name: permission - description: permission. - in: query - schema: - type: string - required: false - example: "edit" - ApiKeyId: - name: keyID - description: API Key ID. - in: path - schema: - type: string - format: uuid - required: true - Limit: - name: limit - description: Size of the subset to retrieve. - in: query - schema: - type: integer - default: 10 - maximum: 100 - minimum: 1 - required: false - Offset: - name: offset - description: Number of items to skip during retrieval. - in: query - schema: - type: integer - default: 0 - minimum: 0 - required: false - Metadata: - name: metadata - description: Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json. - in: query - required: false - schema: - type: object - additionalProperties: {} - Type: - name: type - description: The type of the API Key. - in: query - schema: - type: integer - default: 0 - minimum: 0 - required: false - Subject: - name: subject - description: The subject of an API Key - in: query - schema: - type: string - required: false - - requestBodies: - DomainCreateReq: - description: JSON-formatted document describing the new domain to be registered - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/DomainReqObj" - DomainUpdateReq: - description: JSON-formated document describing the name, alias, tags, and metadata of the domain to be updated - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/DomainUpdate" - AssignUserReq: - description: JSON-formated document describing the policy related to assigning users to a domain - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AssignUserDomainRelationReq" - - UnassignUsersReq: - description: JSON-formated document describing the policy related to unassigning user from a domain - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/UnassignUserDomainRelationReq" - - KeyRequest: - description: JSON-formatted document describing key request. - required: true - content: - application/json: - schema: - type: object - properties: - type: - type: integer - example: 0 - description: API key type. Keys of different type are processed differently. - duration: - type: number - format: integer - example: 23456 - description: Number of seconds issued token is valid for. - - responses: - ServiceError: - description: Unexpected server-side error occurred. - - DomainCreateRes: - description: Create new domain. - headers: - Location: - schema: - type: string - format: url - description: Registered domain relative URL in the format `/domains/` - content: - application/json: - schema: - $ref: "#/components/schemas/Domain" - - DomainRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Domain" - DomainPermissionRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Permissions" - DomainsPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/DomainsPage" - - KeyRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Key" - links: - revoke: - operationId: revokeKey - parameters: - keyID: $response.body#/id - - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "./schemas/HealthInfo.yml" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * Users access: "Authorization: Bearer " - -security: - - bearerAuth: [] diff --git a/api/openapi/certs.yml b/api/openapi/certs.yml deleted file mode 100644 index b5ced9377..000000000 --- a/api/openapi/certs.yml +++ /dev/null @@ -1,313 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.1 -info: - title: Magistrala Certs service - description: | - HTTP API for Certs service - Some useful links: - - [The Magistrala repository](https://github.com/absmach/magistrala) - contact: - email: info@abstractmachines.fr - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/main/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:9019 - - url: https://localhost:9019 - -tags: - - name: certs - description: Everything about your Certs - externalDocs: - description: Find out more about certs - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /{domainID}/certs: - post: - operationId: createCert - summary: Creates a certificate for thing - description: Creates a certificate for thing - tags: - - certs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - requestBody: - $ref: "#/components/requestBodies/CertReq" - responses: - "201": - description: Created - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /{domainID}/certs/{certID}: - get: - operationId: getCert - summary: Retrieves a certificate - description: | - Retrieves a certificate for a given cert ID. - tags: - - certs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/CertID" - responses: - "200": - $ref: "#/components/responses/CertRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: | - Failed to retrieve corresponding certificate. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - delete: - operationId: revokeCert - summary: Revokes a certificate - description: | - Revokes a certificate for a given cert ID. - tags: - - certs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/CertID" - responses: - "200": - $ref: "#/components/responses/RevokeRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: | - Failed to revoke corresponding certificate. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /{domainID}/serials/{thingID}: - get: - operationId: getSerials - summary: Retrieves certificates' serial IDs - description: | - Retrieves a list of certificates' serial IDs for a given thing ID. - tags: - - certs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - responses: - "200": - $ref: "#/components/responses/SerialsPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: | - Failed to retrieve corresponding certificates. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /health: - get: - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - parameters: - ThingID: - name: thingID - description: Thing ID - in: path - schema: - type: string - format: uuid - required: true - CertID: - name: certID - description: Serial of certificate - in: path - schema: - type: string - format: uuid - required: true - - schemas: - Cert: - type: object - properties: - thing_id: - type: string - format: uuid - description: Corresponding Magistrala Thing ID. - client_cert: - type: string - description: Client Certificate. - client_key: - type: string - description: Key for the client_cert. - issuing_ca: - type: string - description: CA Certificate that is used to issue client certs, usually intermediate. - serial: - type: string - description: Certificate serial - expire: - type: string - description: Certificate expiry date - Serial: - type: object - properties: - serial: - type: string - description: Certificate serial - CertsPage: - type: object - properties: - certs: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Cert" - total: - type: integer - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - description: Maximum number of items to return in one page. - SerialsPage: - type: object - properties: - serials: - type: array - description: Certificate serials IDs. - minItems: 0 - uniqueItems: true - items: - type: string - total: - type: integer - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - description: Maximum number of items to return in one page. - Revoke: - type: object - properties: - revocation_time: - type: string - description: Certificate revocation time - - 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. - x509 and ECC certificates are supported when using when Vault is used as PKI. - content: - application/json: - schema: - type: object - required: - - thing_id - - ttl - properties: - thing_id: - type: string - format: uuid - ttl: - type: string - example: "10h" - - responses: - ServiceError: - description: Unexpected server-side error occurred. - CertRes: - description: Certificate data. - content: - application/json: - schema: - $ref: "#/components/schemas/Cert" - links: - serial: - operationId: getSerials - parameters: - thingID: $response.body#/thing_id - delete: - operationId: revokeCert - parameters: - certID: $response.body#/serial - CertsPageRes: - description: Certificates page. - content: - application/json: - schema: - $ref: "#/components/schemas/CertsPage" - SerialsPageRes: - description: Serials page. - content: - application/json: - schema: - $ref: "#/components/schemas/SerialsPage" - RevokeRes: - description: Certificate revoked. - content: - application/json: - schema: - $ref: "#/components/schemas/Revoke" - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "./schemas/HealthInfo.yml" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * Users access: "Authorization: Bearer " - -security: - - bearerAuth: [] diff --git a/api/openapi/http.yml b/api/openapi/http.yml deleted file mode 100644 index f366458bd..000000000 --- a/api/openapi/http.yml +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.1 -info: - title: Magistrala http adapter - description: | - HTTP API for sending messages through communication channels. - Some useful links: - - [The Magistrala repository](https://github.com/absmach/magistrala) - contact: - email: info@abstractmachines.fr - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/main/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:8008 - - url: https://localhost:8008 - -tags: - - name: messages - description: Everything about your Messages - externalDocs: - description: Find out more about messages - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /channels/{id}/messages: - post: - summary: Sends message to the communication channel - description: | - Sends message to the communication channel. Messages can be sent as - JSON formatted SenML or as blob. - tags: - - messages - parameters: - - $ref: "#/components/parameters/ID" - requestBody: - $ref: "#/components/requestBodies/MessageReq" - responses: - "202": - description: Message is accepted for processing. - "400": - description: Message discarded due to its malformed content. - "401": - description: Missing or invalid access token provided. - "404": - description: Message discarded due to invalid channel id. - "415": - description: Message discarded due to invalid or missing content type. - "500": - $ref: "#/components/responses/ServiceError" - /health: - get: - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - schemas: - SenMLRecord: - type: object - properties: - bn: - type: string - description: Base Name - bt: - type: number - format: double - description: Base Time - bu: - type: number - format: double - description: Base Unit - bv: - type: number - format: double - description: Base Value - bs: - type: number - format: double - description: Base Sum - bver: - type: number - format: double - description: Version - n: - type: string - description: Name - u: - type: string - description: Unit - v: - type: number - format: double - description: Value - vs: - type: string - description: String Value - vb: - type: boolean - description: Boolean Value - vd: - type: string - description: Data Value - s: - type: number - format: double - description: Value Sum - t: - type: number - format: double - description: Time - ut: - type: number - format: double - description: Update Time - SenMLArray: - type: array - items: - $ref: "#/components/schemas/SenMLRecord" - - parameters: - ID: - name: id - description: Unique channel identifier. - in: path - schema: - type: string - format: uuid - required: true - - requestBodies: - MessageReq: - description: | - Message to be distributed. Since the platform expects messages to be - properly formatted SenML in order to be post-processed, clients are - obliged to specify Content-Type header for each published message. - Note that all messages that aren't SenML will be accepted and published, - but no post-processing will be applied. - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/SenMLArray" - - responses: - ServiceError: - description: Unexpected server-side error occurred. - - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "./schemas/HealthInfo.yml" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: uuid - description: | - * Thing access: "Authorization: Thing " - - basicAuth: - type: http - scheme: basic - description: | - * Things access: "Authorization: Basic " - -security: - - bearerAuth: [] - - basicAuth: [] diff --git a/api/openapi/invitations.yml b/api/openapi/invitations.yml deleted file mode 100644 index 541e36859..000000000 --- a/api/openapi/invitations.yml +++ /dev/null @@ -1,537 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.3 -info: - title: Magistrala Invitations Service - description: | - This is the Invitations Server based on the OpenAPI 3.0 specification. It is the HTTP API for managing platform invitations. 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: - email: info@abstractmachines.fr - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/main/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:9020 - - url: https://localhost:9020 - -tags: - - name: Invitations - description: Everything about your Invitations - externalDocs: - description: Find out more about Invitations - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /invitations: - post: - operationId: sendInvitation - tags: - - Invitations - summary: Send invitation - description: | - Send invitation to user to join domain. - requestBody: - $ref: "#/components/requestBodies/SendInvitationReq" - security: - - bearerAuth: [] - responses: - "201": - description: Invitation sent. - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "500": - $ref: "#/components/responses/ServiceError" - - get: - operationId: listInvitations - tags: - - Invitations - summary: List invitations - description: | - Retrieves a list of invitations. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/UserID" - - $ref: "#/components/parameters/InvitedBy" - - $ref: "#/components/parameters/DomainID" - - $ref: "#/components/parameters/Relation" - - $ref: "#/components/parameters/State" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/InvitationPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - This endpoint is available only for administrators. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /invitations/accept: - post: - operationId: acceptInvitation - summary: Accept invitation - description: | - Current logged in user accepts invitation to join domain. - tags: - - Invitations - security: - - bearerAuth: [] - requestBody: - $ref: "#/components/requestBodies/AcceptInvitationReq" - responses: - "204": - description: Invitation accepted. - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "500": - $ref: "#/components/responses/ServiceError" - - /invitations/reject: - post: - operationId: rejectInvitation - summary: Reject invitation - description: | - Current logged in user rejects invitation to join domain. - tags: - - Invitations - security: - - bearerAuth: [] - requestBody: - $ref: "#/components/requestBodies/AcceptInvitationReq" - responses: - "204": - description: Invitation rejected. - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "500": - $ref: "#/components/responses/ServiceError" - - /invitations/{user_id}/{domain_id}: - get: - operationId: getInvitation - summary: Retrieves a specific invitation - description: | - Retrieves a specific invitation that is identifier by the user ID and domain ID. - tags: - - Invitations - parameters: - - $ref: "#/components/parameters/user_id" - - $ref: "#/components/parameters/domain_id" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/InvitationRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - delete: - operationId: deleteInvitation - summary: Deletes a specific invitation - description: | - Deletes a specific invitation that is identifier by the user ID and domain ID. - tags: - - Invitations - parameters: - - $ref: "#/components/parameters/user_id" - - $ref: "#/components/parameters/domain_id" - security: - - bearerAuth: [] - responses: - "204": - description: Invitation deleted. - "400": - description: Failed due to malformed JSON. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing user. - "401": - description: Missing or invalid access token provided. - "500": - $ref: "#/components/responses/ServiceError" - - /health: - get: - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - schemas: - SendInvitationReqObj: - type: object - properties: - user_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User unique identifier. - domain_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Domain unique identifier. - relation: - type: string - enum: - - administrator - - editor - - contributor - - member - - guest - - domain - - parent_group - - role_group - - group - - platform - example: editor - description: Relation between user and domain. - resend: - type: boolean - example: true - description: Resend invitation. - required: - - user_id - - domain_id - - relation - - Invitation: - type: object - properties: - invited_by: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User unique identifier. - user_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User unique identifier. - domain_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Domain unique identifier. - relation: - type: string - enum: - - administrator - - editor - - contributor - - member - - guest - - domain - - parent_group - - role_group - - group - - platform - example: editor - description: Relation between user and domain. - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the group was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the group was created. - confirmed_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the group was created. - xml: - name: invitation - - InvitationPage: - type: object - properties: - invitations: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Invitation" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - invitations - - total - - offset - - Error: - type: object - properties: - error: - type: string - description: Error message - example: { "error": "malformed entity specification" } - - HealthRes: - type: object - properties: - status: - type: string - description: Service status. - enum: - - pass - version: - type: string - description: Service version. - example: 0.14.0 - commit: - type: string - description: Service commit hash. - example: 7d6f4dc4f7f0c1fa3dc24eddfb18bb5073ff4f62 - description: - type: string - description: Service description. - example: service - build_time: - type: string - description: Service build time. - example: 1970-01-01_00:00:00 - - parameters: - Offset: - name: offset - description: Number of items to skip during retrieval. - in: query - schema: - type: integer - default: 0 - minimum: 0 - required: false - example: "0" - - Limit: - name: limit - description: Size of the subset to retrieve. - in: query - schema: - type: integer - default: 10 - maximum: 10 - minimum: 1 - required: false - example: "10" - - UserID: - name: user_id - description: Unique user identifier. - in: query - schema: - type: string - format: uuid - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - user_id: - name: user_id - description: Unique user identifier. - in: path - schema: - type: string - format: uuid - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - DomainID: - name: domain_id - description: Unique identifier for a domain. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - domain_id: - name: domain_id - description: Unique identifier for a domain. - in: path - schema: - type: string - format: uuid - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - InvitedBy: - name: invited_by - description: Unique identifier for a user that invited the user. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - Relation: - name: relation - description: Relation between user and domain. - in: query - schema: - type: string - enum: - - administrator - - editor - - contributor - - member - - guest - - domain - - parent_group - - role_group - - group - - platform - required: false - example: editor - - State: - name: state - description: Invitation state. - in: query - schema: - type: string - enum: - - pending - - accepted - - all - required: false - example: accepted - - requestBodies: - SendInvitationReq: - description: JSON-formatted document describing request for sending invitation - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/SendInvitationReqObj" - - AcceptInvitationReq: - description: JSON-formatted document describing request for accepting invitation - required: true - content: - application/json: - schema: - type: object - properties: - domain_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Domain unique identifier. - required: - - domain_id - - responses: - InvitationRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Invitation" - links: - delete: - operationId: deleteInvitation - parameters: - user_id: $response.body#/user_id - domain_id: $response.body#/domain_id - - InvitationPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/InvitationPage" - - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "#/components/schemas/HealthRes" - - ServiceError: - description: Unexpected server-side error occurred. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * User access: "Authorization: Bearer " - -security: - - bearerAuth: [] diff --git a/api/openapi/journal.yml b/api/openapi/journal.yml deleted file mode 100644 index 9cea4a1f1..000000000 --- a/api/openapi/journal.yml +++ /dev/null @@ -1,344 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.3 -info: - title: Magistrala Journal Log Service - description: | - This is the Journal Log Server based on the OpenAPI 3.0 specification. It is the HTTP API for viewing journal log history. 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: - email: info@mainflux.com - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/master/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:9021 - - url: https://localhost:9021 - -tags: - - name: journal-log - description: Everything about your Journal Log - externalDocs: - description: Find out more about Journal Log - url: http://docs.mainflux.io/ - -paths: - /journal/user/{userID}: - get: - tags: - - journal-log - summary: List user journal log - description: | - Retrieves a list of journal. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "#/components/parameters/user_id" - - $ref: "#/components/parameters/offset" - - $ref: "#/components/parameters/limit" - - $ref: "#/components/parameters/operation" - - $ref: "#/components/parameters/with_attributes" - - $ref: "#/components/parameters/with_metadata" - - $ref: "#/components/parameters/from" - - $ref: "#/components/parameters/to" - - $ref: "#/components/parameters/dir" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/JournalsPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/journal/{entityType}/{id}: - get: - tags: - - journal-log - summary: List entity journal log - description: | - Retrieves a list of journal. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "#/components/parameters/domain_id" - - $ref: "#/components/parameters/entity_type" - - $ref: "#/components/parameters/id" - - $ref: "#/components/parameters/offset" - - $ref: "#/components/parameters/limit" - - $ref: "#/components/parameters/operation" - - $ref: "#/components/parameters/with_attributes" - - $ref: "#/components/parameters/with_metadata" - - $ref: "#/components/parameters/from" - - $ref: "#/components/parameters/to" - - $ref: "#/components/parameters/dir" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/JournalsPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /health: - get: - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - schemas: - Journal: - type: object - properties: - operation: - type: string - example: user.create - description: Journal operation. - occurred_at: - type: string - format: date-time - example: "2024-01-11T12:05:07.449053Z" - description: Time when the journal occurred. - attributes: - type: object - description: Journal attributes. - example: - { - "created_at": "2024-06-12T11:34:32.991591Z", - "id": "29d425c8-542b-4614-8a4d-a5951945d720", - "identity": "Gawne-Havlicek@email.com", - "name": "Newgard-Frisina", - "status": "enabled", - "updated_at": "2024-06-12T11:34:33.116795Z", - "updated_by": "ad228f20-4741-47c5-bef7-d871b541c019", - } - metadata: - type: object - description: Journal payload. - example: { "Update": "Calvo-Felkins" } - xml: - name: journal - - JournalPage: - type: object - properties: - journals: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Journal" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - journals - - total - - offset - - Error: - type: object - properties: - error: - type: string - description: Error message - example: { "error": "malformed entity specification" } - - parameters: - domain_id: - name: domainID - description: Unique identifier for a domain. - in: path - schema: - type: string - format: uuid - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - entity_type: - name: entityType - description: Type of entity, e.g. user, group, thing, etc.entityType - in: path - schema: - type: string - enum: - - group - - thing - - channel - required: true - example: group - - user_id: - name: userID - description: Unique identifier for a user. - in: path - schema: - type: string - format: uuid - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - id: - name: id - description: Unique identifier for an entity, e.g. group, channel or thing. Used together with entity_type. - in: path - schema: - type: string - format: uuid - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - offset: - name: offset - description: Number of items to skip during retrieval. - in: query - schema: - type: integer - default: 0 - minimum: 0 - required: false - example: "0" - - limit: - name: limit - description: Size of the subset to retrieve. - in: query - schema: - type: integer - default: 10 - maximum: 10 - minimum: 1 - required: false - example: "10" - - operation: - name: operation - description: Journal operation. - in: query - schema: - type: string - required: false - example: user.create - - with_attributes: - name: with_attributes - description: Include journal attributes. - in: query - schema: - type: boolean - required: false - example: true - - with_metadata: - name: with_metadata - description: Include journal metadata. - in: query - schema: - type: boolean - required: false - example: true - - from: - name: from - description: Start date in unix time. - in: query - schema: - type: string - format: int64 - required: false - example: 1966777289 - - to: - name: to - description: End date in unix time. - in: query - schema: - type: string - format: int64 - required: false - example: 1966777289 - - dir: - name: dir - description: Sort direction. - in: query - schema: - type: string - enum: - - asc - - desc - required: false - example: desc - - responses: - JournalsPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/JournalPage" - - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "./schemas/HealthInfo.yml" - - ServiceError: - description: Unexpected server-side error occurred. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * User access: "Authorization: Bearer " - -security: - - bearerAuth: [] diff --git a/api/openapi/provision.yml b/api/openapi/provision.yml deleted file mode 100644 index 9b814e8bc..000000000 --- a/api/openapi/provision.yml +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.1 -info: - title: Magistrala Provision service - description: | - HTTP API for Provision service - Some useful links: - - [The Magistrala repository](https://github.com/absmach/magistrala) - contact: - email: info@abstracmachines.fr - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/main/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:9016 - - url: https://localhost:9016 - -tags: - - name: provision - description: Everything about your Provision - externalDocs: - description: Find out more about provision - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /{domainID}/mapping: - post: - summary: Adds new device to proxy - description: Adds new device to proxy - tags: - - provision - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - requestBody: - $ref: "#/components/requestBodies/ProvisionReq" - responses: - "201": - description: Created - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - get: - summary: Gets current mapping. - description: Gets current mapping. This can be used in UI - so that when bootstrap config is created from UI matches - configuration created with provision service. - tags: - - provision - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - responses: - "200": - $ref: "#/components/responses/ProvisionRes" - "401": - description: Missing or invalid access token provided. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /health: - get: - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - requestBodies: - ProvisionReq: - description: MAC address of device or other identifier - content: - application/json: - schema: - type: object - required: - - external_id - - external_key - properties: - external_id: - type: string - external_key: - type: string - name: - type: string - - responses: - ServiceError: - description: Unexpected server-side error occurred. - ProvisionRes: - description: Current mapping JSON representation. - content: - application/json: - schema: - type: object - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "./schemas/HealthInfo.yml" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * Users access: "Authorization: Bearer " - -security: - - bearerAuth: [] diff --git a/api/openapi/things.yml b/api/openapi/things.yml deleted file mode 100644 index 4550ef461..000000000 --- a/api/openapi/things.yml +++ /dev/null @@ -1,2067 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.3 -info: - title: Magistrala Things 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. - Some useful links: - - [The Magistrala repository](https://github.com/absmach/magistrala) - contact: - email: info@abstractmachines.fr - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/main/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:9000 - - url: https://localhost:9000 - -tags: - - name: Things - description: Everything about your Things - externalDocs: - description: Find out more about things - url: https://docs.magistrala.abstractmachines.fr/ - - name: Channels - description: Everything about your Channels - externalDocs: - description: Find out more about things channels - url: https://docs.magistrala.abstractmachines.fr/ - - name: Policies - description: Access to things policies - externalDocs: - description: Find out more about things policies - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /{domainID}/things: - post: - operationId: createThing - tags: - - Things - summary: Adds new thing - description: | - Adds new thing to the list of things owned by user identified using - the provided access token. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - requestBody: - $ref: "#/components/requestBodies/ThingCreateReq" - responses: - "201": - $ref: "#/components/responses/ThingCreateRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - get: - operationId: listThings - tags: - - Things - summary: Retrieves things - description: | - Retrieves a list of things. Due to performance concerns, data - is retrieved in subsets. The API things must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/Status" - - $ref: "#/components/parameters/ThingName" - - $ref: "#/components/parameters/Tags" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ThingPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/bulk: - post: - operationId: bulkCreateThings - summary: Bulk provisions new things - description: | - Adds new things to the list of things owned by user identified using - the provided access token. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - tags: - - Things - requestBody: - $ref: "#/components/requestBodies/ThingsCreateReq" - responses: - "200": - $ref: "#/components/responses/ThingPageRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/{thingID}: - get: - operationId: getThing - summary: Retrieves thing info - description: | - Retrieves a specific thing that is identifier by the thing ID. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ThingRes" - "400": - description: Failed due to malformed domain ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - patch: - operationId: updateThing - summary: Updates name and metadata of the thing. - 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 - cannot be changed. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - requestBody: - $ref: "#/components/requestBodies/ThingUpdateReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ThingRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing thing. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - delete: - summary: Delete thing for a thing 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. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - security: - - bearerAuth: [] - responses: - "204": - description: Thing deleted. - "400": - description: Failed due to malformed domain ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access to thing id. - "404": - description: Missing thing. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/{thingID}/tags: - patch: - operationId: updateThingTags - summary: Updates tags the thing. - description: | - Updates tags of the thing with provided ID. Tags is updated using - authorization token and the new tags received in request. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - requestBody: - $ref: "#/components/requestBodies/ThingUpdateTagsReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ThingRes" - "400": - description: Failed due to malformed JSON. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing thing. - "401": - description: Missing or invalid access token provided. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/{thingID}/secret: - patch: - operationId: updateThingSecret - summary: Updates secret of the identified thing. - description: | - Updates secret of the identified in thing. Secret is updated using - authorization token and the new received info. Update is performed by replacing current key with a new one. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - requestBody: - $ref: "#/components/requestBodies/ThingUpdateSecretReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ThingRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing thing. - "409": - description: Specified key already exists. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/{thingID}/disable: - post: - operationId: disableThing - summary: Disables a thing - description: | - Disables a specific thing that is identifier by the thing ID. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ThingRes" - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to already disabled thing. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/{thingID}/enable: - post: - operationId: enableThing - summary: Enables a thing - description: | - Enables a specific thing that is identifier by the thing ID. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ThingRes" - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to already enabled thing. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/{thingID}/share: - post: - operationId: shareThing - summary: Shares a thing - description: | - Shares a specific thing that is identified by the thing ID. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - requestBody: - $ref: "#/components/requestBodies/ShareThingReq" - security: - - bearerAuth: [] - responses: - "200": - description: Thing shared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/{thingID}/unshare: - post: - operationId: unshareThing - summary: Unshares a thing - description: | - Unshares a specific thing that is identified by the thing ID. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - requestBody: - $ref: "#/components/requestBodies/ShareThingReq" - security: - - bearerAuth: [] - responses: - "200": - description: Thing unshared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/things: - get: - operationId: listThingsInaChannel - summary: List of things connected to specified channel - description: | - Retrieves list of things connected to specified channel with pagination - metadata. - tags: - - Things - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Connected" - responses: - "200": - $ref: "#/components/responses/ThingsPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels: - post: - operationId: createChannel - tags: - - Channels - summary: Creates new channel - description: | - Creates new channel in domain. - requestBody: - $ref: "#/components/requestBodies/ChannelCreateReq" - security: - - bearerAuth: [] - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - responses: - "201": - $ref: "#/components/responses/ChannelCreateRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - get: - operationId: listChannels - 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 - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - tags: - - Channels - security: - - bearerAuth: [] - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/ChannelName" - responses: - "200": - $ref: "#/components/responses/ChannelPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Channel does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}: - get: - operationId: getChannel - summary: Retrieves channel info. - description: | - Gets info on a channel specified by id. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ChannelRes" - "400": - description: Failed due to malformed channel's or domain ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Channel does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - put: - operationId: updateChannel - summary: Updates channel data. - description: | - Update is performed by replacing the current resource data with values - provided in a request payload. Note that the channel's ID will not be - affected. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - security: - - bearerAuth: [] - requestBody: - $ref: "#/components/requestBodies/ChannelUpdateReq" - responses: - "200": - $ref: "#/components/responses/ChannelRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Channel does not exist. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - delete: - summary: Delete channel for given channel id. - description: | - Delete channel remove given channel id from repo - and removes all the policies related to channel. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - security: - - bearerAuth: [] - responses: - "204": - description: Channel deleted. - "400": - description: Failed due to malformed domain ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access to thing id. - "404": - description: A non-existent entity request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/enable: - post: - operationId: enableChannel - summary: Enables a channel - description: | - Enables a specific channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ChannelRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to already enabled channel. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/disable: - post: - operationId: disableChannel - summary: Disables a channel - description: | - Disables a specific channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/ChannelRes" - "400": - description: Failed due to malformed channel's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to already disabled channel. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/users/assign: - post: - operationId: assignUsersToChannel - summary: Assigns a member to a channel - description: | - Assigns a specific member to a channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - requestBody: - $ref: "#/components/requestBodies/AssignUserReq" - security: - - bearerAuth: [] - responses: - "200": - description: Thing shared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/users/unassign: - post: - operationId: unassignUsersFromChannel - summary: Unassigns a member from a channel - description: | - Unassigns a specific member from a channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - requestBody: - $ref: "#/components/requestBodies/AssignUserReq" - security: - - bearerAuth: [] - responses: - "204": - description: Thing unshared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/groups/assign: - post: - operationId: assignGroupsToChannel - summary: Assigns a member to a channel - description: | - Assigns a specific member to a channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - requestBody: - $ref: "#/components/requestBodies/AssignUsersReq" - security: - - bearerAuth: [] - responses: - "200": - description: Thing shared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/groups/unassign: - post: - operationId: unassignGroupsFromChannel - summary: Unassigns a member from a channel - description: | - Unassigns a specific member from a channel that is identifier by the channel ID. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/chanID" - requestBody: - $ref: "#/components/requestBodies/AssignUsersReq" - security: - - bearerAuth: [] - responses: - "204": - description: Thing unshared. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/things/{thingID}/channels: - get: - operationId: listChannelsConnectedToThing - summary: List of channels connected to specified thing - description: | - Retrieves list of channels connected to specified thing with pagination - metadata. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ThingID" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Limit" - responses: - "200": - $ref: "#/components/responses/ChannelPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Thing does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/users/{memberID}/channels: - get: - operationId: listChannelsConnectedToUser - summary: List of channels connected to specified user - description: | - Retrieves list of channels connected to specified user with pagination - metadata. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/MemberID" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Limit" - responses: - "200": - $ref: "#/components/responses/ChannelPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Thing does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{memberID}/channels: - get: - operationId: listChannelsConnectedToGroup - summary: List of channels connected to specified group - description: | - Retrieves list of channels connected to specified group with pagination - metadata. - tags: - - Channels - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/MemberID" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Limit" - responses: - "200": - $ref: "#/components/responses/ChannelPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Thing does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/connect: - post: - operationId: connectThingsAndChannels - summary: Connects thing 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. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - tags: - - Policies - requestBody: - $ref: "#/components/requestBodies/ConnCreateReq" - responses: - "201": - $ref: "#/components/responses/ConnCreateRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Entity already exist. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/disconnect: - post: - operationId: disconnectThingsAndChannels - summary: Disconnect things 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. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - tags: - - Policies - requestBody: - $ref: "#/components/requestBodies/DisconnReq" - responses: - "204": - $ref: "#/components/responses/DisconnRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/things/{thingID}/connect: - post: - operationId: connectThingToChannel - summary: Connects a thing to a channel - description: | - Connects a specific thing 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" - responses: - "200": - description: Thing connected. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{chanID}/things/{thingID}/disconnect: - post: - operationId: disconnectThingFromChannel - summary: Disconnects a thing to a channel - description: | - Disconnects a specific thing 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" - responses: - "200": - description: Thing connected. - "400": - description: Failed due to malformed thing's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /health: - get: - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - schemas: - ThingReqObj: - type: object - properties: - name: - type: string - example: thingName - description: Thing name. - tags: - type: array - minItems: 0 - items: - type: string - example: ["tag1", "tag2"] - description: Thing tags. - credentials: - type: object - properties: - identity: - type: string - example: "thingidentity" - description: Thing's identity will be used as its unique identifier - secret: - type: string - format: password - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - minimum: 8 - description: Free-form account secret used for acquiring auth token(s). - metadata: - type: object - example: { "model": "example" } - description: Arbitrary, object-encoded thing's data. - status: - type: string - description: Thing Status - format: string - example: enabled - required: - - credentials - - ChannelReqObj: - type: object - properties: - name: - type: string - example: channelName - description: Free-form channel name. Channel name is unique on the given hierarchy level. - description: - type: string - example: long channel description - description: Channel description, free form text. - parent_id: - type: string - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Id of parent channel, it must be existing channel. - metadata: - type: object - example: { "location": "example" } - description: Arbitrary, object-encoded channels's data. - status: - type: string - description: Channel Status - format: string - example: enabled - required: - - name - - PolicyReqObj: - type: object - properties: - user_ids: - type: array - minItems: 0 - items: - type: string - description: User IDs - example: - [ - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - ] - relation: - type: string - example: "editor" - description: Policy relation. - required: - - user_ids - - relation - - AssignReqObj: - type: object - properties: - members: - type: array - minItems: 0 - items: - type: string - description: Members IDs - example: - [ - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - ] - relation: - type: string - example: "editor" - description: Policy relations. - member_kind: - type: string - example: "user" - description: Member kind. - required: - - members - - relation - - member_kind - - AssignUserReqObj: - type: object - properties: - users_ids: - type: array - minItems: 0 - items: - type: string - description: Users IDs - example: - [ - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - ] - relation: - type: string - example: "editor" - description: Policy relations. - required: - - users_ids - - relation - - AssignUsersReqObj: - type: object - properties: - group_ids: - type: array - minItems: 0 - items: - type: string - description: Group IDs - example: - [ - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - ] - required: - - group_ids - - Thing: - type: object - properties: - id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing unique identifier. - name: - type: string - example: thingName - description: Thing name. - tags: - type: array - minItems: 0 - items: - type: string - example: ["tag1", "tag2"] - description: Thing tags. - domain_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: ID of the domain to which thing belongs. - credentials: - type: object - properties: - identity: - type: string - example: thingidentity - description: Thing Identity for example email address. - secret: - type: string - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing secret password. - metadata: - type: object - example: { "model": "example" } - description: Arbitrary, object-encoded thing's data. - status: - type: string - description: Thing Status - format: string - example: enabled - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the channel was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the channel was created. - xml: - name: thing - - ThingWithEmptySecret: - type: object - properties: - id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Thing unique identifier. - name: - type: string - example: thingName - description: Thing name. - tags: - type: array - minItems: 0 - items: - type: string - example: ["tag1", "tag2"] - description: Thing tags. - domain_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: ID of the domain to which thing belongs. - credentials: - type: object - properties: - identity: - type: string - example: thingidentity - description: Thing Identity for example email address. - secret: - type: string - example: "" - description: Thing secret password. - metadata: - type: object - example: { "model": "example" } - description: Arbitrary, object-encoded thing's data. - status: - type: string - description: Thing Status - format: string - example: enabled - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the channel was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the channel was created. - xml: - name: thing - - Channel: - type: object - properties: - id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Unique channel identifier generated by the service. - name: - type: string - example: channelName - description: Free-form channel name. Channel name is unique on the given hierarchy level. - domain_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: ID of the domain to which the group belongs. - parent_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Channel parent identifier. - description: - type: string - example: long channel description - description: Channel description, free form text. - metadata: - type: object - example: { "role": "general" } - description: Arbitrary, object-encoded channels's data. - path: - type: string - example: bb7edb32-2eac-4aad-aebe-ed96fe073879.bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Hierarchy path, concatenated ids of channel ancestors. - level: - type: integer - description: Level in hierarchy, distance from the root channel. - format: int32 - example: 2 - maximum: 5 - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Datetime when the channel was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Datetime when the channel was created. - status: - type: string - description: Channel Status - format: string - example: enabled - xml: - name: channel - - Policy: - type: object - properties: - owner_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Policy owner identifier. - subject: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Policy subject identifier. - object: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Policy object identifier. - actions: - type: array - minItems: 0 - items: - type: string - example: ["m_write", "g_add"] - description: Policy actions. - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the policy was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the policy was updated. - xml: - name: policy - - ThingsPage: - type: object - properties: - things: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/ThingWithEmptySecret" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - things - - total - - offset - - ChannelsPage: - type: object - properties: - channels: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Channel" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - channels - - total - - offset - - PoliciesPage: - type: object - properties: - policies: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Policy" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - policies - - total - - offset - - ThingUpdate: - type: object - properties: - name: - type: string - example: thingName - description: Thing name. - metadata: - type: object - example: { "role": "general" } - description: Arbitrary, object-encoded thing's data. - required: - - name - - metadata - - ThingTags: - type: object - properties: - tags: - type: array - example: ["tag1", "tag2"] - description: Thing tags. - minItems: 0 - uniqueItems: true - items: - type: string - - ThingSecret: - type: object - properties: - secret: - type: string - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: New thing secret. - required: - - secret - - ChannelUpdate: - type: object - properties: - name: - type: string - example: channelName - description: Free-form channel name. Channel name is unique on the given hierarchy level. - description: - type: string - example: long description but not too long - description: Channel description, free form text. - metadata: - type: object - example: { "role": "general" } - description: Arbitrary, object-encoded channels's data. - required: - - name - - metadata - - description - - ConnectionReqSchema: - type: object - properties: - objects: - type: array - description: Channel IDs. - items: - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - subjects: - type: array - description: Thing IDs - items: - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - permission: - type: array - description: policy actions - items: - example: publish - - DisConnectionReqSchema: - type: object - properties: - objects: - type: array - description: Channel IDs. - items: - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - subjects: - type: array - description: Thing IDs - items: - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - Error: - type: object - properties: - error: - type: string - description: Error message - example: { "error": "malformed entity specification" } - - HealthRes: - type: object - properties: - status: - type: string - description: Service status. - enum: - - pass - version: - type: string - description: Service version. - example: 0.14.0 - commit: - type: string - description: Service commit hash. - example: 7d6f4dc4f7f0c1fa3dc24eddfb18bb5073ff4f62 - description: - type: string - description: Service description. - example: things service - build_time: - type: string - description: Service build time. - example: 1970-01-01_00:00:00 - - parameters: - ThingID: - name: thingID - description: Unique thing identifier. - in: path - schema: - type: string - format: uuid - minLength: 36 - maxLength: 36 - pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - MemberID: - name: memberID - description: Unique member identifier. - in: path - schema: - type: string - format: uuid - minLength: 36 - maxLength: 36 - pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - ThingName: - name: name - description: Thing's name. - in: query - schema: - type: string - required: false - example: "thingName" - - Status: - name: status - description: Thing account status. - in: query - schema: - type: string - default: enabled - required: false - example: enabled - - Tags: - name: tags - description: Thing tags. - in: query - schema: - type: array - minItems: 0 - uniqueItems: true - items: - type: string - required: false - example: ["yello", "orange"] - - ChannelName: - name: name - description: Channel's name. - in: query - schema: - type: string - required: false - example: "channelName" - - ChannelDescription: - name: name - description: Channel's description. - in: query - schema: - type: string - required: false - example: "channel description" - - chanID: - name: chanID - description: Unique channel identifier. - in: path - schema: - type: string - format: uuid - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - ParentId: - name: parentId - description: Unique parent identifier for a channel. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - Level: - name: level - description: Level of hierarchy up to which to retrieve channels from given channel id. - in: query - schema: - type: integer - minimum: 1 - maximum: 5 - required: false - - Tree: - name: tree - description: Specify type of response, JSON array or tree. - in: query - required: false - schema: - type: boolean - default: false - - Metadata: - name: metadata - description: Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json. - in: query - schema: - type: string - minimum: 0 - required: false - - Limit: - name: limit - description: Size of the subset to retrieve. - in: query - schema: - type: integer - default: 10 - maximum: 100 - minimum: 1 - required: false - example: "100" - - Offset: - name: offset - description: Number of items to skip during retrieval. - in: query - schema: - type: integer - default: 0 - minimum: 0 - required: false - example: "0" - - Connected: - name: connected - description: Connection state of the subset to retrieve. - in: query - schema: - type: boolean - default: true - required: false - - requestBodies: - ThingCreateReq: - description: JSON-formatted document describing the new thing to be registered - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ThingReqObj" - - ThingUpdateReq: - description: JSON-formated document describing the metadata and name of thing to be update - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ThingUpdate" - - ThingUpdateTagsReq: - description: JSON-formated document describing the tags of thing to be update - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ThingTags" - - ThingUpdateSecretReq: - description: Secret change data. Thing can change its secret. - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ThingSecret" - - ShareThingReq: - description: JSON-formated document describing the policy related to sharing things - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/PolicyReqObj" - - AssignReq: - description: JSON-formated document describing the policy related to assigning members to a channel - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AssignReqObj" - - AssignUserReq: - description: JSON-formated document describing the policy related to assigning members to a channel - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AssignUserReqObj" - - AssignUsersReq: - description: JSON-formated document describing the policy related to assigning members to a channel - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AssignUsersReqObj" - - ChannelCreateReq: - description: JSON-formatted document describing the new channel to be registered - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ChannelReqObj" - - ChannelUpdateReq: - description: JSON-formated document describing the metadata and name of channel to be update - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ChannelUpdate" - - ThingsCreateReq: - description: JSON-formatted document describing the new things. - required: true - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/ThingReqObj" - - ConnCreateReq: - description: JSON-formatted document describing the new connection. - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ConnectionReqSchema" - - DisconnReq: - description: JSON-formatted document describing the entities for disconnection. - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/DisConnectionReqSchema" - - responses: - ThingCreateRes: - description: Registered new thing. - headers: - Location: - schema: - type: string - format: url - description: Registered thing relative URL in the format `/things/` - content: - application/json: - schema: - $ref: "#/components/schemas/Thing" - links: - get: - operationId: getThing - parameters: - thingID: $response.body#/id - get_channels: - operationId: listChannelsConnectedToThing - parameters: - thingID: $response.body#/id - update: - operationId: updateThing - parameters: - thingID: $response.body#/id - update_tags: - operationId: updateThingTags - parameters: - thingID: $response.body#/id - update_secret: - operationId: updateThingSecret - parameters: - thingID: $response.body#/id - share: - operationId: shareThing - parameters: - thingID: $response.body#/id - unsahre: - operationId: unshareThing - parameters: - thingID: $response.body#/id - disable: - operationId: disableThing - parameters: - thingID: $response.body#/id - enable: - operationId: enableThing - parameters: - thingID: $response.body#/id - - ThingRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Thing" - links: - get_channels: - operationId: listChannelsConnectedToThing - parameters: - thingID: $response.body#/id - share: - operationId: shareThing - parameters: - thingID: $response.body#/id - unsahre: - operationId: unshareThing - parameters: - thingID: $response.body#/id - - ThingPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/ThingsPage" - - ThingsPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/ThingsPage" - - ChannelCreateRes: - description: Registered new channel. - headers: - Location: - schema: - type: string - format: url - description: Registered channel relative URL in the format `/channels/` - content: - application/json: - schema: - $ref: "#/components/schemas/Channel" - links: - get: - operationId: getChannel - parameters: - chanID: $response.body#/id - get_things: - operationId: listThingsInaChannel - parameters: - chanID: $response.body#/id - get_users: - operationId: listChannelsConnectedToUser - parameters: - memberID: $response.body#/id - get_groups: - operationId: listChannelsConnectedToGroup - parameters: - memberID: $response.body#/id - update: - operationId: updateChannel - parameters: - chanID: $response.body#/id - disable: - operationId: disableChannel - parameters: - chanID: $response.body#/id - enable: - operationId: enableChannel - parameters: - chanID: $response.body#/id - assign_users: - operationId: assignUsersToChannel - parameters: - chanID: $response.body#/id - unassign_users: - operationId: unassignUsersFromChannel - parameters: - chanID: $response.body#/id - assign_groups: - operationId: assignGroupsToChannel - parameters: - chanID: $response.body#/id - unassign_groups: - operationId: unassignGroupsFromChannel - parameters: - chanID: $response.body#/id - - ChannelRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Channel" - links: - get_things: - operationId: listThingsInaChannel - parameters: - chanID: $response.body#/id - get_users: - operationId: listChannelsConnectedToUser - parameters: - memberID: $response.body#/id - get_groups: - operationId: listChannelsConnectedToGroup - parameters: - memberID: $response.body#/id - assign_users: - operationId: assignUsersToChannel - parameters: - chanID: $response.body#/id - unassign_users: - operationId: unassignUsersFromChannel - parameters: - chanID: $response.body#/id - assign_groups: - operationId: assignGroupsToChannel - parameters: - chanID: $response.body#/id - unassign_groups: - operationId: unassignGroupsFromChannel - parameters: - chanID: $response.body#/id - - ChannelPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/ChannelsPage" - - ConnCreateRes: - description: Thing registered. - content: - application/json: - schema: - $ref: "#/components/schemas/PoliciesPage" - - DisconnRes: - description: Things disconnected. - - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "#/components/schemas/HealthRes" - - ServiceError: - description: Unexpected server-side error occurred. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * Thing access: "Authorization: Bearer " - -security: - - bearerAuth: [] diff --git a/api/openapi/twins.yml b/api/openapi/twins.yml deleted file mode 100644 index 36261f5ff..000000000 --- a/api/openapi/twins.yml +++ /dev/null @@ -1,431 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.1 -info: - title: Magistrala twins service - description: | - HTTP API for managing digital twins and their states. - Some useful links: - - [The Magistrala repository](https://github.com/absmach/magistrala) - contact: - email: info@abstractmachines.fr - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/main/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:9018 - - url: https://localhost:9018 - -tags: - - name: twins - description: Everything about your Twins - externalDocs: - description: Find out more about twins - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /twins: - post: - operationId: createTwin - summary: Adds new twin - description: | - Adds new twin to the list of twins owned by user identified using - the provided access token. - tags: - - twins - requestBody: - $ref: "#/components/requestBodies/TwinReq" - responses: - "201": - $ref: "#/components/responses/TwinCreateRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - get: - operationId: getTwins - summary: Retrieves twins - description: | - Retrieves a list of twins. Due to performance concerns, data - is retrieved in subsets. - tags: - - twins - parameters: - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Name" - - $ref: "#/components/parameters/Metadata" - responses: - "200": - $ref: "#/components/responses/TwinsPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /twins/{twinID}: - get: - operationId: getTwin - summary: Retrieves twin info - tags: - - twins - parameters: - - $ref: "#/components/parameters/TwinID" - responses: - "200": - $ref: "#/components/responses/TwinRes" - "400": - description: Failed due to malformed twin's ID. - "401": - description: Missing or invalid access token provided. - "404": - description: Twin does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - put: - operationId: updateTwin - summary: Updates twin info - description: | - Update is performed by replacing the current resource data with values - provided in a request payload. Note that the twin's ID cannot be changed. - tags: - - twins - parameters: - - $ref: "#/components/parameters/TwinID" - requestBody: - $ref: "#/components/requestBodies/TwinReq" - responses: - "200": - description: Twin updated. - "400": - description: Failed due to malformed twin's ID or malformed JSON. - "401": - description: Missing or invalid access token provided. - "404": - description: Twin does not exist. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - delete: - operationId: removeTwin - summary: Removes a twin - description: Removes a twin. - tags: - - twins - parameters: - - $ref: "#/components/parameters/TwinID" - responses: - "204": - description: Twin removed. - "400": - description: Failed due to malformed twin's ID. - "401": - description: Missing or invalid access token provided - "404": - description: Twin does not exist. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /states/{twinID}: - get: - operationId: getStates - summary: Retrieves states of twin with id twinID - description: | - Retrieves a list of states. Due to performance concerns, data - is retrieved in subsets. - tags: - - states - parameters: - - $ref: "#/components/parameters/TwinID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - responses: - "200": - $ref: "#/components/responses/StatesPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "404": - description: Twin does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /health: - get: - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - parameters: - Limit: - name: limit - description: Size of the subset to retrieve. - in: query - schema: - type: integer - default: 10 - maximum: 100 - minimum: 1 - required: false - Offset: - name: offset - description: Number of items to skip during retrieval. - in: query - schema: - type: integer - default: 0 - minimum: 0 - required: false - Name: - name: name - description: Twin name - in: query - schema: - type: string - required: false - Metadata: - name: metadata - description: | - Metadata filter. Filtering is performed matching the parameter with - metadata on top level. Parameter is json. - in: query - schema: - type: string - minimum: 0 - required: false - TwinID: - name: twinID - description: Unique twin identifier. - in: path - schema: - type: string - format: uuid - minimum: 1 - required: true - - schemas: - Attribute: - type: object - properties: - name: - type: string - description: Name of the attribute. - channel: - type: string - description: Magistrala channel used by attribute. - subtopic: - type: string - description: Subtopic used by attribute. - persist_state: - type: boolean - description: Trigger state creation based on the attribute. - Definition: - type: object - properties: - delta: - type: number - description: Minimal time delay before new state creation. - attributes: - type: array - minItems: 0 - items: - $ref: "#/components/schemas/Attribute" - TwinReqObj: - type: object - properties: - name: - type: string - description: Free-form twin name. - metadata: - type: object - description: Arbitrary, object-encoded twin's data. - definition: - $ref: "#/components/schemas/Definition" - TwinResObj: - type: object - properties: - owner: - type: string - description: Email address of Magistrala user that owns twin. - id: - type: string - format: uuid - description: Unique twin identifier generated by the service. - name: - type: string - description: Free-form twin name. - revision: - type: number - description: Oridnal revision number of twin. - created: - type: string - format: date - description: Twin creation date and time. - updated: - type: string - format: date - description: Twin update date and time. - definitions: - type: array - minItems: 0 - items: - $ref: "#/components/schemas/Definition" - metadata: - type: object - description: Arbitrary, object-encoded twin's data. - TwinsPage: - type: object - properties: - twins: - type: array - minItems: 0 - items: - $ref: "#/components/schemas/TwinResObj" - total: - type: integer - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - description: Maximum number of items to return in one page. - required: - - twins - State: - type: object - properties: - twin_id: - type: string - format: uuid - description: ID of twin state belongs to. - id: - type: number - description: State position in a time row of states. - created: - type: string - format: date - description: State creation date. - payload: - type: object - description: Object-encoded states's payload. - StatesPage: - type: object - properties: - states: - type: array - minItems: 0 - items: - $ref: "#/components/schemas/State" - total: - type: integer - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - description: Maximum number of items to return in one page. - required: - - states - - requestBodies: - TwinReq: - description: JSON-formatted document describing the twin to create or update. - content: - application/json: - schema: - $ref: "#/components/schemas/TwinReqObj" - required: true - - responses: - TwinCreateRes: - description: Created twin's relative URL (i.e. /twins/{twinID}). - headers: - Location: - content: - text/plain: - schema: - type: string - TwinRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/TwinResObj" - links: - update: - operationId: updateTwin - parameters: - twinID: $response.body#/id - delete: - operationId: removeTwin - parameters: - twinID: $response.body#/id - states: - operationId: getStates - parameters: - twinID: $response.body#/id - TwinsPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/TwinsPage" - StatesPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/StatesPage" - ServiceError: - description: Unexpected server-side error occurred. - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "./schemas/HealthInfo.yml" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * Users access: "Authorization: Bearer " - -security: - - bearerAuth: [] diff --git a/api/openapi/users.yml b/api/openapi/users.yml deleted file mode 100644 index 415a81a0f..000000000 --- a/api/openapi/users.yml +++ /dev/null @@ -1,2312 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.3 -info: - title: Magistrala Users Service - description: | - This is the Users Server based on the OpenAPI 3.0 specification. It is the HTTP API for managing platform users. 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: - email: info@abstractmachines.fr - license: - name: Apache 2.0 - url: https://github.com/absmach/magistrala/blob/main/LICENSE - version: 0.14.0 - -servers: - - url: http://localhost:9002 - - url: https://localhost:9002 - -tags: - - name: Users - description: Everything about your Users - externalDocs: - description: Find out more about users - url: https://docs.magistrala.abstractmachines.fr/ - - name: Groups - description: Everything about your Groups - externalDocs: - description: Find out more about users groups - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /users: - post: - operationId: createUser - tags: - - Users - summary: Registers user account - description: | - Registers new user account given email and password. New account will - be uniquely identified by its email address. - requestBody: - $ref: "#/components/requestBodies/UserCreateReq" - responses: - "201": - $ref: "#/components/responses/UserCreateRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - get: - operationId: listUsers - tags: - - Users - summary: List users - description: | - Retrieves a list of users. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/Status" - - $ref: "#/components/parameters/FirstName" - - $ref: "#/components/parameters/LastName" - - $ref: "#/components/parameters/Username" - - $ref: "#/components/parameters/Email" - - $ref: "#/components/parameters/Tags" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - This endpoint is available only for administrators. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/profile: - get: - operationId: getProfile - summary: Gets info on currently logged in user. - description: | - Gets info on currently logged in user. Info is obtained using - authorization token - tags: - - Users - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}: - get: - operationId: getUser - summary: Retrieves a user - description: | - Retrieves a specific user that is identifier by the user ID. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - patch: - operationId: updateUser - summary: Updates first, last name and metadata of the user. - description: | - Updates name and metadata of the user with provided ID. Name and metadata - is updated using authorization token and the new received info. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - requestBody: - $ref: "#/components/requestBodies/UserUpdateReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed JSON. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing user. - "401": - description: Missing or invalid access token provided. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - delete: - summary: Delete a user - description: | - Delete a specific user that is identifier by the user ID. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - security: - - bearerAuth: [] - responses: - "204": - description: User deleted. - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "405": - description: Method not allowed. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}/username: - patch: - operationId: updateUsername - summary: Updates user's username. - description: | - Updates username of the user with provided ID. Username is - updated using authorization token and the new received username. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - requestBody: - $ref: "#/components/requestBodies/UpdateUsernameReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed JSON. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing user. - "401": - description: Missing or invalid access token provided. - "409": - description: Failed due to using an existing username. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}/tags: - patch: - operationId: updateTags - summary: Updates tags of the user. - description: | - Updates tags of the user with provided ID. Tags is updated using - authorization token and the new tags received in request. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - requestBody: - $ref: "#/components/requestBodies/UserUpdateTagsReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed JSON. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing user. - "401": - description: Missing or invalid access token provided. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}/picture: - patch: - operationId: updateProfilePicture - summary: Updates the user's profile picture. - description: | - Updates the user's profile picture with provided ID. Profile picture is - updated using authorization token and the new received picture. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - requestBody: - $ref: "#/components/requestBodies/UserUpdateProfilePictureReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed JSON. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing user. - "401": - description: Missing or invalid access token provided. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}/email: - patch: - operationId: updateEmail - summary: Updates email of the user. - description: | - Updates email of the user with provided ID. Email is - updated using authorization token and the new received email. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - requestBody: - $ref: "#/components/requestBodies/UserUpdateEmailReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed JSON. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing user. - "401": - description: Missing or invalid access token provided. - "409": - description: Failed due to using an existing email. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}/role: - patch: - operationId: updateRole - summary: Updates the user's role. - description: | - Updates role for the user with provided ID. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - requestBody: - $ref: "#/components/requestBodies/UserUpdateRoleReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed JSON. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Failed due to non existing user. - "401": - description: Missing or invalid access token provided. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}/disable: - post: - operationId: disableUser - summary: Disables a user - description: | - Disables a specific user that is identifier by the user ID. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to already disabled user. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/{userID}/enable: - post: - operationId: enableUser - summary: Enables a user - description: | - Enables a specific user that is identifier by the user ID. - tags: - - Users - parameters: - - $ref: "#/components/parameters/UserID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to already enabled user. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/secret: - patch: - operationId: updateSecret - summary: Updates secret of currently logged in user. - description: | - Updates secret of currently logged in user. Secret is updated using - authorization token and the new received info. - tags: - - Users - requestBody: - $ref: "#/components/requestBodies/UserUpdateSecretReq" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "404": - description: Failed due to non existing user. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/search: - get: - operationId: searchUsers - summary: Search users - description: | - Search users by name and identity. - tags: - - Users - parameters: - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Username" - - $ref: "#/components/parameters/FirstName" - - $ref: "#/components/parameters/LastName" - - $ref: "#/components/parameters/Email" - - $ref: "#/components/parameters/UserID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "500": - $ref: "#/components/responses/ServiceError" - - /password/reset-request: - post: - operationId: requestPasswordReset - summary: User password reset request - description: | - Generates a reset token and sends and - email with link for resetting password. - tags: - - Users - parameters: - - $ref: "#/components/parameters/Referer" - requestBody: - $ref: "#/components/requestBodies/RequestPasswordReset" - responses: - "201": - description: Users link for resetting password. - "400": - description: Failed due to malformed JSON. - "404": - description: A non-existent entity request. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /password/reset: - put: - operationId: resetPassword - summary: User password reset endpoint - description: | - When user gets reset token, after he submitted - email to `/password/reset-request`, posting a - new password along to this endpoint will change password. - tags: - - Users - requestBody: - $ref: "#/components/requestBodies/PasswordReset" - responses: - "201": - description: User link . - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "404": - description: Entity not found. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{groupID}/users: - get: - operationId: listUsersInGroup - tags: - - Users - summary: List users in a group - description: | - Retrieves a list of users in a group. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Level" - - $ref: "#/components/parameters/Tree" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/GroupName" - - $ref: "#/components/parameters/ParentID" - responses: - "200": - $ref: "#/components/responses/MembersPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - This endpoint is available only for administrators. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{channelID}/users: - get: - operationId: listUsersInChannel - tags: - - Users - summary: List users in a channel - description: | - Retrieves a list of users in a channel. Due to performance concerns, data - is retrieved in subsets. The API must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ChannelID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Level" - - $ref: "#/components/parameters/Tree" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/ChannelName" - - $ref: "#/components/parameters/ParentID" - responses: - "200": - $ref: "#/components/responses/MembersPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: | - Missing or invalid access token provided. - This endpoint is available only for administrators. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/tokens/issue: - post: - operationId: issueToken - summary: Issue Token - description: | - Issue Access and Refresh Token used for authenticating into the system. - tags: - - Users - requestBody: - $ref: "#/components/requestBodies/IssueTokenReq" - responses: - "200": - $ref: "#/components/responses/TokenRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /users/tokens/refresh: - post: - operationId: refreshToken - summary: Refresh Token - description: | - Refreshes Access and Refresh Token used for authenticating into the system. - tags: - - Users - security: - - refreshAuth: [] - responses: - "200": - $ref: "#/components/responses/TokenRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups: - post: - operationId: createGroup - tags: - - Groups - summary: Creates new group - description: | - Creates new group that can be used for grouping entities. New account will - be uniquely identified by its identity. - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - requestBody: - $ref: "#/components/requestBodies/GroupCreateReq" - security: - - bearerAuth: [] - responses: - "201": - $ref: "#/components/responses/GroupCreateRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - get: - operationId: listGroups - summary: Lists groups. - description: | - Lists groups up to a max level of hierarchy that can be fetched in one - request ( max level = 5). Result can be filtered by metadata. Groups will - be returned as JSON array or JSON tree. Due to performance concerns, result - is returned in subsets. - tags: - - Groups - security: - - bearerAuth: [] - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Level" - - $ref: "#/components/parameters/Tree" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/GroupName" - - $ref: "#/components/parameters/ParentID" - responses: - "200": - $ref: "#/components/responses/GroupPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Group does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{groupID}: - get: - operationId: getGroup - summary: Gets group info. - description: | - Gets info on a group specified by id. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/GroupRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Group does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - put: - operationId: updateGroup - summary: Updates group data. - description: | - Updates Name, Description or Metadata of a group. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - security: - - bearerAuth: [] - requestBody: - $ref: "#/components/requestBodies/GroupUpdateReq" - responses: - "200": - $ref: "#/components/responses/GroupRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Group does not exist. - "409": - description: Failed due to using an existing identity. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - delete: - summary: Delete group for a group with the given id. - description: | - Delete group removes a group with the given id from repo - and removes all the policies related to this group. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - security: - - bearerAuth: [] - responses: - "204": - description: Group deleted. - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access to group id. - "404": - description: A non-existent entity request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{groupID}/children: - get: - operationId: listChildren - summary: List children of a certain group - description: | - Lists groups up to a max level of hierarchy that can be fetched in one - request ( max level = 5). Result can be filtered by metadata. Groups will - be returned as JSON array or JSON tree. Due to performance concerns, result - is returned in subsets. - tags: - - Groups - security: - - bearerAuth: [] - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Level" - - $ref: "#/components/parameters/Tree" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/GroupName" - - $ref: "#/components/parameters/ParentID" - responses: - "200": - $ref: "#/components/responses/GroupPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Group does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{groupID}/parents: - get: - operationId: listParents - summary: List parents of a certain group - description: | - Lists groups up to a max level of hierarchy that can be fetched in one - request ( max level = 5). Result can be filtered by metadata. Groups will - be returned as JSON array or JSON tree. Due to performance concerns, result - is returned in subsets. - tags: - - Groups - security: - - bearerAuth: [] - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Level" - - $ref: "#/components/parameters/Tree" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/GroupName" - - $ref: "#/components/parameters/ParentID" - responses: - "200": - $ref: "#/components/responses/GroupPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Group does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{groupID}/enable: - post: - operationId: enableGroup - summary: Enables a group - description: | - Enables a specific group that is identifier by the group ID. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/GroupRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to already enabled group. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{groupID}/disable: - post: - operationId: disableGroup - summary: Disables a group - description: | - Disables a specific group that is identifier by the group ID. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/GroupRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "409": - description: Failed due to already disabled group. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{groupID}/users/assign: - post: - operationId: assignUser - summary: Assigns a user to a group - description: | - Assigns a specific user to a group that is identifier by the group ID. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - requestBody: - $ref: "#/components/requestBodies/AssignUserReq" - security: - - bearerAuth: [] - responses: - "200": - description: Member assigned. - "400": - description: Failed due to malformed group's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/groups/{groupID}/users/unassign: - post: - operationId: unassignUser - summary: Unassigns a user to a group - description: | - Unassigns a specific user to a group that is identifier by the group ID. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/GroupID" - requestBody: - $ref: "#/components/requestBodies/AssignUserReq" - security: - - bearerAuth: [] - responses: - "204": - description: Member unassigned. - "400": - description: Failed due to malformed group's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: A non-existent entity request. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/channels/{memberID}/groups: - get: - operationId: listGroupsInChannel - summary: Get group associated with the member - description: | - Gets groups associated with the channel member specified by id. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/MemberID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/Status" - - $ref: "#/components/parameters/Tags" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/GroupPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Group does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /{domainID}/users/{memberID}/groups: - get: - operationId: listGroupsByUser - summary: Get group associated with the member - description: | - Gets groups associated with the user member specified by id. - tags: - - Groups - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/MemberID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/Status" - - $ref: "#/components/parameters/Tags" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/GroupPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Group does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /{domainID}/users: - get: - summary: List users assigned to domain - description: | - List users assigned to domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Metadata" - - $ref: "#/components/parameters/Status" - security: - - bearerAuth: [] - responses: - "200": - $ref: "#/components/responses/UserPageRes" - description: List of users assigned to domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /health: - get: - operationId: health - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - schemas: - UserReqObj: - type: object - properties: - first_name: - type: string - example: firstName - description: User's first name. - last_name: - type: string - example: lastName - description: User's last name. - email: - type: string - example: "admin@example.com" - description: User's email address will be used as its unique identifier. - tags: - type: array - minItems: 0 - items: - type: string - example: ["tag1", "tag2"] - description: User tags. - credentials: - type: object - properties: - username: - type: string - example: "admin" - description: User's username for example 'admin' will be used as its unique identifier. - secret: - type: string - format: password - example: password - minimum: 8 - description: Free-form account secret used for acquiring auth token(s). - metadata: - type: object - example: { "domain": "example.com" } - description: Arbitrary, object-encoded user's data. - profile_picture: - type: string - example: "https://example.com/profile.jpg" - description: User's profile picture URL that is represented as a string. - status: - type: string - description: User Status - format: string - example: enabled - required: - - credentials - - GroupReqObj: - type: object - properties: - name: - type: string - example: groupName - description: Free-form group name. Group name is unique on the given hierarchy level. - description: - type: string - example: long group description - description: Group description, free form text. - parent_id: - type: string - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Id of parent group, it must be existing group. - metadata: - type: object - example: { "domain": "example.com" } - description: Arbitrary, object-encoded groups's data. - status: - type: string - description: Group Status - format: string - example: enabled - required: - - name - - User: - type: object - properties: - id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User unique identifier. - first_name: - type: string - example: John - description: User's first name. - last_name: - type: string - example: Doe - description: User's last name. - tags: - type: array - minItems: 0 - items: - type: string - example: ["tag1", "tag2"] - description: User tags. - email: - type: string - example: "john.doe@magistrala.com" - description: User email for example email address. - credentials: - type: object - properties: - username: - type: string - example: john_doe - description: User's username for example john_doe for Mr John Doe. - metadata: - type: object - example: { "address": "example" } - description: Arbitrary, object-encoded user's data. - profile_picture: - type: string - example: "https://example.com/profile.jpg" - description: User's profile picture URL that is represented as a string. - status: - type: string - description: User Status - format: string - example: enabled - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the group was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the group was created. - xml: - name: user - - Group: - type: object - properties: - id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Unique group identifier generated by the service. - name: - type: string - example: groupName - description: Free-form group name. Group name is unique on the given hierarchy level. - domain_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: ID of the domain to which the group belongs.. - parent_id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Group parent identifier. - description: - type: string - example: long group description - description: Group description, free form text. - metadata: - type: object - example: { "role": "general" } - description: Arbitrary, object-encoded groups's data. - path: - type: string - example: bb7edb32-2eac-4aad-aebe-ed96fe073879.bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: Hierarchy path, concatenated ids of group ancestors. - level: - type: integer - description: Level in hierarchy, distance from the root group. - format: int32 - example: 2 - maximum: 5 - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Datetime when the group was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Datetime when the group was created. - status: - type: string - description: Group Status - format: string - example: enabled - xml: - name: group - - Members: - type: object - properties: - id: - type: string - format: uuid - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - description: User unique identifier. - first_name: - type: string - example: John - description: User's first name. - last_name: - type: string - example: Doe - description: User's last name. - email: - type: string - example: user@magistrala.com - description: User's email address. - tags: - type: array - minItems: 0 - items: - type: string - example: ["computations", "datasets"] - description: User tags. - credentials: - type: object - properties: - username: - type: string - example: john_doe - description: User's username. - secret: - type: string - example: password - minimum: 8 - description: User secret password. - metadata: - type: object - example: { "role": "general" } - description: Arbitrary, object-encoded user's data. - status: - type: string - description: User Status - format: string - example: enabled - created_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the group was created. - updated_at: - type: string - format: date-time - example: "2019-11-26 13:31:52" - description: Time when the group was created. - xml: - name: members - - UsersPage: - type: object - properties: - users: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/User" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - users - - total - - offset - - GroupsPage: - type: object - properties: - groups: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Group" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - groups - - total - - offset - - MembersPage: - type: object - properties: - members: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Members" - total: - type: integer - example: 1 - description: Total number of items. - offset: - type: integer - description: Number of items to skip during retrieval. - limit: - type: integer - example: 10 - description: Maximum number of items to return in one page. - required: - - members - - total - - level - - UserUpdate: - type: object - properties: - first_name: - type: string - example: firstName - description: User's first name. - last_name: - type: string - example: lastName - description: User's last name. - metadata: - type: object - example: { "role": "general" } - description: Arbitrary, object-encoded user's data. - required: - - first_name - - last_name - - metadata - - UserTags: - type: object - properties: - tags: - type: array - example: ["yello", "orange"] - description: User tags. - minItems: 0 - uniqueItems: true - items: - type: string - - UserProfilePicture: - type: object - properties: - profile_picture: - type: string - example: "https://example.com/profile.jpg" - description: User's profile picture URL that is represented as a string. - required: - - profile_picture - - Email: - type: object - properties: - email: - type: string - example: user@magistrala.com - description: User email address. - required: - - email - - UserSecret: - type: object - properties: - old_secret: - type: string - example: oldpassword - minimum: 8 - description: Old user secret password. - new_secret: - type: string - example: newpassword - minimum: 8 - description: New user secret password. - required: - - old_secret - - new_secret - - UserRole: - type: object - properties: - role: - type: string - enum: ["admin", "user"] - example: user - description: User role example. - required: - - role - - Username: - type: object - properties: - username: - type: string - example: "admin" - description: User's username for example 'admin' will be used as its unique identifier. - required: - - username - - GroupUpdate: - type: object - properties: - name: - type: string - example: groupName - description: Free-form group name. Group name is unique on the given hierarchy level. - description: - type: string - example: long description but not too long - description: Group description, free form text. - metadata: - type: object - example: { "role": "general" } - description: Arbitrary, object-encoded groups's data. - required: - - name - - metadata - - description - - AssignReqObj: - type: object - properties: - members: - type: array - minItems: 0 - items: - type: string - description: Members IDs - example: - [ - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - ] - relation: - type: string - example: "m_write" - description: Permission relations. - member_kind: - type: string - example: "user" - description: Member kind. - required: - - members - - relation - - member_kind - - AssignUserReqObj: - type: object - properties: - user_ids: - type: array - minItems: 0 - items: - type: string - description: User IDs - example: - [ - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - "bb7edb32-2eac-4aad-aebe-ed96fe073879", - ] - relation: - type: string - example: "m_write" - description: Permission relations. - required: - - user_ids - - relation - - IssueToken: - type: object - properties: - identity: - type: string - example: user@magistrala.com - description: User identity - email address. - secret: - type: string - example: password - minimum: 8 - description: User secret password. - required: - - identity - - secret - - Error: - type: object - properties: - error: - type: string - description: Error message - example: { "error": "malformed entity specification" } - - HealthRes: - type: object - properties: - status: - type: string - description: Service status. - enum: - - pass - version: - type: string - description: Service version. - example: 0.0.1 - commit: - type: string - description: Service commit hash. - example: 7d6f4dc4f7f0c1fa3dc24eddfb18bb5073ff4f62 - description: - type: string - description: Service description. - example: service - build_time: - type: string - description: Service build time. - example: 1970-01-01_00:00:00 - - parameters: - Referer: - name: Referer - description: Host being sent by browser. - in: header - schema: - type: string - required: true - - UserID: - name: userID - description: Unique user identifier. - in: path - schema: - type: string - format: uuid - minLength: 36 - maxLength: 36 - pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - Username: - name: username - description: User's username. - in: query - schema: - type: string - required: false - example: "username" - - FirstName: - name: first_name - description: User's first name. - in: query - schema: - type: string - required: false - example: "Jane" - - LastName: - name: last_name - description: User's last name. - in: query - schema: - type: string - required: false - example: "Doe" - - Email: - name: email - description: User's email address. - in: query - schema: - type: string - format: email - required: false - example: "admin@example.com" - - Status: - name: status - description: User account status. - in: query - schema: - type: string - default: enabled - required: false - example: enabled - - Tags: - name: tags - description: User tags. - in: query - schema: - type: array - minItems: 0 - uniqueItems: true - items: - type: string - required: false - example: ["yello", "orange"] - - GroupName: - name: name - description: Group's name. - in: query - schema: - type: string - required: false - example: "groupName" - - ChannelName: - name: name - description: Channel's name. - in: query - schema: - type: string - required: false - example: "channelName" - - GroupDescription: - name: name - description: Group's description. - in: query - schema: - type: string - required: false - example: "group description" - - GroupID: - name: groupID - description: Unique group identifier. - in: path - schema: - type: string - format: uuid - minLength: 36 - maxLength: 36 - pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - ChannelID: - name: channelID - description: Unique channel identifier. - in: path - schema: - type: string - format: uuid - minLength: 36 - maxLength: 36 - pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - MemberID: - name: memberID - description: Unique member identifier. - in: path - schema: - type: string - format: uuid - minLength: 36 - maxLength: 36 - pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" - required: true - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - ParentID: - name: parentID - description: Unique parent identifier for a group. - in: query - schema: - type: string - format: uuid - required: false - example: bb7edb32-2eac-4aad-aebe-ed96fe073879 - - Level: - name: level - description: Level of hierarchy up to which to retrieve groups from given group id. - in: query - schema: - type: integer - minimum: 1 - maximum: 5 - required: false - - Tree: - name: tree - description: Specify type of response, JSON array or tree. - in: query - required: false - schema: - type: boolean - default: false - - Metadata: - name: metadata - description: Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json. - in: query - schema: - type: string - minimum: 0 - required: false - - Limit: - name: limit - description: Size of the subset to retrieve. - in: query - schema: - type: integer - default: 10 - maximum: 100 - minimum: 1 - required: false - example: "100" - - Offset: - name: offset - description: Number of items to skip during retrieval. - in: query - schema: - type: integer - default: 0 - minimum: 0 - required: false - example: "0" - - requestBodies: - UserCreateReq: - description: JSON-formatted document describing the new user to be registered - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/UserReqObj" - - UserUpdateReq: - description: JSON-formated document describing the metadata and name of user to be update - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/UserUpdate" - - UserUpdateTagsReq: - description: JSON-formated document describing the tags of user to be update - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/UserTags" - - UserUpdateProfilePictureReq: - description: JSON-formated document describing the profile picture of user to be update - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/UserProfilePicture" - - UserUpdateEmailReq: - description: Email change data. User can change its email. - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Email" - - UserUpdateSecretReq: - description: Secret change data. User can change its secret. - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/UserSecret" - - UserUpdateRoleReq: - description: JSON-formated document describing the role of the user to be updated - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/UserRole" - - UpdateUsernameReq: - description: JSON-formated document describing the username of the user to be updated - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Username" - - GroupCreateReq: - description: JSON-formatted document describing the new group to be registered - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/GroupReqObj" - - GroupUpdateReq: - description: JSON-formated document describing the metadata and name of group to be update - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/GroupUpdate" - - AssignReq: - description: JSON-formated document describing the policy related to assigning members to a group - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AssignReqObj" - - AssignUserReq: - description: JSON-formated document describing the policy related to assigning users to a group - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AssignUserReqObj" - - IssueTokenReq: - description: Login credentials. - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/IssueToken" - - RequestPasswordReset: - description: Initiate password request procedure. - required: true - content: - application/json: - schema: - type: object - properties: - email: - type: string - format: email - description: User email. - host: - type: string - example: examplehost - description: Email host. - - PasswordReset: - description: Password reset request data, new password and token that is appended on password reset link received in email. - content: - application/json: - schema: - type: object - properties: - password: - type: string - format: password - description: New password. - example: 12345678 - minimum: 8 - confirm_password: - type: string - format: password - description: New confirmation password. - example: 12345678 - minimum: 8 - token: - type: string - format: jwt - description: Reset token generated and sent in email. - - PasswordChange: - description: Password change data. User can change its password. - required: true - content: - application/json: - schema: - type: object - properties: - password: - type: string - format: password - minimum: 8 - description: New password. - old_password: - type: string - minimum: 8 - format: password - description: Old password. - - responses: - UserCreateRes: - description: Registered new user. - headers: - Location: - schema: - type: string - format: url - description: Registered user relative URL in the format `/users/` - content: - application/json: - schema: - $ref: "#/components/schemas/User" - links: - get: - operationId: getUser - parameters: - userID: $response.body#/id - get_groups: - operationId: listUsersInGroup - parameters: - groupID: $response.body#/id - get_channels: - operationId: listUsersInChannel - parameters: - channelID: $response.body#/id - update: - operationId: updateUser - parameters: - userID: $response.body#/id - update_username: - operationId: updateUsername - parameters: - userID: $response.body#/id - update_tags: - operationId: updateTags - parameters: - userID: $response.body#/id - update_profile_picture: - operationId: updateProfilePicture - parameters: - userID: $response.body#/id - update_email: - operationId: updateEmail - parameters: - userID: $response.body#/id - update_role: - operationId: updateRole - parameters: - userID: $response.body#/id - disable: - operationId: disableUser - parameters: - userID: $response.body#/id - enable: - operationId: enableUser - parameters: - userID: $response.body#/id - - UserRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/User" - links: - get_groups: - operationId: listUsersInGroup - parameters: - groupID: $response.body#/id - get_channels: - operationId: listUsersInChannel - parameters: - channelID: $response.body#/id - - UserPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/UsersPage" - - GroupCreateRes: - description: Registered new group. - headers: - Location: - schema: - type: string - format: url - description: Registered group relative URL in the format `/groups/` - content: - application/json: - schema: - $ref: "#/components/schemas/Group" - links: - get: - operationId: getGroup - parameters: - groupID: $response.body#/id - get_children: - operationId: listChildren - parameters: - groupID: $response.body#/id - get_parent: - operationId: listParents - parameters: - groupID: $response.body#/id - get_channels: - operationId: listGroupsInChannel - parameters: - memberID: $response.body#/id - get_users: - operationId: listGroupsByUser - parameters: - memberID: $response.body#/id - update: - operationId: updateGroup - parameters: - groupID: $response.body#/id - disable: - operationId: disableGroup - parameters: - groupID: $response.body#/id - enable: - operationId: enableGroup - parameters: - groupID: $response.body#/id - assign: - operationId: assignUser - parameters: - groupID: $response.body#/id - unassign: - operationId: unassignUser - parameters: - groupID: $response.body#/id - - GroupRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Group" - links: - get_children: - operationId: listChildren - parameters: - groupID: $response.body#/id - get_parent: - operationId: listParents - parameters: - groupID: $response.body#/id - get_channels: - operationId: listGroupsInChannel - parameters: - memberID: $response.body#/id - get_users: - operationId: listGroupsByUser - parameters: - memberID: $response.body#/id - assign: - operationId: assignUser - parameters: - groupID: $response.body#/id - unassign: - operationId: unassignUser - parameters: - groupID: $response.body#/id - - GroupPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/GroupsPage" - - MembersPageRes: - description: Group members retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/MembersPage" - - TokenRes: - description: JSON-formated document describing the user access token used for authenticating into the syetem and refresh token used for generating another access token - content: - application/json: - schema: - type: object - properties: - access_token: - type: string - example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjU3OTMwNjksImlhdCI6MTY2NTc1NzA2OSwiaXNzIjoibWFpbmZsdXguYXV0aCIsInN1YiI6ImFkbWluQGV4YW1wbGUuY29tIiwiaXNzdWVyX2lkIjoiZmRjZWVhNWYtNjYxNy00MjY1LWJhZDUtMzYxOTNhOTQ0NjMwIiwidHlwZSI6MH0.3gNd_x01QEiZfQxuQoEyqCqTrcxRkXHO7A4iG_gzu3c - description: User access token. - refresh_token: - type: string - example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjU3OTMwNjksImlhdCI6MTY2NTc1NzA2OSwiaXNzIjoibWFpbmZsdXguYXV0aCIsInN1YiI6ImFkbWluQGV4YW1wbGUuY29tIiwiaXNzdWVyX2lkIjoiZmRjZWVhNWYtNjYxNy00MjY1LWJhZDUtMzYxOTNhOTQ0NjMwIiwidHlwZSI6MH0.3gNd_x01QEiZfQxuQoEyqCqTrcxRkXHO7A4iG_gzu3c - description: User refresh token. - access_type: - type: string - example: access - description: User access token type. - - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "#/components/schemas/HealthRes" - - ServiceError: - description: Unexpected server-side error occurred. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * User access: "Authorization: Bearer " - - refreshAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * User refresh token used to get another access token: "Authorization: Bearer " -security: - - bearerAuth: [] - - refreshAuth: [] diff --git a/auth.pb.go b/auth.pb.go deleted file mode 100644 index d76fd94f7..000000000 --- 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 54015f115..000000000 --- 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 deleted file mode 100644 index 4a991e0fb..000000000 --- a/auth/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# 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`. - -## Authentication - -User service is using Auth service gRPC API to obtain login token or password reset token. Authentication key consists of the following fields: - -- ID - key ID -- Type - one of the three types described below -- IssuerID - an ID of the Magistrala User who issued the key -- Subject - user ID for which the key is issued -- IssuedAt - the timestamp when the key is issued -- ExpiresAt - the timestamp after which the key is invalid - -There are four types of authentication keys: - -- Access key - keys issued to the user upon login request -- Refresh key - keys used to generate new access keys -- Recovery key - password recovery key -- API key - keys issued upon the user request -- Invitation key - keys used to invite new users - -Authentication keys are represented and distributed by the corresponding [JWT](jwt.io). - -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_. - -Recovery key is the password recovery key. It's short-lived token used for password recovery process. - -For in-depth explanation of the aforementioned scenarios, as well as thorough understanding of Magistrala, please check out the [official documentation][doc]. - -The following actions are supported: - -- create (all key types) -- verify (all key types) -- obtain (API keys only) -- revoke (API keys only) - -## 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. - -Domain consists of the following fields: - -- ID - UUID uniquely representing domain -- Name - name of the domain -- Tags - array of tags -- Metadata - Arbitrary, object-encoded domain's data -- Alias - unique alias of the domain -- CreatedAt - timestamp at which the domain is created -- UpdatedAt - timestamp at which the domain is updated -- UpdatedBy - user that updated the domain -- CreatedBy - user that created the domain -- Status - domain status - -## 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_AUTH_LOG_LEVEL | Log level for the Auth service (debug, info, warn, error) | info | -| MG_AUTH_DB_HOST | Database host address | localhost | -| MG_AUTH_DB_PORT | Database host port | 5432 | -| MG_AUTH_DB_USER | Database user | magistrala | -| MG_AUTH_DB_PASSWORD | Database password | magistrala | -| MG_AUTH_DB_NAME | Name of the database used by the service | auth | -| MG_AUTH_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | -| MG_AUTH_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | -| MG_AUTH_DB_SSL_KEY | Path to the PEM encoded key file | "" | -| MG_AUTH_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | -| MG_AUTH_HTTP_HOST | Auth service HTTP host | "" | -| MG_AUTH_HTTP_PORT | Auth service HTTP port | 8189 | -| MG_AUTH_HTTP_SERVER_CERT | Path to the PEM encoded HTTP server certificate file | "" | -| MG_AUTH_HTTP_SERVER_KEY | Path to the PEM encoded HTTP server key file | "" | -| MG_AUTH_GRPC_HOST | Auth service gRPC host | "" | -| MG_AUTH_GRPC_PORT | Auth service gRPC port | 8181 | -| MG_AUTH_GRPC_SERVER_CERT | Path to the PEM encoded gRPC server certificate file | "" | -| MG_AUTH_GRPC_SERVER_KEY | Path to the PEM encoded gRPC server key file | "" | -| MG_AUTH_GRPC_SERVER_CA_CERTS | Path to the PEM encoded gRPC server CA certificate file | "" | -| MG_AUTH_GRPC_CLIENT_CA_CERTS | Path to the PEM encoded gRPC client CA certificate file | "" | -| MG_AUTH_SECRET_KEY | String used for signing tokens | secret | -| MG_AUTH_ACCESS_TOKEN_DURATION | The access token expiration period | 1h | -| MG_AUTH_REFRESH_TOKEN_DURATION | The refresh token expiration period | 24h | -| MG_AUTH_INVITATION_DURATION | The invitation token expiration period | 168h | -| MG_SPICEDB_HOST | SpiceDB host address | localhost | -| MG_SPICEDB_PORT | SpiceDB host port | 50051 | -| MG_SPICEDB_PRE_SHARED_KEY | SpiceDB pre-shared key | 12345678 | -| MG_SPICEDB_SCHEMA_FILE | Path to SpiceDB schema file | ./docker/spicedb/schema.zed | -| 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_AUTH_ADAPTER_INSTANCE_ID | Adapter instance ID | "" | - -## Deployment - -The service itself is distributed as Docker container. Check the [`auth`](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 postgres database, SpiceDB, and Jaeger server. -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 service -make auth - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_AUTH_LOG_LEVEL=info \ -MG_AUTH_DB_HOST=localhost \ -MG_AUTH_DB_PORT=5432 \ -MG_AUTH_DB_USER=magistrala \ -MG_AUTH_DB_PASSWORD=magistrala \ -MG_AUTH_DB_NAME=auth \ -MG_AUTH_DB_SSL_MODE=disable \ -MG_AUTH_DB_SSL_CERT="" \ -MG_AUTH_DB_SSL_KEY="" \ -MG_AUTH_DB_SSL_ROOT_CERT="" \ -MG_AUTH_HTTP_HOST=localhost \ -MG_AUTH_HTTP_PORT=8189 \ -MG_AUTH_HTTP_SERVER_CERT="" \ -MG_AUTH_HTTP_SERVER_KEY="" \ -MG_AUTH_GRPC_HOST=localhost \ -MG_AUTH_GRPC_PORT=8181 \ -MG_AUTH_GRPC_SERVER_CERT="" \ -MG_AUTH_GRPC_SERVER_KEY="" \ -MG_AUTH_GRPC_SERVER_CA_CERTS="" \ -MG_AUTH_GRPC_CLIENT_CA_CERTS="" \ -MG_AUTH_SECRET_KEY=secret \ -MG_AUTH_ACCESS_TOKEN_DURATION=1h \ -MG_AUTH_REFRESH_TOKEN_DURATION=24h \ -MG_AUTH_INVITATION_DURATION=168h \ -MG_SPICEDB_HOST=localhost \ -MG_SPICEDB_PORT=50051 \ -MG_SPICEDB_PRE_SHARED_KEY=12345678 \ -MG_SPICEDB_SCHEMA_FILE=./docker/spicedb/schema.zed \ -MG_JAEGER_URL=http://localhost:14268/api/traces \ -MG_JAEGER_TRACE_RATIO=1.0 \ -MG_SEND_TELEMETRY=true \ -MG_AUTH_ADAPTER_INSTANCE_ID="" \ -$GOBIN/magistrala-auth -``` - -Setting `MG_AUTH_HTTP_SERVER_CERT` and `MG_AUTH_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_AUTH_GRPC_SERVER_CERT` and `MG_AUTH_GRPC_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_AUTH_GRPC_SERVER_CA_CERTS` will enable TLS against the service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. Setting `MG_AUTH_GRPC_CLIENT_CA_CERTS` will enable TLS against the service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. - -## Usage - -For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=auth.yml). - -[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/auth/api/doc.go b/auth/api/doc.go deleted file mode 100644 index 3b92bedaa..000000000 --- a/auth/api/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains implementation of Auth service HTTP API. -package api diff --git a/auth/api/grpc/auth/client.go b/auth/api/grpc/auth/client.go deleted file mode 100644 index f53f4f573..000000000 --- a/auth/api/grpc/auth/client.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -import ( - "context" - "time" - - "github.com/absmach/magistrala" - grpcapi "github.com/absmach/magistrala/auth/api/grpc" - "github.com/go-kit/kit/endpoint" - kitgrpc "github.com/go-kit/kit/transport/grpc" - "google.golang.org/grpc" -) - -const authSvcName = "magistrala.AuthService" - -type authGrpcClient struct { - authenticate endpoint.Endpoint - authorize endpoint.Endpoint - timeout time.Duration -} - -var _ magistrala.AuthServiceClient = (*authGrpcClient)(nil) - -// NewAuthClient returns new auth gRPC client instance. -func NewAuthClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.AuthServiceClient { - return &authGrpcClient{ - authenticate: kitgrpc.NewClient( - conn, - authSvcName, - "Authenticate", - encodeIdentifyRequest, - decodeIdentifyResponse, - magistrala.AuthNRes{}, - ).Endpoint(), - authorize: kitgrpc.NewClient( - conn, - authSvcName, - "Authorize", - encodeAuthorizeRequest, - decodeAuthorizeResponse, - magistrala.AuthZRes{}, - ).Endpoint(), - timeout: timeout, - } -} - -func (client authGrpcClient) Authenticate(ctx context.Context, token *magistrala.AuthNReq, _ ...grpc.CallOption) (*magistrala.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) - } - ir := res.(authenticateRes) - return &magistrala.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 -} - -func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.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) { - ctx, cancel := context.WithTimeout(ctx, client.timeout) - defer cancel() - - res, err := client.authorize(ctx, authReq{ - Domain: req.GetDomain(), - SubjectType: req.GetSubjectType(), - Subject: req.GetSubject(), - SubjectKind: req.GetSubjectKind(), - Relation: req.GetRelation(), - Permission: req.GetPermission(), - ObjectType: req.GetObjectType(), - Object: req.GetObject(), - }) - if err != nil { - return &magistrala.AuthZRes{}, grpcapi.DecodeError(err) - } - - ar := res.(authorizeRes) - return &magistrala.AuthZRes{Authorized: ar.authorized, Id: ar.id}, nil -} - -func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.AuthZRes) - return authorizeRes{authorized: res.Authorized, id: res.Id}, nil -} - -func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(authReq) - return &magistrala.AuthZReq{ - Domain: req.Domain, - SubjectType: req.SubjectType, - Subject: req.Subject, - SubjectKind: req.SubjectKind, - Relation: req.Relation, - Permission: req.Permission, - ObjectType: req.ObjectType, - Object: req.Object, - }, nil -} diff --git a/auth/api/grpc/auth/doc.go b/auth/api/grpc/auth/doc.go deleted file mode 100644 index be7d6b2ee..000000000 --- a/auth/api/grpc/auth/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package auth contains implementation of Auth service gRPC API. -package auth diff --git a/auth/api/grpc/auth/endpoint.go b/auth/api/grpc/auth/endpoint.go deleted file mode 100644 index adc20eaea..000000000 --- a/auth/api/grpc/auth/endpoint.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -import ( - "context" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/policies" - "github.com/go-kit/kit/endpoint" -) - -func authenticateEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(authenticateReq) - if err := req.validate(); err != nil { - return authenticateRes{}, err - } - - key, err := svc.Identify(ctx, req.token) - if err != nil { - return authenticateRes{}, err - } - - return authenticateRes{id: key.Subject, userID: key.User, domainID: key.Domain}, nil - } -} - -func authorizeEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(authReq) - - if err := req.validate(); err != nil { - return authorizeRes{}, err - } - err := svc.Authorize(ctx, policies.Policy{ - Domain: req.Domain, - SubjectType: req.SubjectType, - SubjectKind: req.SubjectKind, - Subject: req.Subject, - Relation: req.Relation, - Permission: req.Permission, - ObjectType: req.ObjectType, - Object: req.Object, - }) - if err != nil { - return authorizeRes{authorized: false}, err - } - return authorizeRes{authorized: true}, nil - } -} diff --git a/auth/api/grpc/auth/endpoint_test.go b/auth/api/grpc/auth/endpoint_test.go deleted file mode 100644 index 4b920617a..000000000 --- a/auth/api/grpc/auth/endpoint_test.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth_test - -import ( - "context" - "fmt" - "net" - "testing" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - grpcapi "github.com/absmach/magistrala/auth/api/grpc/auth" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -const ( - port = 8081 - secret = "secret" - email = "test@example.com" - id = "testID" - thingsType = "things" - usersType = "users" - description = "Description" - groupName = "mgx" - adminpermission = "admin" - - authoritiesObj = "authorities" - memberRelation = "member" - loginDuration = 30 * time.Minute - refreshDuration = 24 * time.Hour - invalidDuration = 7 * 24 * time.Hour - validToken = "valid" - inValidToken = "invalid" - validPolicy = "valid" -) - -var ( - domainID = testsutil.GenerateUUID(&testing.T{}) - authAddr = fmt.Sprintf("localhost:%d", port) -) - -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)) - go func() { - err := server.Serve(listener) - assert.Nil(&testing.T{}, err, fmt.Sprintf(`"Unexpected error creating auth server %s"`, err)) - }() - - return server -} - -func TestIdentify(t *testing.T) { - conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - grpcClient := grpcapi.NewAuthClient(conn, time.Second) - - cases := []struct { - desc string - token string - idt *magistrala.AuthNRes - svcErr error - err error - }{ - { - desc: "authenticate user with valid user token", - token: validToken, - idt: &magistrala.AuthNRes{Id: id, UserId: email, DomainId: domainID}, - err: nil, - }, - { - desc: "authenticate user with invalid user token", - token: "invalid", - idt: &magistrala.AuthNRes{}, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "authenticate user with empty token", - token: "", - idt: &magistrala.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}) - if idt != nil { - assert.Equal(t, tc.idt, idt, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.idt, idt)) - } - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - svcCall.Unset() - } -} - -func TestAuthorize(t *testing.T) { - conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - grpcClient := grpcapi.NewAuthClient(conn, time.Second) - - cases := []struct { - desc string - token string - authRequest *magistrala.AuthZReq - authResponse *magistrala.AuthZRes - err error - }{ - { - desc: "authorize user with authorized token", - token: validToken, - authRequest: &magistrala.AuthZReq{ - Subject: id, - SubjectType: usersType, - Object: authoritiesObj, - ObjectType: usersType, - Relation: memberRelation, - Permission: adminpermission, - }, - authResponse: &magistrala.AuthZRes{Authorized: true}, - err: nil, - }, - { - desc: "authorize user with unauthorized token", - token: inValidToken, - authRequest: &magistrala.AuthZReq{ - Subject: id, - SubjectType: usersType, - Object: authoritiesObj, - ObjectType: usersType, - Relation: memberRelation, - Permission: adminpermission, - }, - authResponse: &magistrala.AuthZRes{Authorized: false}, - err: svcerr.ErrAuthorization, - }, - { - desc: "authorize user with empty subject", - token: validToken, - authRequest: &magistrala.AuthZReq{ - Subject: "", - SubjectType: usersType, - Object: authoritiesObj, - ObjectType: usersType, - Relation: memberRelation, - Permission: adminpermission, - }, - authResponse: &magistrala.AuthZRes{Authorized: false}, - err: apiutil.ErrMissingPolicySub, - }, - { - desc: "authorize user with empty subject type", - token: validToken, - authRequest: &magistrala.AuthZReq{ - Subject: id, - SubjectType: "", - Object: authoritiesObj, - ObjectType: usersType, - Relation: memberRelation, - Permission: adminpermission, - }, - authResponse: &magistrala.AuthZRes{Authorized: false}, - err: apiutil.ErrMissingPolicySub, - }, - { - desc: "authorize user with empty object", - token: validToken, - authRequest: &magistrala.AuthZReq{ - Subject: id, - SubjectType: usersType, - Object: "", - ObjectType: usersType, - Relation: memberRelation, - Permission: adminpermission, - }, - authResponse: &magistrala.AuthZRes{Authorized: false}, - err: apiutil.ErrMissingPolicyObj, - }, - { - desc: "authorize user with empty object type", - token: validToken, - authRequest: &magistrala.AuthZReq{ - Subject: id, - SubjectType: usersType, - Object: authoritiesObj, - ObjectType: "", - Relation: memberRelation, - Permission: adminpermission, - }, - authResponse: &magistrala.AuthZRes{Authorized: false}, - err: apiutil.ErrMissingPolicyObj, - }, - { - desc: "authorize user with empty permission", - token: validToken, - authRequest: &magistrala.AuthZReq{ - Subject: id, - SubjectType: usersType, - Object: authoritiesObj, - ObjectType: usersType, - Relation: memberRelation, - Permission: "", - }, - authResponse: &magistrala.AuthZRes{Authorized: false}, - err: apiutil.ErrMalformedPolicyPer, - }, - } - for _, tc := range cases { - svccall := svc.On("Authorize", mock.Anything, mock.Anything).Return(tc.err) - ar, err := grpcClient.Authorize(context.Background(), tc.authRequest) - if ar != nil { - assert.Equal(t, tc.authResponse, ar, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.authResponse, ar)) - } - 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/auth/requests.go b/auth/api/grpc/auth/requests.go deleted file mode 100644 index 41ef9a91d..000000000 --- a/auth/api/grpc/auth/requests.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -import ( - "github.com/absmach/magistrala/pkg/apiutil" -) - -type authenticateReq struct { - token string -} - -func (req authenticateReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - return nil -} - -// authReq represents authorization request. It contains: -// 1. subject - an action invoker -// 2. object - an entity over which action will be executed -// 3. action - type of action that will be executed (read/write). -type authReq struct { - Domain string - SubjectType string - SubjectKind string - Subject string - Relation string - Permission string - ObjectType string - Object string -} - -func (req authReq) validate() error { - if req.Subject == "" || req.SubjectType == "" { - return apiutil.ErrMissingPolicySub - } - - if req.Object == "" || req.ObjectType == "" { - return apiutil.ErrMissingPolicyObj - } - - if req.Permission == "" { - return apiutil.ErrMalformedPolicyPer - } - - return nil -} diff --git a/auth/api/grpc/auth/responses.go b/auth/api/grpc/auth/responses.go deleted file mode 100644 index dc9ad1cde..000000000 --- a/auth/api/grpc/auth/responses.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -type authenticateRes struct { - id string - userID string - domainID string -} - -type authorizeRes struct { - id string - authorized bool -} diff --git a/auth/api/grpc/auth/server.go b/auth/api/grpc/auth/server.go deleted file mode 100644 index 491b915db..000000000 --- a/auth/api/grpc/auth/server.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -import ( - "context" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - grpcapi "github.com/absmach/magistrala/auth/api/grpc" - kitgrpc "github.com/go-kit/kit/transport/grpc" -) - -var _ magistrala.AuthServiceServer = (*authGrpcServer)(nil) - -type authGrpcServer struct { - magistrala.UnimplementedAuthServiceServer - authorize kitgrpc.Handler - authenticate kitgrpc.Handler -} - -// NewAuthServer returns new AuthnServiceServer instance. -func NewAuthServer(svc auth.Service) magistrala.AuthServiceServer { - return &authGrpcServer{ - authorize: kitgrpc.NewServer( - (authorizeEndpoint(svc)), - decodeAuthorizeRequest, - encodeAuthorizeResponse, - ), - - authenticate: kitgrpc.NewServer( - (authenticateEndpoint(svc)), - decodeAuthenticateRequest, - encodeAuthenticateResponse, - ), - } -} - -func (s *authGrpcServer) Authenticate(ctx context.Context, req *magistrala.AuthNReq) (*magistrala.AuthNRes, error) { - _, res, err := s.authenticate.ServeGRPC(ctx, req) - if err != nil { - return nil, grpcapi.EncodeError(err) - } - return res.(*magistrala.AuthNRes), nil -} - -func (s *authGrpcServer) Authorize(ctx context.Context, req *magistrala.AuthZReq) (*magistrala.AuthZRes, error) { - _, res, err := s.authorize.ServeGRPC(ctx, req) - if err != nil { - return nil, grpcapi.EncodeError(err) - } - return res.(*magistrala.AuthZRes), nil -} - -func decodeAuthenticateRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.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 -} - -func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.AuthZReq) - return authReq{ - Domain: req.GetDomain(), - SubjectType: req.GetSubjectType(), - SubjectKind: req.GetSubjectKind(), - Subject: req.GetSubject(), - Relation: req.GetRelation(), - Permission: req.GetPermission(), - ObjectType: req.GetObjectType(), - Object: req.GetObject(), - }, nil -} - -func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(authorizeRes) - return &magistrala.AuthZRes{Authorized: res.authorized, Id: res.id}, nil -} diff --git a/auth/api/grpc/auth/setup_test.go b/auth/api/grpc/auth/setup_test.go deleted file mode 100644 index b6ff6bdfd..000000000 --- a/auth/api/grpc/auth/setup_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth_test - -import ( - "os" - "testing" - - "github.com/absmach/magistrala/auth/mocks" -) - -var svc *mocks.Service - -func TestMain(m *testing.M) { - svc = new(mocks.Service) - server := startGRPCServer(svc, port) - - code := m.Run() - - server.GracefulStop() - - os.Exit(code) -} diff --git a/auth/api/grpc/domains/client.go b/auth/api/grpc/domains/client.go deleted file mode 100644 index 1b952afc1..000000000 --- a/auth/api/grpc/domains/client.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "context" - "time" - - "github.com/absmach/magistrala" - grpcapi "github.com/absmach/magistrala/auth/api/grpc" - "github.com/go-kit/kit/endpoint" - kitgrpc "github.com/go-kit/kit/transport/grpc" - "google.golang.org/grpc" -) - -const domainsSvcName = "magistrala.DomainsService" - -var _ magistrala.DomainsServiceClient = (*domainsGrpcClient)(nil) - -type domainsGrpcClient struct { - deleteUserFromDomains endpoint.Endpoint - timeout time.Duration -} - -// NewDomainsClient returns new domains gRPC client instance. -func NewDomainsClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.DomainsServiceClient { - return &domainsGrpcClient{ - deleteUserFromDomains: kitgrpc.NewClient( - conn, - domainsSvcName, - "DeleteUserFromDomains", - encodeDeleteUserRequest, - decodeDeleteUserResponse, - magistrala.DeleteUserRes{}, - ).Endpoint(), - - timeout: timeout, - } -} - -func (client domainsGrpcClient) DeleteUserFromDomains(ctx context.Context, in *magistrala.DeleteUserReq, opts ...grpc.CallOption) (*magistrala.DeleteUserRes, error) { - ctx, cancel := context.WithTimeout(ctx, client.timeout) - defer cancel() - - res, err := client.deleteUserFromDomains(ctx, deleteUserPoliciesReq{ - ID: in.GetId(), - }) - if err != nil { - return &magistrala.DeleteUserRes{}, grpcapi.DecodeError(err) - } - - dpr := res.(deleteUserRes) - return &magistrala.DeleteUserRes{Deleted: dpr.deleted}, nil -} - -func decodeDeleteUserResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.DeleteUserRes) - return deleteUserRes{deleted: res.GetDeleted()}, nil -} - -func encodeDeleteUserRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(deleteUserPoliciesReq) - return &magistrala.DeleteUserReq{ - Id: req.ID, - }, nil -} diff --git a/auth/api/grpc/domains/doc.go b/auth/api/grpc/domains/doc.go deleted file mode 100644 index 4ae689977..000000000 --- a/auth/api/grpc/domains/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package grpc contains implementation of Domains service gRPC API. -package domains diff --git a/auth/api/grpc/domains/endpoint.go b/auth/api/grpc/domains/endpoint.go deleted file mode 100644 index 5bbb047e6..000000000 --- a/auth/api/grpc/domains/endpoint.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "context" - - "github.com/absmach/magistrala/auth" - "github.com/go-kit/kit/endpoint" -) - -func deleteUserFromDomainsEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(deleteUserPoliciesReq) - if err := req.validate(); err != nil { - return deleteUserRes{}, err - } - - if err := svc.DeleteUserFromDomains(ctx, req.ID); err != nil { - return deleteUserRes{}, err - } - - return deleteUserRes{deleted: true}, nil - } -} diff --git a/auth/api/grpc/domains/endpoint_test.go b/auth/api/grpc/domains/endpoint_test.go deleted file mode 100644 index 3bddb6914..000000000 --- a/auth/api/grpc/domains/endpoint_test.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains_test - -import ( - "context" - "fmt" - "net" - "testing" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - grpcapi "github.com/absmach/magistrala/auth/api/grpc/domains" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -const ( - port = 8081 - secret = "secret" - email = "test@example.com" - id = "testID" - thingsType = "things" - usersType = "users" - description = "Description" - groupName = "mgx" - adminpermission = "admin" - - authoritiesObj = "authorities" - memberRelation = "member" - loginDuration = 30 * time.Minute - refreshDuration = 24 * time.Hour - invalidDuration = 7 * 24 * time.Hour - validToken = "valid" - inValidToken = "invalid" - validPolicy = "valid" -) - -var authAddr = fmt.Sprintf("localhost:%d", port) - -func startGRPCServer(svc auth.Service, port int) *grpc.Server { - listener, _ := net.Listen("tcp", fmt.Sprintf(":%d", port)) - server := grpc.NewServer() - magistrala.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)) - }() - - return server -} - -func TestDeleteUserFromDomains(t *testing.T) { - conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - grpcClient := grpcapi.NewDomainsClient(conn, time.Second) - - cases := []struct { - desc string - token string - deleteUserReq *magistrala.DeleteUserReq - deleteUserRes *magistrala.DeleteUserRes - err error - }{ - { - desc: "delete valid req", - token: validToken, - deleteUserReq: &magistrala.DeleteUserReq{ - Id: id, - }, - deleteUserRes: &magistrala.DeleteUserRes{Deleted: true}, - err: nil, - }, - { - desc: "delete invalid req with invalid token", - token: inValidToken, - deleteUserReq: &magistrala.DeleteUserReq{}, - deleteUserRes: &magistrala.DeleteUserRes{Deleted: false}, - err: apiutil.ErrMissingID, - }, - { - desc: "delete invalid req with invalid token", - token: inValidToken, - deleteUserReq: &magistrala.DeleteUserReq{ - Id: id, - }, - deleteUserRes: &magistrala.DeleteUserRes{Deleted: false}, - err: apiutil.ErrMissingPolicyEntityType, - }, - } - for _, tc := range cases { - repoCall := svc.On("DeleteUserFromDomains", mock.Anything, tc.deleteUserReq.Id).Return(tc.err) - dpr, err := grpcClient.DeleteUserFromDomains(context.Background(), tc.deleteUserReq) - assert.Equal(t, tc.deleteUserRes.GetDeleted(), dpr.GetDeleted(), fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.deleteUserRes.GetDeleted(), dpr.GetDeleted())) - 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/auth/api/grpc/domains/requests.go b/auth/api/grpc/domains/requests.go deleted file mode 100644 index 8e9892879..000000000 --- a/auth/api/grpc/domains/requests.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "github.com/absmach/magistrala/pkg/apiutil" -) - -type deleteUserPoliciesReq struct { - ID string -} - -func (req deleteUserPoliciesReq) validate() error { - if req.ID == "" { - return apiutil.ErrMissingID - } - - return nil -} diff --git a/auth/api/grpc/domains/responses.go b/auth/api/grpc/domains/responses.go deleted file mode 100644 index 09b883089..000000000 --- a/auth/api/grpc/domains/responses.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -type deleteUserRes struct { - deleted bool -} diff --git a/auth/api/grpc/domains/server.go b/auth/api/grpc/domains/server.go deleted file mode 100644 index fdfc55ce3..000000000 --- a/auth/api/grpc/domains/server.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "context" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - grpcapi "github.com/absmach/magistrala/auth/api/grpc" - kitgrpc "github.com/go-kit/kit/transport/grpc" -) - -var _ magistrala.DomainsServiceServer = (*domainsGrpcServer)(nil) - -type domainsGrpcServer struct { - magistrala.UnimplementedDomainsServiceServer - deleteUserFromDomains kitgrpc.Handler -} - -func NewDomainsServer(svc auth.Service) magistrala.DomainsServiceServer { - return &domainsGrpcServer{ - deleteUserFromDomains: kitgrpc.NewServer( - (deleteUserFromDomainsEndpoint(svc)), - decodeDeleteUserRequest, - encodeDeleteUserResponse, - ), - } -} - -func decodeDeleteUserRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.DeleteUserReq) - return deleteUserPoliciesReq{ - ID: req.GetId(), - }, nil -} - -func encodeDeleteUserResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(deleteUserRes) - return &magistrala.DeleteUserRes{Deleted: res.deleted}, nil -} - -func (s *domainsGrpcServer) DeleteUserFromDomains(ctx context.Context, req *magistrala.DeleteUserReq) (*magistrala.DeleteUserRes, error) { - _, res, err := s.deleteUserFromDomains.ServeGRPC(ctx, req) - if err != nil { - return nil, grpcapi.EncodeError(err) - } - return res.(*magistrala.DeleteUserRes), nil -} diff --git a/auth/api/grpc/domains/setup_test.go b/auth/api/grpc/domains/setup_test.go deleted file mode 100644 index d65f23e79..000000000 --- a/auth/api/grpc/domains/setup_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains_test - -import ( - "os" - "testing" - - "github.com/absmach/magistrala/auth/mocks" -) - -var svc *mocks.Service - -func TestMain(m *testing.M) { - svc = new(mocks.Service) - server := startGRPCServer(svc, port) - - code := m.Run() - - server.GracefulStop() - - os.Exit(code) -} diff --git a/auth/api/grpc/token/client.go b/auth/api/grpc/token/client.go deleted file mode 100644 index ffb8247a5..000000000 --- a/auth/api/grpc/token/client.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package token - -import ( - "context" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - grpcapi "github.com/absmach/magistrala/auth/api/grpc" - "github.com/go-kit/kit/endpoint" - kitgrpc "github.com/go-kit/kit/transport/grpc" - "google.golang.org/grpc" -) - -const tokenSvcName = "magistrala.TokenService" - -type tokenGrpcClient struct { - issue endpoint.Endpoint - refresh endpoint.Endpoint - timeout time.Duration -} - -var _ magistrala.TokenServiceClient = (*tokenGrpcClient)(nil) - -// NewAuthClient returns new auth gRPC client instance. -func NewTokenClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.TokenServiceClient { - return &tokenGrpcClient{ - issue: kitgrpc.NewClient( - conn, - tokenSvcName, - "Issue", - encodeIssueRequest, - decodeIssueResponse, - magistrala.Token{}, - ).Endpoint(), - refresh: kitgrpc.NewClient( - conn, - tokenSvcName, - "Refresh", - encodeRefreshRequest, - decodeRefreshResponse, - magistrala.Token{}, - ).Endpoint(), - timeout: timeout, - } -} - -func (client tokenGrpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ ...grpc.CallOption) (*magistrala.Token, error) { - ctx, cancel := context.WithTimeout(ctx, client.timeout) - defer cancel() - - res, err := client.issue(ctx, issueReq{ - userID: req.GetUserId(), - keyType: auth.KeyType(req.GetType()), - }) - if err != nil { - return &magistrala.Token{}, grpcapi.DecodeError(err) - } - return res.(*magistrala.Token), nil -} - -func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(issueReq) - return &magistrala.IssueReq{ - UserId: req.userID, - Type: uint32(req.keyType), - }, nil -} - -func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - return grpcRes, nil -} - -func (client tokenGrpcClient) Refresh(ctx context.Context, req *magistrala.RefreshReq, _ ...grpc.CallOption) (*magistrala.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 res.(*magistrala.Token), nil -} - -func encodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(refreshReq) - return &magistrala.RefreshReq{RefreshToken: req.refreshToken}, nil -} - -func decodeRefreshResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - return grpcRes, nil -} diff --git a/auth/api/grpc/token/doc.go b/auth/api/grpc/token/doc.go deleted file mode 100644 index a91e3873b..000000000 --- a/auth/api/grpc/token/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package grpc contains implementation of Auth service gRPC API. -package token diff --git a/auth/api/grpc/token/endpoint.go b/auth/api/grpc/token/endpoint.go deleted file mode 100644 index ba2566a3d..000000000 --- a/auth/api/grpc/token/endpoint.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package token - -import ( - "context" - - "github.com/absmach/magistrala/auth" - "github.com/go-kit/kit/endpoint" -) - -func issueEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(issueReq) - if err := req.validate(); err != nil { - return issueRes{}, err - } - - key := auth.Key{ - Type: req.keyType, - User: req.userID, - } - tkn, err := svc.Issue(ctx, "", key) - if err != nil { - return issueRes{}, err - } - ret := issueRes{ - accessToken: tkn.AccessToken, - refreshToken: tkn.RefreshToken, - accessType: tkn.AccessType, - } - return ret, nil - } -} - -func refreshEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(refreshReq) - if err := req.validate(); err != nil { - return issueRes{}, err - } - - key := auth.Key{Type: auth.RefreshKey} - tkn, err := svc.Issue(ctx, req.refreshToken, key) - if err != nil { - return issueRes{}, err - } - ret := issueRes{ - accessToken: tkn.AccessToken, - refreshToken: tkn.RefreshToken, - accessType: tkn.AccessType, - } - return ret, nil - } -} diff --git a/auth/api/grpc/token/endpoint_test.go b/auth/api/grpc/token/endpoint_test.go deleted file mode 100644 index 8e0b8b7a9..000000000 --- a/auth/api/grpc/token/endpoint_test.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package token_test - -import ( - "context" - "fmt" - "net" - "testing" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - grpcapi "github.com/absmach/magistrala/auth/api/grpc/token" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -const ( - port = 8081 - secret = "secret" - email = "test@example.com" - id = "testID" - thingsType = "things" - usersType = "users" - description = "Description" - groupName = "mgx" - adminpermission = "admin" - - authoritiesObj = "authorities" - memberRelation = "member" - loginDuration = 30 * time.Minute - refreshDuration = 24 * time.Hour - invalidDuration = 7 * 24 * time.Hour - validToken = "valid" - inValidToken = "invalid" - validPolicy = "valid" -) - -var ( - validID = testsutil.GenerateUUID(&testing.T{}) - authAddr = fmt.Sprintf("localhost:%d", port) -) - -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)) - go func() { - err := server.Serve(listener) - assert.Nil(&testing.T{}, err, fmt.Sprintf(`"Unexpected error creating auth server %s"`, err)) - }() - - return server -} - -func TestIssue(t *testing.T) { - conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - grpcClient := grpcapi.NewTokenClient(conn, time.Second) - - cases := []struct { - desc string - userId string - kind auth.KeyType - issueResponse auth.Token - err error - }{ - { - desc: "issue for user with valid token", - userId: validID, - kind: auth.AccessKey, - issueResponse: auth.Token{ - AccessToken: validToken, - RefreshToken: validToken, - }, - err: nil, - }, - { - desc: "issue recovery key", - userId: validID, - kind: auth.RecoveryKey, - issueResponse: auth.Token{ - AccessToken: validToken, - RefreshToken: validToken, - }, - err: nil, - }, - { - desc: "issue API key unauthenticated", - userId: validID, - kind: auth.APIKey, - issueResponse: auth.Token{}, - err: svcerr.ErrAuthentication, - }, - { - desc: "issue for invalid key type", - userId: validID, - kind: 32, - issueResponse: auth.Token{}, - err: errors.ErrMalformedEntity, - }, - { - desc: "issue for user that does notexist", - userId: "", - kind: auth.APIKey, - issueResponse: auth.Token{}, - err: svcerr.ErrAuthentication, - }, - } - - 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() - }) - } -} - -func TestRefresh(t *testing.T) { - conn, err := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - assert.Nil(t, err, fmt.Sprintf("Unexpected error creating client connection %s", err)) - grpcClient := grpcapi.NewTokenClient(conn, time.Second) - - cases := []struct { - desc string - token string - issueResponse auth.Token - err error - }{ - { - desc: "refresh token with valid token", - token: validToken, - issueResponse: auth.Token{ - AccessToken: validToken, - RefreshToken: validToken, - }, - err: nil, - }, - { - desc: "refresh token with invalid token", - token: inValidToken, - issueResponse: auth.Token{}, - err: svcerr.ErrAuthentication, - }, - { - desc: "refresh token with empty token", - token: "", - issueResponse: auth.Token{}, - err: apiutil.ErrMissingSecret, - }, - } - - 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() - }) - } -} diff --git a/auth/api/grpc/token/requests.go b/auth/api/grpc/token/requests.go deleted file mode 100644 index 24c4a4d82..000000000 --- a/auth/api/grpc/token/requests.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package token - -import ( - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/apiutil" -) - -type issueReq struct { - userID string - keyType auth.KeyType -} - -func (req issueReq) validate() error { - if req.keyType != auth.AccessKey && - req.keyType != auth.APIKey && - req.keyType != auth.RecoveryKey && - req.keyType != auth.InvitationKey { - return apiutil.ErrInvalidAuthKey - } - - return nil -} - -type refreshReq struct { - refreshToken string -} - -func (req refreshReq) validate() error { - if req.refreshToken == "" { - return apiutil.ErrMissingSecret - } - - return nil -} diff --git a/auth/api/grpc/token/responses.go b/auth/api/grpc/token/responses.go deleted file mode 100644 index cb62744ee..000000000 --- a/auth/api/grpc/token/responses.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package token - -type issueRes struct { - accessToken string - refreshToken string - accessType string -} diff --git a/auth/api/grpc/token/server.go b/auth/api/grpc/token/server.go deleted file mode 100644 index a2432b323..000000000 --- a/auth/api/grpc/token/server.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package token - -import ( - "context" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - grpcapi "github.com/absmach/magistrala/auth/api/grpc" - kitgrpc "github.com/go-kit/kit/transport/grpc" -) - -var _ magistrala.TokenServiceServer = (*tokenGrpcServer)(nil) - -type tokenGrpcServer struct { - magistrala.UnimplementedTokenServiceServer - issue kitgrpc.Handler - refresh kitgrpc.Handler -} - -// NewAuthServer returns new AuthnServiceServer instance. -func NewTokenServer(svc auth.Service) magistrala.TokenServiceServer { - return &tokenGrpcServer{ - issue: kitgrpc.NewServer( - (issueEndpoint(svc)), - decodeIssueRequest, - encodeIssueResponse, - ), - refresh: kitgrpc.NewServer( - (refreshEndpoint(svc)), - decodeRefreshRequest, - encodeIssueResponse, - ), - } -} - -func (s *tokenGrpcServer) Issue(ctx context.Context, req *magistrala.IssueReq) (*magistrala.Token, error) { - _, res, err := s.issue.ServeGRPC(ctx, req) - if err != nil { - return nil, grpcapi.EncodeError(err) - } - return res.(*magistrala.Token), nil -} - -func (s *tokenGrpcServer) Refresh(ctx context.Context, req *magistrala.RefreshReq) (*magistrala.Token, error) { - _, res, err := s.refresh.ServeGRPC(ctx, req) - if err != nil { - return nil, grpcapi.EncodeError(err) - } - return res.(*magistrala.Token), nil -} - -func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.IssueReq) - return issueReq{ - userID: req.GetUserId(), - keyType: auth.KeyType(req.GetType()), - }, nil -} - -func decodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { - req := grpcReq.(*magistrala.RefreshReq) - return refreshReq{refreshToken: req.GetRefreshToken()}, nil -} - -func encodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(issueRes) - - return &magistrala.Token{ - AccessToken: res.accessToken, - RefreshToken: &res.refreshToken, - AccessType: res.accessType, - }, nil -} diff --git a/auth/api/grpc/token/setup_test.go b/auth/api/grpc/token/setup_test.go deleted file mode 100644 index 8a8c2e0c4..000000000 --- a/auth/api/grpc/token/setup_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package token_test - -import ( - "os" - "testing" - - "github.com/absmach/magistrala/auth/mocks" -) - -var svc *mocks.Service - -func TestMain(m *testing.M) { - svc = new(mocks.Service) - server := startGRPCServer(svc, port) - - code := m.Run() - - server.GracefulStop() - - os.Exit(code) -} diff --git a/auth/api/grpc/utils.go b/auth/api/grpc/utils.go deleted file mode 100644 index 5ad0cf4cd..000000000 --- a/auth/api/grpc/utils.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpc - -import ( - "fmt" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func EncodeError(err error) error { - switch { - case errors.Contains(err, nil): - return nil - case errors.Contains(err, errors.ErrMalformedEntity), - errors.Contains(err, svcerr.ErrInvalidPolicy), - 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, auth.ErrKeyExpired), - err == apiutil.ErrMissingEmail, - err == apiutil.ErrBearerToken: - return status.Error(codes.Unauthenticated, err.Error()) - case errors.Contains(err, svcerr.ErrAuthorization), - errors.Contains(err, svcerr.ErrDomainAuthorization): - return status.Error(codes.PermissionDenied, err.Error()) - case errors.Contains(err, svcerr.ErrNotFound): - return status.Error(codes.NotFound, err.Error()) - case errors.Contains(err, svcerr.ErrConflict): - return status.Error(codes.AlreadyExists, err.Error()) - default: - return status.Error(codes.Internal, err.Error()) - } -} - -func DecodeError(err error) error { - if st, ok := status.FromError(err); ok { - switch st.Code() { - case codes.NotFound: - return errors.Wrap(svcerr.ErrNotFound, errors.New(st.Message())) - case codes.InvalidArgument: - return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) - case codes.AlreadyExists: - return errors.Wrap(svcerr.ErrConflict, errors.New(st.Message())) - case codes.Unauthenticated: - return errors.Wrap(svcerr.ErrAuthentication, errors.New(st.Message())) - case codes.OK: - if msg := st.Message(); msg != "" { - return errors.Wrap(errors.ErrUnidentified, errors.New(msg)) - } - return nil - case codes.FailedPrecondition: - return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) - case codes.PermissionDenied: - return errors.Wrap(svcerr.ErrAuthorization, errors.New(st.Message())) - 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/auth/api/http/doc.go b/auth/api/http/doc.go deleted file mode 100644 index 59a5a1b44..000000000 --- a/auth/api/http/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package http diff --git a/auth/api/http/domains/decode.go b/auth/api/http/domains/decode.go deleted file mode 100644 index e0c58ecc5..000000000 --- a/auth/api/http/domains/decode.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "context" - "encoding/json" - "net/http" - "strings" - - "github.com/absmach/magistrala/auth" - "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 decodeCreateDomainRequest(_ 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 := createDomainReq{ - token: apiutil.ExtractBearerToken(r), - } - 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 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 -} - -func decodeUpdateDomainRequest(_ 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 := updateDomainReq{ - 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 decodeListDomainRequest(ctx context.Context, r *http.Request) (interface{}, error) { - page, err := decodePageRequest(ctx, r) - if err != nil { - return nil, err - } - req := listDomainsReq{ - token: apiutil.ExtractBearerToken(r), - page: page, - } - - return req, nil -} - -func decodeEnableDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := enableDomainReq{ - token: apiutil.ExtractBearerToken(r), - domainID: chi.URLParam(r, "domainID"), - } - return req, nil -} - -func decodeDisableDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := disableDomainReq{ - token: apiutil.ExtractBearerToken(r), - domainID: chi.URLParam(r, "domainID"), - } - return req, nil -} - -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) - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - or, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder) - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DefDir) - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - n, err := apiutil.ReadStringQuery(r, api.NameKey, "") - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - t, err := apiutil.ReadStringQuery(r, api.TagKey, "") - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - p, err := apiutil.ReadStringQuery(r, api.PermissionKey, "") - if err != nil { - return page{}, errors.Wrap(apiutil.ErrValidation, err) - } - - return page{ - offset: o, - order: or, - dir: dir, - limit: l, - name: n, - metadata: m, - tag: t, - permission: p, - status: st, - }, nil -} diff --git a/auth/api/http/domains/endpoint.go b/auth/api/http/domains/endpoint.go deleted file mode 100644 index ffb00a36e..000000000 --- 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/domains/endpoint_test.go b/auth/api/http/domains/endpoint_test.go deleted file mode 100644 index 2fe1fd7d3..000000000 --- a/auth/api/http/domains/endpoint_test.go +++ /dev/null @@ -1,1310 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains_test - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/auth" - httpapi "github.com/absmach/magistrala/auth/api/http/domains" - "github.com/absmach/magistrala/auth/mocks" - "github.com/absmach/magistrala/internal/testsutil" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - policies "github.com/absmach/magistrala/pkg/policies" - "github.com/go-chi/chi/v5" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - validCMetadata = auth.Metadata{"role": "client"} - ID = testsutil.GenerateUUID(&testing.T{}) - domain = auth.Domain{ - ID: ID, - Name: "domainname", - Tags: []string{"tag1", "tag2"}, - Metadata: validCMetadata, - Status: auth.EnabledStatus, - Alias: "mydomain", - } - validToken = "token" - inValidToken = "invalid" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - - id = "testID" -) - -const ( - contentType = "application/json" - refreshDuration = 24 * time.Hour - invalidDuration = 7 * 24 * time.Hour -) - -type testRequest struct { - client *http.Client - method string - url string - contentType string - token string - body io.Reader -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, tr.body) - if err != nil { - return nil, err - } - - if tr.token != "" { - req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) - } - - if tr.contentType != "" { - req.Header.Set("Content-Type", tr.contentType) - } - - req.Header.Set("Referer", "http://localhost") - - return tr.client.Do(req) -} - -func toJSON(data interface{}) string { - jsonData, err := json.Marshal(data) - if err != nil { - return "" - } - return string(jsonData) -} - -func newDomainsServer() (*httptest.Server, *mocks.Service) { - logger := mglog.NewMock() - mux := chi.NewRouter() - svc := new(mocks.Service) - httpapi.MakeHandler(svc, mux, logger) - return httptest.NewServer(mux), svc -} - -func TestCreateDomain(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - domain auth.Domain - token string - contentType string - svcErr error - status int - err error - }{ - { - desc: "register a new domain successfully", - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - token: validToken, - contentType: contentType, - status: http.StatusCreated, - err: nil, - }, - { - desc: "register a new domain with empty token", - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "register a new domain with invalid token", - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - token: inValidToken, - contentType: contentType, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "register a new domain with an empty name", - domain: auth.Domain{ - ID: ID, - Name: "", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrMissingName, - }, - { - desc: "register a new domain with an empty alias", - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "", - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrMissingAlias, - }, - { - desc: "register a new domain with invalid content type", - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - token: validToken, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "register a new domain that cant be marshalled", - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - data := toJSON(tc.domain) - req := testRequest{ - client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains", ds.URL), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(data), - } - - svcCall := svc.On("CreateDomain", mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - } -} - -func TestListDomains(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - token string - query string - listDomainsRequest auth.DomainsPage - status int - svcErr error - err error - }{ - { - desc: "list domains with valid token", - token: validToken, - status: http.StatusOK, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - err: nil, - }, - { - desc: "list domains with empty token", - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "list domains with invalid token", - token: inValidToken, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list domains with offset", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "offset=1", - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains with invalid offset", - token: validToken, - query: "offset=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with limit", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "limit=1", - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains with invalid limit", - token: validToken, - query: "limit=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with name", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "name=domainname", - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains with empty name", - token: validToken, - query: "name= ", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with duplicate name", - token: validToken, - query: "name=1&name=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list domains with status", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "status=enabled", - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains with invalid status", - token: validToken, - query: "status=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with duplicate status", - token: validToken, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list domains with tags", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains with empty tags", - token: validToken, - query: "tag= ", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with duplicate tags", - token: validToken, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list domains with metadata", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains with invalid metadata", - token: validToken, - query: "metadata=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with duplicate metadata", - token: validToken, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list domains with permissions", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "permission=view", - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains with invalid permissions", - token: validToken, - query: "permission= ", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with duplicate permissions", - token: validToken, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list domains with order", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "order=name", - status: http.StatusOK, - }, - { - desc: "list domains with invalid order", - token: validToken, - query: "order= ", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with duplicate order", - token: validToken, - query: "order=name&order=name", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list domains with dir", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "dir=asc", - status: http.StatusOK, - }, - { - desc: "list domains with invalid dir", - token: validToken, - query: "dir= ", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains with duplicate dir", - token: validToken, - query: "dir=asc&dir=asc", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, tc := range cases { - req := testRequest{ - client: ds.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/domains?", ds.URL) + tc.query, - token: tc.token, - } - - svcCall := svc.On("ListDomains", mock.Anything, mock.Anything, mock.Anything).Return(tc.listDomainsRequest, tc.svcErr) - 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() - } -} - -func TestViewDomain(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - token string - domainID string - status int - svcErr error - err error - }{ - { - desc: "view domain successfully", - token: validToken, - domainID: id, - status: http.StatusOK, - err: nil, - }, - { - desc: "view domain with empty token", - token: "", - domainID: id, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "view domain with invalid token", - token: inValidToken, - domainID: id, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - req := testRequest{ - client: ds.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/domains/%s", ds.URL, tc.domainID), - token: tc.token, - } - - svcCall := svc.On("RetrieveDomain", mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - } -} - -func TestViewDomainPermissions(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - token string - domainID string - status int - svcErr error - err error - }{ - { - desc: "view domain permissions successfully", - token: validToken, - domainID: id, - status: http.StatusOK, - err: nil, - }, - { - desc: "view domain permissions with empty token", - token: "", - domainID: id, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "view domain permissions with invalid token", - token: inValidToken, - domainID: id, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "view domain permissions with empty domainID", - token: validToken, - domainID: "", - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - req := testRequest{ - client: ds.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/domains/%s/permissions", ds.URL, tc.domainID), - token: tc.token, - } - - svcCall := svc.On("RetrieveDomainPermissions", mock.Anything, mock.Anything, mock.Anything).Return(policies.Permissions{}, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - - 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() - } -} - -func TestUpdateDomain(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - token string - domain auth.Domain - contentType string - status int - svcErr error - err error - }{ - { - desc: "update domain successfully", - token: validToken, - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - contentType: contentType, - status: http.StatusOK, - err: nil, - }, - { - desc: "update domain with empty token", - token: "", - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "update domain with invalid token", - token: inValidToken, - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - contentType: contentType, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update domain with invalid content type", - token: validToken, - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: auth.Metadata{"role": "domain"}, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "update domain with data that cant be marshalled", - token: validToken, - domain: auth.Domain{ - ID: ID, - Name: "test", - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - Tags: []string{"tag1", "tag2"}, - Alias: "test", - }, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - data := toJSON(tc.domain) - req := testRequest{ - client: ds.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/domains/%s", ds.URL, tc.domain.ID), - body: strings.NewReader(data), - contentType: tc.contentType, - token: tc.token, - } - - svcCall := svc.On("UpdateDomain", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - - 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() - } -} - -func TestEnableDomain(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - disabledDomain := domain - disabledDomain.Status = auth.DisabledStatus - - cases := []struct { - desc string - domain auth.Domain - response auth.Domain - token string - status int - svcErr error - err error - }{ - { - desc: "enable domain with valid token", - domain: disabledDomain, - response: auth.Domain{ - ID: domain.ID, - Status: auth.EnabledStatus, - }, - token: validToken, - status: http.StatusOK, - err: nil, - }, - { - desc: "enable domain with invalid token", - domain: disabledDomain, - token: inValidToken, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "enable domain with empty token", - domain: disabledDomain, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "enable domain with empty id", - domain: auth.Domain{ - ID: "", - }, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "enable domain with invalid id", - domain: auth.Domain{ - ID: "invalid", - }, - token: validToken, - status: http.StatusForbidden, - svcErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - data := toJSON(tc.domain) - req := testRequest{ - client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains/%s/enable", ds.URL, tc.domain.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) - 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() - } -} - -func TestDisableDomain(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - domain auth.Domain - response auth.Domain - token string - status int - svcErr error - err error - }{ - { - desc: "disable domain with valid token", - domain: domain, - response: auth.Domain{ - ID: domain.ID, - Status: auth.DisabledStatus, - }, - token: validToken, - status: http.StatusOK, - err: nil, - }, - { - desc: "disable domain with invalid token", - domain: domain, - token: inValidToken, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "disable domain with empty token", - domain: domain, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "disable domain with empty id", - domain: auth.Domain{ - ID: "", - }, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "disable domain with invalid id", - domain: auth.Domain{ - ID: "invalid", - }, - token: validToken, - status: http.StatusForbidden, - svcErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - data := toJSON(tc.domain) - req := testRequest{ - client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains/%s/disable", ds.URL, tc.domain.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) - 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() - } -} - -func TestFreezeDomain(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - domain auth.Domain - response auth.Domain - token string - status int - svcErr error - err error - }{ - { - desc: "freeze domain with valid token", - domain: domain, - response: auth.Domain{ - ID: domain.ID, - Status: auth.FreezeStatus, - }, - token: validToken, - status: http.StatusOK, - err: nil, - }, - { - desc: "freeze domain with invalid token", - domain: domain, - token: inValidToken, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "freeze domain with empty token", - domain: domain, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "freeze domain with empty id", - domain: auth.Domain{ - ID: "", - }, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "freeze domain with invalid id", - domain: auth.Domain{ - ID: "invalid", - }, - token: validToken, - status: http.StatusForbidden, - svcErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - data := toJSON(tc.domain) - req := testRequest{ - client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains/%s/freeze", ds.URL, tc.domain.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) - 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() - } -} - -func TestAssignDomainUsers(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - data string - domainID string - contentType string - token string - status int - err error - }{ - { - desc: "assign domain users with valid token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - domainID: domain.ID, - contentType: contentType, - token: validToken, - status: http.StatusCreated, - err: nil, - }, - { - desc: "assign domain users with invalid token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - domainID: domain.ID, - contentType: contentType, - token: inValidToken, - status: http.StatusUnauthorized, - err: svcerr.ErrAuthentication, - }, - { - desc: "assign domain users with empty token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - domainID: domain.ID, - contentType: contentType, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "assign domain users with empty id", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - domainID: "", - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "assign domain users with invalid id", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - domainID: "invalid", - contentType: contentType, - token: validToken, - status: http.StatusForbidden, - err: svcerr.ErrAuthorization, - }, - { - desc: "assign domain users with malformed data", - data: fmt.Sprintf(`{"relation": "%s", user_ids : ["%s", "%s"]}`, "editor", validID, validID), - domainID: domain.ID, - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "assign domain users with invalid content type", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - domainID: domain.ID, - contentType: "application/xml", - token: validToken, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "assign domain users with empty user ids", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : []}`, "editor"), - domainID: domain.ID, - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "assign domain users with empty relation", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "", validID, validID), - domainID: domain.ID, - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingRelation, - }, - } - - for _, tc := range cases { - req := testRequest{ - client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains/%s/users/assign", ds.URL, tc.domainID), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - - svcCall := svc.On("AssignUsers", mock.Anything, tc.token, tc.domainID, 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)) - svcCall.Unset() - } -} - -func TestUnassignDomainUser(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - data string - domainID string - contentType string - token string - status int - err error - }{ - { - desc: "unassign domain user with valid token", - data: fmt.Sprintf(`{"relation": "%s", "user_id" : "%s"}`, "editor", validID), - domainID: domain.ID, - contentType: contentType, - token: validToken, - status: http.StatusNoContent, - err: nil, - }, - { - desc: "unassign domain user with invalid token", - data: fmt.Sprintf(`{"relation": "%s", "user_id" : "%s"}`, "editor", validID), - domainID: domain.ID, - contentType: contentType, - token: inValidToken, - status: http.StatusUnauthorized, - err: svcerr.ErrAuthentication, - }, - { - desc: "unassign domain user with empty token", - data: fmt.Sprintf(`{"relation": "%s", "user_id" : "%s"}`, "editor", validID), - domainID: domain.ID, - contentType: contentType, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "unassign domain user with empty domain id", - data: fmt.Sprintf(`{"relation": "%s", "user_id" : "%s"}`, "editor", validID), - domainID: "", - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "unassign domain user with invalid id", - data: fmt.Sprintf(`{"relation": "%s", "user_id" : "%s"}`, "editor", validID), - domainID: "invalid", - contentType: contentType, - token: validToken, - status: http.StatusForbidden, - err: svcerr.ErrAuthorization, - }, - { - desc: "unassign domain user with malformed data", - data: fmt.Sprintf(`{"relation": "%s", "user_id" : "%s}`, "editor", validID), - domainID: domain.ID, - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "unassign domain user with invalid content type", - data: fmt.Sprintf(`{"relation": "%s", "user_id" : "%s"}`, "editor", validID), - domainID: domain.ID, - contentType: "application/xml", - token: validToken, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "unassign domain user with empty user id", - data: fmt.Sprintf(`{"relation": "%s", "user_id" : ""}`, "editor"), - domainID: domain.ID, - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - req := testRequest{ - client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains/%s/users/unassign", ds.URL, tc.domainID), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - - svcCall := svc.On("UnassignUser", mock.Anything, tc.token, tc.domainID, 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)) - svcCall.Unset() - } -} - -func TestListDomainsByUserID(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - token string - query string - listDomainsRequest auth.DomainsPage - userID string - status int - svcErr error - err error - }{ - { - desc: "list domains by user id with valid token", - token: validToken, - status: http.StatusOK, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - userID: validID, - err: nil, - }, - { - desc: "list domains by user id with empty user id", - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "list domains by user id with empty token", - token: "", - userID: validID, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "list domains by user id with invalid token", - token: inValidToken, - userID: validID, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list domains by user id with offset", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "offset=1", - userID: validID, - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains by user id with invalid offset", - token: validToken, - query: "offset=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list domains by user id with limit", - token: validToken, - listDomainsRequest: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{domain}, - }, - query: "limit=1", - userID: validID, - status: http.StatusOK, - err: nil, - }, - { - desc: "list domains by user id with invalid limit", - token: validToken, - query: "limit=invalid", - userID: validID, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - for _, tc := range cases { - req := testRequest{ - client: ds.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/users/%s/domains?", ds.URL, tc.userID) + tc.query, - token: tc.token, - } - - svcCall := svc.On("ListUserDomains", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.listDomainsRequest, tc.svcErr) - 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() - } -} - -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"` -} diff --git a/auth/api/http/domains/requests.go b/auth/api/http/domains/requests.go deleted file mode 100644 index 5abbddd0d..000000000 --- a/auth/api/http/domains/requests.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/apiutil" -) - -type page struct { - offset uint64 - limit uint64 - order string - dir string - name string - metadata map[string]interface{} - tag string - permission string - status auth.Status -} - -type createDomainReq struct { - token string - Name string `json:"name"` - Metadata map[string]interface{} `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` - Alias string `json:"alias"` -} - -func (req createDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.Name == "" { - return apiutil.ErrMissingName - } - - if req.Alias == "" { - return apiutil.ErrMissingAlias - } - - return nil -} - -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 - } - - return nil -} - -type updateDomainReq struct { - token string - domainID string - Name *string `json:"name,omitempty"` - Metadata *map[string]interface{} `json:"metadata,omitempty"` - Tags *[]string `json:"tags,omitempty"` - Alias *string `json:"alias,omitempty"` -} - -func (req updateDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type listDomainsReq struct { - token string - page -} - -func (req listDomainsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - return nil -} - -type enableDomainReq struct { - token string - domainID string -} - -func (req enableDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type disableDomainReq struct { - token string - domainID string -} - -func (req disableDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -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/auth/api/http/domains/responses.go deleted file mode 100644 index 3eb277ef0..000000000 --- a/auth/api/http/domains/responses.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" -) - -var ( - _ magistrala.Response = (*createDomainRes)(nil) - _ magistrala.Response = (*retrieveDomainRes)(nil) - _ magistrala.Response = (*assignUsersRes)(nil) - _ magistrala.Response = (*unassignUsersRes)(nil) - _ magistrala.Response = (*listDomainsRes)(nil) -) - -type createDomainRes struct { - auth.Domain -} - -func (res createDomainRes) Code() int { - return http.StatusCreated -} - -func (res createDomainRes) Headers() map[string]string { - return map[string]string{} -} - -func (res createDomainRes) Empty() bool { - return false -} - -type retrieveDomainRes struct { - auth.Domain -} - -func (res retrieveDomainRes) Code() int { - return http.StatusOK -} - -func (res retrieveDomainRes) Headers() map[string]string { - return map[string]string{} -} - -func (res retrieveDomainRes) Empty() bool { - return false -} - -type retrieveDomainPermissionsRes struct { - Permissions []string `json:"permissions"` -} - -func (res retrieveDomainPermissionsRes) Code() int { - return http.StatusOK -} - -func (res retrieveDomainPermissionsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res retrieveDomainPermissionsRes) Empty() bool { - return false -} - -type updateDomainRes struct { - auth.Domain -} - -func (res updateDomainRes) Code() int { - return http.StatusOK -} - -func (res updateDomainRes) Headers() map[string]string { - return map[string]string{} -} - -func (res updateDomainRes) Empty() bool { - return false -} - -type listDomainsRes struct { - auth.DomainsPage -} - -func (res listDomainsRes) Code() int { - return http.StatusOK -} - -func (res listDomainsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res listDomainsRes) Empty() bool { - return false -} - -type enableDomainRes struct{} - -func (res enableDomainRes) Code() int { - return http.StatusOK -} - -func (res enableDomainRes) Headers() map[string]string { - return map[string]string{} -} - -func (res enableDomainRes) Empty() bool { - return true -} - -type disableDomainRes struct{} - -func (res disableDomainRes) Code() int { - return http.StatusOK -} - -func (res disableDomainRes) Headers() map[string]string { - return map[string]string{} -} - -func (res disableDomainRes) Empty() bool { - return true -} - -type freezeDomainRes struct{} - -func (res freezeDomainRes) Code() int { - return http.StatusOK -} - -func (res freezeDomainRes) Headers() map[string]string { - return map[string]string{} -} - -func (res freezeDomainRes) 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 listUserDomainsRes struct { - auth.DomainsPage -} - -func (res listUserDomainsRes) Code() int { - return http.StatusOK -} - -func (res listUserDomainsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res listUserDomainsRes) Empty() bool { - return false -} diff --git a/auth/api/http/domains/transport.go b/auth/api/http/domains/transport.go deleted file mode 100644 index 332e9b78a..000000000 --- a/auth/api/http/domains/transport.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package domains - -import ( - "log/slog" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - mux.Route("/domains", func(r chi.Router) { - 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.Route("/{domainID}", func(r chi.Router) { - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - retrieveDomainEndpoint(svc), - decodeRetrieveDomainRequest, - api.EncodeResponse, - 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, - api.EncodeResponse, - opts..., - ), "update_domain").ServeHTTP) - - r.Post("/enable", otelhttp.NewHandler(kithttp.NewServer( - enableDomainEndpoint(svc), - decodeEnableDomainRequest, - api.EncodeResponse, - opts..., - ), "enable_domain").ServeHTTP) - - r.Post("/disable", otelhttp.NewHandler(kithttp.NewServer( - disableDomainEndpoint(svc), - decodeDisableDomainRequest, - api.EncodeResponse, - opts..., - ), "disable_domain").ServeHTTP) - - r.Post("/freeze", otelhttp.NewHandler(kithttp.NewServer( - freezeDomainEndpoint(svc), - decodeFreezeDomainRequest, - api.EncodeResponse, - opts..., - ), "freeze_domain").ServeHTTP) - - 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) - - return mux -} diff --git a/auth/api/http/keys/endpoint.go b/auth/api/http/keys/endpoint.go deleted file mode 100644 index 4c3d1b7ec..000000000 --- a/auth/api/http/keys/endpoint.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package keys - -import ( - "context" - "time" - - "github.com/absmach/magistrala/auth" - "github.com/go-kit/kit/endpoint" -) - -func issueEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(issueKeyReq) - if err := req.validate(); err != nil { - return nil, err - } - - now := time.Now().UTC() - newKey := auth.Key{ - IssuedAt: now, - Type: req.Type, - } - - duration := time.Duration(req.Duration * time.Second) - if duration != 0 { - exp := now.Add(duration) - newKey.ExpiresAt = exp - } - - tkn, err := svc.Issue(ctx, req.token, newKey) - if err != nil { - return nil, err - } - - res := issueKeyRes{ - Value: tkn.AccessToken, - } - - return res, nil - } -} - -func retrieveEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(keyReq) - - if err := req.validate(); err != nil { - return nil, err - } - - key, err := svc.RetrieveKey(ctx, req.token, req.id) - if err != nil { - return nil, err - } - ret := retrieveKeyRes{ - ID: key.ID, - IssuerID: key.Issuer, - Subject: key.Subject, - Type: key.Type, - IssuedAt: key.IssuedAt, - } - if !key.ExpiresAt.IsZero() { - ret.ExpiresAt = &key.ExpiresAt - } - - return ret, nil - } -} - -func revokeEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(keyReq) - - if err := req.validate(); err != nil { - return nil, err - } - - if err := svc.Revoke(ctx, req.token, req.id); err != nil { - return nil, err - } - - return revokeKeyRes{}, nil - } -} diff --git a/auth/api/http/keys/endpoint_test.go b/auth/api/http/keys/endpoint_test.go deleted file mode 100644 index 4ed62a340..000000000 --- a/auth/api/http/keys/endpoint_test.go +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package keys_test - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/auth" - httpapi "github.com/absmach/magistrala/auth/api/http" - "github.com/absmach/magistrala/auth/jwt" - "github.com/absmach/magistrala/auth/mocks" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/apiutil" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - policymocks "github.com/absmach/magistrala/pkg/policies/mocks" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const ( - secret = "secret" - contentType = "application/json" - id = "123e4567-e89b-12d3-a456-000000000001" - email = "user@example.com" - loginDuration = 30 * time.Minute - refreshDuration = 24 * time.Hour - invalidDuration = 7 * 24 * time.Hour -) - -type issueRequest struct { - Duration time.Duration `json:"duration,omitempty"` - Type uint32 `json:"type,omitempty"` -} - -type testRequest struct { - client *http.Client - method string - url string - contentType string - token string - body io.Reader -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, tr.body) - if err != nil { - return nil, err - } - if tr.token != "" { - req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) - } - if tr.contentType != "" { - req.Header.Set("Content-Type", tr.contentType) - } - - req.Header.Set("Referer", "http://localhost") - return tr.client.Do(req) -} - -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 -} - -func newServer(svc auth.Service) *httptest.Server { - mux := httpapi.MakeHandler(svc, mglog.NewMock(), "") - return httptest.NewServer(mux) -} - -func toJSON(data interface{}) string { - jsonData, err := json.Marshal(data) - if err != nil { - return "" - } - return string(jsonData) -} - -func TestIssue(t *testing.T) { - svc, krepo := newService() - token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - - ts := newServer(svc) - defer ts.Close() - client := ts.Client() - - lk := issueRequest{Type: uint32(auth.AccessKey)} - ak := issueRequest{Type: uint32(auth.APIKey), Duration: time.Hour} - rk := issueRequest{Type: uint32(auth.RecoveryKey)} - - cases := []struct { - desc string - req string - ct string - token string - status int - }{ - { - desc: "issue login key with empty token", - req: toJSON(lk), - ct: contentType, - token: "", - status: http.StatusUnauthorized, - }, - { - desc: "issue API key", - req: toJSON(ak), - ct: contentType, - token: token.AccessToken, - status: http.StatusCreated, - }, - { - desc: "issue recovery key", - req: toJSON(rk), - ct: contentType, - token: token.AccessToken, - status: http.StatusCreated, - }, - { - desc: "issue login key wrong content type", - req: toJSON(lk), - ct: "", - token: token.AccessToken, - status: http.StatusUnsupportedMediaType, - }, - { - desc: "issue recovery key wrong content type", - req: toJSON(rk), - ct: "", - token: token.AccessToken, - status: http.StatusUnsupportedMediaType, - }, - { - desc: "issue key with an invalid token", - req: toJSON(ak), - ct: contentType, - token: "wrong", - status: http.StatusUnauthorized, - }, - { - desc: "issue recovery key with empty token", - req: toJSON(rk), - ct: contentType, - token: "", - status: http.StatusUnauthorized, - }, - { - desc: "issue key with invalid request", - req: "{", - ct: contentType, - token: token.AccessToken, - status: http.StatusBadRequest, - }, - { - desc: "issue key with invalid JSON", - req: "{invalid}", - ct: contentType, - token: token.AccessToken, - status: http.StatusBadRequest, - }, - { - desc: "issue key with invalid JSON content", - req: `{"Type":{"key":"AccessToken"}}`, - ct: contentType, - token: token.AccessToken, - status: http.StatusBadRequest, - }, - } - - for _, tc := range cases { - req := testRequest{ - client: client, - method: http.MethodPost, - url: fmt.Sprintf("%s/keys", ts.URL), - contentType: tc.ct, - token: tc.token, - body: strings.NewReader(tc.req), - } - repocall := krepo.On("Save", mock.Anything, mock.Anything).Return("", nil) - 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)) - repocall.Unset() - } -} - -func TestRetrieve(t *testing.T) { - svc, krepo := newService() - token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - key := auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), Subject: id} - - repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - k, err := svc.Issue(context.Background(), token.AccessToken, key) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall.Unset() - - ts := newServer(svc) - defer ts.Close() - client := ts.Client() - - cases := []struct { - desc string - id string - token string - key auth.Key - status int - err error - }{ - { - desc: "retrieve an existing key", - id: k.AccessToken, - token: token.AccessToken, - key: auth.Key{ - Subject: id, - Type: auth.AccessKey, - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(refreshDuration), - }, - status: http.StatusOK, - err: nil, - }, - { - desc: "retrieve a non-existing key", - id: "non-existing", - token: token.AccessToken, - status: http.StatusBadRequest, - err: svcerr.ErrNotFound, - }, - { - desc: "retrieve a key with an invalid token", - id: k.AccessToken, - token: "wrong", - status: http.StatusUnauthorized, - err: svcerr.ErrAuthentication, - }, - { - desc: "retrieve a key with an empty token", - token: "", - id: k.AccessToken, - status: http.StatusUnauthorized, - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - req := testRequest{ - client: client, - method: http.MethodGet, - url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id), - token: tc.token, - } - repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(tc.key, 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)) - repocall.Unset() - } -} - -func TestRevoke(t *testing.T) { - svc, krepo := newService() - token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - key := auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), Subject: id} - - repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - k, err := svc.Issue(context.Background(), token.AccessToken, key) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall.Unset() - - ts := newServer(svc) - defer ts.Close() - client := ts.Client() - - cases := []struct { - desc string - id string - token string - status int - }{ - { - desc: "revoke an existing key", - id: k.AccessToken, - token: token.AccessToken, - status: http.StatusNoContent, - }, - { - desc: "revoke a non-existing key", - id: "non-existing", - token: token.AccessToken, - status: http.StatusNoContent, - }, - { - desc: "revoke key with invalid token", - id: k.AccessToken, - token: "wrong", - status: http.StatusUnauthorized, - }, - { - desc: "revoke key with empty token", - id: k.AccessToken, - token: "", - status: http.StatusUnauthorized, - }, - } - - for _, tc := range cases { - req := testRequest{ - client: client, - method: http.MethodDelete, - url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id), - token: tc.token, - } - repocall := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(nil) - 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)) - repocall.Unset() - } -} diff --git a/auth/api/http/keys/requests.go b/auth/api/http/keys/requests.go deleted file mode 100644 index 53542c60e..000000000 --- a/auth/api/http/keys/requests.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package keys - -import ( - "time" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/apiutil" -) - -type issueKeyReq struct { - token string - Type auth.KeyType `json:"type,omitempty"` - Duration time.Duration `json:"duration,omitempty"` -} - -// It is not possible to issue Reset key using HTTP API. -func (req issueKeyReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.Type != auth.AccessKey && - req.Type != auth.RecoveryKey && - req.Type != auth.APIKey { - return apiutil.ErrInvalidAPIKey - } - - return nil -} - -type keyReq struct { - token string - id string -} - -func (req keyReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.id == "" { - return apiutil.ErrMissingID - } - return nil -} diff --git a/auth/api/http/keys/requests_test.go b/auth/api/http/keys/requests_test.go deleted file mode 100644 index 6172f2434..000000000 --- a/auth/api/http/keys/requests_test.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package keys - -import ( - "testing" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/stretchr/testify/assert" -) - -var valid = "valid" - -func TestIssueKeyReqValidate(t *testing.T) { - cases := []struct { - desc string - req issueKeyReq - err error - }{ - { - desc: "valid request", - req: issueKeyReq{ - token: valid, - Type: auth.AccessKey, - }, - err: nil, - }, - { - desc: "empty token", - req: issueKeyReq{ - token: "", - Type: auth.AccessKey, - }, - err: apiutil.ErrBearerToken, - }, - { - desc: "invalid key type", - req: issueKeyReq{ - token: valid, - Type: auth.KeyType(100), - }, - err: apiutil.ErrInvalidAPIKey, - }, - } - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err) - } -} - -func TestKeyReqValidate(t *testing.T) { - cases := []struct { - desc string - req keyReq - err error - }{ - { - desc: "valid request", - req: keyReq{ - token: valid, - id: valid, - }, - err: nil, - }, - { - desc: "empty token", - req: keyReq{ - token: "", - id: valid, - }, - err: apiutil.ErrBearerToken, - }, - { - desc: "empty id", - req: keyReq{ - token: valid, - id: "", - }, - err: apiutil.ErrMissingID, - }, - } - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err) - } -} diff --git a/auth/api/http/keys/responses.go b/auth/api/http/keys/responses.go deleted file mode 100644 index ca99b9cec..000000000 --- a/auth/api/http/keys/responses.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package keys - -import ( - "net/http" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" -) - -var ( - _ magistrala.Response = (*issueKeyRes)(nil) - _ magistrala.Response = (*revokeKeyRes)(nil) -) - -type issueKeyRes struct { - ID string `json:"id,omitempty"` - Value string `json:"value,omitempty"` - IssuedAt time.Time `json:"issued_at,omitempty"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` -} - -func (res issueKeyRes) Code() int { - return http.StatusCreated -} - -func (res issueKeyRes) Headers() map[string]string { - return map[string]string{} -} - -func (res issueKeyRes) Empty() bool { - return res.Value == "" -} - -type retrieveKeyRes struct { - ID string `json:"id,omitempty"` - IssuerID string `json:"issuer_id,omitempty"` - Subject string `json:"subject,omitempty"` - Type auth.KeyType `json:"type,omitempty"` - IssuedAt time.Time `json:"issued_at,omitempty"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` -} - -func (res retrieveKeyRes) Code() int { - return http.StatusOK -} - -func (res retrieveKeyRes) Headers() map[string]string { - return map[string]string{} -} - -func (res retrieveKeyRes) Empty() bool { - return false -} - -type revokeKeyRes struct{} - -func (res revokeKeyRes) Code() int { - return http.StatusNoContent -} - -func (res revokeKeyRes) Headers() map[string]string { - return map[string]string{} -} - -func (res revokeKeyRes) Empty() bool { - return true -} diff --git a/auth/api/http/keys/transport.go b/auth/api/http/keys/transport.go deleted file mode 100644 index 9554df3ba..000000000 --- a/auth/api/http/keys/transport.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package keys - -import ( - "context" - "encoding/json" - "log/slog" - "net/http" - "strings" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" -) - -const contentType = "application/json" - -// MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - mux.Route("/keys", func(r chi.Router) { - r.Post("/", kithttp.NewServer( - issueEndpoint(svc), - decodeIssue, - api.EncodeResponse, - opts..., - ).ServeHTTP) - - r.Get("/{id}", kithttp.NewServer( - (retrieveEndpoint(svc)), - decodeKeyReq, - api.EncodeResponse, - opts..., - ).ServeHTTP) - - r.Delete("/{id}", kithttp.NewServer( - (revokeEndpoint(svc)), - decodeKeyReq, - api.EncodeResponse, - opts..., - ).ServeHTTP) - }) - return mux -} - -func decodeIssue(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, apiutil.ErrUnsupportedContentType - } - - req := issueKeyReq{token: apiutil.ExtractBearerToken(r)} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - return req, nil -} - -func decodeKeyReq(_ context.Context, r *http.Request) (interface{}, error) { - req := keyReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "id"), - } - return req, nil -} diff --git a/auth/api/http/transport.go b/auth/api/http/transport.go deleted file mode 100644 index 5e31ee553..000000000 --- a/auth/api/http/transport.go +++ /dev/null @@ -1,28 +0,0 @@ -// 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/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" -) - -// MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc auth.Service, logger *slog.Logger, instanceID string) http.Handler { - 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()) - - return mux -} diff --git a/auth/api/logging.go b/auth/api/logging.go deleted file mode 100644 index 30182bb4c..000000000 --- a/auth/api/logging.go +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/policies" -) - -var _ auth.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc auth.Service -} - -// LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc auth.Service, logger *slog.Logger) auth.Service { - return &loggingMiddleware{logger, svc} -} - -func (lm *loggingMiddleware) Issue(ctx context.Context, token string, key auth.Key) (tkn auth.Token, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("key", - slog.String("subject", key.Subject), - slog.Any("type", key.Type), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Issue key failed", args...) - return - } - lm.logger.Info("Issue key completed successfully", args...) - }(time.Now()) - - return lm.svc.Issue(ctx, token, key) -} - -func (lm *loggingMiddleware) Revoke(ctx context.Context, token, id string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("key_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Revoke key failed", args...) - return - } - lm.logger.Info("Revoke key completed successfully", args...) - }(time.Now()) - - return lm.svc.Revoke(ctx, token, id) -} - -func (lm *loggingMiddleware) RetrieveKey(ctx context.Context, token, id string) (key auth.Key, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("key_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Retrieve key failed", args...) - return - } - lm.logger.Info("Retrieve key completed successfully", args...) - }(time.Now()) - - return lm.svc.RetrieveKey(ctx, token, id) -} - -func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id auth.Key, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("key", - slog.String("subject", id.Subject), - slog.Any("type", id.Type), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Identify key failed", args...) - return - } - lm.logger.Info("Identify key completed successfully", args...) - }(time.Now()) - - return lm.svc.Identify(ctx, token) -} - -func (lm *loggingMiddleware) Authorize(ctx context.Context, pr policies.Policy) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("object", - slog.String("id", pr.Object), - slog.String("type", pr.ObjectType), - ), - slog.Group("subject", - slog.String("id", pr.Subject), - slog.String("kind", pr.SubjectKind), - slog.String("type", pr.SubjectType), - ), - slog.String("permission", pr.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, 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 deleted file mode 100644 index 1e2befa8d..000000000 --- a/auth/api/metrics.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "context" - "time" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/policies" - "github.com/go-kit/kit/metrics" -) - -var _ auth.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc auth.Service -} - -// MetricsMiddleware instruments core service by tracking request count and latency. -func MetricsMiddleware(svc auth.Service, counter metrics.Counter, latency metrics.Histogram) auth.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -func (ms *metricsMiddleware) Issue(ctx context.Context, token string, key auth.Key) (auth.Token, error) { - defer func(begin time.Time) { - ms.counter.With("method", "issue_key").Add(1) - ms.latency.With("method", "issue_key").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.Issue(ctx, token, key) -} - -func (ms *metricsMiddleware) Revoke(ctx context.Context, token, id string) error { - defer func(begin time.Time) { - ms.counter.With("method", "revoke_key").Add(1) - ms.latency.With("method", "revoke_key").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.Revoke(ctx, token, id) -} - -func (ms *metricsMiddleware) RetrieveKey(ctx context.Context, token, id string) (auth.Key, error) { - defer func(begin time.Time) { - ms.counter.With("method", "retrieve_key").Add(1) - ms.latency.With("method", "retrieve_key").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.RetrieveKey(ctx, token, id) -} - -func (ms *metricsMiddleware) Identify(ctx context.Context, token string) (auth.Key, error) { - defer func(begin time.Time) { - ms.counter.With("method", "identify").Add(1) - ms.latency.With("method", "identify").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.Identify(ctx, token) -} - -func (ms *metricsMiddleware) Authorize(ctx context.Context, pr policies.Policy) 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, 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/domains.go b/auth/domains.go deleted file mode 100644 index e9efc5803..000000000 --- a/auth/domains.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -import ( - "context" - "encoding/json" - "strings" - "time" - - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/policies" -) - -// Status represents Domain status. -type Status uint8 - -// Possible Domain status values. -const ( - // EnabledStatus represents enabled Domain. - EnabledStatus Status = iota - // DisabledStatus represents disabled Domain. - DisabledStatus - // FreezeStatus represents domain is in freezed state. - FreezeStatus - - // AllStatus is used for querying purposes to list Domains irrespective - // of their status - enabled, disabled, freezed, deleting. It is never stored in the - // database as the actual domain status and should always be the larger than freeze status - // value in this enumeration. - AllStatus -) - -// String representation of the possible status values. -const ( - Disabled = "disabled" - Enabled = "enabled" - Freezed = "freezed" - All = "all" - Unknown = "unknown" -) - -// String converts client/group status to string literal. -func (s Status) String() string { - switch s { - case DisabledStatus: - return Disabled - case EnabledStatus: - return Enabled - case AllStatus: - return All - case FreezeStatus: - return Freezed - default: - return Unknown - } -} - -// ToStatus converts string value to a valid Domain status. -func ToStatus(status string) (Status, error) { - switch status { - case "", Enabled: - return EnabledStatus, nil - case Disabled: - return DisabledStatus, nil - case Freezed: - return FreezeStatus, nil - case All: - return AllStatus, nil - } - return Status(0), svcerr.ErrInvalidStatus -} - -// Custom Marshaller for Domains status. -func (s Status) MarshalJSON() ([]byte, error) { - return json.Marshal(s.String()) -} - -// Custom Unmarshaler for Domains status. -func (s *Status) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToStatus(str) - *s = val - return err -} - -type DomainReq struct { - Name *string `json:"name,omitempty"` - Metadata *Metadata `json:"metadata,omitempty"` - Tags *[]string `json:"tags,omitempty"` - Alias *string `json:"alias,omitempty"` - Status *Status `json:"status,omitempty"` -} -type Domain struct { - ID string `json:"id"` - Name string `json:"name"` - Metadata Metadata `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` - Alias string `json:"alias,omitempty"` - Status Status `json:"status"` - Permission string `json:"permission,omitempty"` - CreatedBy string `json:"created_by,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedBy string `json:"updated_by,omitempty"` - 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"` - Limit uint64 `json:"limit"` - Name string `json:"name,omitempty"` - Order string `json:"-"` - Dir string `json:"-"` - Metadata Metadata `json:"metadata,omitempty"` - Tag string `json:"tag,omitempty"` - Permission string `json:"permission,omitempty"` - Status Status `json:"status,omitempty"` - ID string `json:"id,omitempty"` - IDs []string `json:"-"` - Identity string `json:"identity,omitempty"` - SubjectID string `json:"-"` -} - -type DomainsPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Domains []Domain `json:"domains"` -} - -func (page DomainsPage) MarshalJSON() ([]byte, error) { - type Alias DomainsPage - a := struct { - Alias - }{ - Alias: Alias(page), - } - - if a.Domains == nil { - a.Domains = make([]Domain, 0) - } - - return json.Marshal(a) -} - -type Policy struct { - SubjectType string `json:"subject_type,omitempty"` - SubjectID string `json:"subject_id,omitempty"` - SubjectRelation string `json:"subject_relation,omitempty"` - Relation string `json:"relation,omitempty"` - ObjectType string `json:"object_type,omitempty"` - 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) - DeleteUserFromDomains(ctx context.Context, id string) error -} - -// DomainsRepository specifies Domain persistence API. -// -//go:generate mockery --name DomainsRepository --output=./mocks --filename domains.go --quiet --note "Copyright (c) Abstract Machines" -type DomainsRepository 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) - - // Update updates the client name and metadata. - Update(ctx context.Context, id string, userID string, d DomainReq) (Domain, error) - - // 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 - - // DeleteUserPolicies deletes user policies from domains database. - DeleteUserPolicies(ctx context.Context, id string) (err error) -} diff --git a/auth/domains_test.go b/auth/domains_test.go deleted file mode 100644 index 82875bccd..000000000 --- a/auth/domains_test.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth_test - -import ( - "testing" - - "github.com/absmach/magistrala/auth" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/stretchr/testify/assert" -) - -func TestStatusString(t *testing.T) { - cases := []struct { - desc string - status auth.Status - expected string - }{ - { - desc: "Enabled", - status: auth.EnabledStatus, - expected: "enabled", - }, - { - desc: "Disabled", - status: auth.DisabledStatus, - expected: "disabled", - }, - { - desc: "Freezed", - status: auth.FreezeStatus, - expected: "freezed", - }, - { - desc: "All", - status: auth.AllStatus, - expected: "all", - }, - { - desc: "Unknown", - status: auth.Status(100), - expected: "unknown", - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - got := tc.status.String() - assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected) - }) - } -} - -func TestToStatus(t *testing.T) { - cases := []struct { - desc string - status string - expetcted auth.Status - err error - }{ - { - desc: "Enabled", - status: "enabled", - expetcted: auth.EnabledStatus, - err: nil, - }, - { - desc: "Disabled", - status: "disabled", - expetcted: auth.DisabledStatus, - err: nil, - }, - { - desc: "Freezed", - status: "freezed", - expetcted: auth.FreezeStatus, - err: nil, - }, - { - desc: "All", - status: "all", - expetcted: auth.AllStatus, - err: nil, - }, - { - desc: "Unknown", - status: "unknown", - expetcted: auth.Status(0), - err: svcerr.ErrInvalidStatus, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - got, err := auth.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) - }) - } -} - -func TestStatusMarshalJSON(t *testing.T) { - cases := []struct { - desc string - expected []byte - status auth.Status - err error - }{ - { - desc: "Enabled", - expected: []byte(`"enabled"`), - status: auth.EnabledStatus, - err: nil, - }, - { - desc: "Disabled", - expected: []byte(`"disabled"`), - status: auth.DisabledStatus, - err: nil, - }, - { - desc: "All", - expected: []byte(`"all"`), - status: auth.AllStatus, - err: nil, - }, - { - desc: "Unknown", - expected: []byte(`"unknown"`), - status: auth.Status(100), - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - got, err := tc.status.MarshalJSON() - assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err) - assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected) - }) - } -} - -func TestStatusUnmarshalJSON(t *testing.T) { - cases := []struct { - desc string - expected auth.Status - status []byte - err error - }{ - { - desc: "Enabled", - expected: auth.EnabledStatus, - status: []byte(`"enabled"`), - err: nil, - }, - { - desc: "Disabled", - expected: auth.DisabledStatus, - status: []byte(`"disabled"`), - err: nil, - }, - { - desc: "All", - expected: auth.AllStatus, - status: []byte(`"all"`), - err: nil, - }, - { - desc: "Unknown", - expected: auth.Status(0), - status: []byte(`"unknown"`), - err: svcerr.ErrInvalidStatus, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var s auth.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/auth/events/doc.go deleted file mode 100644 index a115b5f92..000000000 --- a/auth/events/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// 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/auth/events/events.go b/auth/events/events.go deleted file mode 100644 index e0fe609a0..000000000 --- a/auth/events/events.go +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "time" - - "github.com/absmach/magistrala/auth" - "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" -) - -var ( - _ events.Event = (*createDomainEvent)(nil) - _ events.Event = (*retrieveDomainEvent)(nil) - _ events.Event = (*retrieveDomainPermissionsEvent)(nil) - _ events.Event = (*updateDomainEvent)(nil) - _ events.Event = (*changeDomainStatusEvent)(nil) - _ events.Event = (*listDomainsEvent)(nil) - _ events.Event = (*assignUsersEvent)(nil) - _ events.Event = (*unassignUsersEvent)(nil) - _ events.Event = (*listUserDomainsEvent)(nil) -) - -type createDomainEvent struct { - auth.Domain -} - -func (cde createDomainEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": domainCreate, - "id": cde.ID, - "alias": cde.Alias, - "status": cde.Status.String(), - "created_at": cde.CreatedAt, - "created_by": cde.CreatedBy, - } - - if cde.Name != "" { - val["name"] = cde.Name - } - if cde.Permission != "" { - val["permission"] = cde.Permission - } - if len(cde.Tags) > 0 { - val["tags"] = cde.Tags - } - if cde.Metadata != nil { - val["metadata"] = cde.Metadata - } - - return val, nil -} - -type retrieveDomainEvent struct { - auth.Domain -} - -func (rde retrieveDomainEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": domainRetrieve, - "id": rde.ID, - "alias": rde.Alias, - "status": rde.Status.String(), - "created_at": rde.CreatedAt, - } - - if rde.Name != "" { - val["name"] = rde.Name - } - if len(rde.Tags) > 0 { - val["tags"] = rde.Tags - } - if rde.Metadata != nil { - val["metadata"] = rde.Metadata - } - - if !rde.UpdatedAt.IsZero() { - val["updated_at"] = rde.UpdatedAt - } - if rde.UpdatedBy != "" { - val["updated_by"] = rde.UpdatedBy - } - 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 -} - -func (ude updateDomainEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": domainUpdate, - "id": ude.ID, - "alias": ude.Alias, - "status": ude.Status.String(), - "created_at": ude.CreatedAt, - "created_by": ude.CreatedBy, - "updated_at": ude.UpdatedAt, - "updated_by": ude.UpdatedBy, - } - - if ude.Name != "" { - val["name"] = ude.Name - } - if len(ude.Tags) > 0 { - val["tags"] = ude.Tags - } - if ude.Metadata != nil { - val["metadata"] = ude.Metadata - } - - return val, nil -} - -type changeDomainStatusEvent struct { - domainID string - status auth.Status - updatedAt time.Time - updatedBy string -} - -func (cdse changeDomainStatusEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": domainChangeStatus, - "id": cdse.domainID, - "status": cdse.status.String(), - "updated_at": cdse.updatedAt, - "updated_by": cdse.updatedBy, - }, nil -} - -type listDomainsEvent struct { - auth.Page - total uint64 -} - -func (lde listDomainsEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": domainList, - "total": lde.total, - "offset": lde.Offset, - "limit": lde.Limit, - } - - if lde.Name != "" { - val["name"] = lde.Name - } - if lde.Order != "" { - val["order"] = lde.Order - } - 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 -} - -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 - userID string -} - -func (lde listUserDomainsEvent) 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 - } - 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/auth/events/streams.go b/auth/events/streams.go deleted file mode 100644 index 702242cfb..000000000 --- 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/jwt/token_test.go b/auth/jwt/token_test.go deleted file mode 100644 index 32eb72e2c..000000000 --- a/auth/jwt/token_test.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package jwt_test - -import ( - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/auth" - authjwt "github.com/absmach/magistrala/auth/jwt" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/lestrrat-go/jwx/v2/jwa" - "github.com/lestrrat-go/jwx/v2/jwt" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - tokenType = "type" - userField = "user" - domainField = "domain" - issuerName = "magistrala.auth" - secret = "test" -) - -var ( - errInvalidIssuer = errors.New("invalid token issuer value") - reposecret = []byte("test") -) - -func newToken(issuerName string, key auth.Key) string { - builder := jwt.NewBuilder() - builder. - Issuer(issuerName). - IssuedAt(key.IssuedAt). - Claim(tokenType, "r"). - Expiration(key.ExpiresAt) - builder.Claim(userField, key.User) - if key.Domain != "" { - builder.Claim(domainField, key.Domain) - } - if key.Subject != "" { - builder.Subject(key.Subject) - } - if key.ID != "" { - builder.JwtID(key.ID) - } - tkn, _ := builder.Build() - tokn, _ := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, reposecret)) - return string(tokn) -} - -func TestIssue(t *testing.T) { - tokenizer := authjwt.New([]byte(secret)) - - cases := []struct { - desc string - key auth.Key - err error - }{ - { - desc: "issue new token", - key: key(), - err: nil, - }, - { - desc: "issue token with OAuth token", - key: auth.Key{ - ID: testsutil.GenerateUUID(t), - Type: auth.AccessKey, - Subject: testsutil.GenerateUUID(t), - User: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - IssuedAt: time.Now().Add(-10 * time.Second).Round(time.Second), - ExpiresAt: time.Now().Add(10 * time.Minute).Round(time.Second), - }, - err: nil, - }, - { - desc: "issue token without a domain", - key: auth.Key{ - ID: testsutil.GenerateUUID(t), - Type: auth.AccessKey, - Subject: testsutil.GenerateUUID(t), - User: testsutil.GenerateUUID(t), - Domain: "", - IssuedAt: time.Now().Add(-10 * time.Second).Round(time.Second), - }, - err: nil, - }, - { - desc: "issue token without a subject", - key: auth.Key{ - ID: testsutil.GenerateUUID(t), - Type: auth.AccessKey, - Subject: "", - User: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - IssuedAt: time.Now().Add(-10 * time.Second).Round(time.Second), - }, - err: nil, - }, - { - desc: "issue token without a domain and subject", - key: auth.Key{ - ID: testsutil.GenerateUUID(t), - Type: auth.AccessKey, - Subject: "", - User: testsutil.GenerateUUID(t), - Domain: "", - IssuedAt: time.Now().Add(-10 * time.Second).Round(time.Second), - ExpiresAt: time.Now().Add(10 * time.Minute).Round(time.Second), - }, - err: nil, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - tkn, err := tokenizer.Issue(tc.key) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err)) - if err != nil { - assert.NotEmpty(t, tkn, fmt.Sprintf("%s expected token, got empty string", tc.desc)) - } - }) - } -} - -func TestParse(t *testing.T) { - tokenizer := authjwt.New([]byte(secret)) - - token, err := tokenizer.Issue(key()) - require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err)) - - apiKey := key() - apiKey.Type = auth.APIKey - apiKey.ExpiresAt = time.Now().UTC().Add(-1 * time.Minute).Round(time.Second) - apiToken, err := tokenizer.Issue(apiKey) - require.Nil(t, err, fmt.Sprintf("issuing user key expected to succeed: %s", err)) - - expKey := key() - expKey.ExpiresAt = time.Now().UTC().Add(-1 * time.Minute).Round(time.Second) - expToken, err := tokenizer.Issue(expKey) - require.Nil(t, err, fmt.Sprintf("issuing expired key expected to succeed: %s", err)) - - emptyDomainKey := key() - emptyDomainKey.Domain = "" - emptyDomainToken, err := tokenizer.Issue(emptyDomainKey) - require.Nil(t, err, fmt.Sprintf("issuing user key expected to succeed: %s", err)) - - emptySubjectKey := key() - emptySubjectKey.Subject = "" - emptySubjectToken, err := tokenizer.Issue(emptySubjectKey) - require.Nil(t, err, fmt.Sprintf("issuing user key expected to succeed: %s", err)) - - emptyKey := key() - emptyKey.Domain = "" - emptyKey.Subject = "" - emptyToken, err := tokenizer.Issue(emptyKey) - require.Nil(t, err, fmt.Sprintf("issuing user key expected to succeed: %s", err)) - - inValidToken := newToken("invalid", key()) - - cases := []struct { - desc string - key auth.Key - token string - err error - }{ - { - desc: "parse valid key", - key: key(), - token: token, - err: nil, - }, - { - desc: "parse invalid key", - key: auth.Key{}, - token: "invalid", - err: svcerr.ErrAuthentication, - }, - { - desc: "parse expired key", - key: auth.Key{}, - token: expToken, - err: auth.ErrExpiry, - }, - { - desc: "parse expired API key", - key: apiKey, - token: apiToken, - err: auth.ErrExpiry, - }, - { - desc: "parse token with invalid issuer", - key: auth.Key{}, - token: inValidToken, - err: errInvalidIssuer, - }, - { - desc: "parse token with invalid content", - key: auth.Key{}, - token: newToken(issuerName, key()), - err: authjwt.ErrJSONHandle, - }, - { - desc: "parse token with empty domain", - key: emptyDomainKey, - token: emptyDomainToken, - err: nil, - }, - { - desc: "parse token with empty subject", - key: emptySubjectKey, - token: emptySubjectToken, - err: nil, - }, - { - desc: "parse token with empty domain and subject", - key: emptyKey, - token: emptyToken, - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - key, err := tokenizer.Parse(tc.token) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err)) - if err == nil { - assert.Equal(t, tc.key, key, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.key, key)) - } - }) - } -} - -func key() auth.Key { - exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second) - return auth.Key{ - ID: "66af4a67-3823-438a-abd7-efdb613eaef6", - Type: auth.AccessKey, - Issuer: "magistrala.auth", - Subject: "66af4a67-3823-438a-abd7-efdb613eaef6", - IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second), - ExpiresAt: exp, - } -} diff --git a/auth/jwt/tokenizer.go b/auth/jwt/tokenizer.go deleted file mode 100644 index df7e51f98..000000000 --- a/auth/jwt/tokenizer.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package jwt - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/lestrrat-go/jwx/v2/jwa" - "github.com/lestrrat-go/jwx/v2/jwt" -) - -var ( - // errInvalidIssuer is returned when the issuer is not magistrala.auth. - errInvalidIssuer = errors.New("invalid token issuer value") - // errInvalidType is returned when there is no type field. - errInvalidType = errors.New("invalid token type") - // errJWTExpiryKey is used to check if the token is expired. - errJWTExpiryKey = errors.New(`"exp" not satisfied`) - // ErrSignJWT indicates an error in signing jwt token. - ErrSignJWT = errors.New("failed to sign jwt token") - // ErrValidateJWTToken indicates a failure to validate JWT token. - ErrValidateJWTToken = errors.New("failed to validate jwt token") - // ErrJSONHandle indicates an error in handling JSON. - ErrJSONHandle = errors.New("failed to perform operation JSON") -) - -const ( - issuerName = "magistrala.auth" - tokenType = "type" - userField = "user" - oauthProviderField = "oauth_provider" - oauthAccessTokenField = "access_token" - oauthRefreshTokenField = "refresh_token" -) - -type tokenizer struct { - secret []byte -} - -var _ auth.Tokenizer = (*tokenizer)(nil) - -// NewRepository instantiates an implementation of Token repository. -func New(secret []byte) auth.Tokenizer { - return &tokenizer{ - secret: secret, - } -} - -func (tok *tokenizer) Issue(key auth.Key) (string, error) { - builder := jwt.NewBuilder() - builder. - Issuer(issuerName). - IssuedAt(key.IssuedAt). - Claim(tokenType, key.Type). - Expiration(key.ExpiresAt) - builder.Claim(userField, key.User) - if key.Subject != "" { - builder.Subject(key.Subject) - } - if key.ID != "" { - builder.JwtID(key.ID) - } - tkn, err := builder.Build() - if err != nil { - return "", errors.Wrap(svcerr.ErrAuthentication, err) - } - signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, tok.secret)) - if err != nil { - return "", errors.Wrap(ErrSignJWT, err) - } - return string(signedTkn), nil -} - -func (tok *tokenizer) Parse(token string) (auth.Key, error) { - tkn, err := tok.validateToken(token) - if err != nil { - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - - key, err := toKey(tkn) - if err != nil { - return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - - return key, nil -} - -func (tok *tokenizer) validateToken(token string) (jwt.Token, error) { - tkn, err := jwt.Parse( - []byte(token), - jwt.WithValidate(true), - jwt.WithKey(jwa.HS512, tok.secret), - ) - if err != nil { - if errors.Contains(err, errJWTExpiryKey) { - return nil, auth.ErrExpiry - } - - return nil, err - } - validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) jwt.ValidationError { - if t.Issuer() != issuerName { - return jwt.NewValidationError(errInvalidIssuer) - } - return nil - }) - if err := jwt.Validate(tkn, jwt.WithValidator(validator)); err != nil { - return nil, errors.Wrap(ErrValidateJWTToken, err) - } - - return tkn, nil -} - -func toKey(tkn jwt.Token) (auth.Key, error) { - data, err := json.Marshal(tkn.PrivateClaims()) - if err != nil { - return auth.Key{}, errors.Wrap(ErrJSONHandle, err) - } - var key auth.Key - if err := json.Unmarshal(data, &key); err != nil { - return auth.Key{}, errors.Wrap(ErrJSONHandle, err) - } - - tType, ok := tkn.Get(tokenType) - if !ok { - return auth.Key{}, errInvalidType - } - ktype, err := strconv.ParseInt(fmt.Sprintf("%v", tType), 10, 64) - if err != nil { - return auth.Key{}, err - } - - key.ID = tkn.JwtID() - key.Type = auth.KeyType(ktype) - key.Issuer = tkn.Issuer() - key.Subject = tkn.Subject() - key.IssuedAt = tkn.IssuedAt() - key.ExpiresAt = tkn.Expiration() - - return key, nil -} diff --git a/auth/keys.go b/auth/keys.go deleted file mode 100644 index aa21ee48e..000000000 --- a/auth/keys.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -import ( - "context" - "errors" - "fmt" - "time" -) - -// ErrKeyExpired indicates that the Key is expired. -var ErrKeyExpired = errors.New("use of expired key") - -type Token struct { - AccessToken string // AccessToken contains the security credentials for a login session and identifies the client. - RefreshToken string // RefreshToken is a credential artifact that OAuth can use to get a new access token without client interaction. - AccessType string // AccessType is the specific type of access token issued. It can be Bearer, Client or Basic. -} - -type KeyType uint32 - -const ( - // AccessKey is temporary User key received on successful login. - AccessKey KeyType = iota - // RefreshKey is a temporary User key used to generate a new access key. - RefreshKey - // RecoveryKey represents a key for resseting password. - RecoveryKey - // APIKey enables the one to act on behalf of the user. - APIKey - // InvitationKey is a key for inviting new users. - InvitationKey -) - -func (kt KeyType) String() string { - switch kt { - case AccessKey: - return "access" - case RefreshKey: - return "refresh" - case RecoveryKey: - return "recovery" - case APIKey: - return "API" - default: - return "unknown" - } -} - -// Key represents API key. -type Key struct { - ID string `json:"id,omitempty"` - Type KeyType `json:"type,omitempty"` - Issuer string `json:"issuer,omitempty"` - Subject string `json:"subject,omitempty"` // user ID - User string `json:"user,omitempty"` - Domain string `json:"domain,omitempty"` // domain user ID - IssuedAt time.Time `json:"issued_at,omitempty"` - ExpiresAt time.Time `json:"expires_at,omitempty"` -} - -func (key Key) String() string { - return fmt.Sprintf(`{ - id: %s, - type: %s, - issuer_id: %s, - subject: %s, - user: %s, - domain: %s, - iat: %v, - eat: %v -}`, key.ID, key.Type, key.Issuer, key.Subject, key.User, key.Domain, key.IssuedAt, key.ExpiresAt) -} - -// Expired verifies if the key is expired. -func (key Key) Expired() bool { - if key.Type == APIKey && key.ExpiresAt.IsZero() { - return false - } - return key.ExpiresAt.UTC().Before(time.Now().UTC()) -} - -// KeyRepository specifies Key persistence API. -// -//go:generate mockery --name KeyRepository --output=./mocks --filename keys.go --quiet --note "Copyright (c) Abstract Machines" -type KeyRepository interface { - // Save persists the Key. A non-nil error is returned to indicate - // operation failure - Save(ctx context.Context, key Key) (id string, err error) - - // Retrieve retrieves Key by its unique identifier. - Retrieve(ctx context.Context, issuer string, id string) (key Key, err error) - - // Remove removes Key with provided ID. - Remove(ctx context.Context, issuer string, id string) error -} diff --git a/auth/keys_test.go b/auth/keys_test.go deleted file mode 100644 index aaf5d3b81..000000000 --- a/auth/keys_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth_test - -import ( - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/auth" - "github.com/stretchr/testify/assert" -) - -func TestExpired(t *testing.T) { - exp := time.Now().Add(5 * time.Minute) - exp1 := time.Now() - cases := []struct { - desc string - key auth.Key - expired bool - }{ - { - desc: "not expired key", - key: auth.Key{ - IssuedAt: time.Now(), - ExpiresAt: exp, - }, - expired: false, - }, - { - desc: "expired key", - key: auth.Key{ - IssuedAt: time.Now().UTC().Add(2 * time.Minute), - ExpiresAt: exp1, - }, - expired: true, - }, - { - desc: "user key with no expiration date", - key: auth.Key{ - IssuedAt: time.Now(), - }, - expired: true, - }, - { - desc: "API key with no expiration date", - key: auth.Key{ - IssuedAt: time.Now(), - Type: auth.APIKey, - }, - expired: false, - }, - } - - for _, tc := range cases { - res := tc.key.Expired() - assert.Equal(t, tc.expired, res, fmt.Sprintf("%s: expected %t got %t\n", tc.desc, tc.expired, res)) - } -} diff --git a/auth/mocks/authz.go b/auth/mocks/authz.go deleted file mode 100644 index 79c2e127f..000000000 --- a/auth/mocks/authz.go +++ /dev/null @@ -1,49 +0,0 @@ -// 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" -) - -// Authz is an autogenerated mock type for the Authz type -type Authz struct { - mock.Mock -} - -// Authorize provides a mock function with given fields: ctx, pr -func (_m *Authz) Authorize(ctx context.Context, pr policies.Policy) error { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for Authorize") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) error); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewAuthz creates a new instance of Authz. 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 NewAuthz(t interface { - mock.TestingT - Cleanup(func()) -}) *Authz { - mock := &Authz{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/auth/mocks/domains.go b/auth/mocks/domains.go deleted file mode 100644 index c9bc09c95..000000000 --- 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/domains_client.go b/auth/mocks/domains_client.go deleted file mode 100644 index 7950316f8..000000000 --- a/auth/mocks/domains_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" -) - -// DomainsServiceClient is an autogenerated mock type for the DomainsServiceClient type -type DomainsServiceClient struct { - mock.Mock -} - -type DomainsServiceClient_Expecter struct { - mock *mock.Mock -} - -func (_m *DomainsServiceClient) EXPECT() *DomainsServiceClient_Expecter { - return &DomainsServiceClient_Expecter{mock: &_m.Mock} -} - -// 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) { - _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 DeleteUserFromDomains") - } - - var r0 *magistrala.DeleteUserRes - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.DeleteUserReq, ...grpc.CallOption) (*magistrala.DeleteUserRes, error)); ok { - return rf(ctx, in, opts...) - } - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.DeleteUserReq, ...grpc.CallOption) *magistrala.DeleteUserRes); ok { - r0 = rf(ctx, in, opts...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.DeleteUserRes) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *magistrala.DeleteUserReq, ...grpc.CallOption) error); ok { - r1 = rf(ctx, in, opts...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DomainsServiceClient_DeleteUserFromDomains_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteUserFromDomains' -type DomainsServiceClient_DeleteUserFromDomains_Call struct { - *mock.Call -} - -// DeleteUserFromDomains is a helper method to define mock.On call -// - ctx context.Context -// - in *magistrala.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 { - _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.DeleteUserReq), variadicArgs...) - }) - return _c -} - -func (_c *DomainsServiceClient_DeleteUserFromDomains_Call) Return(_a0 *magistrala.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 { - _c.Call.Return(run) - return _c -} - -// NewDomainsServiceClient creates a new instance of DomainsServiceClient. 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 NewDomainsServiceClient(t interface { - mock.TestingT - Cleanup(func()) -}) *DomainsServiceClient { - mock := &DomainsServiceClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/auth/mocks/keys.go b/auth/mocks/keys.go deleted file mode 100644 index 6f75c2e00..000000000 --- a/auth/mocks/keys.go +++ /dev/null @@ -1,106 +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" -) - -// KeyRepository is an autogenerated mock type for the KeyRepository type -type KeyRepository struct { - mock.Mock -} - -// Remove provides a mock function with given fields: ctx, issuer, id -func (_m *KeyRepository) Remove(ctx context.Context, issuer string, id string) error { - ret := _m.Called(ctx, issuer, id) - - if len(ret) == 0 { - panic("no return value specified for Remove") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, issuer, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Retrieve provides a mock function with given fields: ctx, issuer, id -func (_m *KeyRepository) Retrieve(ctx context.Context, issuer string, id string) (auth.Key, error) { - ret := _m.Called(ctx, issuer, id) - - if len(ret) == 0 { - panic("no return value specified for Retrieve") - } - - var r0 auth.Key - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (auth.Key, error)); ok { - return rf(ctx, issuer, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) auth.Key); ok { - r0 = rf(ctx, issuer, id) - } else { - r0 = ret.Get(0).(auth.Key) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, issuer, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, key -func (_m *KeyRepository) Save(ctx context.Context, key auth.Key) (string, error) { - ret := _m.Called(ctx, key) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, auth.Key) (string, error)); ok { - return rf(ctx, key) - } - if rf, ok := ret.Get(0).(func(context.Context, auth.Key) string); ok { - r0 = rf(ctx, key) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, auth.Key) error); ok { - r1 = rf(ctx, key) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewKeyRepository creates a new instance of KeyRepository. 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 NewKeyRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *KeyRepository { - mock := &KeyRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/auth/mocks/service.go b/auth/mocks/service.go deleted file mode 100644 index 80ec2714f..000000000 --- a/auth/mocks/service.go +++ /dev/null @@ -1,406 +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" - - policies "github.com/absmach/magistrala/pkg/policies" -) - -// Service is an autogenerated mock type for the Service type -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) - - if len(ret) == 0 { - panic("no return value specified for Authorize") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) error); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Error(0) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for Identify") - } - - var r0 auth.Key - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (auth.Key, error)); ok { - return rf(ctx, token) - } - if rf, ok := ret.Get(0).(func(context.Context, string) auth.Key); ok { - r0 = rf(ctx, token) - } else { - r0 = ret.Get(0).(auth.Key) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, token) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Issue provides a mock function with given fields: ctx, token, key -func (_m *Service) Issue(ctx context.Context, token string, key auth.Key) (auth.Token, error) { - ret := _m.Called(ctx, token, key) - - if len(ret) == 0 { - panic("no return value specified for Issue") - } - - var r0 auth.Token - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, auth.Key) (auth.Token, error)); ok { - return rf(ctx, token, key) - } - if rf, ok := ret.Get(0).(func(context.Context, string, auth.Key) auth.Token); ok { - r0 = rf(ctx, token, key) - } else { - r0 = ret.Get(0).(auth.Token) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, auth.Key) error); ok { - r1 = rf(ctx, token, key) - } else { - r1 = ret.Error(1) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for RetrieveKey") - } - - var r0 auth.Key - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (auth.Key, error)); ok { - return rf(ctx, token, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) auth.Key); ok { - r0 = rf(ctx, token, id) - } else { - r0 = ret.Get(0).(auth.Key) - } - - 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 -} - -// Revoke provides a mock function with given fields: ctx, token, id -func (_m *Service) Revoke(ctx context.Context, token string, id string) error { - ret := _m.Called(ctx, token, id) - - if len(ret) == 0 { - panic("no return value specified for Revoke") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, token, id) - } else { - r0 = ret.Error(0) - } - - 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 { - mock.TestingT - Cleanup(func()) -}) *Service { - mock := &Service{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/auth/mocks/token_client.go b/auth/mocks/token_client.go deleted file mode 100644 index ae2e03e7f..000000000 --- a/auth/mocks/token_client.go +++ /dev/null @@ -1,192 +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" -) - -// TokenServiceClient is an autogenerated mock type for the TokenServiceClient type -type TokenServiceClient struct { - mock.Mock -} - -type TokenServiceClient_Expecter struct { - mock *mock.Mock -} - -func (_m *TokenServiceClient) EXPECT() *TokenServiceClient_Expecter { - return &TokenServiceClient_Expecter{mock: &_m.Mock} -} - -// 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) { - _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 Issue") - } - - var r0 *magistrala.Token - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.IssueReq, ...grpc.CallOption) (*magistrala.Token, error)); ok { - return rf(ctx, in, opts...) - } - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.IssueReq, ...grpc.CallOption) *magistrala.Token); ok { - r0 = rf(ctx, in, opts...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.Token) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *magistrala.IssueReq, ...grpc.CallOption) error); ok { - r1 = rf(ctx, in, opts...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// TokenServiceClient_Issue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Issue' -type TokenServiceClient_Issue_Call struct { - *mock.Call -} - -// Issue is a helper method to define mock.On call -// - ctx context.Context -// - in *magistrala.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 { - _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.IssueReq), variadicArgs...) - }) - return _c -} - -func (_c *TokenServiceClient_Issue_Call) Return(_a0 *magistrala.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 { - _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) { - _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 Refresh") - } - - var r0 *magistrala.Token - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.RefreshReq, ...grpc.CallOption) (*magistrala.Token, error)); ok { - return rf(ctx, in, opts...) - } - if rf, ok := ret.Get(0).(func(context.Context, *magistrala.RefreshReq, ...grpc.CallOption) *magistrala.Token); ok { - r0 = rf(ctx, in, opts...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.Token) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *magistrala.RefreshReq, ...grpc.CallOption) error); ok { - r1 = rf(ctx, in, opts...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// TokenServiceClient_Refresh_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Refresh' -type TokenServiceClient_Refresh_Call struct { - *mock.Call -} - -// Refresh is a helper method to define mock.On call -// - ctx context.Context -// - in *magistrala.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 { - _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.RefreshReq), variadicArgs...) - }) - return _c -} - -func (_c *TokenServiceClient_Refresh_Call) Return(_a0 *magistrala.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 { - _c.Call.Return(run) - return _c -} - -// NewTokenServiceClient creates a new instance of TokenServiceClient. 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 NewTokenServiceClient(t interface { - mock.TestingT - Cleanup(func()) -}) *TokenServiceClient { - mock := &TokenServiceClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/auth/postgres/doc.go b/auth/postgres/doc.go deleted file mode 100644 index ac5c81ae1..000000000 --- a/auth/postgres/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// 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/auth/postgres/domains.go deleted file mode 100644 index 40ef9682e..000000000 --- a/auth/postgres/domains.go +++ /dev/null @@ -1,633 +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/auth" - "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/jackc/pgtype" - "github.com/jmoiron/sqlx" -) - -var _ auth.DomainsRepository = (*domainRepo)(nil) - -type domainRepo struct { - db postgres.Database -} - -// NewDomainRepository instantiates a PostgreSQL -// implementation of Domain repository. -func NewDomainRepository(db postgres.Database) auth.DomainsRepository { - return &domainRepo{ - db: db, - } -} - -func (repo domainRepo) Save(ctx context.Context, d auth.Domain) (ad auth.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) - } - - row, err := repo.db.NamedQueryContext(ctx, q, dbd) - if err != nil { - return auth.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) - } - - domain, err := toDomain(dbd) - if err != nil { - return auth.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) { - 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` - - dbdp := dbDomainsPage{ - ID: id, - } - - rows, err := repo.db.NamedQueryContext(ctx, q, dbdp) - if err != nil { - return auth.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) - } - - domain, err := toDomain(dbd) - if err != nil { - return auth.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 -} - -// RetrieveAllByIDs retrieves for given Domain IDs . -func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth.DomainsPage, error) { - var q string - if len(pm.IDs) == 0 { - return auth.DomainsPage{}, nil - } - query, err := buildPageQuery(pm) - if err != nil { - return auth.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 - FROM domains d` - q = fmt.Sprintf("%s %s LIMIT %d OFFSET %d;", q, query, pm.Limit, pm.Offset) - - dbPage, err := toDBClientsPage(pm) - if err != nil { - return auth.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) - } - defer rows.Close() - - domains, err := repo.processRows(rows) - if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - cq := "SELECT COUNT(*) FROM domains d" - if query != "" { - cq = fmt.Sprintf(" %s %s", cq, query) - } - - total, err := postgres.Total(ctx, repo.db, cq, dbPage) - if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - return auth.DomainsPage{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - Domains: domains, - }, nil -} - -// ListDomains list domains of user. -func (repo domainRepo) ListDomains(ctx context.Context, pm auth.Page) (auth.DomainsPage, error) { - var q string - query, err := buildPageQuery(pm) - if err != nil { - return auth.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 - FROM domains as d - JOIN policies pc - ON pc.object_id = d.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) - - dbPage, err := toDBClientsPage(pm) - if err != nil { - return auth.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) - } - defer rows.Close() - - domains, err := repo.processRows(rows) - if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - cq := "SELECT COUNT(*) FROM domains d JOIN policies pc ON pc.object_id = d.id" - if pm.SubjectID == "" { - cq = "SELECT COUNT(*) FROM domains d" - } - if query != "" { - cq = fmt.Sprintf(" %s %s", cq, query) - } - - total, err := postgres.Total(ctx, repo.db, cq, dbPage) - if err != nil { - return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - return auth.DomainsPage{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - Domains: domains, - }, nil -} - -// Update updates the client name and metadata. -func (repo domainRepo) Update(ctx context.Context, id, userID string, dr auth.DomainReq) (auth.Domain, error) { - var query []string - var upq string - var ws string = "AND status = :status" - d := auth.Domain{ID: id} - if dr.Name != nil && *dr.Name != "" { - query = append(query, "name = :name, ") - d.Name = *dr.Name - } - if dr.Metadata != nil { - query = append(query, "metadata = :metadata, ") - d.Metadata = *dr.Metadata - } - if dr.Tags != nil { - query = append(query, "tags = :tags, ") - d.Tags = *dr.Tags - } - if dr.Status != nil { - ws = "" - query = append(query, "status = :status, ") - d.Status = *dr.Status - } - if dr.Alias != nil { - query = append(query, "alias = :alias, ") - d.Alias = *dr.Alias - } - d.UpdatedAt = time.Now() - d.UpdatedBy = userID - if len(query) > 0 { - upq = strings.Join(query, " ") - } - q := fmt.Sprintf(`UPDATE domains SET %s updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id %s - RETURNING id, name, tags, alias, metadata, created_at, updated_at, updated_by, created_by, status;`, - upq, ws) - - dbd, err := toDBDomain(d) - if err != nil { - return auth.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) - } - - // defer row.Close() - row.Next() - dbd = dbDomain{} - if err := row.StructScan(&dbd); err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - - domain, err := toDomain(dbd) - if err != nil { - return auth.Domain{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - - return domain, nil -} - -// Delete delete domain from database. -func (repo domainRepo) Delete(ctx context.Context, id string) error { - q := "DELETE FROM domains WHERE id = $1;" - - res, err := repo.db.ExecContext(ctx, q, id) - if err != nil { - return postgres.HandleError(repoerr.ErrRemoveEntity, err) - } - if rows, _ := res.RowsAffected(); rows == 0 { - return repoerr.ErrNotFound - } - - 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 - for rows.Next() { - dbd := dbDomain{} - if err := rows.StructScan(&dbd); err != nil { - return items, err - } - d, err := toDomain(dbd) - if err != nil { - return items, err - } - items = append(items, d) - } - return items, nil -} - -type dbDomain struct { - ID string `db:"id"` - Name string `db:"name"` - Metadata []byte `db:"metadata,omitempty"` - Tags pgtype.TextArray `db:"tags,omitempty"` - Alias *string `db:"alias,omitempty"` - Status auth.Status `db:"status"` - Permission string `db:"relation"` - CreatedBy string `db:"created_by"` - CreatedAt time.Time `db:"created_at"` - UpdatedBy *string `db:"updated_by,omitempty"` - UpdatedAt sql.NullTime `db:"updated_at,omitempty"` -} - -func toDBDomain(d auth.Domain) (dbDomain, error) { - data := []byte("{}") - if len(d.Metadata) > 0 { - b, err := json.Marshal(d.Metadata) - if err != nil { - return dbDomain{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - data = b - } - var tags pgtype.TextArray - if err := tags.Set(d.Tags); err != nil { - return dbDomain{}, err - } - var alias *string - if d.Alias != "" { - alias = &d.Alias - } - - var updatedBy *string - if d.UpdatedBy != "" { - updatedBy = &d.UpdatedBy - } - var updatedAt sql.NullTime - if d.UpdatedAt != (time.Time{}) { - updatedAt = sql.NullTime{Time: d.UpdatedAt, Valid: true} - } - - return dbDomain{ - ID: d.ID, - Name: d.Name, - Metadata: data, - Tags: tags, - Alias: alias, - Status: d.Status, - Permission: d.Permission, - CreatedBy: d.CreatedBy, - CreatedAt: d.CreatedAt, - UpdatedBy: updatedBy, - UpdatedAt: updatedAt, - }, nil -} - -func toDomain(d dbDomain) (auth.Domain, error) { - var metadata auth.Metadata - if d.Metadata != nil { - if err := json.Unmarshal([]byte(d.Metadata), &metadata); err != nil { - return auth.Domain{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - } - var tags []string - for _, e := range d.Tags.Elements { - tags = append(tags, e.String) - } - var alias string - if d.Alias != nil { - alias = *d.Alias - } - var updatedBy string - if d.UpdatedBy != nil { - updatedBy = *d.UpdatedBy - } - var updatedAt time.Time - if d.UpdatedAt.Valid { - updatedAt = d.UpdatedAt.Time - } - - return auth.Domain{ - ID: d.ID, - Name: d.Name, - Metadata: metadata, - Tags: tags, - Alias: alias, - Permission: d.Permission, - Status: d.Status, - CreatedBy: d.CreatedBy, - CreatedAt: d.CreatedAt, - UpdatedBy: updatedBy, - UpdatedAt: updatedAt, - }, nil -} - -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"` -} - -func toDBClientsPage(pm auth.Page) (dbDomainsPage, error) { - _, data, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return dbDomainsPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - return dbDomainsPage{ - Total: pm.Total, - Limit: pm.Limit, - Offset: pm.Offset, - Order: pm.Order, - Dir: pm.Dir, - Name: pm.Name, - Permission: pm.Permission, - ID: pm.ID, - IDs: pm.IDs, - Metadata: data, - Tag: pm.Tag, - Status: pm.Status, - SubjectID: pm.SubjectID, - }, nil -} - -func buildPageQuery(pm auth.Page) (string, error) { - var query []string - var emq string - - if pm.ID != "" { - query = append(query, "d.id = :id") - } - - if len(pm.IDs) != 0 { - query = append(query, fmt.Sprintf("d.id IN ('%s')", strings.Join(pm.IDs, "','"))) - } - - if (pm.Status >= auth.EnabledStatus) && (pm.Status < auth.AllStatus) { - query = append(query, "d.status = :status") - } else { - query = append(query, fmt.Sprintf("d.status < %d", auth.AllStatus)) - } - - if pm.Name != "" { - query = append(query, "d.name = :name") - } - - if pm.SubjectID != "" { - query = append(query, "pc.subject_id = :subject_id") - } - - if pm.Permission != "" && pm.SubjectID != "" { - query = append(query, "pc.relation = :permission") - } - - if pm.Tag != "" { - query = append(query, ":tag = ANY(d.tags)") - } - - mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return "", errors.Wrap(repoerr.ErrViewEntity, err) - } - if mq != "" { - query = append(query, mq) - } - - if len(query) > 0 { - emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")) - } - - return emq, nil -} - -type dbPolicy struct { - SubjectType string `db:"subject_type,omitempty"` - SubjectID string `db:"subject_id,omitempty"` - SubjectRelation string `db:"subject_relation,omitempty"` - Relation string `db:"relation,omitempty"` - ObjectType string `db:"object_type,omitempty"` - ObjectID string `db:"object_id,omitempty"` -} - -func toDBPolicies(pcs ...auth.Policy) []dbPolicy { - var dbpcs []dbPolicy - for _, pc := range pcs { - dbpcs = append(dbpcs, dbPolicy{ - SubjectType: pc.SubjectType, - SubjectID: pc.SubjectID, - SubjectRelation: pc.SubjectRelation, - Relation: pc.Relation, - ObjectType: pc.ObjectType, - ObjectID: pc.ObjectID, - }) - } - return dbpcs -} - -func toDBPolicy(pc auth.Policy) dbPolicy { - return dbPolicy{ - SubjectType: pc.SubjectType, - SubjectID: pc.SubjectID, - SubjectRelation: pc.SubjectRelation, - Relation: pc.Relation, - ObjectType: pc.ObjectType, - ObjectID: pc.ObjectID, - } -} diff --git a/auth/postgres/domains_test.go b/auth/postgres/domains_test.go deleted file mode 100644 index 1e1997a90..000000000 --- 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 deleted file mode 100644 index ae69c3a0c..000000000 --- a/auth/postgres/init.go +++ /dev/null @@ -1,62 +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" -) - -// Migration of Auth service. -func Migration() *migrate.MemoryMigrationSource { - return &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "auth_1", - Up: []string{ - `CREATE TABLE IF NOT EXISTS keys ( - id VARCHAR(254) NOT NULL, - type SMALLINT, - subject VARCHAR(254) NOT NULL, - issuer_id VARCHAR(254) NOT NULL, - issued_at TIMESTAMP NOT NULL, - expires_at TIMESTAMP, - PRIMARY KEY (id, issuer_id) - )`, - - `CREATE TABLE IF NOT EXISTS domains ( - id VARCHAR(36) PRIMARY KEY, - name VARCHAR(254), - tags TEXT[], - metadata JSONB, - alias VARCHAR(254) 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) - );`, - `CREATE TABLE IF NOT EXISTS policies ( - subject_type VARCHAR(254) NOT NULL, - subject_id VARCHAR(254) NOT NULL, - subject_relation VARCHAR(254) NOT NULL, - relation VARCHAR(254) NOT NULL, - object_type VARCHAR(254) NOT NULL, - object_id VARCHAR(254) NOT NULL, - CONSTRAINT unique_policy_constraint UNIQUE (subject_type, subject_id, subject_relation, relation, object_type, object_id) - );`, - }, - Down: []string{ - `DROP TABLE IF EXISTS keys`, - }, - }, - { - Id: "auth_2", - Up: []string{ - `ALTER TABLE domains ALTER COLUMN alias SET NOT NULL`, - }, - }, - }, - } -} diff --git a/auth/postgres/key.go b/auth/postgres/key.go deleted file mode 100644 index 8a638b29a..000000000 --- a/auth/postgres/key.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "database/sql" - "time" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/postgres" -) - -var ( - errSave = errors.New("failed to save key in database") - errRetrieve = errors.New("failed to retrieve key from database") - errDelete = errors.New("failed to delete key from database") -) -var _ auth.KeyRepository = (*repo)(nil) - -type repo struct { - db postgres.Database -} - -// New instantiates a PostgreSQL implementation of key repository. -func New(db postgres.Database) auth.KeyRepository { - return &repo{ - db: db, - } -} - -func (kr *repo) Save(ctx context.Context, key auth.Key) (string, error) { - q := `INSERT INTO keys (id, type, issuer_id, subject, issued_at, expires_at) - VALUES (:id, :type, :issuer_id, :subject, :issued_at, :expires_at)` - - dbKey := toDBKey(key) - if _, err := kr.db.NamedExecContext(ctx, q, dbKey); err != nil { - return "", postgres.HandleError(errSave, err) - } - - return dbKey.ID, nil -} - -func (kr *repo) Retrieve(ctx context.Context, issuerID, id string) (auth.Key, error) { - q := `SELECT id, type, issuer_id, subject, issued_at, expires_at FROM keys WHERE issuer_id = $1 AND id = $2` - key := dbKey{} - if err := kr.db.QueryRowxContext(ctx, q, issuerID, id).StructScan(&key); err != nil { - if err == sql.ErrNoRows { - return auth.Key{}, repoerr.ErrNotFound - } - - return auth.Key{}, postgres.HandleError(errRetrieve, err) - } - - return toKey(key), nil -} - -func (kr *repo) Remove(ctx context.Context, issuerID, id string) error { - q := `DELETE FROM keys WHERE issuer_id = :issuer_id AND id = :id` - key := dbKey{ - ID: id, - Issuer: issuerID, - } - if _, err := kr.db.NamedExecContext(ctx, q, key); err != nil { - return errors.Wrap(errDelete, err) - } - - return nil -} - -type dbKey struct { - ID string `db:"id"` - Type uint32 `db:"type"` - Issuer string `db:"issuer_id"` - Subject string `db:"subject"` - IssuedAt time.Time `db:"issued_at"` - ExpiresAt sql.NullTime `db:"expires_at,omitempty"` -} - -func toDBKey(key auth.Key) dbKey { - ret := dbKey{ - ID: key.ID, - Type: uint32(key.Type), - Issuer: key.Issuer, - Subject: key.Subject, - IssuedAt: key.IssuedAt, - } - if !key.ExpiresAt.IsZero() { - ret.ExpiresAt = sql.NullTime{Time: key.ExpiresAt, Valid: true} - } - - return ret -} - -func toKey(key dbKey) auth.Key { - ret := auth.Key{ - ID: key.ID, - Type: auth.KeyType(key.Type), - Issuer: key.Issuer, - Subject: key.Subject, - IssuedAt: key.IssuedAt, - } - if key.ExpiresAt.Valid { - ret.ExpiresAt = key.ExpiresAt.Time - } - - return ret -} diff --git a/auth/postgres/key_test.go b/auth/postgres/key_test.go deleted file mode 100644 index e415524b0..000000000 --- a/auth/postgres/key_test.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/auth/postgres" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - expTime = time.Now().Add(5 * time.Minute) - idProvider = uuid.New() - invalidID = strings.Repeat("a", 255) -) - -func generateID(t *testing.T) string { - id, err := idProvider.ID() - require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - return id -} - -func TestKeySave(t *testing.T) { - repo := postgres.New(database) - - keyID := generateID(t) - issuer := generateID(t) - - cases := []struct { - desc string - key auth.Key - err error - }{ - { - desc: "save a new key", - key: auth.Key{ - ID: keyID, - Type: auth.APIKey, - Issuer: issuer, - Subject: generateID(t), - IssuedAt: time.Now(), - ExpiresAt: expTime, - }, - err: nil, - }, - { - desc: "save with duplicate id", - key: auth.Key{ - ID: keyID, - Type: auth.APIKey, - Issuer: issuer, - Subject: generateID(t), - IssuedAt: time.Now(), - ExpiresAt: expTime, - }, - err: repoerr.ErrConflict, - }, - { - desc: "save with empty id", - key: auth.Key{ - Type: auth.APIKey, - Issuer: issuer, - Subject: generateID(t), - IssuedAt: time.Now(), - ExpiresAt: expTime, - }, - err: nil, - }, - { - desc: "save with empty subject", - key: auth.Key{ - ID: generateID(t), - Type: auth.APIKey, - Issuer: issuer, - IssuedAt: time.Now(), - ExpiresAt: expTime, - }, - err: nil, - }, - { - desc: "save with empty issuer", - key: auth.Key{ - ID: generateID(t), - Type: auth.APIKey, - Issuer: "", - Subject: generateID(t), - IssuedAt: time.Now(), - ExpiresAt: expTime, - }, - err: nil, - }, - { - desc: "save with empty issued at", - key: auth.Key{ - ID: generateID(t), - Type: auth.APIKey, - Issuer: issuer, - Subject: generateID(t), - IssuedAt: time.Time{}, - ExpiresAt: expTime, - }, - err: nil, - }, - { - desc: "save with invalid id", - key: auth.Key{ - ID: invalidID, - Type: auth.APIKey, - Issuer: issuer, - Subject: generateID(t), - IssuedAt: time.Now(), - ExpiresAt: expTime, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "save with invalid subject", - key: auth.Key{ - ID: generateID(t), - Type: auth.APIKey, - Issuer: issuer, - Subject: invalidID, - IssuedAt: time.Now(), - ExpiresAt: expTime, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "save with invalid issuer", - key: auth.Key{ - ID: generateID(t), - Type: auth.APIKey, - Issuer: invalidID, - Subject: generateID(t), - IssuedAt: time.Now(), - ExpiresAt: expTime, - }, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - _, err := repo.Save(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)) - } -} - -func TestKeyRetrieve(t *testing.T) { - repo := postgres.New(database) - - key := auth.Key{ - ID: generateID(t), - Subject: generateID(t), - IssuedAt: time.Now(), - Issuer: generateID(t), - ExpiresAt: expTime, - } - _, err := repo.Save(context.Background(), key) - assert.Nil(t, err, fmt.Sprintf("Storing Key expected to succeed: %s", err)) - - cases := []struct { - desc string - id string - issuer string - err error - }{ - { - desc: "retrieve an existing key", - id: key.ID, - issuer: key.Issuer, - err: nil, - }, - { - desc: "retrieve key with empty issuer id", - id: key.ID, - issuer: "", - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve non-existent key", - id: "", - issuer: key.Issuer, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve non-existent key with empty issuer id", - id: "", - issuer: "", - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - _, err := repo.Retrieve(context.Background(), tc.issuer, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestKeyRemove(t *testing.T) { - repo := postgres.New(database) - - key := auth.Key{ - ID: generateID(t), - Subject: generateID(t), - IssuedAt: time.Now(), - Issuer: generateID(t), - ExpiresAt: expTime, - } - _, err := repo.Save(context.Background(), key) - assert.Nil(t, err, fmt.Sprintf("Storing Key expected to succeed: %s", err)) - - cases := []struct { - desc string - id string - issuer string - err error - }{ - { - desc: "remove an existing key", - id: key.ID, - issuer: key.Issuer, - err: nil, - }, - { - desc: "remove key that has already been removed", - id: key.ID, - issuer: key.Issuer, - err: nil, - }, - { - desc: "remove key that does not exist", - id: generateID(t), - issuer: generateID(t), - err: nil, - }, - { - desc: "remove key with empty issuer id", - id: key.ID, - issuer: "", - err: nil, - }, - { - desc: "remove key with empty id", - id: "", - issuer: key.Issuer, - err: nil, - }, - { - desc: "remove key with empty id and issuer id", - id: "", - issuer: "", - err: nil, - }, - } - - for _, tc := range cases { - err := repo.Remove(context.Background(), tc.issuer, tc.id) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} diff --git a/auth/postgres/setup_test.go b/auth/postgres/setup_test.go deleted file mode 100644 index 89a6b2136..000000000 --- a/auth/postgres/setup_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// 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/auth/service.go b/auth/service.go deleted file mode 100644 index 8d6b3ec6a..000000000 --- a/auth/service.go +++ /dev/null @@ -1,924 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -import ( - "context" - "fmt" - "strings" - "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" -) - -const ( - recoveryDuration = 5 * time.Minute - defLimit = 100 -) - -var ( - // ErrExpiry indicates that the token is expired. - ErrExpiry = errors.New("token is expired") - - errIssueUser = errors.New("failed to issue new login key") - errIssueTmp = errors.New("failed to issue new temporary key") - errRevoke = errors.New("failed to remove key") - errRetrieve = errors.New("failed to retrieve key data") - errIdentify = errors.New("failed to validate token") - errPlatform = errors.New("invalid platform id") - errCreateDomainPolicy = errors.New("failed to create domain policy") - errAddPolicies = errors.New("failed to add policies") - errRemovePolicies = errors.New("failed to remove the policies") - errRollbackPolicy = errors.New("failed to rollback policy") - errRemoveLocalPolicy = errors.New("failed to remove from local policy copy") - errRemovePolicyEngine = errors.New("failed to remove from policy engine") -) - -// Authz represents a authorization service. It exposes -// functionalities through `auth` to perform authorization. -// -//go:generate mockery --name Authz --output=./mocks --filename authz.go --quiet --note "Copyright (c) Abstract Machines" -type Authz interface { - // Authorize checks authorization of the given `subject`. Basically, - // Authorize verifies that Is `subject` allowed to `relation` on - // `object`. Authorize returns a non-nil error if the subject has - // no relation on the object (which simply means the operation is - // denied). - Authorize(ctx context.Context, pr policies.Policy) error -} - -// Authn specifies an API that must be fulfilled by the domain service -// implementation, and all of its decorators (e.g. logging & metrics). -// Token is a string value of the actual Key and is used to authenticate -// an Auth service request. -type Authn interface { - // Issue issues a new Key, returning its token value alongside. - Issue(ctx context.Context, token string, key Key) (Token, error) - - // Revoke removes the Key with the provided id that is - // issued by the user identified by the provided key. - Revoke(ctx context.Context, token, id string) error - - // RetrieveKey retrieves data for the Key identified by the provided - // ID, that is issued by the user identified by the provided key. - RetrieveKey(ctx context.Context, token, id string) (Key, error) - - // Identify validates token token. If token is valid, content - // is returned. If token is invalid, or invocation failed for some - // other reason, non-nil error value is returned in response. - Identify(ctx context.Context, token string) (Key, error) -} - -// Service specifies an API that must be fulfilled by the domain service -// implementation, and all of its decorators (e.g. logging & metrics). -// Token is a string value of the actual Key and is used to authenticate -// an Auth service request. - -//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" -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 - tokenizer Tokenizer - loginDuration time.Duration - refreshDuration time.Duration - invitationDuration time.Duration -} - -// 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 { - return &service{ - tokenizer: tokenizer, - domains: domains, - keys: keys, - idProvider: idp, - evaluator: policyEvaluator, - policysvc: policyService, - loginDuration: loginDuration, - refreshDuration: refreshDuration, - invitationDuration: invitationDuration, - } -} - -func (svc service) Issue(ctx context.Context, token string, key Key) (Token, error) { - key.IssuedAt = time.Now().UTC() - switch key.Type { - case APIKey: - return svc.userKey(ctx, token, key) - case RefreshKey: - return svc.refreshKey(ctx, token, key) - case RecoveryKey: - return svc.tmpKey(recoveryDuration, key) - case InvitationKey: - return svc.invitationKey(ctx, key) - default: - return svc.accessKey(ctx, key) - } -} - -func (svc service) Revoke(ctx context.Context, token, id string) error { - issuerID, _, err := svc.authenticate(token) - if err != nil { - return errors.Wrap(errRevoke, err) - } - if err := svc.keys.Remove(ctx, issuerID, id); err != nil { - return errors.Wrap(errRevoke, err) - } - return nil -} - -func (svc service) RetrieveKey(ctx context.Context, token, id string) (Key, error) { - issuerID, _, err := svc.authenticate(token) - if err != nil { - return Key{}, errors.Wrap(errRetrieve, err) - } - - key, err := svc.keys.Retrieve(ctx, issuerID, id) - if err != nil { - return Key{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - return key, nil -} - -func (svc service) Identify(ctx context.Context, token string) (Key, error) { - key, err := svc.tokenizer.Parse(token) - if errors.Contains(err, ErrExpiry) { - err = svc.keys.Remove(ctx, key.Issuer, key.ID) - return Key{}, errors.Wrap(svcerr.ErrAuthentication, errors.Wrap(ErrKeyExpired, err)) - } - if err != nil { - return Key{}, errors.Wrap(svcerr.ErrAuthentication, errors.Wrap(errIdentify, err)) - } - - switch key.Type { - case RecoveryKey, AccessKey, InvitationKey, RefreshKey: - return key, nil - case APIKey: - _, err := svc.keys.Retrieve(ctx, key.Issuer, key.ID) - if err != nil { - return Key{}, svcerr.ErrAuthentication - } - return key, nil - default: - return Key{}, svcerr.ErrAuthentication - } -} - -func (svc service) Authorize(ctx context.Context, pr policies.Policy) error { - if err := svc.PolicyValidation(pr); err != nil { - return errors.Wrap(svcerr.ErrMalformedEntity, err) - } - if pr.SubjectKind == policies.TokenKind { - key, err := svc.Identify(ctx, pr.Subject) - if err != nil { - return errors.Wrap(svcerr.ErrAuthentication, err) - } - if key.Subject == "" { - if pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ThingType || pr.ObjectType == policies.DomainType { - return svcerr.ErrDomainAuthorization - } - return svcerr.ErrAuthentication - } - pr.Subject = key.Subject - pr.Domain = key.Domain - } - if err := svc.checkPolicy(ctx, pr); err != nil { - return err - } - return nil -} - -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) { - domainID := pr.Domain - if domainID == "" { - if pr.ObjectType != policies.DomainType { - return svcerr.ErrDomainAuthorization - } - domainID = pr.Object - } - if err := svc.checkDomain(ctx, pr.SubjectType, pr.Subject, domainID); err != nil { - return err - } - } - if err := svc.evaluator.CheckPolicy(ctx, pr); err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - return nil -} - -func (svc service) checkDomain(ctx context.Context, subjectType, subject, domainID string) error { - if err := svc.evaluator.CheckPolicy(ctx, policies.Policy{ - Subject: subject, - SubjectType: subjectType, - Permission: policies.MembershipPermission, - Object: domainID, - ObjectType: policies.DomainType, - }); err != nil { - return svcerr.ErrDomainAuthorization - } - - d, err := svc.domains.RetrieveByID(ctx, domainID) - if err != nil { - return errors.Wrap(svcerr.ErrViewEntity, err) - } - - 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 - } - - return nil -} - -func (svc service) PolicyValidation(pr policies.Policy) error { - if pr.ObjectType == policies.PlatformType && pr.Object != policies.MagistralaObject { - return errPlatform - } - return nil -} - -func (svc service) tmpKey(duration time.Duration, key Key) (Token, error) { - key.ExpiresAt = time.Now().Add(duration) - value, err := svc.tokenizer.Issue(key) - if err != nil { - return Token{}, errors.Wrap(errIssueTmp, err) - } - - return Token{AccessToken: value}, nil -} - -func (svc service) accessKey(ctx context.Context, key Key) (Token, error) { - var err error - key.Type = AccessKey - key.ExpiresAt = time.Now().Add(svc.loginDuration) - - key.Subject, err = svc.checkUserDomain(ctx, key) - if err != nil { - return Token{}, errors.Wrap(svcerr.ErrAuthorization, err) - } - - access, err := svc.tokenizer.Issue(key) - if err != nil { - return Token{}, errors.Wrap(errIssueTmp, err) - } - - key.ExpiresAt = time.Now().Add(svc.refreshDuration) - key.Type = RefreshKey - refresh, err := svc.tokenizer.Issue(key) - if err != nil { - return Token{}, errors.Wrap(errIssueTmp, err) - } - - return Token{AccessToken: access, RefreshToken: refresh}, nil -} - -func (svc service) invitationKey(ctx context.Context, key Key) (Token, error) { - var err error - key.Type = InvitationKey - key.ExpiresAt = time.Now().Add(svc.invitationDuration) - - key.Subject, err = svc.checkUserDomain(ctx, key) - if err != nil { - return Token{}, err - } - - access, err := svc.tokenizer.Issue(key) - if err != nil { - return Token{}, errors.Wrap(errIssueTmp, err) - } - - return Token{AccessToken: access}, nil -} - -func (svc service) refreshKey(ctx context.Context, token string, key Key) (Token, error) { - k, err := svc.tokenizer.Parse(token) - if err != nil { - return Token{}, errors.Wrap(errRetrieve, err) - } - if k.Type != RefreshKey { - return Token{}, errIssueUser - } - key.ID = k.ID - if key.Domain == "" { - key.Domain = k.Domain - } - key.User = k.User - key.Type = AccessKey - - key.Subject, err = svc.checkUserDomain(ctx, key) - if err != nil { - return Token{}, errors.Wrap(svcerr.ErrAuthorization, err) - } - - key.ExpiresAt = time.Now().Add(svc.loginDuration) - access, err := svc.tokenizer.Issue(key) - if err != nil { - return Token{}, errors.Wrap(errIssueTmp, err) - } - - key.ExpiresAt = time.Now().Add(svc.refreshDuration) - key.Type = RefreshKey - refresh, err := svc.tokenizer.Issue(key) - if err != nil { - return Token{}, errors.Wrap(errIssueTmp, err) - } - - return Token{AccessToken: access, RefreshToken: refresh}, nil -} - -func (svc service) checkUserDomain(ctx context.Context, key Key) (subject string, err error) { - if key.Domain != "" { - // Check user is platform admin. - if err = svc.Authorize(ctx, policies.Policy{ - Subject: key.User, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err == nil { - return key.User, nil - } - // Check user is domain member. - domainUserSubject := EncodeDomainUserID(key.Domain, key.User) - if err = svc.Authorize(ctx, policies.Policy{ - Subject: domainUserSubject, - SubjectType: policies.UserType, - Permission: policies.MembershipPermission, - Object: key.Domain, - ObjectType: policies.DomainType, - }); err != nil { - return "", err - } - return domainUserSubject, nil - } - return "", nil -} - -func (svc service) userKey(ctx context.Context, token string, key Key) (Token, error) { - id, sub, err := svc.authenticate(token) - if err != nil { - return Token{}, errors.Wrap(errIssueUser, err) - } - - key.Issuer = id - if key.Subject == "" { - key.Subject = sub - } - - keyID, err := svc.idProvider.ID() - if err != nil { - return Token{}, errors.Wrap(errIssueUser, err) - } - key.ID = keyID - - if _, err := svc.keys.Save(ctx, key); err != nil { - return Token{}, errors.Wrap(errIssueUser, err) - } - - tkn, err := svc.tokenizer.Issue(key) - if err != nil { - return Token{}, errors.Wrap(errIssueUser, err) - } - - return Token{AccessToken: tkn}, nil -} - -func (svc service) authenticate(token string) (string, string, error) { - key, err := svc.tokenizer.Parse(token) - if err != nil { - return "", "", errors.Wrap(svcerr.ErrAuthentication, err) - } - // Only login key token is valid for login. - if key.Type != AccessKey || key.Issuer == "" { - return "", "", svcerr.ErrAuthentication - } - - return key.Issuer, key.Subject, nil -} - -// Switch the relative permission for the relation. -func SwitchToPermission(relation string) string { - switch relation { - case policies.AdministratorRelation: - return policies.AdminPermission - case policies.EditorRelation: - return policies.EditPermission - case policies.ContributorRelation: - return policies.ViewPermission - case policies.MemberRelation: - return policies.MembershipPermission - case policies.GuestRelation: - return policies.ViewPermission - default: - return relation - } -} - -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.checkSuperAdmin(ctx, res.User); err != nil { - 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 - } - subject := res.User - if err := svc.checkSuperAdmin(ctx, res.User); err != nil { - 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 - } - subject = domainUserSubject - } - - lp, err := svc.policysvc.ListPermissions(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: subject, - 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.checkSuperAdmin(ctx, key.User); err != nil { - 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.checkSuperAdmin(ctx, key.User); err != nil { - 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.checkSuperAdmin(ctx, key.User); 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) - } - - if err := svc.checkSuperAdmin(ctx, res.User); err != nil { - 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) - } - - if err := svc.checkSuperAdmin(ctx, res.User); err != nil { - 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.checkSuperAdmin(ctx, res.User); 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 "" - } - return domainID + "_" + userID -} - -func DecodeDomainUserID(domainUserID string) (string, string) { - if domainUserID == "" { - return domainUserID, domainUserID - } - duid := strings.Split(domainUserID, "_") - - switch { - case len(duid) == 2: - return duid[0], duid[1] - case len(duid) == 1: - return duid[0], "" - case len(duid) == 0 || len(duid) > 2: - fallthrough - default: - 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 -} - -func (svc service) checkSuperAdmin(ctx context.Context, userID string) error { - if err := svc.evaluator.CheckPolicy(ctx, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err != nil { - return svcerr.ErrAuthorization - } - - return nil -} diff --git a/auth/service_test.go b/auth/service_test.go deleted file mode 100644 index b338a44eb..000000000 --- a/auth/service_test.go +++ /dev/null @@ -1,2696 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/auth/jwt" - "github.com/absmach/magistrala/auth/mocks" - "github.com/absmach/magistrala/internal/testsutil" - "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" - policymocks "github.com/absmach/magistrala/pkg/policies/mocks" - "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("token is expired") - errRollbackPolicy = errors.New("failed to rollback policy") - errAddPolicies = errors.New("failed to add policies") - errPlatform = errors.New("invalid platform id") - 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{}) -) - -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() - - t := jwt.New([]byte(secret)) - key := auth.Key{ - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(refreshDuration), - Subject: id, - Type: auth.AccessKey, - User: userID, - Domain: groupName, - } - token, _ := t.Issue(key) - - return auth.New(krepo, drepo, idProvider, t, pEvaluator, pService, loginDuration, refreshDuration, invalidDuration), token -} - -func TestIssue(t *testing.T) { - svc, accessToken := newService() - - n := jwt.New([]byte(secret)) - - apikey := auth.Key{ - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(refreshDuration), - Subject: id, - Type: auth.APIKey, - User: email, - Domain: groupName, - } - apiToken, err := n.Issue(apikey) - assert.Nil(t, err, fmt.Sprintf("Issuing API key expected to succeed: %s", err)) - - refreshkey := auth.Key{ - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(refreshDuration), - Subject: id, - Type: auth.RefreshKey, - User: email, - Domain: groupName, - } - refreshToken, err := n.Issue(refreshkey) - assert.Nil(t, err, fmt.Sprintf("Issuing refresh key expected to succeed: %s", err)) - - cases := []struct { - desc string - key auth.Key - token string - err error - }{ - { - desc: "issue recovery key", - key: auth.Key{ - Type: auth.RecoveryKey, - IssuedAt: time.Now(), - }, - token: "", - err: nil, - }, - } - - for _, tc := range cases { - _, 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)) - } - - cases2 := []struct { - desc string - key auth.Key - saveResponse auth.Key - retrieveByIDResponse auth.Domain - token string - saveErr error - checkPolicyRequest policies.Policy - checkPlatformPolicyReq policies.Policy - checkDomainPolicyReq policies.Policy - checkPolicyErr error - checkPolicyErr1 error - retreiveByIDErr error - err error - }{ - { - desc: "issue login key", - key: auth.Key{ - Type: auth.AccessKey, - IssuedAt: time.Now(), - }, - checkPolicyRequest: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - token: accessToken, - err: nil, - }, - { - desc: "issue login key with domain", - key: auth.Key{ - Type: auth.AccessKey, - IssuedAt: time.Now(), - Domain: groupName, - }, - checkPolicyRequest: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - token: accessToken, - err: nil, - }, - { - desc: "issue login key with failed check on platform admin", - key: auth.Key{ - Type: auth.AccessKey, - IssuedAt: time.Now(), - Domain: groupName, - }, - token: accessToken, - checkPolicyRequest: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkPlatformPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - Object: groupName, - }, - checkPolicyErr: repoerr.ErrNotFound, - retrieveByIDResponse: auth.Domain{}, - retreiveByIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "issue login key with failed check on platform admin with enabled status", - key: auth.Key{ - Type: auth.AccessKey, - IssuedAt: time.Now(), - Domain: groupName, - }, - token: accessToken, - checkPolicyRequest: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkPlatformPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - Object: groupName, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkDomainPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr: svcerr.ErrAuthorization, - checkPolicyErr1: svcerr.ErrAuthorization, - retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, - err: svcerr.ErrAuthorization, - }, - { - desc: "issue login key with membership permission", - key: auth.Key{ - Type: auth.AccessKey, - IssuedAt: time.Now(), - Domain: groupName, - }, - token: accessToken, - checkPolicyRequest: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkPlatformPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - Object: groupName, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkDomainPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr: svcerr.ErrAuthorization, - checkPolicyErr1: svcerr.ErrAuthorization, - retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, - err: svcerr.ErrAuthorization, - }, - { - desc: "issue login key with membership permission with failed to authorize", - key: auth.Key{ - Type: auth.AccessKey, - IssuedAt: time.Now(), - Domain: groupName, - }, - token: accessToken, - checkPolicyRequest: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkPlatformPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - Object: groupName, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkDomainPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr: svcerr.ErrAuthorization, - checkPolicyErr1: svcerr.ErrAuthorization, - retrieveByIDResponse: auth.Domain{Status: auth.EnabledStatus}, - 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() - }) - } - - cases3 := []struct { - desc string - key auth.Key - token string - saveErr error - err error - }{ - { - desc: "issue API key", - key: auth.Key{ - Type: auth.APIKey, - IssuedAt: time.Now(), - }, - token: accessToken, - err: nil, - }, - { - desc: "issue API key with an invalid token", - key: auth.Key{ - Type: auth.APIKey, - IssuedAt: time.Now(), - }, - token: "invalid", - err: svcerr.ErrAuthentication, - }, - { - desc: " issue API key with invalid key request", - key: auth.Key{ - Type: auth.APIKey, - IssuedAt: time.Now(), - }, - token: apiToken, - err: svcerr.ErrAuthentication, - }, - { - desc: "issue API key with failed to save", - key: auth.Key{ - Type: auth.APIKey, - IssuedAt: time.Now(), - }, - token: accessToken, - saveErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - } - for _, tc := range cases3 { - repoCall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, tc.saveErr) - _, 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() - } - - cases4 := []struct { - desc string - key auth.Key - token string - checkPolicyRequest policies.Policy - checkDOmainPolicyReq policies.Policy - checkPolicyErr error - retrieveByIDErr error - err error - }{ - { - desc: "issue refresh key", - key: auth.Key{ - Type: auth.RefreshKey, - IssuedAt: time.Now(), - }, - checkPolicyRequest: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - token: refreshToken, - err: nil, - }, - { - desc: "issue refresh token with invalid pService", - key: auth.Key{ - Type: auth.RefreshKey, - IssuedAt: time.Now(), - Domain: groupName, - }, - checkPolicyRequest: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkDOmainPolicyReq: policies.Policy{ - Subject: "mgx_test@example.com", - SubjectType: policies.UserType, - Object: groupName, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - token: refreshToken, - checkPolicyErr: svcerr.ErrAuthorization, - retrieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrAuthorization, - }, - { - desc: "issue refresh key with invalid token", - key: auth.Key{ - Type: auth.RefreshKey, - IssuedAt: time.Now(), - }, - checkDOmainPolicyReq: policies.Policy{ - Subject: "mgx_test@example.com", - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - token: accessToken, - err: errIssueUser, - }, - { - desc: "issue refresh key with empty token", - key: auth.Key{ - Type: auth.RefreshKey, - IssuedAt: time.Now(), - }, - checkDOmainPolicyReq: policies.Policy{ - Subject: "mgx_test@example.com", - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - token: "", - err: errRetrieve, - }, - { - desc: "issue invitation key", - key: auth.Key{ - Type: auth.InvitationKey, - IssuedAt: time.Now(), - }, - checkPolicyRequest: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - token: "", - err: nil, - }, - { - desc: "issue invitation key with invalid pService", - key: auth.Key{ - Type: auth.InvitationKey, - IssuedAt: time.Now(), - Domain: groupName, - }, - checkPolicyRequest: policies.Policy{ - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkDOmainPolicyReq: policies.Policy{ - SubjectType: policies.UserType, - Object: groupName, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - token: refreshToken, - checkPolicyErr: svcerr.ErrAuthorization, - retrieveByIDErr: repoerr.ErrNotFound, - 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) - _, 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() - } -} - -func TestRevoke(t *testing.T) { - svc, _ := newService() - repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, errIssueUser) - secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) - repocall.Unset() - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall1 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - key := auth.Key{ - Type: auth.APIKey, - IssuedAt: time.Now(), - Subject: id, - } - _, err = svc.Issue(context.Background(), secret.AccessToken, key) - assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err)) - repocall1.Unset() - - cases := []struct { - desc string - id string - token string - err error - }{ - { - desc: "revoke login key", - token: secret.AccessToken, - err: nil, - }, - { - desc: "revoke non-existing login key", - token: secret.AccessToken, - err: nil, - }, - { - desc: "revoke with empty login key", - token: "", - err: svcerr.ErrAuthentication, - }, - { - desc: "revoke login key with failed to remove", - id: "invalidID", - token: secret.AccessToken, - err: svcerr.ErrNotFound, - }, - } - - 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() - }) - } -} - -func TestRetrieve(t *testing.T) { - svc, _ := newService() - repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall.Unset() - key := auth.Key{ - ID: "id", - Type: auth.APIKey, - Subject: id, - IssuedAt: time.Now(), - } - - repocall1 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - userToken, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall1.Unset() - - repocall2 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - apiToken, err := svc.Issue(context.Background(), secret.AccessToken, key) - assert.Nil(t, err, fmt.Sprintf("Issuing login's key expected to succeed: %s", err)) - repocall2.Unset() - - repocall3 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - resetToken, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.RecoveryKey, IssuedAt: time.Now()}) - assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err)) - repocall3.Unset() - - cases := []struct { - desc string - id string - token string - err error - }{ - { - desc: "retrieve login key", - token: userToken.AccessToken, - err: nil, - }, - { - desc: "retrieve non-existing login key", - id: "invalid", - token: userToken.AccessToken, - err: svcerr.ErrNotFound, - }, - { - desc: "retrieve with wrong login key", - token: "wrong", - err: svcerr.ErrAuthentication, - }, - { - desc: "retrieve with API token", - token: apiToken.AccessToken, - err: svcerr.ErrAuthentication, - }, - { - desc: "retrieve with reset token", - token: resetToken.AccessToken, - err: svcerr.ErrAuthentication, - }, - } - - 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() - }) - } -} - -func TestIdentify(t *testing.T) { - svc, _ := newService() - - repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - repocall1 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) - loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, User: id, IssuedAt: time.Now(), Domain: groupName}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall.Unset() - repocall1.Unset() - - repocall2 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - recoverySecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.RecoveryKey, IssuedAt: time.Now(), Subject: id}) - assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err)) - repocall2.Unset() - - repocall3 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - apiSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: auth.APIKey, Subject: id, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall3.Unset() - - repocall4 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - exp0 := time.Now().UTC().Add(-10 * time.Second).Round(time.Second) - exp1 := time.Now().UTC().Add(-1 * time.Minute).Round(time.Second) - expSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: auth.APIKey, IssuedAt: exp0, ExpiresAt: exp1}) - assert.Nil(t, err, fmt.Sprintf("Issuing expired login key expected to succeed: %s", err)) - repocall4.Unset() - - te := jwt.New([]byte(secret)) - key := auth.Key{ - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(refreshDuration), - Subject: id, - Type: 7, - User: email, - Domain: groupName, - } - invalidTokenType, _ := te.Issue(key) - - cases := []struct { - desc string - key string - idt string - err error - }{ - { - desc: "identify login key", - key: loginSecret.AccessToken, - idt: id, - err: nil, - }, - { - desc: "identify refresh key", - key: loginSecret.RefreshToken, - idt: id, - err: nil, - }, - { - desc: "identify recovery key", - key: recoverySecret.AccessToken, - idt: id, - err: nil, - }, - { - desc: "identify API key", - key: apiSecret.AccessToken, - idt: id, - err: nil, - }, - { - desc: "identify expired API key", - key: expSecret.AccessToken, - idt: "", - err: auth.ErrKeyExpired, - }, - { - desc: "identify API key with failed to retrieve", - key: apiSecret.AccessToken, - idt: "", - err: svcerr.ErrAuthentication, - }, - { - desc: "identify invalid key", - key: "invalid", - idt: "", - err: svcerr.ErrAuthentication, - }, - { - desc: "identify invalid key type", - key: invalidTokenType, - idt: "", - err: svcerr.ErrAuthentication, - }, - } - - 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() - }) - } -} - -func TestAuthorize(t *testing.T) { - svc, accessToken := newService() - - repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - repocall1 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) - loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, User: id, IssuedAt: time.Now(), Domain: groupName}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall.Unset() - repocall1.Unset() - saveCall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - exp1 := time.Now().Add(-2 * time.Second) - expSecret, err := svc.Issue(context.Background(), loginSecret.AccessToken, auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), ExpiresAt: exp1}) - assert.Nil(t, err, fmt.Sprintf("Issuing expired login key expected to succeed: %s", err)) - saveCall.Unset() - - repocall2 := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil) - repocall3 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(nil) - emptySubject, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, User: "", IssuedAt: time.Now(), Domain: groupName}) - assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err)) - repocall2.Unset() - repocall3.Unset() - - te := jwt.New([]byte(secret)) - key := auth.Key{ - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(refreshDuration), - Subject: id, - Type: auth.AccessKey, - User: email, - } - emptyDomain, _ := te.Issue(key) - - cases := []struct { - desc string - policyReq policies.Policy - retrieveDomainRes auth.Domain - checkPolicyReq3 policies.Policy - checkAdminPolicyReq policies.Policy - checkDomainPolicyReq policies.Policy - checkPolicyErr error - checkPolicyErr1 error - checkPolicyErr2 error - err error - }{ - { - desc: "authorize token successfully", - policyReq: policies.Policy{ - Subject: accessToken, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - Domain: "", - Subject: id, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: nil, - }, - { - desc: "authorize token for group type with empty domain", - policyReq: policies.Policy{ - Subject: emptyDomain, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: "", - ObjectType: policies.GroupType, - Permission: policies.AdminPermission, - }, - checkPolicyReq3: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Object: "", - 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, - }, - { - desc: "authorize token with disabled domain", - 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, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Permission: policies.AdminPermission, - Object: validID, - ObjectType: policies.DomainType, - }, - 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, - }, - err: nil, - }, - { - desc: "authorize token with disabled domain with failed to authorize", - 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, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - SubjectKind: policies.TokenKind, - Permission: policies.AdminPermission, - Object: validID, - ObjectType: policies.DomainType, - }, - 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.DisabledStatus, - }, - checkPolicyErr1: svcerr.ErrDomainAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "authorize token with frozen domain", - 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.FreezeStatus, - }, - err: nil, - }, - { - desc: "authorize token with frozen domain with failed to authorize", - 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.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, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: id, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: svcerr.ErrDomainAuthorization, - }, - } - 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)) - }) - } -} - -func TestSwitchToPermission(t *testing.T) { - cases := []struct { - desc string - relation string - result string - }{ - { - desc: "switch to admin permission", - relation: policies.AdministratorRelation, - result: policies.AdminPermission, - }, - { - desc: "switch to editor permission", - relation: policies.EditorRelation, - result: policies.EditPermission, - }, - { - desc: "switch to contributor permission", - relation: policies.ContributorRelation, - result: policies.ViewPermission, - }, - { - desc: "switch to member permission", - relation: policies.MemberRelation, - result: policies.MembershipPermission, - }, - { - desc: "switch to group permission", - relation: policies.GroupRelation, - result: policies.GroupRelation, - }, - { - desc: "switch to guest permission", - relation: policies.GuestRelation, - result: policies.ViewPermission, - }, - } - 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 - checkAdminErr error - checkPolicyErr error - err error - }{ - { - desc: "retrieve domain successfully as super admin", - token: accessToken, - domainID: validID, - err: nil, - }, - { - desc: "retrieve domain successfully as domain admin", - token: accessToken, - domainID: validID, - checkAdminErr: svcerr.ErrAuthorization, - 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) - policyCall := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }).Return(tc.checkAdminErr) - policyCall1 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: auth.EncodeDomainUserID(tc.domainID, userID), - SubjectType: policies.UserType, - Permission: policies.MembershipPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).Return(tc.checkPolicyErr) - policyCall2 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).Return(tc.checkPolicyErr) - policyCall3 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: auth.EncodeDomainUserID(tc.domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Permission: policies.MembershipPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).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() - repoCall2.Unset() - policyCall.Unset() - policyCall1.Unset() - policyCall2.Unset() - policyCall3.Unset() - }) - } -} - -func TestRetrieveDomainPermissions(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - retreivePermissionsErr error - retreiveByIDErr error - checkAdminErr error - checkPolicyErr error - err error - }{ - { - desc: "retrieve domain permissions successfully as platform admin", - token: accessToken, - domainID: validID, - err: nil, - }, - { - desc: "retrieve domain permissions successfully as domain admin", - token: accessToken, - domainID: validID, - checkAdminErr: svcerr.ErrAuthorization, - 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: "", - retreivePermissionsErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - 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, - checkAdminErr: svcerr.ErrAuthorization, - 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) - policyCall := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }).Return(tc.checkAdminErr) - policyCall1 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: auth.EncodeDomainUserID(tc.domainID, userID), - SubjectType: policies.UserType, - Permission: policies.MembershipPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).Return(tc.checkPolicyErr) - policyCall2 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).Return(tc.checkPolicyErr) - policyCall3 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: auth.EncodeDomainUserID(tc.domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Permission: policies.MembershipPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).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() - policyCall.Unset() - policyCall1.Unset() - policyCall2.Unset() - policyCall3.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 - checkAdminErr error - err error - }{ - { - desc: "update domain successfully as platform admin", - token: accessToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - err: nil, - }, - { - desc: "update domain successfully as domain admin", - token: accessToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - checkAdminErr: svcerr.ErrAuthorization, - 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, - checkAdminErr: 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, - checkAdminErr: svcerr.ErrAuthorization, - 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) { - policyCall := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }).Return(tc.checkAdminErr) - policyCall1 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: auth.EncodeDomainUserID(tc.domainID, userID), - SubjectType: policies.UserType, - Permission: policies.MembershipPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).Return(tc.checkPolicyErr) - policyCall2 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).Return(tc.checkPolicyErr) - policyCall3 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: auth.EncodeDomainUserID(tc.domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Permission: policies.EditPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).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)) - repoCall1.Unset() - repoCall2.Unset() - policyCall.Unset() - policyCall1.Unset() - policyCall2.Unset() - policyCall3.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 - checkAdminErr error - updateErr error - err error - }{ - { - desc: "change domain status successfully as platform admin", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - err: nil, - }, - { - desc: "change domain status successfully as platform admin", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - checkAdminErr: svcerr.ErrAuthorization, - 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, - }, - checkAdminErr: svcerr.ErrAuthorization, - retreieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "change domain status with unauthorized domain ID", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - checkAdminErr: svcerr.ErrAuthorization, - 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) - policyCall := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }).Return(tc.checkAdminErr) - policyCall1 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: auth.EncodeDomainUserID(tc.domainID, userID), - SubjectType: policies.UserType, - Permission: policies.MembershipPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).Return(tc.checkPolicyErr) - policyCall2 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).Return(tc.checkPolicyErr) - policyCall3 := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: auth.EncodeDomainUserID(tc.domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Permission: policies.AdminPermission, - Object: tc.domainID, - ObjectType: policies.DomainType, - }).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() - repoCall2.Unset() - policyCall.Unset() - policyCall1.Unset() - policyCall2.Unset() - policyCall3.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 - checkPlatformAdminErr error - err error - }{ - { - desc: "assign users successfully as platform admin", - 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 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, - }, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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, - }, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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, - checkPlatformAdminErr: 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, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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, - checkPlatformAdminErr: 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, - }, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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) - policyCall := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }).Return(tc.checkPlatformAdminErr) - 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() - policyCall.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 - checkPlatformAdminErr error - err error - }{ - { - desc: "unassign user successfully as platform admin", - 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 user successfully as domain admin", - 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, - }, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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, - }, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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, - checkPlatformAdminErr: 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, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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, - checkPlatformAdminErr: svcerr.ErrAuthorization, - 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) - policyCall := pEvaluator.On("CheckPolicy", mock.Anything, policies.Policy{ - Subject: userID, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }).Return(tc.checkPlatformAdminErr) - policyCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkPolicyErr) - policyCall2 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkAdminPolicyReq).Return(tc.checkPolicyErr1) - policyCall3 := 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() - policyCall.Unset() - policyCall1.Unset() - policyCall2.Unset() - repoCall4.Unset() - policyCall3.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() - }) - } -} - -func TestEncodeDomainUserID(t *testing.T) { - cases := []struct { - desc string - domainID string - userID string - response string - }{ - { - desc: "encode domain user id successfully", - domainID: validID, - userID: validID, - response: validID + "_" + validID, - }, - { - desc: "encode domain user id with empty userID", - domainID: validID, - userID: "", - response: "", - }, - { - desc: "encode domain user id with empty domain ID", - domainID: "", - userID: validID, - response: "", - }, - { - desc: "encode domain user id with empty domain ID and userID", - domainID: "", - userID: "", - response: "", - }, - } - - 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)) - }) - } -} - -func TestDecodeDomainUserID(t *testing.T) { - cases := []struct { - desc string - domainUserID string - respDomainID string - respUserID string - }{ - { - desc: "decode domain user id successfully", - domainUserID: validID + "_" + validID, - respDomainID: validID, - respUserID: validID, - }, - { - desc: "decode domain user id with empty domainUserID", - domainUserID: "", - respDomainID: "", - respUserID: "", - }, - { - desc: "decode domain user id with empty UserID", - domainUserID: validID, - respDomainID: validID, - respUserID: "", - }, - { - desc: "decode domain user id with invalid domainuserId", - domainUserID: validID + "_" + validID + "_" + validID + "_" + validID, - respDomainID: "", - respUserID: "", - }, - } - - 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)) - }) - } -} diff --git a/auth/tokenizer.go b/auth/tokenizer.go deleted file mode 100644 index 1aaed7df4..000000000 --- a/auth/tokenizer.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package auth - -// Tokenizer specifies API for encoding and decoding between string and Key. -type Tokenizer interface { - // Issue converts API Key to its string representation. - Issue(key Key) (token string, err error) - - // Parse extracts API Key data from string token. - Parse(token string) (key Key, err error) -} diff --git a/auth/tracing/doc.go b/auth/tracing/doc.go deleted file mode 100644 index 5aa1b44b9..000000000 --- a/auth/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala Users service. -// -// This package provides tracing middleware for Magistrala Users service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala Users 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/auth/tracing/tracing.go b/auth/tracing/tracing.go deleted file mode 100644 index 97b5f1790..000000000 --- a/auth/tracing/tracing.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - "fmt" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/pkg/policies" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ auth.Service = (*tracingMiddleware)(nil) - -type tracingMiddleware struct { - tracer trace.Tracer - svc auth.Service -} - -// New returns a new group service with tracing capabilities. -func New(svc auth.Service, tracer trace.Tracer) auth.Service { - return &tracingMiddleware{tracer, svc} -} - -func (tm *tracingMiddleware) Issue(ctx context.Context, token string, key auth.Key) (auth.Token, error) { - ctx, span := tm.tracer.Start(ctx, "issue", trace.WithAttributes( - attribute.String("type", fmt.Sprintf("%d", key.Type)), - attribute.String("subject", key.Subject), - )) - defer span.End() - - return tm.svc.Issue(ctx, token, key) -} - -func (tm *tracingMiddleware) Revoke(ctx context.Context, token, id string) error { - ctx, span := tm.tracer.Start(ctx, "revoke", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - - return tm.svc.Revoke(ctx, token, id) -} - -func (tm *tracingMiddleware) RetrieveKey(ctx context.Context, token, id string) (auth.Key, error) { - ctx, span := tm.tracer.Start(ctx, "retrieve_key", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - - return tm.svc.RetrieveKey(ctx, token, id) -} - -func (tm *tracingMiddleware) Identify(ctx context.Context, token string) (auth.Key, error) { - ctx, span := tm.tracer.Start(ctx, "identify") - defer span.End() - - return tm.svc.Identify(ctx, token) -} - -func (tm *tracingMiddleware) Authorize(ctx context.Context, pr policies.Policy) error { - ctx, span := tm.tracer.Start(ctx, "authorize", trace.WithAttributes( - attribute.String("subject", pr.Subject), - attribute.String("subject_type", pr.SubjectType), - attribute.String("subject_relation", pr.SubjectRelation), - attribute.String("object", pr.Object), - attribute.String("object_type", pr.ObjectType), - attribute.String("relation", pr.Relation), - attribute.String("permission", pr.Permission), - )) - defer span.End() - - 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 a9bb42ddb..000000000 --- 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 9fb053887..dcd2ddfe1 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 | +| State | What it means | +| -------- | ---------------------------------------------- | +| 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 | +| ------------------------------ | -------------------------------------------------------------------------------- | --------------------------------- | +| SMQ_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | info | +| SMQ_BOOTSTRAP_DB_HOST | Database host address | localhost | +| SMQ_BOOTSTRAP_DB_PORT | Database host port | 5432 | +| SMQ_BOOTSTRAP_DB_USER | Database user | magistrala | +| SMQ_BOOTSTRAP_DB_PASS | Database password | magistrala | +| SMQ_BOOTSTRAP_DB_NAME | Name of the database used by the service | bootstrap | +| SMQ_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | +| SMQ_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | +| SMQ_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | "" | +| SMQ_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | +| SMQ_BOOTSTRAP_ENCRYPT_KEY | Secret key for secure bootstrapping encryption | 12345678910111213141516171819202 | +| SMQ_BOOTSTRAP_HTTP_HOST | Bootstrap service HTTP host | "" | +| SMQ_BOOTSTRAP_HTTP_PORT | Bootstrap service HTTP port | 9013 | +| SMQ_BOOTSTRAP_HTTP_SERVER_CERT | Path to server certificate in pem format | "" | +| SMQ_BOOTSTRAP_HTTP_SERVER_KEY | Path to server key in pem format | "" | +| SMQ_BOOTSTRAP_EVENT_CONSUMER | Bootstrap service event source consumer name | bootstrap | +| SMQ_ES_URL | Event store URL | | +| SMQ_AUTH_GRPC_URL | Auth service Auth gRPC URL | | +| SMQ_AUTH_GRPC_TIMEOUT | Auth service Auth gRPC request timeout in seconds | 1s | +| SMQ_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service Auth gRPC client certificate file | "" | +| SMQ_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service Auth gRPC client key file | "" | +| SMQ_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server Auth gRPC server trusted CA certificate file | "" | +| SMQ_CLIENTS_URL | Base URL for Magistrala Clients | | +| SMQ_JAEGER_URL | Jaeger server URL | | +| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| SMQ_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| SMQ_BOOTSTRAP_INSTANCE_ID | Bootstrap service instance ID | "" | ## Deployment @@ -84,38 +84,38 @@ make bootstrap make install # set the environment variables and run the service -MG_BOOTSTRAP_LOG_LEVEL=info \ -MG_BOOTSTRAP_DB_HOST=localhost \ -MG_BOOTSTRAP_DB_PORT=5432 \ -MG_BOOTSTRAP_DB_USER=magistrala \ -MG_BOOTSTRAP_DB_PASS=magistrala \ -MG_BOOTSTRAP_DB_NAME=bootstrap \ -MG_BOOTSTRAP_DB_SSL_MODE=disable \ -MG_BOOTSTRAP_DB_SSL_CERT="" \ -MG_BOOTSTRAP_DB_SSL_KEY="" \ -MG_BOOTSTRAP_DB_SSL_ROOT_CERT="" \ -MG_BOOTSTRAP_HTTP_HOST=localhost \ -MG_BOOTSTRAP_HTTP_PORT=9013 \ -MG_BOOTSTRAP_HTTP_SERVER_CERT="" \ -MG_BOOTSTRAP_HTTP_SERVER_KEY="" \ -MG_BOOTSTRAP_EVENT_CONSUMER=bootstrap \ -MG_ES_URL=nats://localhost:4222 \ -MG_AUTH_GRPC_URL=localhost:8181 \ -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_JAEGER_URL=http://localhost:14268/api/traces \ -MG_JAEGER_TRACE_RATIO=1.0 \ -MG_SEND_TELEMETRY=true \ -MG_BOOTSTRAP_INSTANCE_ID="" \ +SMQ_BOOTSTRAP_LOG_LEVEL=info \ +SMQ_BOOTSTRAP_DB_HOST=localhost \ +SMQ_BOOTSTRAP_DB_PORT=5432 \ +SMQ_BOOTSTRAP_DB_USER=magistrala \ +SMQ_BOOTSTRAP_DB_PASS=magistrala \ +SMQ_BOOTSTRAP_DB_NAME=bootstrap \ +SMQ_BOOTSTRAP_DB_SSL_MODE=disable \ +SMQ_BOOTSTRAP_DB_SSL_CERT="" \ +SMQ_BOOTSTRAP_DB_SSL_KEY="" \ +SMQ_BOOTSTRAP_DB_SSL_ROOT_CERT="" \ +SMQ_BOOTSTRAP_HTTP_HOST=localhost \ +SMQ_BOOTSTRAP_HTTP_PORT=9013 \ +SMQ_BOOTSTRAP_HTTP_SERVER_CERT="" \ +SMQ_BOOTSTRAP_HTTP_SERVER_KEY="" \ +SMQ_BOOTSTRAP_EVENT_CONSUMER=bootstrap \ +SMQ_ES_URL=nats://localhost:4222 \ +SMQ_AUTH_GRPC_URL=localhost:8181 \ +SMQ_AUTH_GRPC_TIMEOUT=1s \ +SMQ_AUTH_GRPC_CLIENT_CERT="" \ +SMQ_AUTH_GRPC_CLIENT_KEY="" \ +SMQ_AUTH_GRPC_SERVER_CERTS="" \ +SMQ_CLIENTS_URL=http://localhost:9000 \ +SMQ_JAEGER_URL=http://localhost:14268/api/traces \ +SMQ_JAEGER_TRACE_RATIO=1.0 \ +SMQ_SEND_TELEMETRY=true \ +SMQ_BOOTSTRAP_INSTANCE_ID="" \ $GOBIN/magistrala-bootstrap ``` -Setting `MG_BOOTSTRAP_HTTP_SERVER_CERT` and `MG_BOOTSTRAP_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 `SMQ_BOOTSTRAP_HTTP_SERVER_CERT` and `SMQ_BOOTSTRAP_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_AUTH_GRPC_CLIENT_CERT` and `MG_AUTH_GRPC_CLIENT_KEY` will enable TLS against the auth service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_AUTH_GRPC_SERVER_CERTS` will enable TLS against the auth service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. +Setting `SMQ_AUTH_GRPC_CLIENT_CERT` and `SMQ_AUTH_GRPC_CLIENT_KEY` will enable TLS against the auth service. The service expects a file in PEM format for both the certificate and the key. Setting `SMQ_AUTH_GRPC_SERVER_CERTS` will enable TLS against the auth service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. ## Usage diff --git a/bootstrap/api/endpoint.go b/bootstrap/api/endpoint.go index 1bf7cf975..7889c1e98 100644 --- a/bootstrap/api/endpoint.go +++ b/bootstrap/api/endpoint.go @@ -6,12 +6,12 @@ package api import ( "context" - "github.com/absmach/magistrala/bootstrap" - "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" + api "github.com/absmach/supermq/api/http" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/bootstrap" + "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" + svcerr "github.com/absmach/supermq/pkg/errors/service" "github.com/go-kit/kit/endpoint" ) @@ -33,7 +33,7 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint { } config := bootstrap.Config{ - ThingID: req.ThingID, + ClientID: req.ClientID, ExternalID: req.ExternalID, ExternalKey: req.ExternalKey, Channels: channels, @@ -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 02a0d7464..9429d3378 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -18,16 +18,16 @@ import ( "strings" "testing" - "github.com/absmach/magistrala/bootstrap" - bsapi "github.com/absmach/magistrala/bootstrap/api" - "github.com/absmach/magistrala/bootstrap/mocks" "github.com/absmach/magistrala/internal/testsutil" - 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" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/bootstrap" + bsapi "github.com/absmach/supermq/bootstrap/api" + "github.com/absmach/supermq/bootstrap/mocks" + smqlog "github.com/absmach/supermq/logger" + smqauthn "github.com/absmach/supermq/pkg/authn" + authnmocks "github.com/absmach/supermq/pkg/authn/mocks" + "github.com/absmach/supermq/pkg/errors" + svcerr "github.com/absmach/supermq/pkg/errors/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -49,44 +49,44 @@ 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"` + ClientSecret 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, + ClientSecret: addClientSecret, + ExternalID: addExternalID, + ExternalKey: addExternalKey, + Channels: []string{"1"}, + Name: "name", + Content: "config", } updateReq = struct { - Channels []string `json:"channels,omitempty"` - Content string `json:"content,omitempty"` - State bootstrap.State `json:"state,omitempty"` - ClientCert string `json:"client_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` - CACert string `json:"ca_cert,omitempty"` + Channels []string `json:"channels,omitempty"` + Content string `json:"content,omitempty"` + State bootstrap.State `json:"state,omitempty"` + ClientCert string `json:"client_cert,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + CACert string `json:"ca_cert,omitempty"` }{ - Channels: []string{"1"}, - Content: "config update", - State: 1, - ClientCert: "newcert", - ClientKey: "newkey", - CACert: "newca", + Channels: []string{"1"}, + Content: "config update", + State: 1, + ClientCert: "newcert", + ClientSecret: "newkey", + CACert: "newca", } missingIDRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrMissingID.Error(), Msg: apiutil.ErrValidation.Error()}) @@ -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 != "" { @@ -177,7 +177,7 @@ func dec(in []byte) ([]byte, error) { } func newBootstrapServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - logger := mglog.NewMock() + logger := smqlog.NewMock() svc := new(mocks.Service) authn := new(authnmocks.Authentication) mux := bsapi.MakeHandler(svc, authn, bootstrap.NewConfigReader(encKey), logger, instanceID) @@ -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 @@ -212,7 +212,7 @@ func TestAdd(t *testing.T) { req string domainID string token string - session mgauthn.Session + session smqauthn.Session contentType string status int location string @@ -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, }, { @@ -324,7 +324,7 @@ func TestAdd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} + tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) @@ -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,20 +359,20 @@ 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 { desc string token string - session mgauthn.Session + session smqauthn.Session id string status int res config @@ -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, @@ -425,14 +425,14 @@ func TestView(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} + tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) svcCall := svc.On("View", mock.Anything, tc.session, tc.id).Return(c, tc.err) 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() @@ -467,7 +467,7 @@ func TestUpdate(t *testing.T) { req string id string token string - session mgauthn.Session + session smqauthn.Session contentType string status int authenticateErr error @@ -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, @@ -542,14 +542,14 @@ func TestUpdate(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} + tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) svcCall := svc.On("Update", mock.Anything, tc.session, mock.Anything).Return(tc.err) 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), @@ -575,7 +575,7 @@ func TestUpdateCert(t *testing.T) { req string id string token string - session mgauthn.Session + session smqauthn.Session contentType string status int authenticateErr error @@ -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, @@ -650,14 +650,14 @@ func TestUpdateCert(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} + tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) svcCall := svc.On("UpdateCert", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(c, tc.err) 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), @@ -687,7 +687,7 @@ func TestUpdateConnections(t *testing.T) { req string id string token string - session mgauthn.Session + session smqauthn.Session contentType string status int authenticateErr error @@ -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, @@ -771,14 +771,14 @@ func TestUpdateConnections(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} + tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) repoCall := svc.On("UpdateConnections", mock.Anything, tc.session, tc.token, mock.Anything, mock.Anything).Return(tc.err) 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(), smqauthn.Session{}, validToken, list[i].ClientID, state) assert.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err)) svcCall.Unset() @@ -849,7 +849,7 @@ func TestList(t *testing.T) { cases := []struct { desc string token string - session mgauthn.Session + session smqauthn.Session url string status int res configPage @@ -1040,7 +1040,7 @@ func TestList(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} + tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) svcCall := svc.On("List", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(bootstrap.ConfigsPage{Total: tc.res.Total, Offset: tc.res.Offset, Limit: tc.res.Limit}, tc.err) @@ -1077,14 +1077,14 @@ func TestRemove(t *testing.T) { desc string id string token string - session mgauthn.Session + session smqauthn.Session status int authenticateErr error err error }{ { 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, @@ -1123,14 +1123,14 @@ func TestRemove(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} + tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) svcCall := svc.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(tc.err) 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() @@ -1287,7 +1287,7 @@ func TestChangeState(t *testing.T) { desc string id string token string - session mgauthn.Session + session smqauthn.Session state string contentType string status int @@ -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, @@ -1372,14 +1372,14 @@ func TestChangeState(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} + tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} } authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) svcCall := svc.On("ChangeState", mock.Anything, tc.session, tc.token, mock.Anything, mock.Anything).Return(tc.err) 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 f1279b442..a83e31904 100644 --- a/bootstrap/api/requests.go +++ b/bootstrap/api/requests.go @@ -4,15 +4,15 @@ package api import ( - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/pkg/apiutil" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/bootstrap" ) const maxLimitSize = 100 type addReq struct { token string - ThingID string `json:"thing_id"` + ClientID string `json:"client_id"` ExternalID string `json:"external_id"` ExternalKey string `json:"external_key"` Channels []string `json:"channels"` @@ -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 73ac1df9d..caf9e8fb8 100644 --- a/bootstrap/api/requests_test.go +++ b/bootstrap/api/requests_test.go @@ -7,9 +7,9 @@ import ( "fmt" "testing" - "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/apiutil" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/bootstrap" "github.com/stretchr/testify/assert" ) @@ -151,20 +151,20 @@ func TestUpdateReqValidation(t *testing.T) { func TestUpdateCertReqValidation(t *testing.T) { cases := []struct { - desc string - thingID string - err error + desc string + clientID string + err error }{ { - desc: "empty thing id", - thingID: "", - err: apiutil.ErrMissingID, + 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 59d166f7c..59d73df74 100644 --- a/bootstrap/api/responses.go +++ b/bootstrap/api/responses.go @@ -7,16 +7,16 @@ import ( "fmt" "net/http" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/bootstrap" + "github.com/absmach/supermq" + "github.com/absmach/supermq/bootstrap" ) var ( - _ magistrala.Response = (*removeRes)(nil) - _ magistrala.Response = (*configRes)(nil) - _ magistrala.Response = (*stateRes)(nil) - _ magistrala.Response = (*viewRes)(nil) - _ magistrala.Response = (*listRes)(nil) + _ supermq.Response = (*removeRes)(nil) + _ supermq.Response = (*configRes)(nil) + _ supermq.Response = (*stateRes)(nil) + _ supermq.Response = (*viewRes)(nil) + _ supermq.Response = (*listRes)(nil) ) type removeRes struct{} @@ -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_secret,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"` } 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 742ba51e3..c5c9baacf 100644 --- a/bootstrap/api/transport.go +++ b/bootstrap/api/transport.go @@ -11,12 +11,12 @@ import ( "net/url" "strings" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/bootstrap" - "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/supermq" + api "github.com/absmach/supermq/api/http" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/bootstrap" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -33,21 +33,21 @@ 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") ) // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc bootstrap.Service, authn mgauthn.Authentication, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler { +func MakeHandler(svc bootstrap.Service, authn smqauthn.Authentication, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } 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, @@ -121,7 +121,7 @@ func MakeHandler(svc bootstrap.Service, authn mgauthn.Authentication, reader boo opts...), "bootstrap_secure").ServeHTTP) }) - r.Get("/health", magistrala.Health("bootstrap", instanceID)) + r.Get("/health", supermq.Health("bootstrap", instanceID)) r.Handle("/metrics", promhttp.Handler()) return r @@ -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 24c8ecde9..2e42d1f28 100644 --- a/bootstrap/configs.go +++ b/bootstrap/configs.go @@ -7,30 +7,30 @@ import ( "context" "time" - "github.com/absmach/magistrala/things" + "github.com/absmach/supermq/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. +// as well as info about corresponding SuperMQ entities. +// MGClient represents corresponding SuperMQ Client ID. +// MGKey is key of corresponding SuperMQ Client. +// MGChannels is a list of SuperMQ Channels corresponding SuperMQ 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 SuperMQ channel corresponding SuperMQ 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/doc.go b/bootstrap/doc.go index 606c44a9e..2e939673d 100644 --- a/bootstrap/doc.go +++ b/bootstrap/doc.go @@ -2,5 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 // Package bootstrap contains the domain concept definitions needed to support -// Magistrala bootstrap service functionality. +// SuperMQ bootstrap service functionality. package bootstrap diff --git a/bootstrap/events/consumer/events.go b/bootstrap/events/consumer/events.go index a3a059965..71ded144a 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 7c0d5bcbf..1e34e48e2 100644 --- a/bootstrap/events/consumer/streams.go +++ b/bootstrap/events/consumer/streams.go @@ -7,21 +7,21 @@ import ( "context" "time" - "github.com/absmach/magistrala/bootstrap" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/events" + "github.com/absmach/supermq/bootstrap" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/pkg/events" ) 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 86f5c4309..dd7ece9c2 100644 --- a/bootstrap/events/producer/events.go +++ b/bootstrap/events/producer/events.go @@ -4,8 +4,8 @@ package producer import ( - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/pkg/events" + "github.com/absmach/supermq/bootstrap" + "github.com/absmach/supermq/pkg/events" ) const ( @@ -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 6202c168e..55408e07e 100644 --- a/bootstrap/events/producer/streams.go +++ b/bootstrap/events/producer/streams.go @@ -6,9 +6,9 @@ package producer import ( "context" - "github.com/absmach/magistrala/bootstrap" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/events" + "github.com/absmach/supermq/bootstrap" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/events" ) var _ bootstrap.Service = (*eventStore)(nil) @@ -27,7 +27,7 @@ func NewEventStoreMiddleware(svc bootstrap.Service, publisher events.Publisher) } } -func (es *eventStore) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) { +func (es *eventStore) Add(ctx context.Context, session smqauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) { saved, err := es.svc.Add(ctx, session, token, cfg) if err != nil { return saved, err @@ -44,7 +44,7 @@ func (es *eventStore) Add(ctx context.Context, session mgauthn.Session, token st return saved, err } -func (es *eventStore) View(ctx context.Context, session mgauthn.Session, id string) (bootstrap.Config, error) { +func (es *eventStore) View(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) { cfg, err := es.svc.View(ctx, session, id) if err != nil { return cfg, err @@ -60,7 +60,7 @@ func (es *eventStore) View(ctx context.Context, session mgauthn.Session, id stri return cfg, err } -func (es *eventStore) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) error { +func (es *eventStore) Update(ctx context.Context, session smqauthn.Session, cfg bootstrap.Config) error { if err := es.svc.Update(ctx, session, cfg); err != nil { return err } @@ -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 smqauthn.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, @@ -92,20 +92,20 @@ func (es eventStore) UpdateCert(ctx context.Context, session mgauthn.Session, th return cfg, nil } -func (es *eventStore) UpdateConnections(ctx context.Context, session mgauthn.Session, token, id string, connections []string) error { +func (es *eventStore) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error { if err := es.svc.UpdateConnections(ctx, session, token, id, connections); err != nil { return err } ev := updateConnectionsEvent{ - mgThing: id, + mgClient: id, mgChannels: connections, } return es.Publish(ctx, ev) } -func (es *eventStore) List(ctx context.Context, session mgauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) { +func (es *eventStore) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) { bp, err := es.svc.List(ctx, session, filter, offset, limit) if err != nil { return bp, err @@ -125,13 +125,13 @@ func (es *eventStore) List(ctx context.Context, session mgauthn.Session, filter return bp, nil } -func (es *eventStore) Remove(ctx context.Context, session mgauthn.Session, id string) error { +func (es *eventStore) Remove(ctx context.Context, session smqauthn.Session, id string) error { if err := es.svc.Remove(ctx, session, id); err != nil { return err } ev := removeConfigEvent{ - mgThing: id, + client: id, } return es.Publish(ctx, ev) @@ -157,14 +157,14 @@ func (es *eventStore) Bootstrap(ctx context.Context, externalKey, externalID str return cfg, err } -func (es *eventStore) ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state bootstrap.State) error { +func (es *eventStore) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) error { if err := es.svc.ChangeState(ctx, session, token, id, state); err != nil { return err } 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 aa5f1de86..3c8bb2ddc 100644 --- a/bootstrap/events/producer/streams_test.go +++ b/bootstrap/events/producer/streams_test.go @@ -11,20 +11,20 @@ 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" - 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/events/store" - policysvc "github.com/absmach/magistrala/pkg/policies" - policymocks "github.com/absmach/magistrala/pkg/policies/mocks" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/absmach/magistrala/pkg/uuid" + "github.com/absmach/supermq/bootstrap" + "github.com/absmach/supermq/bootstrap/events/producer" + "github.com/absmach/supermq/bootstrap/mocks" + "github.com/absmach/supermq/pkg/authn" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/pkg/events/store" + policysvc "github.com/absmach/supermq/pkg/policies" + policymocks "github.com/absmach/supermq/pkg/policies/mocks" + mgsdk "github.com/absmach/supermq/pkg/sdk" + sdkmocks "github.com/absmach/supermq/pkg/sdk/mocks" + "github.com/absmach/supermq/pkg/uuid" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -32,13 +32,13 @@ import ( ) const ( - streamID = "magistrala.bootstrap" - email = "user@example.com" - validToken = "validToken" - invalidToken = "invalid" - unknownThingID = "unknown" - channelsNum = 3 - defaultTimout = 5 + streamID = "supermq.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 smqauthn.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", @@ -191,8 +191,8 @@ 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)) + tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} + 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,13 +226,13 @@ func TestView(t *testing.T) { tv := newTestVariable(t, redisURL) nonExisting := config - nonExisting.ThingID = unknownThingID + nonExisting.ClientID = unknownClientID cases := []struct { desc string config bootstrap.Config token string - session mgauthn.Session + session smqauthn.Session id string domainID string retrieveErr error @@ -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, @@ -271,9 +271,9 @@ 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) + tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} + 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} @@ -324,7 +324,7 @@ func TestUpdate(t *testing.T) { desc string config bootstrap.Config token string - session mgauthn.Session + session smqauthn.Session id string domainID string updateErr error @@ -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(), @@ -365,7 +365,7 @@ func TestUpdate(t *testing.T) { lastID := "0" for _, tc := range cases { - tc.session = mgauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} + tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} repoCall := tv.boot.On("Update", context.Background(), mock.Anything).Return(tc.updateErr) err := tv.svc.Update(context.Background(), tc.session, tc.config) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -401,9 +401,9 @@ func TestUpdateConnections(t *testing.T) { id string domainID string token string - session mgauthn.Session + session smqauthn.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, @@ -474,7 +474,7 @@ func TestUpdateConnections(t *testing.T) { lastID := "0" for _, tc := range cases { - tc.session = mgauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} + tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} sdkCall := tv.sdk.On("Channel", mock.Anything, tc.domainID, tc.token).Return(mgsdk.Channel{}, tc.channelErr) repoCall := tv.boot.On("RetrieveByID", context.Background(), tc.domainID, tc.configID).Return(config, tc.retrieveErr) repoCall1 := tv.boot.On("ListExisting", context.Background(), domainID, mock.Anything, mock.Anything).Return(config.Channels, tc.listErr) @@ -514,7 +514,7 @@ func TestUpdateCert(t *testing.T) { userID string domainID string token string - session mgauthn.Session + session smqauthn.Session clientCert string clientKey string caCert string @@ -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,19 +600,19 @@ 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(), }, }, } lastID := "0" for _, tc := range cases { - tc.session = mgauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} + tc.session = smqauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} repoCall := tv.boot.On("UpdateCert", context.Background(), tc.domainID, tc.configID, tc.clientCert, tc.clientKey, tc.caCert).Return(config, tc.updateErr) _, err := tv.svc.UpdateCert(context.Background(), tc.session, tc.configID, tc.clientCert, tc.clientKey, tc.caCert) @@ -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) @@ -656,7 +656,7 @@ func TestList(t *testing.T) { cases := []struct { desc string token string - session mgauthn.Session + session smqauthn.Session userID string domainID string config bootstrap.ConfigsPage @@ -674,7 +674,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, config: bootstrap.ConfigsPage{ Total: uint64(len(saved)), Offset: 0, @@ -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, @@ -702,7 +702,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, config: bootstrap.ConfigsPage{ Total: uint64(len(saved)), Offset: 0, @@ -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, @@ -730,7 +730,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, config: bootstrap.ConfigsPage{ Total: uint64(len(saved)), Offset: 0, @@ -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, @@ -758,7 +758,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, filter: bootstrap.Filter{}, offset: 0, limit: 10, @@ -773,7 +773,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, filter: bootstrap.Filter{}, offset: 0, limit: 10, @@ -787,7 +787,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, filter: bootstrap.Filter{}, offset: 0, limit: 10, @@ -801,7 +801,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, filter: bootstrap.Filter{}, offset: 0, limit: 10, @@ -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 @@ -859,27 +859,27 @@ func TestRemove(t *testing.T) { userID string domainID string token string - session mgauthn.Session + session smqauthn.Session removeErr error err error event map[string]interface{} }{ { 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, @@ -891,7 +891,7 @@ func TestRemove(t *testing.T) { lastID := "0" for _, tc := range cases { - tc.session = mgauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} + tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} repoCall := tv.boot.On("Remove", context.Background(), mock.Anything, mock.Anything).Return(tc.removeErr) err := tv.svc.Remove(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)) @@ -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, }, }, } @@ -988,9 +988,9 @@ func TestChangeState(t *testing.T) { userID string domainID string token string - session mgauthn.Session + session smqauthn.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, @@ -1052,9 +1052,9 @@ func TestChangeState(t *testing.T) { lastID := "0" for _, tc := range cases { - tc.session = mgauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} + tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID} repoCall := tv.boot.On("RetrieveByID", context.Background(), tc.domainID, tc.id).Return(config, tc.retrieveErr) - sdkCall1 := tv.sdk.On("Connect", mock.Anything, mock.Anything, mock.Anything).Return(errors.NewSDKError(tc.connectErr)) + sdkCall1 := tv.sdk.On("ConnectClients", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.NewSDKError(tc.connectErr)) repoCall1 := tv.boot.On("ChangeState", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(tc.stateErr) err := tv.svc.ChangeState(context.Background(), tc.session, tc.token, tc.id, tc.state) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -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 cc14e55a1..de04f65fb 100644 --- a/bootstrap/middleware/authorization.go +++ b/bootstrap/middleware/authorization.go @@ -6,29 +6,29 @@ package middleware import ( "context" - "github.com/absmach/magistrala/bootstrap" - mgauthn "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" + "github.com/absmach/supermq/bootstrap" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/authz" + smqauthz "github.com/absmach/supermq/pkg/authz" + "github.com/absmach/supermq/pkg/policies" ) var _ bootstrap.Service = (*authorizationMiddleware)(nil) type authorizationMiddleware struct { svc bootstrap.Service - authz mgauthz.Authorization + authz smqauthz.Authorization } // AuthorizationMiddleware adds authorization to the clients service. -func AuthorizationMiddleware(svc bootstrap.Service, authz mgauthz.Authorization) bootstrap.Service { +func AuthorizationMiddleware(svc bootstrap.Service, authz smqauthz.Authorization) bootstrap.Service { return &authorizationMiddleware{ svc: svc, authz: authz, } } -func (am *authorizationMiddleware) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) { +func (am *authorizationMiddleware) Add(ctx context.Context, session smqauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) { if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID); err != nil { return bootstrap.Config{}, err } @@ -36,39 +36,39 @@ func (am *authorizationMiddleware) Add(ctx context.Context, session mgauthn.Sess return am.svc.Add(ctx, session, token, cfg) } -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 { +func (am *authorizationMiddleware) View(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) { + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.ViewPermission, policies.ClientType, id); err != nil { return bootstrap.Config{}, err } return am.svc.View(ctx, session, id) } -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 { +func (am *authorizationMiddleware) Update(ctx context.Context, session smqauthn.Session, cfg bootstrap.Config) error { + 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 smqauthn.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 { +func (am *authorizationMiddleware) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error { + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.EditPermission, policies.ClientType, id); err != nil { return err } return am.svc.UpdateConnections(ctx, session, token, id, connections) } -func (am *authorizationMiddleware) List(ctx context.Context, session mgauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) { +func (am *authorizationMiddleware) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) { if err := am.checkSuperAdmin(ctx, session.DomainUserID); err == nil { session.SuperAdmin = true } @@ -79,8 +79,8 @@ func (am *authorizationMiddleware) List(ctx context.Context, session mgauthn.Ses return am.svc.List(ctx, session, filter, offset, limit) } -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 { +func (am *authorizationMiddleware) Remove(ctx context.Context, session smqauthn.Session, id string) error { + if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, policies.DeletePermission, policies.ClientType, id); err != nil { return err } @@ -91,7 +91,7 @@ func (am *authorizationMiddleware) Bootstrap(ctx context.Context, externalKey, e return am.svc.Bootstrap(ctx, externalKey, externalID, secure) } -func (am *authorizationMiddleware) ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state bootstrap.State) error { +func (am *authorizationMiddleware) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) error { return am.svc.ChangeState(ctx, session, token, id, state) } @@ -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 { @@ -121,7 +121,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID Subject: adminID, Permission: policies.AdminPermission, ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, + Object: policies.SuperMQObject, }); err != nil { return err } diff --git a/bootstrap/middleware/logging.go b/bootstrap/middleware/logging.go index 362920d84..a6d23497c 100644 --- a/bootstrap/middleware/logging.go +++ b/bootstrap/middleware/logging.go @@ -10,8 +10,8 @@ import ( "log/slog" "time" - "github.com/absmach/magistrala/bootstrap" - mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/supermq/bootstrap" + smqauthn "github.com/absmach/supermq/pkg/authn" ) var _ bootstrap.Service = (*loggingMiddleware)(nil) @@ -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) { +func (lm *loggingMiddleware) Add(ctx context.Context, session smqauthn.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) { +func (lm *loggingMiddleware) View(ctx context.Context, session smqauthn.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) { +func (lm *loggingMiddleware) Update(ctx context.Context, session smqauthn.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 smqauthn.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,16 +102,16 @@ 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. // If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateConnections(ctx context.Context, session mgauthn.Session, token, id string, connections []string) (err error) { +func (lm *loggingMiddleware) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) (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), slog.Any("connections", connections), } if err != nil { @@ -127,7 +127,7 @@ func (lm *loggingMiddleware) UpdateConnections(ctx context.Context, session mgau // List logs the list request. It logs offset, limit and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) List(ctx context.Context, session mgauthn.Session, filter bootstrap.Filter, offset, limit uint64) (res bootstrap.ConfigsPage, err error) { +func (lm *loggingMiddleware) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (res bootstrap.ConfigsPage, err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), @@ -151,11 +151,11 @@ func (lm *loggingMiddleware) List(ctx context.Context, session mgauthn.Session, // Remove logs the remove request. It logs bootstrap ID and the time it took to complete the request. // If the request fails, it logs the error. -func (lm *loggingMiddleware) Remove(ctx context.Context, session mgauthn.Session, id string) (err error) { +func (lm *loggingMiddleware) Remove(ctx context.Context, session smqauthn.Session, id string) (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)) @@ -185,7 +185,7 @@ func (lm *loggingMiddleware) Bootstrap(ctx context.Context, externalKey, externa return lm.svc.Bootstrap(ctx, externalKey, externalID, secure) } -func (lm *loggingMiddleware) ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state bootstrap.State) (err error) { +func (lm *loggingMiddleware) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) (err error) { defer func(begin time.Time) { args := []any{ slog.String("duration", time.Since(begin).String()), @@ -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 cd95e4e6c..89f64ecd4 100644 --- a/bootstrap/middleware/metrics.go +++ b/bootstrap/middleware/metrics.go @@ -9,8 +9,8 @@ import ( "context" "time" - "github.com/absmach/magistrala/bootstrap" - mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/supermq/bootstrap" + smqauthn "github.com/absmach/supermq/pkg/authn" "github.com/go-kit/kit/metrics" ) @@ -32,7 +32,7 @@ func MetricsMiddleware(svc bootstrap.Service, counter metrics.Counter, latency m } // Add instruments Add method with metrics. -func (mm *metricsMiddleware) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (saved bootstrap.Config, err error) { +func (mm *metricsMiddleware) Add(ctx context.Context, session smqauthn.Session, token string, cfg bootstrap.Config) (saved bootstrap.Config, err error) { defer func(begin time.Time) { mm.counter.With("method", "add").Add(1) mm.latency.With("method", "add").Observe(time.Since(begin).Seconds()) @@ -42,7 +42,7 @@ func (mm *metricsMiddleware) Add(ctx context.Context, session mgauthn.Session, t } // View instruments View method with metrics. -func (mm *metricsMiddleware) View(ctx context.Context, session mgauthn.Session, id string) (saved bootstrap.Config, err error) { +func (mm *metricsMiddleware) View(ctx context.Context, session smqauthn.Session, id string) (saved bootstrap.Config, err error) { defer func(begin time.Time) { mm.counter.With("method", "view").Add(1) mm.latency.With("method", "view").Observe(time.Since(begin).Seconds()) @@ -52,7 +52,7 @@ func (mm *metricsMiddleware) View(ctx context.Context, session mgauthn.Session, } // Update instruments Update method with metrics. -func (mm *metricsMiddleware) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) (err error) { +func (mm *metricsMiddleware) Update(ctx context.Context, session smqauthn.Session, cfg bootstrap.Config) (err error) { defer func(begin time.Time) { mm.counter.With("method", "update").Add(1) mm.latency.With("method", "update").Observe(time.Since(begin).Seconds()) @@ -62,17 +62,17 @@ 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 smqauthn.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. -func (mm *metricsMiddleware) UpdateConnections(ctx context.Context, session mgauthn.Session, token, id string, connections []string) (err error) { +func (mm *metricsMiddleware) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) (err error) { defer func(begin time.Time) { mm.counter.With("method", "update_connections").Add(1) mm.latency.With("method", "update_connections").Observe(time.Since(begin).Seconds()) @@ -82,7 +82,7 @@ func (mm *metricsMiddleware) UpdateConnections(ctx context.Context, session mgau } // List instruments List method with metrics. -func (mm *metricsMiddleware) List(ctx context.Context, session mgauthn.Session, filter bootstrap.Filter, offset, limit uint64) (saved bootstrap.ConfigsPage, err error) { +func (mm *metricsMiddleware) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (saved bootstrap.ConfigsPage, err error) { defer func(begin time.Time) { mm.counter.With("method", "list").Add(1) mm.latency.With("method", "list").Observe(time.Since(begin).Seconds()) @@ -92,7 +92,7 @@ func (mm *metricsMiddleware) List(ctx context.Context, session mgauthn.Session, } // Remove instruments Remove method with metrics. -func (mm *metricsMiddleware) Remove(ctx context.Context, session mgauthn.Session, id string) (err error) { +func (mm *metricsMiddleware) Remove(ctx context.Context, session smqauthn.Session, id string) (err error) { defer func(begin time.Time) { mm.counter.With("method", "remove").Add(1) mm.latency.With("method", "remove").Observe(time.Since(begin).Seconds()) @@ -112,7 +112,7 @@ func (mm *metricsMiddleware) Bootstrap(ctx context.Context, externalKey, externa } // ChangeState instruments ChangeState method with metrics. -func (mm *metricsMiddleware) ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state bootstrap.State) (err error) { +func (mm *metricsMiddleware) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) (err error) { defer func(begin time.Time) { mm.counter.With("method", "change_state").Add(1) mm.latency.With("method", "change_state").Observe(time.Since(begin).Seconds()) @@ -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/config_reader.go b/bootstrap/mocks/config_reader.go index 5a3361bd5..e4a0d4d9d 100644 --- a/bootstrap/mocks/config_reader.go +++ b/bootstrap/mocks/config_reader.go @@ -5,7 +5,7 @@ package mocks import ( - bootstrap "github.com/absmach/magistrala/bootstrap" + bootstrap "github.com/absmach/supermq/bootstrap" mock "github.com/stretchr/testify/mock" ) diff --git a/bootstrap/mocks/configs.go b/bootstrap/mocks/configs.go index d088cb135..2d80ed0b4 100644 --- a/bootstrap/mocks/configs.go +++ b/bootstrap/mocks/configs.go @@ -7,7 +7,7 @@ package mocks import ( context "context" - bootstrap "github.com/absmach/magistrala/bootstrap" + bootstrap "github.com/absmach/supermq/bootstrap" mock "github.com/stretchr/testify/mock" ) @@ -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 851e6ef16..b822601c4 100644 --- a/bootstrap/mocks/service.go +++ b/bootstrap/mocks/service.go @@ -5,8 +5,8 @@ package mocks import ( - bootstrap "github.com/absmach/magistrala/bootstrap" - authn "github.com/absmach/magistrala/pkg/authn" + bootstrap "github.com/absmach/supermq/bootstrap" + authn "github.com/absmach/supermq/pkg/authn" context "context" @@ -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 6c46a3fe1..5bc6f3b59 100644 --- a/bootstrap/postgres/configs.go +++ b/bootstrap/postgres/configs.go @@ -12,11 +12,11 @@ import ( "strings" "time" - "github.com/absmach/magistrala/bootstrap" - "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/absmach/supermq/bootstrap" + "github.com/absmach/supermq/clients" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" + "github.com/absmach/supermq/pkg/postgres" "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 584ddd42b..32ca71b8b 100644 --- a/bootstrap/postgres/configs_test.go +++ b/bootstrap/postgres/configs_test.go @@ -9,11 +9,11 @@ import ( "strconv" "testing" - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/bootstrap/postgres" "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" + "github.com/absmach/supermq/bootstrap" + "github.com/absmach/supermq/bootstrap/postgres" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" "github.com/gofrs/uuid/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -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: "smq-client", + ClientSecret: "smq-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 f562551ce..5ab55938d 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/postgres/setup_test.go b/bootstrap/postgres/setup_test.go index 3848cd499..4151768d9 100644 --- a/bootstrap/postgres/setup_test.go +++ b/bootstrap/postgres/setup_test.go @@ -9,16 +9,16 @@ import ( "os" "testing" - "github.com/absmach/magistrala/bootstrap/postgres" - mglog "github.com/absmach/magistrala/logger" - pgclient "github.com/absmach/magistrala/pkg/postgres" + "github.com/absmach/supermq/bootstrap/postgres" + smqlog "github.com/absmach/supermq/logger" + pgclient "github.com/absmach/supermq/pkg/postgres" "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" ) var ( - testLog, _ = mglog.New(os.Stdout, "info") + testLog, _ = smqlog.New(os.Stdout, "info") db *sqlx.DB ) diff --git a/bootstrap/reader.go b/bootstrap/reader.go index dd4358085..84fa1ef2c 100644 --- a/bootstrap/reader.go +++ b/bootstrap/reader.go @@ -12,17 +12,17 @@ import ( "net/http" ) -// bootstrapRes represent Magistrala Response to the Bootatrap request. +// bootstrapRes represent SuperMQ Response to the Bootatrap request. // 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 c283f3365..28a36bd42 100644 --- a/bootstrap/reader_test.go +++ b/bootstrap/reader_test.go @@ -11,9 +11,9 @@ import ( "net/http" "testing" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/supermq" + "github.com/absmach/supermq/bootstrap" + "github.com/absmach/supermq/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -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,27 +50,27 @@ 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: "smq_id", + ClientCert: "client_cert", + ClientKey: "client_key", + CACert: "ca_cert", + ClientSecret: "smq_key", Channels: []bootstrap.Channel{ { - ID: "mg_id", - Name: "mg_name", + ID: "smq_id", + Name: "smq_name", Metadata: map[string]interface{}{"key": "value}"}, }, }, Content: "content", } ret := readResp{ - ThingID: "mg_id", - ThingKey: "mg_key", + ClientID: "smq_id", + ClientSecret: "smq_key", Channels: []readChan{ { - ID: "mg_id", - Name: "mg_name", + ID: "smq_id", + Name: "smq_name", Metadata: map[string]interface{}{"key": "value}"}, }, }, @@ -118,7 +118,7 @@ func TestReadConfig(t *testing.T) { b, err := json.Marshal(res) assert.Nil(t, err, fmt.Sprintf("Marshalling expected to succeed: %s.\n", err)) assert.Equal(t, tc.enc, b, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.enc, b)) - resp, ok := res.(magistrala.Response) + resp, ok := res.(supermq.Response) assert.True(t, ok, "If not encrypted, reader should return response.") assert.False(t, resp.Empty(), fmt.Sprintf("Response should not be empty %s.", err)) assert.Equal(t, http.StatusOK, resp.Code(), "Default config response code should be 200.") diff --git a/bootstrap/service.go b/bootstrap/service.go index 91976bd56..566483b07 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -9,19 +9,19 @@ import ( "crypto/cipher" "encoding/hex" - "github.com/absmach/magistrala" - 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" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" + "github.com/absmach/supermq" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/pkg/policies" + mgsdk "github.com/absmach/supermq/pkg/sdk" ) var ( - // ErrThings indicates failure to communicate with Magistrala Things service. + // ErrClients indicates failure to communicate with SuperMQ 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,34 +60,34 @@ 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(ctx context.Context, session mgauthn.Session, token string, cfg Config) (Config, error) + // Add adds new Client Config to the user identified by the provided token. + Add(ctx context.Context, session smqauthn.Session, token string, cfg Config) (Config, error) - // View returns Thing Config with given ID belonging to the user identified by the given token. - View(ctx context.Context, session mgauthn.Session, id string) (Config, error) + // View returns Client Config with given ID belonging to the user identified by the given token. + View(ctx context.Context, session smqauthn.Session, id string) (Config, error) // Update updates editable fields of the provided Config. - Update(ctx context.Context, session mgauthn.Session, cfg Config) error + Update(ctx context.Context, session smqauthn.Session, cfg Config) error // 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 smqauthn.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 + UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error // List returns subset of Configs with given search params that belong to the // user identified by the given token. - List(ctx context.Context, session mgauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error) + List(ctx context.Context, session smqauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error) // 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 + Remove(ctx context.Context, session smqauthn.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(ctx context.Context, session mgauthn.Session, token, id string, state State) error + // ChangeState changes state of the Client with given client ID and domain ID. + ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state State) error // Methods RemoveConfig, UpdateChannel, and RemoveChannel are used as // handlers for events. That's why these methods surpass ownership check. @@ -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 @@ -123,11 +123,11 @@ type bootstrapService struct { configs ConfigRepository sdk mgsdk.SDK encKey []byte - idProvider magistrala.IDProvider + idProvider supermq.IDProvider } // New returns new Bootstrap service. -func New(policyService policies.Service, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte, idp magistrala.IDProvider) Service { +func New(policyService policies.Service, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte, idp supermq.IDProvider) Service { return &bootstrapService{ configs: configs, sdk: sdk, @@ -137,7 +137,7 @@ func New(policyService policies.Service, configs ConfigRepository, sdk mgsdk.SDK } } -func (bs bootstrapService) Add(ctx context.Context, session mgauthn.Session, token string, cfg Config) (Config, error) { +func (bs bootstrapService) Add(ctx context.Context, session smqauthn.Session, token string, cfg Config) (Config, error) { toConnect := bs.toIDList(cfg.Channels) // Check if channels exist. This is the way to prevent fetching channels that already exist. @@ -151,42 +151,42 @@ 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 } -func (bs bootstrapService) View(ctx context.Context, session mgauthn.Session, id string) (Config, error) { +func (bs bootstrapService) View(ctx context.Context, session smqauthn.Session, id string) (Config, error) { cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id) if err != nil { return Config{}, errors.Wrap(svcerr.ErrViewEntity, err) @@ -194,7 +194,7 @@ func (bs bootstrapService) View(ctx context.Context, session mgauthn.Session, id return cfg, nil } -func (bs bootstrapService) Update(ctx context.Context, session mgauthn.Session, cfg Config) error { +func (bs bootstrapService) Update(ctx context.Context, session smqauthn.Session, cfg Config) error { cfg.DomainID = session.DomainID if err := bs.configs.Update(ctx, cfg); err != nil { return errors.Wrap(errUpdateConnections, err) @@ -202,15 +202,15 @@ 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 smqauthn.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) } return cfg, nil } -func (bs bootstrapService) UpdateConnections(ctx context.Context, session mgauthn.Session, token, id string, connections []string) error { +func (bs bootstrapService) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error { cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id) if err != nil { return errors.Wrap(errUpdateConnections, err) @@ -238,21 +238,22 @@ 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.DisconnectClients(c, []string{id}, []string{"Publish", "Subscribe"}, 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, + ChannelIDs: []string{c}, + ClientIDs: []string{id}, + Types: []string{"Publish", "Subscribe"}, } 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 +267,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) @@ -274,18 +275,18 @@ func (bs bootstrapService) listClientIDs(ctx context.Context, userID string) ([] return tids.Policies, nil } -func (bs bootstrapService) List(ctx context.Context, session mgauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error) { +func (bs bootstrapService) List(ctx context.Context, session smqauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error) { if session.SuperAdmin { return bs.configs.RetrieveAll(ctx, session.DomainID, []string{}, filter, offset, limit), nil } // 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,10 +295,10 @@ 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 { +func (bs bootstrapService) Remove(ctx context.Context, session smqauthn.Session, id string) error { if err := bs.configs.Remove(ctx, session.DomainID, id); err != nil { return errors.Wrap(errRemoveBootstrap, err) } @@ -323,7 +324,7 @@ func (bs bootstrapService) Bootstrap(ctx context.Context, externalKey, externalI return cfg, nil } -func (bs bootstrapService) ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state State) error { +func (bs bootstrapService) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state State) error { cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id) if err != nil { return errors.Wrap(errChangeState, err) @@ -336,25 +337,21 @@ func (bs bootstrapService) ChangeState(ctx context.Context, session mgauthn.Sess switch state { case Active: for _, c := range cfg.Channels { - conIDs := mgsdk.Connection{ - ChannelID: c.ID, - ThingID: cfg.ThingID, - } - if err := bs.sdk.Connect(conIDs, session.DomainID, token); err != nil { + if err := bs.sdk.ConnectClients(c.ID, []string{cfg.ClientID}, []string{"Publish", "Subscribe"}, 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.DisconnectClients(c.ID, []string{cfg.ClientID}, []string{"Publish", "Subscribe"}, session.DomainID, token); err != nil { if errors.Contains(err, repoerr.ErrNotFound) { continue } - return ErrThings + return ErrClients } } } @@ -372,7 +369,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 +382,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 SuperMQ 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 f2918f2ec..5f269640c 100644 --- a/bootstrap/service_test.go +++ b/bootstrap/service_test.go @@ -14,17 +14,17 @@ import ( "sort" "testing" - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/bootstrap/mocks" "github.com/absmach/magistrala/internal/testsutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - policysvc "github.com/absmach/magistrala/pkg/policies" - policymocks "github.com/absmach/magistrala/pkg/policies/mocks" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/absmach/magistrala/pkg/uuid" + "github.com/absmach/supermq/bootstrap" + "github.com/absmach/supermq/bootstrap/mocks" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" + svcerr "github.com/absmach/supermq/pkg/errors/service" + policysvc "github.com/absmach/supermq/pkg/policies" + policymocks "github.com/absmach/supermq/pkg/policies/mocks" + mgsdk "github.com/absmach/supermq/pkg/sdk" + sdkmocks "github.com/absmach/supermq/pkg/sdk/mocks" + "github.com/absmach/supermq/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -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 @@ -103,15 +103,15 @@ func TestAdd(t *testing.T) { desc string config bootstrap.Config token string - session mgauthn.Session + session smqauthn.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 }{ { @@ -123,13 +123,13 @@ func TestAdd(t *testing.T) { err: nil, }, { - desc: "add a config with an invalid ID", - config: neID, - token: validToken, - userID: validID, - domainID: domainID, - thingErr: errors.NewSDKError(svcerr.ErrNotFound), - err: svcerr.ErrNotFound, + desc: "add a config with an invalid ID", + config: neID, + token: validToken, + userID: validID, + domainID: domainID, + clientErr: errors.NewSDKError(svcerr.ErrNotFound), + err: svcerr.ErrNotFound, }, { desc: "add a config with invalid list of channels", @@ -151,10 +151,10 @@ 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) + tc.session = smqauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} + 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) @@ -172,53 +172,53 @@ func TestView(t *testing.T) { svc := newService() cases := []struct { - desc string - configID string - userID string - domain string - thingDomain string - token string - session mgauthn.Session - retrieveErr error - thingErr error - channelErr error - err error + desc string + configID string + userID string + domain string + clientDomain string + token string + session smqauthn.Session + retrieveErr error + clientErr error + channelErr error + err error }{ { - desc: "view an existing config", - configID: config.ThingID, - userID: validID, - thingDomain: domainID, - domain: domainID, - token: validToken, - err: nil, + desc: "view an existing config", + configID: config.ClientID, + userID: validID, + clientDomain: domainID, + domain: domainID, + token: validToken, + err: nil, }, { - desc: "view a non-existing config", - configID: unknown, - userID: validID, - thingDomain: domainID, - domain: domainID, - token: validToken, - retrieveErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, + desc: "view a non-existing config", + configID: unknown, + userID: validID, + clientDomain: domainID, + domain: domainID, + token: validToken, + retrieveErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, }, { - desc: "view a config with invalid domain", - configID: config.ThingID, - userID: validID, - thingDomain: invalidDomainID, - domain: invalidDomainID, - token: validToken, - retrieveErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, + desc: "view a config with invalid domain", + configID: config.ClientID, + userID: validID, + clientDomain: invalidDomainID, + domain: invalidDomainID, + token: validToken, + retrieveErr: svcerr.ErrNotFound, + err: svcerr.ErrNotFound, }, } 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) + tc.session = smqauthn.Session{UserID: tc.userID, DomainID: tc.domain, DomainUserID: validID} + 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,13 +239,13 @@ func TestUpdate(t *testing.T) { modifiedCreated.Name = "new name" nonExisting := c - nonExisting.ThingID = unknown + nonExisting.ClientID = unknown cases := []struct { desc string config bootstrap.Config token string - session mgauthn.Session + session smqauthn.Session userID string domainID string updateErr error @@ -281,7 +281,7 @@ func TestUpdate(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} + tc.session = smqauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} repoCall := boot.On("Update", context.Background(), mock.Anything).Return(tc.updateErr) err := svc.Update(context.Background(), tc.session, tc.config) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -301,10 +301,10 @@ func TestUpdateCert(t *testing.T) { cases := []struct { desc string token string - session mgauthn.Session + session smqauthn.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", @@ -356,9 +356,9 @@ func TestUpdateCert(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} + tc.session = smqauthn.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 @@ -386,14 +386,14 @@ func TestUpdateConnections(t *testing.T) { cases := []struct { desc string token string - session mgauthn.Session + session smqauthn.Session id string state bootstrap.State userID string 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, @@ -433,7 +433,7 @@ func TestUpdateConnections(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} + tc.session = smqauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} sdkCall := sdk.On("Channel", mock.Anything, tc.domainID, tc.token).Return(mgsdk.Channel{}, tc.channelErr) repoCall := boot.On("RetrieveByID", context.Background(), tc.domainID, tc.id).Return(c, tc.retrieveErr) repoCall1 := boot.On("ListExisting", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(c.Channels, tc.listErr) @@ -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) @@ -470,7 +470,7 @@ func TestList(t *testing.T) { offset uint64 limit uint64 token string - session mgauthn.Session + session smqauthn.Session userID string domainID string listObjectsResponse policysvc.PolicyPage @@ -488,7 +488,7 @@ func TestList(t *testing.T) { }, filter: bootstrap.Filter{}, token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, userID: validID, domainID: domainID, offset: 0, @@ -500,7 +500,7 @@ func TestList(t *testing.T) { config: bootstrap.ConfigsPage{}, filter: bootstrap.Filter{}, token: validID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, userID: validID, domainID: domainID, listObjectsResponse: policysvc.PolicyPage{}, @@ -520,7 +520,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, listObjectsResponse: policysvc.PolicyPage{}, offset: 0, limit: 10, @@ -538,7 +538,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, listObjectsResponse: policysvc.PolicyPage{Policies: []string{"test", "test"}}, offset: 0, limit: 10, @@ -554,7 +554,7 @@ func TestList(t *testing.T) { }, filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "95"}}, token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, userID: validID, domainID: domainID, offset: 0, @@ -573,7 +573,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, offset: 0, limit: 100, err: nil, @@ -590,7 +590,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, listObjectsResponse: policysvc.PolicyPage{Policies: []string{"test", "test"}}, offset: 0, limit: 100, @@ -608,7 +608,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, offset: 95, limit: 10, err: nil, @@ -625,7 +625,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, offset: 95, limit: 10, err: nil, @@ -642,7 +642,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, listObjectsResponse: policysvc.PolicyPage{Policies: []string{"test", "test"}}, offset: 95, limit: 10, @@ -660,7 +660,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, offset: 35, limit: 20, err: nil, @@ -677,7 +677,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, offset: 35, limit: 20, err: nil, @@ -694,7 +694,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, listObjectsResponse: policysvc.PolicyPage{Policies: []string{"test", "test"}}, offset: 35, limit: 20, @@ -709,7 +709,7 @@ func TestList(t *testing.T) { token: validToken, userID: validID, domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, + session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, listObjectsResponse: policysvc.PolicyPage{}, listObjectsErr: svcerr.ErrNotFound, err: svcerr.ErrNotFound, @@ -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) @@ -744,7 +744,7 @@ func TestRemove(t *testing.T) { desc string id string token string - session mgauthn.Session + session smqauthn.Session userID string domainID string removeErr error @@ -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, @@ -779,7 +779,7 @@ func TestRemove(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} + tc.session = smqauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} repoCall := boot.On("Remove", context.Background(), mock.Anything, mock.Anything).Return(tc.removeErr) err := svc.Remove(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)) @@ -867,7 +867,7 @@ func TestChangeState(t *testing.T) { state bootstrap.State id string token string - session mgauthn.Session + session smqauthn.Session userID string domainID string retrieveErr error @@ -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, @@ -937,9 +937,9 @@ func TestChangeState(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} + tc.session = smqauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID} repoCall := boot.On("RetrieveByID", context.Background(), tc.domainID, tc.id).Return(c, tc.retrieveErr) - sdkCall := sdk.On("Connect", mock.Anything, mock.Anything, mock.Anything).Return(tc.connectErr) + sdkCall := sdk.On("ConnectClients", mock.Anything, mock.Anything, []string{"Publish", "Subscribe"}, mock.Anything, tc.token).Return(tc.connectErr) repoCall1 := boot.On("ChangeState", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(tc.stateErr) err := svc.ChangeState(context.Background(), tc.session, tc.token, tc.id, tc.state) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -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 da8acccbb..e97aedb0c 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 SuperMQ. 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 SuperMQ 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 SuperMQ | +// | Active | Client is able to communicate using SuperMQ |. type State int // String returns string representation of State. diff --git a/bootstrap/tracing/doc.go b/bootstrap/tracing/doc.go index 5aa1b44b9..aa43565e4 100644 --- a/bootstrap/tracing/doc.go +++ b/bootstrap/tracing/doc.go @@ -1,12 +1,12 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package tracing provides tracing instrumentation for Magistrala Users service. +// Package tracing provides tracing instrumentation for SuperMQ Users service. // -// This package provides tracing middleware for Magistrala Users service. +// This package provides tracing middleware for SuperMQ Users service. // It can be used to trace incoming requests and add tracing capabilities to -// Magistrala Users service. +// SuperMQ Users service. // -// For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. +// For more details about tracing instrumentation for SuperMQ messaging refer +// to the documentation at https://docs.supermq.abstractmachines.fr/tracing/. package tracing diff --git a/bootstrap/tracing/tracing.go b/bootstrap/tracing/tracing.go index fee7e3547..c18530e03 100644 --- a/bootstrap/tracing/tracing.go +++ b/bootstrap/tracing/tracing.go @@ -6,8 +6,8 @@ package tracing import ( "context" - "github.com/absmach/magistrala/bootstrap" - mgauthn "github.com/absmach/magistrala/pkg/authn" + "github.com/absmach/supermq/bootstrap" + smqauthn "github.com/absmach/supermq/pkg/authn" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) @@ -25,9 +25,9 @@ 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) { +func (tm *tracingMiddleware) Add(ctx context.Context, session smqauthn.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), @@ -40,7 +40,7 @@ func (tm *tracingMiddleware) Add(ctx context.Context, session mgauthn.Session, t } // View traces the "View" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) View(ctx context.Context, session mgauthn.Session, id string) (bootstrap.Config, error) { +func (tm *tracingMiddleware) View(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) { ctx, span := tm.tracer.Start(ctx, "svc_view_user", trace.WithAttributes( attribute.String("id", id), )) @@ -50,11 +50,11 @@ func (tm *tracingMiddleware) View(ctx context.Context, session mgauthn.Session, } // Update traces the "Update" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) error { +func (tm *tracingMiddleware) Update(ctx context.Context, session smqauthn.Session, cfg bootstrap.Config) error { 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,17 +63,17 @@ 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 smqauthn.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. -func (tm *tracingMiddleware) UpdateConnections(ctx context.Context, session mgauthn.Session, token, id string, connections []string) error { +func (tm *tracingMiddleware) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error { ctx, span := tm.tracer.Start(ctx, "svc_update_connections", trace.WithAttributes( attribute.String("id", id), attribute.StringSlice("connections", connections), @@ -84,7 +84,7 @@ func (tm *tracingMiddleware) UpdateConnections(ctx context.Context, session mgau } // List traces the "List" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) List(ctx context.Context, session mgauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) { +func (tm *tracingMiddleware) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) { ctx, span := tm.tracer.Start(ctx, "svc_list_users", trace.WithAttributes( attribute.Int64("offset", int64(offset)), attribute.Int64("limit", int64(limit)), @@ -95,7 +95,7 @@ func (tm *tracingMiddleware) List(ctx context.Context, session mgauthn.Session, } // Remove traces the "Remove" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) Remove(ctx context.Context, session mgauthn.Session, id string) error { +func (tm *tracingMiddleware) Remove(ctx context.Context, session smqauthn.Session, id string) error { ctx, span := tm.tracer.Start(ctx, "svc_remove_user", trace.WithAttributes( attribute.String("id", id), )) @@ -117,7 +117,7 @@ func (tm *tracingMiddleware) Bootstrap(ctx context.Context, externalKey, externa } // ChangeState traces the "ChangeState" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state bootstrap.State) error { +func (tm *tracingMiddleware) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) error { ctx, span := tm.tracer.Start(ctx, "svc_change_state", trace.WithAttributes( attribute.String("id", id), attribute.String("state", state.String()), @@ -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 deleted file mode 100644 index b7f2b3cf6..000000000 --- a/certs/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Certs Service - -Issues certificates for things. `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 - -When `MG_CERTS_VAULT_HOST` is set it is presumed that `Vault` is installed and `certs` service will issue certificates using `Vault` API. -First you'll need to set up `Vault`. -To setup `Vault` follow steps in [Build Your Own Certificate Authority (CA)](https://learn.hashicorp.com/tutorials/vault/pki-engine). - -For lab purposes you can use docker-compose and script for setting up PKI in [https://github.com/absmach/magistrala/blob/main/docker/addons/vault/README.md](https://github.com/absmach/magistrala/blob/main/docker/addons/vault/README.md) - -```bash -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= -``` - -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. - -```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"}' -``` - -## 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 | "" | - -## 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. -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 certs -make certs - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_CERTS_LOG_LEVEL=info \ -MG_CERTS_HTTP_HOST=localhost \ -MG_CERTS_HTTP_PORT=9019 \ -MG_CERTS_HTTP_SERVER_CERT="" \ -MG_CERTS_HTTP_SERVER_KEY="" \ -MG_AUTH_GRPC_URL=localhost:8181 \ -MG_AUTH_GRPC_TIMEOUT=1s \ -MG_AUTH_GRPC_CLIENT_CERT="" \ -MG_AUTH_GRPC_CLIENT_KEY="" \ -MG_AUTH_GRPC_SERVER_CERTS="" \ -MG_CERTS_SIGN_CA_PATH=ca.crt \ -MG_CERTS_SIGN_CA_KEY_PATH=ca.key \ -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_DB_HOST=localhost \ -MG_CERTS_DB_PORT=5432 \ -MG_CERTS_DB_PASS=magistrala \ -MG_CERTS_DB_USER=magistrala \ -MG_CERTS_DB_NAME=certs \ -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_JAEGER_URL=http://localhost:14268/api/traces \ -MG_JAEGER_TRACE_RATIO=1.0 \ -MG_SEND_TELEMETRY=true \ -MG_CERTS_INSTANCE_ID="" \ -$GOBIN/magistrala-certs -``` - -Setting `MG_CERTS_HTTP_SERVER_CERT` and `MG_CERTS_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_AUTH_GRPC_CLIENT_CERT` and `MG_AUTH_GRPC_CLIENT_KEY` will enable TLS against the auth service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_AUTH_GRPC_SERVER_CERTS` will enable TLS against the auth service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. - -## Usage - -For more information about service capabilities and its usage, please check out the [Certs section](https://docs.magistrala.abstractmachines.fr/certs/). diff --git a/certs/api/doc.go b/certs/api/doc.go deleted file mode 100644 index 943cf198b..000000000 --- a/certs/api/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains implementation of certs service HTTP API. -package api diff --git a/certs/api/endpoint.go b/certs/api/endpoint.go deleted file mode 100644 index 8e03f4728..000000000 --- a/certs/api/endpoint.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - - "github.com/absmach/magistrala/certs" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/go-kit/kit/endpoint" -) - -func issueCert(svc certs.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(addCertsReq) - 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) - if err != nil { - return certsRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - return certsRes{ - SerialNumber: res.SerialNumber, - ThingID: res.ThingID, - Certificate: res.Certificate, - ExpiryTime: res.ExpiryTime, - Revoked: res.Revoked, - issued: true, - }, nil - } -} - -func listSerials(svc certs.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - page, err := svc.ListSerials(ctx, req.thingID, req.pm) - if err != nil { - return certsPageRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - res := certsPageRes{ - pageRes: pageRes{ - Total: page.Total, - Offset: page.Offset, - Limit: page.Limit, - }, - Certs: []certsRes{}, - } - - for _, cert := range page.Certificates { - cr := certsRes{ - SerialNumber: cert.SerialNumber, - ExpiryTime: cert.ExpiryTime, - Revoked: cert.Revoked, - ThingID: cert.ThingID, - } - res.Certs = append(res.Certs, cr) - } - return res, nil - } -} - -func viewCert(svc certs.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(viewReq) - if err := req.validate(); err != nil { - return certsRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - cert, err := svc.ViewCert(ctx, req.serialID) - if err != nil { - return certsRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - return certsRes{ - ThingID: cert.ThingID, - Certificate: cert.Certificate, - Key: cert.Key, - SerialNumber: cert.SerialNumber, - ExpiryTime: cert.ExpiryTime, - Revoked: cert.Revoked, - issued: false, - }, nil - } -} - -func revokeCert(svc certs.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(revokeReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - res, err := svc.RevokeCert(ctx, req.domainID, req.token, req.certID) - if err != nil { - return nil, err - } - return revokeCertsRes{ - RevocationTime: res.RevocationTime, - }, nil - } -} diff --git a/certs/api/endpoint_test.go b/certs/api/endpoint_test.go deleted file mode 100644 index 6cc2c143b..000000000 --- a/certs/api/endpoint_test.go +++ /dev/null @@ -1,672 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/certs" - httpapi "github.com/absmach/magistrala/certs/api" - "github.com/absmach/magistrala/certs/mocks" - "github.com/absmach/magistrala/internal/testsutil" - 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" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - contentType = "application/json" - valid = "valid" - invalid = "invalid" - thingID = testsutil.GenerateUUID(&testing.T{}) - serial = testsutil.GenerateUUID(&testing.T{}) - ttl = "1h" - cert = certs.Cert{ - ThingID: thingID, - SerialNumber: serial, - ExpiryTime: time.Now().Add(time.Hour), - } - validID = testsutil.GenerateUUID(&testing.T{}) -) - -type testRequest struct { - client *http.Client - method string - url string - contentType string - token string - body io.Reader -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, tr.body) - if err != nil { - return nil, err - } - if tr.token != "" { - req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) - } - if tr.contentType != "" { - req.Header.Set("Content-Type", tr.contentType) - } - - return tr.client.Do(req) -} - -func newCertServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - svc := new(mocks.Service) - logger := mglog.NewMock() - authn := new(authnmocks.Authentication) - mux := httpapi.MakeHandler(svc, authn, logger, "") - - return httptest.NewServer(mux), svc, authn -} - -func TestIssueCert(t *testing.T) { - cs, svc, auth := newCertServer() - defer cs.Close() - - validReqString := `{"thing_id": "%s","ttl": "%s"}` - invalidReqString := `{"thing_id": "%s","ttl": %s}` - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - contentType string - thingID string - ttl string - request string - status int - authenticateErr error - svcRes certs.Cert - svcErr error - err error - }{ - { - desc: "issue cert successfully", - token: valid, - domainID: valid, - contentType: contentType, - thingID: thingID, - ttl: ttl, - request: fmt.Sprintf(validReqString, thingID, ttl), - status: http.StatusCreated, - svcRes: certs.Cert{SerialNumber: serial}, - svcErr: nil, - err: nil, - }, - { - desc: "issue cert with failed service", - token: valid, - domainID: valid, - contentType: contentType, - thingID: thingID, - ttl: ttl, - request: fmt.Sprintf(validReqString, thingID, ttl), - status: http.StatusUnprocessableEntity, - svcRes: certs.Cert{}, - svcErr: svcerr.ErrCreateEntity, - err: svcerr.ErrCreateEntity, - }, - { - desc: "issue with invalid token", - token: invalid, - contentType: contentType, - thingID: thingID, - ttl: ttl, - request: fmt.Sprintf(validReqString, thingID, ttl), - status: http.StatusUnauthorized, - svcRes: certs.Cert{}, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "issue with empty token", - domainID: valid, - contentType: contentType, - request: fmt.Sprintf(validReqString, thingID, ttl), - status: http.StatusUnauthorized, - svcRes: certs.Cert{}, - svcErr: nil, - err: apiutil.ErrBearerToken, - }, - { - desc: "issue with empty domain id", - token: valid, - domainID: "", - contentType: contentType, - request: fmt.Sprintf(validReqString, thingID, ttl), - status: http.StatusBadRequest, - svcRes: certs.Cert{}, - svcErr: nil, - err: apiutil.ErrMissingDomainID, - }, - { - desc: "issue with empty thing id", - token: valid, - domainID: valid, - contentType: contentType, - request: fmt.Sprintf(validReqString, "", ttl), - status: http.StatusBadRequest, - svcRes: certs.Cert{}, - svcErr: nil, - err: apiutil.ErrMissingID, - }, - { - desc: "issue with empty ttl", - token: valid, - domainID: valid, - contentType: contentType, - request: fmt.Sprintf(validReqString, thingID, ""), - status: http.StatusBadRequest, - svcRes: certs.Cert{}, - svcErr: nil, - err: apiutil.ErrMissingCertData, - }, - { - desc: "issue with invalid ttl", - token: valid, - domainID: valid, - contentType: contentType, - request: fmt.Sprintf(validReqString, thingID, invalid), - status: http.StatusBadRequest, - svcRes: certs.Cert{}, - svcErr: nil, - err: apiutil.ErrInvalidCertData, - }, - { - desc: "issue with invalid content type", - token: valid, - domainID: valid, - contentType: "application/xml", - request: fmt.Sprintf(validReqString, thingID, ttl), - status: http.StatusUnsupportedMediaType, - svcRes: certs.Cert{}, - svcErr: nil, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "issue with invalid request body", - token: valid, - domainID: valid, - contentType: contentType, - request: fmt.Sprintf(invalidReqString, thingID, ttl), - status: http.StatusInternalServerError, - svcRes: certs.Cert{}, - svcErr: nil, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: cs.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/certs", cs.URL, tc.domainID), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.request), - } - 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("IssueCert", mock.Anything, tc.domainID, tc.token, tc.thingID, 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 - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - authCall.Unset() - }) - } -} - -func TestViewCert(t *testing.T) { - cs, svc, auth := newCertServer() - defer cs.Close() - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - serialID string - status int - authenticateRes mgauthn.Session - authenticateErr error - svcRes certs.Cert - svcErr error - err error - }{ - { - desc: "view cert successfully", - token: valid, - domainID: valid, - serialID: serial, - status: http.StatusOK, - svcRes: certs.Cert{SerialNumber: serial}, - svcErr: nil, - err: nil, - }, - { - desc: "view with invalid token", - token: invalid, - serialID: serial, - status: http.StatusUnauthorized, - svcRes: certs.Cert{}, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "view with empty token", - token: "", - domainID: valid, - serialID: serial, - status: http.StatusUnauthorized, - svcRes: certs.Cert{}, - svcErr: nil, - err: apiutil.ErrBearerToken, - }, - { - desc: "view non-existing cert", - token: valid, - domainID: valid, - serialID: invalid, - status: http.StatusNotFound, - svcRes: certs.Cert{}, - svcErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: cs.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/certs/%s", cs.URL, tc.domainID, tc.serialID), - 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("ViewCert", mock.Anything, tc.serialID).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 - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - authCall.Unset() - }) - } -} - -func TestRevokeCert(t *testing.T) { - cs, svc, auth := newCertServer() - defer cs.Close() - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - serialID string - status int - authenticateErr error - svcRes certs.Revoke - svcErr error - err error - }{ - { - desc: "revoke cert successfully", - token: valid, - domainID: valid, - serialID: serial, - status: http.StatusOK, - svcRes: certs.Revoke{RevocationTime: time.Now()}, - svcErr: nil, - err: nil, - }, - { - desc: "revoke with invalid token", - token: invalid, - serialID: serial, - status: http.StatusUnauthorized, - svcRes: certs.Revoke{}, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "revoke with empty domain id", - token: valid, - domainID: "", - serialID: serial, - status: http.StatusBadRequest, - svcErr: nil, - err: apiutil.ErrMissingDomainID, - }, - { - desc: "revoke with empty token", - token: "", - domainID: valid, - serialID: serial, - status: http.StatusUnauthorized, - svcErr: nil, - err: apiutil.ErrBearerToken, - }, - { - desc: "revoke non-existing cert", - token: valid, - domainID: valid, - serialID: invalid, - status: http.StatusNotFound, - svcRes: certs.Revoke{}, - svcErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: cs.Client(), - method: http.MethodDelete, - url: fmt.Sprintf("%s/%s/certs/%s", cs.URL, tc.domainID, tc.serialID), - 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("RevokeCert", mock.Anything, tc.domainID, tc.token, tc.serialID).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 - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - authCall.Unset() - }) - } -} - -func TestListSerials(t *testing.T) { - cs, svc, auth := newCertServer() - defer cs.Close() - revoked := "false" - - cases := []struct { - desc string - token string - domainID string - session mgauthn.Session - thingID string - revoked string - offset uint64 - limit uint64 - query string - status int - authenticateErr error - svcRes certs.CertPage - svcErr error - err error - }{ - { - desc: "list certs successfully with default limit", - domainID: valid, - token: valid, - thingID: thingID, - revoked: revoked, - offset: 0, - limit: 10, - query: "", - status: http.StatusOK, - svcRes: certs.CertPage{ - Total: 1, - Offset: 0, - Limit: 10, - Certificates: []certs.Cert{cert}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "list certs successfully with default revoke", - domainID: valid, - token: valid, - thingID: thingID, - revoked: revoked, - offset: 0, - limit: 10, - query: "", - status: http.StatusOK, - svcRes: certs.CertPage{ - Total: 1, - Offset: 0, - Limit: 10, - Certificates: []certs.Cert{cert}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "list certs successfully with all certs", - domainID: valid, - token: valid, - thingID: thingID, - revoked: "all", - offset: 0, - limit: 10, - query: "?revoked=all", - status: http.StatusOK, - svcRes: certs.CertPage{ - Total: 1, - Offset: 0, - Limit: 10, - Certificates: []certs.Cert{cert}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "list certs successfully with limit", - domainID: valid, - token: valid, - thingID: thingID, - revoked: revoked, - offset: 0, - limit: 5, - query: "?limit=5", - status: http.StatusOK, - svcRes: certs.CertPage{ - Total: 1, - Offset: 0, - Limit: 5, - Certificates: []certs.Cert{cert}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "list certs successfully with offset", - domainID: valid, - token: valid, - thingID: thingID, - revoked: revoked, - offset: 1, - limit: 10, - query: "?offset=1", - status: http.StatusOK, - svcRes: certs.CertPage{ - Total: 1, - Offset: 1, - Limit: 10, - Certificates: []certs.Cert{}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "list certs successfully with offset and limit", - domainID: valid, - token: valid, - thingID: thingID, - revoked: revoked, - offset: 1, - limit: 5, - query: "?offset=1&limit=5", - status: http.StatusOK, - svcRes: certs.CertPage{ - Total: 1, - Offset: 1, - Limit: 5, - Certificates: []certs.Cert{}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "list with invalid token", - domainID: valid, - token: invalid, - thingID: thingID, - revoked: revoked, - offset: 0, - limit: 10, - query: "", - status: http.StatusUnauthorized, - svcRes: certs.CertPage{}, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list with empty token", - domainID: valid, - token: "", - thingID: thingID, - revoked: revoked, - offset: 0, - limit: 10, - query: "", - status: http.StatusUnauthorized, - svcRes: certs.CertPage{}, - svcErr: nil, - err: apiutil.ErrBearerToken, - }, - { - desc: "list with limit exceeding max limit", - domainID: valid, - token: valid, - thingID: thingID, - revoked: revoked, - query: "?limit=1000", - status: http.StatusBadRequest, - svcRes: certs.CertPage{}, - svcErr: nil, - err: apiutil.ErrLimitSize, - }, - { - desc: "list with invalid offset", - domainID: valid, - token: valid, - thingID: thingID, - revoked: revoked, - query: "?offset=invalid", - status: http.StatusBadRequest, - svcRes: certs.CertPage{}, - svcErr: nil, - err: apiutil.ErrValidation, - }, - { - desc: "list with invalid limit", - domainID: valid, - token: valid, - thingID: thingID, - revoked: revoked, - query: "?limit=invalid", - status: http.StatusBadRequest, - svcRes: certs.CertPage{}, - svcErr: nil, - err: apiutil.ErrValidation, - }, - { - desc: "list with invalid thing id", - domainID: valid, - token: valid, - thingID: invalid, - revoked: revoked, - offset: 0, - limit: 10, - query: "", - status: http.StatusNotFound, - svcRes: certs.CertPage{}, - svcErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(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, - 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) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - authCall.Unset() - }) - } -} - -type respBody struct { - Err string `json:"error"` - Message string `json:"message"` -} diff --git a/certs/api/logging.go b/certs/api/logging.go deleted file mode 100644 index 7a8c3b7d3..000000000 --- a/certs/api/logging.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/certs" -) - -var _ certs.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc certs.Service -} - -// LoggingMiddleware adds logging facilities to the bootstrap service. -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. -// 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", thingID), - slog.String("ttl", ttl), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Issue certificate failed", args...) - return - } - lm.logger.Info("Issue certificate completed successfully", args...) - }(time.Now()) - - return lm.svc.IssueCert(ctx, domainID, token, thingID, 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", thingID), - slog.Group("page", - slog.Uint64("offset", cp.Offset), - slog.Uint64("limit", cp.Limit), - slog.Uint64("total", cp.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List certificates failed", args...) - return - } - lm.logger.Info("List certificates completed successfully", args...) - }(time.Now()) - - return lm.svc.ListCerts(ctx, thingID, pm) -} - -// ListSerials logs the list_serials request. It logs the thing 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", thingID), - slog.String("revoke", pm.Revoked), - slog.Group("page", - slog.Uint64("offset", cp.Offset), - slog.Uint64("limit", cp.Limit), - slog.Uint64("total", cp.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List certifcates serials failed", args...) - return - } - lm.logger.Info("List certificates serials completed successfully", args...) - }(time.Now()) - - return lm.svc.ListSerials(ctx, thingID, pm) -} - -// ViewCert logs the view_cert request. It logs the serial ID and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ViewCert(ctx context.Context, serialID string) (c certs.Cert, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("serial_id", serialID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View certificate failed", args...) - return - } - lm.logger.Info("View certificate completed successfully", args...) - }(time.Now()) - - 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. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) RevokeCert(ctx context.Context, domainID, token, thingID 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), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Revoke certificate failed", args...) - return - } - lm.logger.Info("Revoke certificate completed successfully", args...) - }(time.Now()) - - return lm.svc.RevokeCert(ctx, domainID, token, thingID) -} diff --git a/certs/api/metrics.go b/certs/api/metrics.go deleted file mode 100644 index 9f78fd012..000000000 --- a/certs/api/metrics.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "context" - "time" - - "github.com/absmach/magistrala/certs" - "github.com/go-kit/kit/metrics" -) - -var _ certs.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc certs.Service -} - -// MetricsMiddleware instruments core service by tracking request count and latency. -func MetricsMiddleware(svc certs.Service, counter metrics.Counter, latency metrics.Histogram) certs.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -// IssueCert instruments IssueCert method with metrics. -func (ms *metricsMiddleware) IssueCert(ctx context.Context, domainID, token, thingID, 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) -} - -// ListCerts instruments ListCerts method with metrics. -func (ms *metricsMiddleware) ListCerts(ctx context.Context, thingID 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) -} - -// ListSerials instruments ListSerials method with metrics. -func (ms *metricsMiddleware) ListSerials(ctx context.Context, thingID 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) -} - -// ViewCert instruments ViewCert method with metrics. -func (ms *metricsMiddleware) ViewCert(ctx context.Context, serialID string) (certs.Cert, error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_cert").Add(1) - ms.latency.With("method", "view_cert").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.ViewCert(ctx, serialID) -} - -// RevokeCert instruments RevokeCert method with metrics. -func (ms *metricsMiddleware) RevokeCert(ctx context.Context, domainID, token, thingID 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) -} diff --git a/certs/api/requests.go b/certs/api/requests.go deleted file mode 100644 index 54bea166b..000000000 --- a/certs/api/requests.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "time" - - "github.com/absmach/magistrala/certs" - "github.com/absmach/magistrala/pkg/apiutil" -) - -const maxLimitSize = 100 - -type addCertsReq struct { - token string - domainID string - ThingID string `json:"thing_id"` - TTL string `json:"ttl"` -} - -func (req addCertsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - - if req.ThingID == "" { - return apiutil.ErrMissingID - } - - if req.TTL == "" { - return apiutil.ErrMissingCertData - } - - if _, err := time.ParseDuration(req.TTL); err != nil { - return apiutil.ErrInvalidCertData - } - - return nil -} - -type listReq struct { - thingID string - pm certs.PageMetadata -} - -func (req *listReq) validate() error { - if req.pm.Limit > maxLimitSize { - return apiutil.ErrLimitSize - } - - return nil -} - -type viewReq struct { - serialID string -} - -func (req *viewReq) validate() error { - if req.serialID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type revokeReq struct { - token string - certID string - domainID string -} - -func (req *revokeReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - - if req.certID == "" { - return apiutil.ErrMissingID - } - - return nil -} diff --git a/certs/api/responses.go b/certs/api/responses.go deleted file mode 100644 index 4b5f15d48..000000000 --- a/certs/api/responses.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "net/http" - "time" -) - -type pageRes struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` -} - -type certsPageRes struct { - pageRes - Certs []certsRes `json:"certs"` -} - -type certsRes struct { - ThingID string `json:"thing_id"` - Certificate string `json:"certificate,omitempty"` - Key string `json:"key,omitempty"` - SerialNumber string `json:"serial_number"` - ExpiryTime time.Time `json:"expiry_time"` - Revoked bool `json:"revoked"` - issued bool -} - -type revokeCertsRes struct { - RevocationTime time.Time `json:"revocation_time"` -} - -func (res certsPageRes) Code() int { - return http.StatusOK -} - -func (res certsPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res certsPageRes) Empty() bool { - return false -} - -func (res certsRes) Code() int { - if res.issued { - return http.StatusCreated - } - return http.StatusOK -} - -func (res certsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res certsRes) Empty() bool { - return false -} - -func (res revokeCertsRes) Code() int { - return http.StatusOK -} - -func (res revokeCertsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res revokeCertsRes) Empty() bool { - return false -} diff --git a/certs/api/transport.go b/certs/api/transport.go deleted file mode 100644 index 4d71d1aac..000000000 --- a/certs/api/transport.go +++ /dev/null @@ -1,136 +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" - "github.com/absmach/magistrala/certs" - "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/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" -) - -const ( - contentType = "application/json" - offsetKey = "offset" - limitKey = "limit" - revokeKey = "revoked" - defRevoke = "false" - defOffset = 0 - defLimit = 10 -) - -// MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc certs.Service, authn mgauthn.Authentication, logger *slog.Logger, instanceID string) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - r := chi.NewRouter() - - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, true)) - r.Route("/{domainID}", func(r chi.Router) { - r.Route("/certs", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - issueCert(svc), - decodeCerts, - api.EncodeResponse, - opts..., - ), "issue").ServeHTTP) - r.Get("/{certID}", otelhttp.NewHandler(kithttp.NewServer( - viewCert(svc), - decodeViewCert, - api.EncodeResponse, - opts..., - ), "view").ServeHTTP) - r.Delete("/{certID}", otelhttp.NewHandler(kithttp.NewServer( - revokeCert(svc), - decodeRevokeCerts, - api.EncodeResponse, - opts..., - ), "revoke").ServeHTTP) - }) - r.Get("/serials/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - listSerials(svc), - decodeListCerts, - api.EncodeResponse, - opts..., - ), "list_serials").ServeHTTP) - }) - }) - r.Handle("/metrics", promhttp.Handler()) - r.Get("/health", magistrala.Health("certs", instanceID)) - - return r -} - -func decodeListCerts(_ context.Context, r *http.Request) (interface{}, error) { - l, err := apiutil.ReadNumQuery[uint64](r, limitKey, defLimit) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - o, err := apiutil.ReadNumQuery[uint64](r, offsetKey, defOffset) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - rv, err := apiutil.ReadStringQuery(r, revokeKey, defRevoke) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - req := listReq{ - thingID: chi.URLParam(r, "thingID"), - pm: certs.PageMetadata{ - Offset: o, - Limit: l, - Revoked: rv, - }, - } - return req, nil -} - -func decodeViewCert(_ context.Context, r *http.Request) (interface{}, error) { - req := viewReq{ - serialID: chi.URLParam(r, "certID"), - } - - return req, nil -} - -func decodeCerts(_ context.Context, r *http.Request) (interface{}, error) { - if r.Header.Get("Content-Type") != contentType { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := addCertsReq{ - 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, err) - } - - return req, nil -} - -func decodeRevokeCerts(_ context.Context, r *http.Request) (interface{}, error) { - req := revokeReq{ - token: apiutil.ExtractBearerToken(r), - certID: chi.URLParam(r, "certID"), - domainID: chi.URLParam(r, "domainID"), - } - - return req, nil -} diff --git a/certs/certs.go b/certs/certs.go deleted file mode 100644 index f1d4f1bb6..000000000 --- a/certs/certs.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package certs - -import ( - "crypto/tls" - "crypto/x509" - "encoding/pem" - "os" - "time" - - "github.com/absmach/magistrala/pkg/errors" -) - -type Cert struct { - SerialNumber string `json:"serial_number"` - Certificate string `json:"certificate,omitempty"` - Key string `json:"key,omitempty"` - Revoked bool `json:"revoked"` - ExpiryTime time.Time `json:"expiry_time"` - ThingID string `json:"entity_id"` -} - -type CertPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Certificates []Cert `json:"certificates,omitempty"` -} - -type PageMetadata struct { - Total uint64 `json:"total,omitempty"` - Offset uint64 `json:"offset,omitempty"` - Limit uint64 `json:"limit,omitempty"` - ThingID string `json:"thing_id,omitempty"` - Token string `json:"token,omitempty"` - CommonName string `json:"common_name,omitempty"` - Revoked string `json:"revoked,omitempty"` -} - -var ErrMissingCerts = errors.New("CA path or CA key path not set") - -func LoadCertificates(caPath, caKeyPath string) (tls.Certificate, *x509.Certificate, error) { - if caPath == "" || caKeyPath == "" { - return tls.Certificate{}, &x509.Certificate{}, ErrMissingCerts - } - - _, err := os.Stat(caPath) - if os.IsNotExist(err) || os.IsPermission(err) { - return tls.Certificate{}, &x509.Certificate{}, err - } - - _, err = os.Stat(caKeyPath) - if os.IsNotExist(err) || os.IsPermission(err) { - return tls.Certificate{}, &x509.Certificate{}, err - } - - tlsCert, err := tls.LoadX509KeyPair(caPath, caKeyPath) - if err != nil { - return tlsCert, &x509.Certificate{}, err - } - - b, err := os.ReadFile(caPath) - if err != nil { - return tlsCert, &x509.Certificate{}, err - } - - caCert, err := ReadCert(b) - if err != nil { - return tlsCert, &x509.Certificate{}, err - } - - return tlsCert, caCert, nil -} - -func ReadCert(b []byte) (*x509.Certificate, error) { - block, _ := pem.Decode(b) - if block == nil { - return nil, errors.New("failed to decode PEM data") - } - - return x509.ParseCertificate(block.Bytes) -} diff --git a/certs/certs_test.go b/certs/certs_test.go deleted file mode 100644 index 3ee7dc746..000000000 --- a/certs/certs_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package certs_test - -import ( - "fmt" - "testing" - - "github.com/absmach/magistrala/certs" - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" -) - -func TestLoadCertificates(t *testing.T) { - cases := []struct { - desc string - caPath string - caKeyPath string - err error - }{ - { - desc: "load valid tls certificate and valid key", - caPath: "../docker/ssl/certs/ca.crt", - caKeyPath: "../docker/ssl/certs/ca.key", - err: nil, - }, - { - desc: "load valid tls certificate and missing key", - caPath: "../docker/ssl/certs/ca.crt", - caKeyPath: "", - err: certs.ErrMissingCerts, - }, - { - desc: "load missing tls certificate and valid key", - caPath: "", - caKeyPath: "../docker/ssl/certs/ca.key", - err: certs.ErrMissingCerts, - }, - { - desc: "load empty tls certificate and empty key", - caPath: "", - caKeyPath: "", - err: certs.ErrMissingCerts, - }, - { - desc: "load valid tls certificate and invalid key", - caPath: "../docker/ssl/certs/ca.crt", - caKeyPath: "certs.go", - err: errors.New("tls: failed to find any PEM data in key input"), - }, - { - desc: "load invalid tls certificate and valid key", - caPath: "certs.go", - caKeyPath: "../docker/ssl/certs/ca.key", - err: errors.New("tls: failed to find any PEM data in certificate input"), - }, - { - desc: "load invalid tls certificate and invalid key", - caPath: "certs.go", - caKeyPath: "certs.go", - err: errors.New("tls: failed to find any PEM data in certificate input"), - }, - - { - desc: "load valid tls certificate and non-existing key", - caPath: "../docker/ssl/certs/ca.crt", - caKeyPath: "ca.key", - err: errors.New("stat ca.key: no such file or directory"), - }, - { - desc: "load non-existing tls certificate and valid key", - caPath: "ca.crt", - caKeyPath: "../docker/ssl/certs/ca.key", - err: errors.New("stat ca.crt: no such file or directory"), - }, - { - desc: "load non-existing tls certificate and non-existing key", - caPath: "ca.crt", - caKeyPath: "ca.key", - err: errors.New("stat ca.crt: no such file or directory"), - }, - } - - for _, tc := range cases { - tlsCert, caCert, err := certs.LoadCertificates(tc.caPath, tc.caKeyPath) - 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.NotNil(t, tlsCert) - assert.NotNil(t, caCert) - } - } -} diff --git a/certs/doc.go b/certs/doc.go deleted file mode 100644 index 24a198743..000000000 --- a/certs/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package certs contains the domain concept definitions needed to support -// Magistrala certs service functionality. -package certs diff --git a/certs/mocks/doc.go b/certs/mocks/doc.go deleted file mode 100644 index 16ed198af..000000000 --- a/certs/mocks/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mocks contains mocks for testing purposes. -package mocks diff --git a/certs/mocks/pki.go b/certs/mocks/pki.go deleted file mode 100644 index 3daf93184..000000000 --- a/certs/mocks/pki.go +++ /dev/null @@ -1,257 +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 ( - amcerts "github.com/absmach/magistrala/certs/pki/amcerts" - mock "github.com/stretchr/testify/mock" - - sdk "github.com/absmach/certs/sdk" -) - -// Agent is an autogenerated mock type for the Agent type -type Agent struct { - mock.Mock -} - -type Agent_Expecter struct { - mock *mock.Mock -} - -func (_m *Agent) EXPECT() *Agent_Expecter { - return &Agent_Expecter{mock: &_m.Mock} -} - -// Issue provides a mock function with given fields: entityId, ttl, ipAddrs -func (_m *Agent) Issue(entityId string, ttl string, ipAddrs []string) (amcerts.Cert, error) { - ret := _m.Called(entityId, ttl, ipAddrs) - - if len(ret) == 0 { - panic("no return value specified for Issue") - } - - var r0 amcerts.Cert - var r1 error - if rf, ok := ret.Get(0).(func(string, string, []string) (amcerts.Cert, error)); ok { - return rf(entityId, ttl, ipAddrs) - } - if rf, ok := ret.Get(0).(func(string, string, []string) amcerts.Cert); ok { - r0 = rf(entityId, ttl, ipAddrs) - } else { - r0 = ret.Get(0).(amcerts.Cert) - } - - if rf, ok := ret.Get(1).(func(string, string, []string) error); ok { - r1 = rf(entityId, ttl, ipAddrs) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Agent_Issue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Issue' -type Agent_Issue_Call struct { - *mock.Call -} - -// Issue is a helper method to define mock.On call -// - entityId string -// - ttl string -// - ipAddrs []string -func (_e *Agent_Expecter) Issue(entityId interface{}, ttl interface{}, ipAddrs interface{}) *Agent_Issue_Call { - return &Agent_Issue_Call{Call: _e.mock.On("Issue", entityId, ttl, ipAddrs)} -} - -func (_c *Agent_Issue_Call) Run(run func(entityId string, ttl string, ipAddrs []string)) *Agent_Issue_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].([]string)) - }) - return _c -} - -func (_c *Agent_Issue_Call) Return(_a0 amcerts.Cert, _a1 error) *Agent_Issue_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Agent_Issue_Call) RunAndReturn(run func(string, string, []string) (amcerts.Cert, error)) *Agent_Issue_Call { - _c.Call.Return(run) - return _c -} - -// ListCerts provides a mock function with given fields: pm -func (_m *Agent) ListCerts(pm sdk.PageMetadata) (amcerts.CertPage, error) { - ret := _m.Called(pm) - - if len(ret) == 0 { - panic("no return value specified for ListCerts") - } - - var r0 amcerts.CertPage - var r1 error - if rf, ok := ret.Get(0).(func(sdk.PageMetadata) (amcerts.CertPage, error)); ok { - return rf(pm) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata) amcerts.CertPage); ok { - r0 = rf(pm) - } else { - r0 = ret.Get(0).(amcerts.CertPage) - } - - if rf, ok := ret.Get(1).(func(sdk.PageMetadata) error); ok { - r1 = rf(pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Agent_ListCerts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListCerts' -type Agent_ListCerts_Call struct { - *mock.Call -} - -// ListCerts is a helper method to define mock.On call -// - pm sdk.PageMetadata -func (_e *Agent_Expecter) ListCerts(pm interface{}) *Agent_ListCerts_Call { - return &Agent_ListCerts_Call{Call: _e.mock.On("ListCerts", pm)} -} - -func (_c *Agent_ListCerts_Call) Run(run func(pm sdk.PageMetadata)) *Agent_ListCerts_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(sdk.PageMetadata)) - }) - return _c -} - -func (_c *Agent_ListCerts_Call) Return(_a0 amcerts.CertPage, _a1 error) *Agent_ListCerts_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Agent_ListCerts_Call) RunAndReturn(run func(sdk.PageMetadata) (amcerts.CertPage, error)) *Agent_ListCerts_Call { - _c.Call.Return(run) - return _c -} - -// Revoke provides a mock function with given fields: serialNumber -func (_m *Agent) Revoke(serialNumber string) error { - ret := _m.Called(serialNumber) - - if len(ret) == 0 { - panic("no return value specified for Revoke") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(serialNumber) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Agent_Revoke_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Revoke' -type Agent_Revoke_Call struct { - *mock.Call -} - -// Revoke is a helper method to define mock.On call -// - serialNumber string -func (_e *Agent_Expecter) Revoke(serialNumber interface{}) *Agent_Revoke_Call { - return &Agent_Revoke_Call{Call: _e.mock.On("Revoke", serialNumber)} -} - -func (_c *Agent_Revoke_Call) Run(run func(serialNumber string)) *Agent_Revoke_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *Agent_Revoke_Call) Return(_a0 error) *Agent_Revoke_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Agent_Revoke_Call) RunAndReturn(run func(string) error) *Agent_Revoke_Call { - _c.Call.Return(run) - return _c -} - -// View provides a mock function with given fields: serialNumber -func (_m *Agent) View(serialNumber string) (amcerts.Cert, error) { - ret := _m.Called(serialNumber) - - if len(ret) == 0 { - panic("no return value specified for View") - } - - var r0 amcerts.Cert - var r1 error - if rf, ok := ret.Get(0).(func(string) (amcerts.Cert, error)); ok { - return rf(serialNumber) - } - if rf, ok := ret.Get(0).(func(string) amcerts.Cert); ok { - r0 = rf(serialNumber) - } else { - r0 = ret.Get(0).(amcerts.Cert) - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(serialNumber) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Agent_View_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'View' -type Agent_View_Call struct { - *mock.Call -} - -// View is a helper method to define mock.On call -// - serialNumber string -func (_e *Agent_Expecter) View(serialNumber interface{}) *Agent_View_Call { - return &Agent_View_Call{Call: _e.mock.On("View", serialNumber)} -} - -func (_c *Agent_View_Call) Run(run func(serialNumber string)) *Agent_View_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *Agent_View_Call) Return(_a0 amcerts.Cert, _a1 error) *Agent_View_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Agent_View_Call) RunAndReturn(run func(string) (amcerts.Cert, error)) *Agent_View_Call { - _c.Call.Return(run) - return _c -} - -// NewAgent creates a new instance of Agent. 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 NewAgent(t interface { - mock.TestingT - Cleanup(func()) -}) *Agent { - mock := &Agent{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/certs/mocks/service.go b/certs/mocks/service.go deleted file mode 100644 index 864f3e28d..000000000 --- a/certs/mocks/service.go +++ /dev/null @@ -1,172 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - certs "github.com/absmach/magistrala/certs" - - mock "github.com/stretchr/testify/mock" -) - -// Service is an autogenerated mock type for the Service type -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) - - if len(ret) == 0 { - panic("no return value specified for IssueCert") - } - - 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) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) certs.Cert); ok { - r0 = rf(ctx, domainID, token, thingID, 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) - } else { - r1 = ret.Error(1) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for ListCerts") - } - - 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) - } - if rf, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) certs.CertPage); ok { - r0 = rf(ctx, thingID, 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) - } else { - r1 = ret.Error(1) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for ListSerials") - } - - 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) - } - if rf, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) certs.CertPage); ok { - r0 = rf(ctx, thingID, 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) - } else { - r1 = ret.Error(1) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for RevokeCert") - } - - 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) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string, string) certs.Revoke); ok { - r0 = rf(ctx, domainID, token, thingID) - } 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) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ViewCert provides a mock function with given fields: ctx, serialID -func (_m *Service) ViewCert(ctx context.Context, serialID string) (certs.Cert, error) { - ret := _m.Called(ctx, serialID) - - if len(ret) == 0 { - panic("no return value specified for ViewCert") - } - - var r0 certs.Cert - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (certs.Cert, error)); ok { - return rf(ctx, serialID) - } - if rf, ok := ret.Get(0).(func(context.Context, string) certs.Cert); ok { - r0 = rf(ctx, serialID) - } else { - r0 = ret.Get(0).(certs.Cert) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, serialID) - } 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/certs/pki/amcerts/am_certs.go b/certs/pki/amcerts/am_certs.go deleted file mode 100644 index b5247aecb..000000000 --- a/certs/pki/amcerts/am_certs.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package amcerts - -import ( - "time" - - "github.com/absmach/certs/sdk" -) - -type Cert struct { - SerialNumber string `json:"serial_number"` - Certificate string `json:"certificate,omitempty"` - Key string `json:"key,omitempty"` - Revoked bool `json:"revoked"` - ExpiryTime time.Time `json:"expiry_time"` - ThingID string `json:"entity_id"` - DownloadUrl string `json:"-"` -} - -type CertPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Certificates []Cert `json:"certificates,omitempty"` -} - -type Agent interface { - Issue(entityId, ttl string, ipAddrs []string) (Cert, error) - - View(serialNumber string) (Cert, error) - - Revoke(serialNumber string) error - - ListCerts(pm sdk.PageMetadata) (CertPage, error) -} - -type sdkAgent struct { - sdk sdk.SDK -} - -func NewAgent(host, certsURL string, TLSVerification bool) (Agent, error) { - msgContentType := string(sdk.CTJSONSenML) - certConfig := sdk.Config{ - CertsURL: certsURL, - HostURL: host, - MsgContentType: sdk.ContentType(msgContentType), - TLSVerification: TLSVerification, - } - - return sdkAgent{ - sdk: sdk.NewSDK(certConfig), - }, nil -} - -func (c sdkAgent) Issue(entityId, ttl string, ipAddrs []string) (Cert, error) { - cert, err := c.sdk.IssueCert(entityId, ttl, ipAddrs, sdk.Options{CommonName: "Magistrala"}) - if err != nil { - return Cert{}, err - } - - return Cert{ - SerialNumber: cert.SerialNumber, - Certificate: cert.Certificate, - Revoked: cert.Revoked, - ExpiryTime: cert.ExpiryTime, - ThingID: cert.EntityID, - }, nil -} - -func (c sdkAgent) View(serial string) (Cert, error) { - cert, err := c.sdk.ViewCert(serial) - if err != nil { - return Cert{}, err - } - return Cert{ - SerialNumber: cert.SerialNumber, - Certificate: cert.Certificate, - Key: cert.Key, - Revoked: cert.Revoked, - ExpiryTime: cert.ExpiryTime, - ThingID: cert.EntityID, - }, nil -} - -func (c sdkAgent) Revoke(serial string) error { - if err := c.sdk.RevokeCert(serial); err != nil { - return err - } - - return nil -} - -func (c sdkAgent) ListCerts(pm sdk.PageMetadata) (CertPage, error) { - certPage, err := c.sdk.ListCerts(pm) - if err != nil { - return CertPage{}, err - } - - var crts []Cert - for _, c := range certPage.Certificates { - crts = append(crts, Cert{ - SerialNumber: c.SerialNumber, - Certificate: c.Certificate, - Key: c.Key, - Revoked: c.Revoked, - ExpiryTime: c.ExpiryTime, - ThingID: c.EntityID, - }) - } - - return CertPage{ - Total: certPage.Total, - Limit: certPage.Limit, - Offset: certPage.Offset, - Certificates: crts, - }, nil -} diff --git a/certs/pki/amcerts/doc.go b/certs/pki/amcerts/doc.go deleted file mode 100644 index cedf1854c..000000000 --- a/certs/pki/amcerts/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package amcerts diff --git a/certs/pki/vault/doc.go b/certs/pki/vault/doc.go deleted file mode 100644 index cbd2d9795..000000000 --- a/certs/pki/vault/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package pki contains the domain concept definitions needed to -// support Magistrala Certs service functionality. -// It provides the abstraction of the PKI (Public Key Infrastructure) -// Valut service, which is used to issue and revoke certificates. -package pki diff --git a/certs/pki/vault/vault.go b/certs/pki/vault/vault.go deleted file mode 100644 index 2bde972a5..000000000 --- a/certs/pki/vault/vault.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package pki wraps vault client -package pki - -import ( - "context" - "encoding/json" - "fmt" - "log/slog" - "time" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/hashicorp/vault/api" - "github.com/hashicorp/vault/api/auth/approle" - "github.com/mitchellh/mapstructure" -) - -const ( - issue = "issue" - cert = "cert" - revoke = "revoke" -) - -var ( - errFailedCertDecoding = errors.New("failed to decode response from vault service") - errFailedToLogin = errors.New("failed to login to Vault") - errFailedAppRole = errors.New("failed to create vault new app role") - errNoAuthInfo = errors.New("no auth information from Vault") - errNonRenewal = errors.New("token is not configured to be renewable") - errRenewWatcher = errors.New("unable to initialize new lifetime watcher for renewing auth token") - errFailedRenew = errors.New("failed to renew token") - errCouldNotRenew = errors.New("token can no longer be renewed") -) - -type Cert struct { - ClientCert string `json:"client_cert" mapstructure:"certificate"` - IssuingCA string `json:"issuing_ca" mapstructure:"issuing_ca"` - CAChain []string `json:"ca_chain" mapstructure:"ca_chain"` - ClientKey string `json:"client_key" mapstructure:"private_key"` - PrivateKeyType string `json:"private_key_type" mapstructure:"private_key_type"` - Serial string `json:"serial" mapstructure:"serial_number"` - Expire int64 `json:"expire" mapstructure:"expiration"` -} - -// Agent represents the Vault PKI interface. -type Agent interface { - // IssueCert issues certificate on PKI - IssueCert(cn, ttl string) (Cert, error) - - // Read retrieves certificate from PKI - Read(serial string) (Cert, error) - - // Revoke revokes certificate from PKI - Revoke(serial string) (time.Time, error) - - // Login to PKI and renews token - LoginAndRenew(ctx context.Context) error -} - -type pkiAgent struct { - appRole string - appSecret string - namespace string - path string - role string - host string - issueURL string - readURL string - revokeURL string - client *api.Client - secret *api.Secret - logger *slog.Logger -} - -type certReq struct { - CommonName string `json:"common_name"` - TTL string `json:"ttl"` -} - -type certRevokeReq struct { - SerialNumber string `json:"serial_number"` -} - -// NewVaultClient instantiates a Vault client. -func NewVaultClient(appRole, appSecret, host, namespace, path, role string, logger *slog.Logger) (Agent, error) { - conf := api.DefaultConfig() - conf.Address = host - - client, err := api.NewClient(conf) - if err != nil { - return nil, err - } - if namespace != "" { - client.SetNamespace(namespace) - } - - p := pkiAgent{ - appRole: appRole, - appSecret: appSecret, - host: host, - namespace: namespace, - role: role, - path: path, - client: client, - logger: logger, - issueURL: "/" + path + "/" + issue + "/" + role, - readURL: "/" + path + "/" + cert + "/", - revokeURL: "/" + path + "/" + revoke, - } - return &p, nil -} - -func (p *pkiAgent) IssueCert(cn, ttl string) (Cert, error) { - cReq := certReq{ - CommonName: cn, - TTL: ttl, - } - - var certIssueReq map[string]interface{} - data, err := json.Marshal(cReq) - if err != nil { - return Cert{}, err - } - if err := json.Unmarshal(data, &certIssueReq); err != nil { - return Cert{}, nil - } - - s, err := p.client.Logical().Write(p.issueURL, certIssueReq) - if err != nil { - return Cert{}, err - } - - cert := Cert{} - if err = mapstructure.Decode(s.Data, &cert); err != nil { - return Cert{}, errors.Wrap(errFailedCertDecoding, err) - } - - return cert, nil -} - -func (p *pkiAgent) Read(serial string) (Cert, error) { - s, err := p.client.Logical().Read(p.readURL + serial) - if err != nil { - return Cert{}, err - } - cert := Cert{} - if err = mapstructure.Decode(s.Data, &cert); err != nil { - return Cert{}, errors.Wrap(errFailedCertDecoding, err) - } - return cert, nil -} - -func (p *pkiAgent) Revoke(serial string) (time.Time, error) { - cReq := certRevokeReq{ - SerialNumber: serial, - } - - var certRevokeReq map[string]interface{} - data, err := json.Marshal(cReq) - if err != nil { - return time.Time{}, err - } - if err := json.Unmarshal(data, &certRevokeReq); err != nil { - return time.Time{}, nil - } - - s, err := p.client.Logical().Write(p.revokeURL, certRevokeReq) - if err != nil { - return time.Time{}, err - } - - // Vault will return a response without errors but with a warning if the certificate is expired. - // The response will not have "revocation_time" in such cases. - if revokeTime, ok := s.Data["revocation_time"]; ok { - switch v := revokeTime.(type) { - case json.Number: - rev, err := v.Float64() - if err != nil { - return time.Time{}, err - } - return time.Unix(0, int64(rev)*int64(time.Second)), nil - - default: - return time.Time{}, fmt.Errorf("unsupported type for revocation_time: %T", v) - } - } - - return time.Time{}, nil -} - -func (p *pkiAgent) LoginAndRenew(ctx context.Context) error { - for { - select { - case <-ctx.Done(): - p.logger.Info("pki login and renew function stopping") - return nil - default: - err := p.login(ctx) - if err != nil { - p.logger.Info("unable to authenticate to Vault", slog.Any("error", err)) - time.Sleep(5 * time.Second) - break - } - tokenErr := p.manageTokenLifecycle() - if tokenErr != nil { - p.logger.Info("unable to start managing token lifecycle", slog.Any("error", tokenErr)) - time.Sleep(5 * time.Second) - } - } - } -} - -func (p *pkiAgent) login(ctx context.Context) error { - secretID := &approle.SecretID{FromString: p.appSecret} - - authMethod, err := approle.NewAppRoleAuth( - p.appRole, - secretID, - ) - if err != nil { - return errors.Wrap(errFailedAppRole, err) - } - if p.namespace != "" { - p.client.SetNamespace(p.namespace) - } - secret, err := p.client.Auth().Login(ctx, authMethod) - if err != nil { - return errors.Wrap(errFailedToLogin, err) - } - if secret == nil { - return errNoAuthInfo - } - p.secret = secret - return nil -} - -func (p *pkiAgent) manageTokenLifecycle() error { - renew := p.secret.Auth.Renewable - if !renew { - return errNonRenewal - } - - watcher, err := p.client.NewLifetimeWatcher(&api.LifetimeWatcherInput{ - Secret: p.secret, - Increment: 3600, // Requesting token for 3600s = 1h, If this is more than token_max_ttl, then response token will have token_max_ttl - }) - if err != nil { - return errors.Wrap(errRenewWatcher, err) - } - - go watcher.Start() - defer watcher.Stop() - - for { - select { - case err := <-watcher.DoneCh(): - if err != nil { - return errors.Wrap(errFailedRenew, err) - } - // This occurs once the token has reached max TTL or if token is disabled for renewal. - return errCouldNotRenew - - case renewal := <-watcher.RenewCh(): - p.logger.Info("Successfully renewed token", slog.Any("renewed_at", renewal.RenewedAt)) - } - } -} diff --git a/certs/service.go b/certs/service.go deleted file mode 100644 index d5e398053..000000000 --- a/certs/service.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package certs - -import ( - "context" - "time" - - "github.com/absmach/certs/sdk" - pki "github.com/absmach/magistrala/certs/pki/amcerts" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" -) - -var ( - // ErrFailedCertCreation failed to create certificate. - ErrFailedCertCreation = errors.New("failed to create client certificate") - - // ErrFailedCertRevocation failed to revoke certificate. - ErrFailedCertRevocation = errors.New("failed to revoke certificate") - - ErrFailedToRemoveCertFromDB = errors.New("failed to remove cert serial from db") - - ErrFailedReadFromPKI = errors.New("failed to read certificate from PKI") -) - -var _ Service = (*certsService)(nil) - -// Service specifies an API that must be fulfilled by the domain service -// implementation, and all of its decorators (e.g. logging & metrics). -// -//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) - - // ListCerts lists certificates issued for a given thing ID - ListCerts(ctx context.Context, thingID 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) - - // 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) -} - -type certsService struct { - sdk mgsdk.SDK - pki pki.Agent -} - -// New returns new Certs service. -func New(sdk mgsdk.SDK, pkiAgent pki.Agent) Service { - return &certsService{ - sdk: sdk, - pki: pkiAgent, - } -} - -// Revoke defines the conditions to revoke a certificate. -type Revoke struct { - RevocationTime time.Time `mapstructure:"revocation_time"` -} - -func (cs *certsService) IssueCert(ctx context.Context, domainID, token, thingID, ttl string) (Cert, error) { - var err error - - thing, err := cs.sdk.Thing(thingID, domainID, token) - if err != nil { - return Cert{}, errors.Wrap(ErrFailedCertCreation, err) - } - - cert, err := cs.pki.Issue(thing.ID, ttl, []string{}) - if err != nil { - return Cert{}, errors.Wrap(ErrFailedCertCreation, err) - } - - return Cert{ - SerialNumber: cert.SerialNumber, - Certificate: cert.Certificate, - Key: cert.Key, - Revoked: cert.Revoked, - ExpiryTime: cert.ExpiryTime, - ThingID: cert.ThingID, - }, err -} - -func (cs *certsService) RevokeCert(ctx context.Context, domainID, token, thingID string) (Revoke, error) { - var revoke Revoke - var err error - - thing, err := cs.sdk.Thing(thingID, 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}) - if err != nil { - return revoke, errors.Wrap(ErrFailedCertRevocation, err) - } - - for _, c := range cp.Certificates { - err := cs.pki.Revoke(c.SerialNumber) - if err != nil { - return revoke, errors.Wrap(ErrFailedCertRevocation, err) - } - revoke.RevocationTime = time.Now() - } - - 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}) - if err != nil { - return CertPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - var crts []Cert - - for _, c := range cp.Certificates { - crts = append(crts, Cert{ - SerialNumber: c.SerialNumber, - Certificate: c.Certificate, - Key: c.Key, - Revoked: c.Revoked, - ExpiryTime: c.ExpiryTime, - ThingID: c.ThingID, - }) - } - - return CertPage{ - Total: cp.Total, - Limit: cp.Limit, - Offset: cp.Offset, - Certificates: crts, - }, 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}) - if err != nil { - return CertPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - var certs []Cert - for _, c := range cp.Certificates { - if (pm.Revoked == "true" && c.Revoked) || (pm.Revoked == "false" && !c.Revoked) || (pm.Revoked == "all") { - certs = append(certs, Cert{ - SerialNumber: c.SerialNumber, - ThingID: c.ThingID, - ExpiryTime: c.ExpiryTime, - Revoked: c.Revoked, - }) - } - } - - return CertPage{ - Offset: cp.Offset, - Limit: cp.Limit, - Total: uint64(len(certs)), - Certificates: certs, - }, nil -} - -func (cs *certsService) ViewCert(ctx context.Context, serialID string) (Cert, error) { - cert, err := cs.pki.View(serialID) - if err != nil { - return Cert{}, errors.Wrap(ErrFailedReadFromPKI, err) - } - - return Cert{ - SerialNumber: cert.SerialNumber, - Certificate: cert.Certificate, - Key: cert.Key, - Revoked: cert.Revoked, - ExpiryTime: cert.ExpiryTime, - ThingID: cert.ThingID, - }, nil -} diff --git a/certs/service_test.go b/certs/service_test.go deleted file mode 100644 index 540885877..000000000 --- a/certs/service_test.go +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package certs_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/certs" - "github.com/absmach/magistrala/certs/mocks" - mgcrt "github.com/absmach/magistrala/certs/pki/amcerts" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -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" -) - -func newService(_ *testing.T) (certs.Service, *mocks.Agent, *sdkmocks.SDK) { - agent := new(mocks.Agent) - sdk := new(sdkmocks.SDK) - - return certs.New(sdk, agent), agent, sdk -} - -var cert = mgcrt.Cert{ - ThingID: thingID, - SerialNumber: "Serial", - ExpiryTime: time.Now().Add(time.Duration(1000)), - Revoked: false, -} - -func TestIssueCert(t *testing.T) { - svc, agent, sdk := newService(t) - cases := []struct { - domainID string - token string - desc string - thingID string - ttl string - ipAddr []string - key string - cert mgcrt.Cert - thingErr errors.SDKError - issueCertErr error - err error - }{ - { - desc: "issue new cert", - domainID: domain, - token: token, - thingID: thingID, - ttl: ttl, - ipAddr: []string{}, - cert: cert, - }, - { - desc: "issue new for failed pki", - domainID: domain, - token: token, - thingID: thingID, - ttl: ttl, - ipAddr: []string{}, - thingErr: 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 invalid token", - domainID: domain, - token: invalid, - thingID: thingID, - ttl: ttl, - ipAddr: []string{}, - thingErr: 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) - 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() - agentCall.Unset() - }) - } -} - -func TestRevokeCert(t *testing.T) { - svc, agent, sdk := newService(t) - cases := []struct { - domainID string - token string - desc string - thingID string - page mgcrt.CertPage - authErr error - thingErr errors.SDKError - revokeErr error - listErr error - err error - }{ - { - desc: "revoke cert", - domainID: domain, - token: token, - thingID: thingID, - 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, - 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 with failed to list certs", - domainID: domain, - token: token, - thingID: thingID, - page: mgcrt.CertPage{}, - listErr: certs.ErrFailedCertRevocation, - err: certs.ErrFailedCertRevocation, - }, - } - - 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("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) - 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() - agentCall1.Unset() - }) - } -} - -func TestListCerts(t *testing.T) { - svc, agent, _ := newService(t) - var mycerts []mgcrt.Cert - for i := 0; i < certNum; i++ { - c := mgcrt.Cert{ - ThingID: thingID, - SerialNumber: fmt.Sprintf("%d", i), - ExpiryTime: time.Now().Add(time.Hour), - } - mycerts = append(mycerts, c) - } - - cases := []struct { - desc string - thingID 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 with failed pki", - thingID: thingID, - 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 last cert successfully", - thingID: thingID, - 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}) - 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)) - agentCall.Unset() - }) - } -} - -func TestListSerials(t *testing.T) { - svc, agent, _ := newService(t) - revoke := "false" - - var issuedCerts []mgcrt.Cert - for i := 0; i < certNum; i++ { - crt := mgcrt.Cert{ - ThingID: cert.ThingID, - SerialNumber: cert.SerialNumber, - ExpiryTime: cert.ExpiryTime, - Revoked: false, - } - issuedCerts = append(issuedCerts, crt) - } - - cases := []struct { - desc string - thingID 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 with failed pki", - thingID: thingID, - 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 last cert successfully", - thingID: thingID, - 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}) - 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() - }) - } -} - -func TestViewCert(t *testing.T) { - svc, agent, _ := newService(t) - - cases := []struct { - desc string - serialID string - cert mgcrt.Cert - repoErr error - agentErr error - err error - }{ - { - desc: "view cert with valid serial", - serialID: cert.SerialNumber, - cert: cert, - }, - { - desc: "list cert with invalid serial", - serialID: invalid, - cert: mgcrt.Cert{}, - agentErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - agentCall := agent.On("View", tc.serialID).Return(tc.cert, tc.agentErr) - res, err := svc.ViewCert(context.Background(), tc.serialID) - assert.Equal(t, tc.cert.SerialNumber, res.SerialNumber, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.cert.SerialNumber, res.SerialNumber)) - 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/doc.go b/certs/tracing/doc.go deleted file mode 100644 index 6a419f3b5..000000000 --- a/certs/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala Users Groups service. -// -// This package provides tracing middleware for Magistrala Users Groups service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala Users Groups 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/certs/tracing/tracing.go b/certs/tracing/tracing.go deleted file mode 100644 index 48a0173df..000000000 --- a/certs/tracing/tracing.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/certs" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ certs.Service = (*tracingMiddleware)(nil) - -type tracingMiddleware struct { - tracer trace.Tracer - svc certs.Service -} - -// New returns a new certs service with tracing capabilities. -func New(svc certs.Service, tracer trace.Tracer) certs.Service { - return &tracingMiddleware{tracer, svc} -} - -// 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) { - ctx, span := tm.tracer.Start(ctx, "svc_create_group", trace.WithAttributes( - attribute.String("thing_id", thingID), - attribute.String("ttl", ttl), - )) - defer span.End() - - return tm.svc.IssueCert(ctx, domainID, token, thingID, 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) { - ctx, span := tm.tracer.Start(ctx, "svc_list_certs", trace.WithAttributes( - attribute.String("thing_id", thingID), - attribute.Int64("offset", int64(pm.Offset)), - attribute.Int64("limit", int64(pm.Limit)), - )) - defer span.End() - - return tm.svc.ListCerts(ctx, thingID, 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) { - ctx, span := tm.tracer.Start(ctx, "svc_list_serials", trace.WithAttributes( - attribute.String("thing_id", thingID), - attribute.Int64("offset", int64(pm.Offset)), - attribute.Int64("limit", int64(pm.Limit)), - )) - defer span.End() - - return tm.svc.ListSerials(ctx, thingID, pm) -} - -// ViewCert traces the "ViewCert" operation of the wrapped certs.Service. -func (tm *tracingMiddleware) ViewCert(ctx context.Context, serialID string) (certs.Cert, error) { - ctx, span := tm.tracer.Start(ctx, "svc_view_cert", trace.WithAttributes( - attribute.String("serial_id", serialID), - )) - defer span.End() - - return tm.svc.ViewCert(ctx, serialID) -} - -// RevokeCert traces the "RevokeCert" operation of the wrapped certs.Service. -func (tm *tracingMiddleware) RevokeCert(ctx context.Context, domainID, token, serialID string) (certs.Revoke, error) { - ctx, span := tm.tracer.Start(ctx, "svc_revoke_cert", trace.WithAttributes( - attribute.String("serial_id", serialID), - )) - defer span.End() - - return tm.svc.RevokeCert(ctx, domainID, token, serialID) -} diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index 58800b7a0..000000000 --- a/cli/README.md +++ /dev/null @@ -1,411 +0,0 @@ -# Magistrala CLI - -## Build - -From the project root: - -```bash -make cli -``` - -## Usage - -### Service - -#### Get Magistrala Services Health Check - -```bash -magistrala-cli health -``` - -### Users management - -#### Create User - -```bash -magistrala-cli users create - -magistrala-cli users create -``` - -#### Login User - -```bash -magistrala-cli users token -``` - -#### Get User - -```bash -magistrala-cli users get -``` - -#### Get Users - -```bash -magistrala-cli users get all -``` - -#### Update User Metadata - -```bash -magistrala-cli users update '{"name":"value1", "metadata":{"value2": "value3"}}' -``` - -#### Update User Password - -```bash -magistrala-cli users password -``` - -#### Enable User - -```bash -magistrala-cli users enable -``` - -#### Disable User - -```bash -magistrala-cli users disable -``` - -### System Provisioning - -#### Create Thing - -```bash -magistrala-cli things create '{"name":"myThing"}' -``` - -#### Create Thing with metadata - -```bash -magistrala-cli things create '{"name":"myThing", "metadata": {"key1":"value1"}}' -``` - -#### Bulk Provision Things - -```bash -magistrala-cli provision things -``` - -- `file` - A CSV or JSON file containing thing 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, -``` - -in which the first column is the thing's name. - -A comparable JSON file would be - -```json -[ - { - "name": "", - "status": "enabled" - }, - { - "name": "", - "status": "disabled" - }, - { - "name": "", - "status": "enabled", - "credentials": { - "identity": "", - "secret": "" - } - } -] -``` - -With JSON you can be able to specify more fields of the channels you want to create - -#### Update Thing - -```bash -magistrala-cli things update '{"name":"value1", "metadata":{"key1": "value2"}}' -``` - -#### Identify Thing - -```bash -magistrala-cli things identify -``` - -#### Enable Thing - -```bash -magistrala-cli things enable -``` - -#### Disable Thing - -```bash -magistrala-cli things disable -``` - -#### Get Thing - -```bash -magistrala-cli things get -``` - -#### Get Things - -```bash -magistrala-cli things get all -``` - -#### Get a subset list of provisioned Things - -```bash -magistrala-cli things get all --offset=1 --limit=5 -``` - -#### Create Channel - -```bash -magistrala-cli channels create '{"name":"myChannel"}' -``` - -#### Bulk Provision Channels - -```bash -magistrala-cli provision channels -``` - -- `file` - A CSV or JSON file containing channel 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 -, -, -, -``` - -in which the first column is channel names. - -A comparable JSON file would be - -```json -[ - { - "name": "", - "description": "", - "status": "enabled" - }, - { - "name": "", - "description": "", - "status": "disabled" - }, - { - "name": "", - "description": "", - "status": "enabled" - } -] -``` - -With JSON you can be able to specify more fields of the channels you want to create - -#### Update Channel - -```bash -magistrala-cli channels update '{"id":"","name":"myNewName"}' -``` - -#### Enable Channel - -```bash -magistrala-cli channels enable -``` - -#### Disable Channel - -```bash -magistrala-cli channels disable -``` - -#### Get Channel - -```bash -magistrala-cli channels get -``` - -#### Get Channels - -```bash -magistrala-cli channels get all -``` - -#### Get a subset list of provisioned Channels - -```bash -magistrala-cli channels get all --offset=1 --limit=5 -``` - -### Access control - -#### Connect Thing to Channel - -```bash -magistrala-cli things connect -``` - -#### Bulk Connect Things to Channels - -```bash -magistrala-cli provision connect -``` - -- `file` - A CSV or JSON file containing thing 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. - -A comparable JSON file would be - -```json -{ - "client_ids": ["", ""], - "group_ids": ["", ""] -} -``` - -#### Disconnect Thing from Channel - -```bash -magistrala-cli things disconnect -``` - -#### Get a subset list of Channels connected to Thing - -```bash -magistrala-cli things connections -``` - -#### Get a subset list of Things connected to Channel - -```bash -magistrala-cli channels connections -``` - -### Messaging - -#### 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}]' -``` - -#### Read messages over HTTP - -```bash -magistrala-cli messages read -R -``` - -### Bootstrap - -#### Add configuration - -```bash -magistrala-cli bootstrap create '{"external_id": "myExtID", "external_key": "myExtKey", "name": "myName", "content": "myContent"}' -b -``` - -#### View configuration - -```bash -magistrala-cli bootstrap get -b -``` - -#### Update configuration - -```bash -magistrala-cli bootstrap update '{"thing_id":"", "name": "newName", "content": "newContent"}' -b -``` - -#### Remove configuration - -```bash -magistrala-cli bootstrap remove -b -``` - -#### Bootstrap configuration - -```bash -magistrala-cli bootstrap bootstrap -b -``` - -### Groups - -#### Create Group - -```bash -magistrala-cli groups create '{"name":"","description":"","parentID":"","metadata":""}' -``` - -#### Get Group - -```bash -magistrala-cli groups get -``` - -#### Get Groups - -```bash -magistrala-cli groups get all -``` - -#### Get Group Members - -```bash -magistrala-cli groups members -``` - -#### Get Memberships - -```bash -magistrala-cli groups membership -``` - -#### Assign Members to Group - -```bash -magistrala-cli groups assign -``` - -#### Unassign Members to Group - -```bash -magistrala-cli groups unassign -``` - -#### Enable Group - -```bash -magistrala-cli groups enable -``` - -#### Disable Group - -```bash -magistrala-cli groups disable -``` diff --git a/cli/bootstrap.go b/cli/bootstrap.go deleted file mode 100644 index dde560fa0..000000000 --- a/cli/bootstrap.go +++ /dev/null @@ -1,216 +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/spf13/cobra" -) - -var cmdBootstrap = []cobra.Command{ - { - Use: "create ", - Short: "Create config", - Long: `Create new Thing 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) - return - } - - var cfg mgxsdk.BootstrapConfig - if err := json.Unmarshal([]byte(args[0]), &cfg); err != nil { - logErrorCmd(*cmd, err) - return - } - - id, err := sdk.AddBootstrap(cfg, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logCreatedCmd(*cmd, id) - }, - }, - { - Use: "get [all | ] ", - Short: "Get config", - Long: `Get Thing Config with given ID belonging to the user identified by the given key. - all - lists all config - - view config of `, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - pageMetadata := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - State: State, - Name: Name, - } - if args[0] == "all" { - l, err := sdk.Bootstraps(pageMetadata, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - return - } - - c, err := sdk.ViewBootstrap(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, c) - }, - }, - { - Use: "update [config | connection | certs ] ", - 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. - channel_ids - '["channel_id1", ...]' - certs - Update bootstrap config certificates.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - if args[0] == "config" { - var cfg mgxsdk.BootstrapConfig - if err := json.Unmarshal([]byte(args[1]), &cfg); err != nil { - logErrorCmd(*cmd, err) - return - } - - if err := sdk.UpdateBootstrap(cfg, args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - return - } - if args[0] == "connection" { - var ids []string - if err := json.Unmarshal([]byte(args[2]), &ids); err != nil { - logErrorCmd(*cmd, err) - return - } - if err := sdk.UpdateBootstrapConnection(args[1], ids, args[3], args[4]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - return - } - if args[0] == "certs" { - cfg, err := sdk.UpdateBootstrapCerts(args[0], args[1], args[2], args[3], args[4], args[5]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, cfg) - return - } - logUsageCmd(*cmd, cmd.Use) - }, - }, - { - 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) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.RemoveBootstrap(args[0], args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "bootstrap [ | secure ]", - Short: "Bootstrap config", - Long: `Returns Config to the Thing 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 { - logUsageCmd(*cmd, cmd.Use) - return - } - if args[0] == "secure" { - c, err := sdk.BootstrapSecure(args[1], args[2], args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, c) - return - } - c, err := sdk.Bootstrap(args[0], args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, c) - }, - }, - { - Use: "whitelist ", - Short: "Whitelist config", - Long: `Whitelist updates thing state config with given id from the authenticated user`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - var cfg mgxsdk.BootstrapConfig - if err := json.Unmarshal([]byte(args[0]), &cfg); err != nil { - logErrorCmd(*cmd, err) - return - } - - if err := sdk.Whitelist(cfg.ThingID, cfg.State, args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, -} - -// NewBootstrapCmd returns bootstrap command. -func NewBootstrapCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "bootstrap [create | get | update | remove | bootstrap | whitelist]", - Short: "Bootstrap management", - Long: `Bootstrap management: create, get, update, delete or whitelist Bootstrap config`, - } - - for i := range cmdBootstrap { - cmd.AddCommand(&cmdBootstrap[i]) - } - - return &cmd -} diff --git a/cli/bootstrap_test.go b/cli/bootstrap_test.go deleted file mode 100644 index 3fdacb659..000000000 --- a/cli/bootstrap_test.go +++ /dev/null @@ -1,622 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var bootConfig = mgsdk.BootstrapConfig{ - ThingID: thing.ID, - Channels: []string{channel.ID}, - Name: "Test Bootstrap", - ExternalID: "09:6:0:sb:sa", - ExternalKey: "key", -} - -func TestCreateBootstrapConfigCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - 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) - cases := []struct { - desc string - args []string - logType outputLog - response string - sdkErr errors.SDKError - errLogMessage string - id string - }{ - { - desc: "create bootstrap config successfully", - args: []string{ - jsonConfig, - domainID, - validToken, - }, - logType: createLog, - id: thing.ID, - response: fmt.Sprintf("\ncreated: %s\n\n", thing.ID), - }, - { - desc: "create bootstrap config with invald args", - args: []string{ - jsonConfig, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "create bootstrap config with invald json", - args: []string{ - invalidJson, - domainID, - validToken, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "create bootstrap config with invald token", - args: []string{ - jsonConfig, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("AddBootstrap", mock.Anything, mock.Anything, mock.Anything).Return(tc.id, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...) - - switch tc.logType { - case createLog: - assert.Equal(t, tc.response, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.response, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestGetBootstrapConfigCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - bootCmd := cli.NewBootstrapCmd() - rootCmd := setFlags(bootCmd) - - var boot mgsdk.BootstrapConfig - var page mgsdk.BootstrapPage - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - page mgsdk.BootstrapPage - boot mgsdk.BootstrapConfig - logType outputLog - errLogMessage string - }{ - { - desc: "get all bootstrap config successfully", - args: []string{ - all, - domainID, - token, - }, - page: mgsdk.BootstrapPage{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Configs: []mgsdk.BootstrapConfig{bootConfig}, - }, - logType: entityLog, - }, - { - desc: "get bootstrap config with id", - args: []string{ - channel.ID, - domainID, - token, - }, - logType: entityLog, - boot: bootConfig, - }, - { - desc: "get bootstrap config with invalid args", - args: []string{ - all, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get all bootstrap config with invalid token", - args: []string{ - all, - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "get bootstrap config with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ViewBootstrap", tc.args[0], tc.args[1], tc.args[2]).Return(tc.boot, tc.sdkErr) - sdkCall1 := sdkMock.On("Bootstraps", mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) - - out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - if tc.args[0] == all { - err := json.Unmarshal([]byte(out), &page) - assert.Nil(t, err) - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - } else { - err := json.Unmarshal([]byte(out), &boot) - assert.Nil(t, err) - assert.Equal(t, tc.boot, boot, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.boot, boot)) - } - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} - -func TestRemoveBootstrapConfigCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - bootCmd := cli.NewBootstrapCmd() - rootCmd := setFlags(bootCmd) - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - logType outputLog - errLogMessage string - }{ - { - desc: "remove bootstrap config successfully", - args: []string{ - thing.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "remove bootstrap config with invalid args", - args: []string{ - thing.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "remove bootstrap config with invalid thing id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "remove bootstrap config with invalid token", - args: []string{ - thing.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("RemoveBootstrap", tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{rmCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUpdateBootstrapConfigCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - bootCmd := cli.NewBootstrapCmd() - rootCmd := setFlags(bootCmd) - - config := "config" - connection := "connection" - - newConfigJson := "{\"name\" : \"New Bootstrap\"}" - chanIDsJson := fmt.Sprintf("[\"%s\"]", channel.ID) - cases := []struct { - desc string - args []string - boot mgsdk.BootstrapConfig - sdkErr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "update bootstrap config successfully", - args: []string{ - config, - newConfigJson, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "update bootstrap config with invalid token", - args: []string{ - config, - newConfigJson, - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "update bootstrap connections successfully", - args: []string{ - connection, - thing.ID, - chanIDsJson, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "update bootstrap connections with invalid json", - args: []string{ - connection, - thing.ID, - fmt.Sprintf("[\"%s\"", thing.ID), - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "update bootstrap connections with invalid token", - args: []string{ - connection, - thing.ID, - chanIDsJson, - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "update bootstrap certs successfully", - args: []string{ - "certs", - thing.ID, - "client cert", - "client key", - "ca", - domainID, - token, - }, - boot: bootConfig, - logType: entityLog, - }, - { - desc: "update bootstrap certs with invalid token", - args: []string{ - "certs", - thing.ID, - "client cert", - "client key", - "ca", - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "update bootstrap config with invalid args", - args: []string{ - newConfigJson, - domainID, - token, - }, - logType: usageLog, - }, - { - desc: "update bootstrap config with invalid json", - args: []string{ - config, - "{\"name\" : \"New Bootstrap\"", - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "update bootstrap with invalid args", - args: []string{ - extraArg, - extraArg, - extraArg, - extraArg, - extraArg, - }, - logType: usageLog, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var boot mgsdk.BootstrapConfig - sdkCall := sdkMock.On("UpdateBootstrap", mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr) - sdkCall1 := sdkMock.On("UpdateBootstrapConnection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr) - sdkCall2 := sdkMock.On("UpdateBootstrapCerts", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{updCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &boot) - assert.Nil(t, err) - assert.Equal(t, tc.boot, boot, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.boot, boot)) - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - sdkCall2.Unset() - }) - } -} - -func TestWhitelistConfigCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - bootCmd := cli.NewBootstrapCmd() - rootCmd := setFlags(bootCmd) - - jsonConfig := fmt.Sprintf("{\"thing_id\": \"%s\", \"state\":%d}", thing.ID, 1) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "whitelist config successfully", - args: []string{ - jsonConfig, - domainID, - validToken, - }, - logType: okLog, - }, - { - desc: "whitelist config with invalid args", - args: []string{ - jsonConfig, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "whitelist config with invalid json", - args: []string{ - fmt.Sprintf("{\"thing_id\": \"%s\", \"state\":%d", thing.ID, 1), - domainID, - validToken, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "whitelist config with invalid token", - args: []string{ - jsonConfig, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Whitelist", mock.Anything, mock.Anything, tc.args[1], tc.args[2]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{whitelistCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestBootstrapConfigCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - bootCmd := cli.NewBootstrapCmd() - rootCmd := setFlags(bootCmd) - - var boot mgsdk.BootstrapConfig - crptoKey := "v7aT0HGxJxt2gULzr3RHwf4WIf6DusPp" - invalidKey := "invalid key" - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - boot mgsdk.BootstrapConfig - }{ - { - desc: "bootstrap secure config successfully", - args: []string{ - "secure", - bootConfig.ExternalID, - bootConfig.ExternalKey, - crptoKey, - }, - boot: bootConfig, - logType: entityLog, - }, - { - desc: "bootstrap config successfully", - args: []string{ - bootConfig.ExternalID, - bootConfig.ExternalKey, - }, - boot: bootConfig, - logType: entityLog, - }, - { - desc: "bootstrap secure config with invalid args", - args: []string{ - crptoKey, - }, - - logType: usageLog, - }, - { - desc: "bootstrap secure config with invalid key", - args: []string{ - "secure", - bootConfig.ExternalID, - invalidKey, - crptoKey, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - { - desc: "bootstrap config with invalid key", - args: []string{ - bootConfig.ExternalID, - invalidKey, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("BootstrapSecure", mock.Anything, mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr) - sdkCall1 := sdkMock.On("Bootstrap", mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{bootStrapCmd}, tc.args...)...) - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &boot) - assert.Nil(t, err) - assert.Equal(t, tc.boot, boot, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.boot, boot)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} diff --git a/cli/certs.go b/cli/certs.go deleted file mode 100644 index 988e0c20e..000000000 --- a/cli/certs.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "github.com/spf13/cobra" -) - -var cmdCerts = []cobra.Command{ - { - Use: "get [ | thing ] ", - Short: "Get certificate", - Long: `Gets a certificate for a given cert ID.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - if args[0] == "thing" { - cert, err := sdk.ViewCertByThing(args[1], args[2], args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, cert) - return - } - cert, err := sdk.ViewCert(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, cert) - }, - }, - { - Use: "revoke ", - Short: "Revoke certificate", - Long: `Revokes a certificate for a given thing ID.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - rtime, err := sdk.RevokeCert(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logRevokedTimeCmd(*cmd, rtime) - }, - }, -} - -// NewCertsCmd returns certificate command. -func NewCertsCmd() *cobra.Command { - var ttl string - - issueCmd := cobra.Command{ - Use: "issue [--ttl=8760h]", - Short: "Issue certificate", - Long: `Issues new certificate for a thing`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - thingID := args[0] - - c, err := sdk.IssueCert(thingID, ttl, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, c) - }, - } - - issueCmd.Flags().StringVar(&ttl, "ttl", "8760h", "certificate time to live in duration") - - cmd := cobra.Command{ - Use: "certs [issue | get | revoke ]", - Short: "Certificates management", - Long: `Certificates management: issue, get or revoke certificates for things"`, - } - - cmdCerts = append(cmdCerts, issueCmd) - - for i := range cmdCerts { - cmd.AddCommand(&cmdCerts[i]) - } - - return &cmd -} diff --git a/cli/certs_test.go b/cli/certs_test.go deleted file mode 100644 index efc057c10..000000000 --- a/cli/certs_test.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var cert = mgsdk.Cert{ - ThingID: thing.ID, -} - -func TestGetCertCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - certCmd := cli.NewCertsCmd() - rootCmd := setFlags(certCmd) - - var ct mgsdk.Cert - var cts mgsdk.CertSerials - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - logType outputLog - serials mgsdk.CertSerials - cert mgsdk.Cert - }{ - { - desc: "get cert successfully", - args: []string{ - "thing", - thing.ID, - domainID, - validToken, - }, - logType: entityLog, - serials: mgsdk.CertSerials{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Certs: []mgsdk.Cert{cert}, - }, - }, - { - desc: "get cert successfully by id", - args: []string{ - thing.ID, - domainID, - validToken, - }, - logType: entityLog, - cert: cert, - }, - { - desc: "get cert with invalid token", - args: []string{ - "thing", - thing.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - { - desc: "get cert by id with invalid token", - args: []string{ - thing.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - { - desc: "get cert with invalid args", - args: []string{ - thing.ID, - }, - logType: usageLog, - }, - } - - 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) - 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" { - 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)) - } else { - err := json.Unmarshal([]byte(out), &ct) - assert.Nil(t, err) - assert.Equal(t, tc.cert, ct, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.cert, ct)) - } - - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} - -func TestRevokeCertCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - certCmd := cli.NewCertsCmd() - rootCmd := setFlags(certCmd) - - revokeTime := time.Now() - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - logType outputLog - errLogMessage string - time time.Time - response string - }{ - { - desc: "revoke cert successfully", - args: []string{ - thing.ID, - domainID, - token, - }, - logType: revokeLog, - response: fmt.Sprintf("\nrevoked: %s\n\n", revokeTime), - time: revokeTime, - }, - { - desc: "revoke cert with invalid args", - args: []string{ - thing.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "revoke cert with invalid token", - args: []string{ - thing.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("RevokeCert", tc.args[0], tc.args[1], tc.args[2]).Return(tc.time, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{revokeCmd}, tc.args...)...) - - switch tc.logType { - case revokeLog: - assert.Equal(t, tc.response, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.response, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestIssueCertCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - certCmd := cli.NewCertsCmd() - rootCmd := setFlags(certCmd) - - cert := mgsdk.Cert{ - SerialNumber: "serial", - } - - var cs mgsdk.Cert - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - cert mgsdk.Cert - }{ - { - desc: "issue cert successfully", - args: []string{ - thing.ID, - domainID, - validToken, - }, - cert: cert, - logType: entityLog, - }, - { - desc: "issue cert with invalid args", - args: []string{ - thing.ID, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "issue cert with invalid token", - args: []string{ - thing.ID, - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("IssueCert", mock.Anything, mock.Anything, tc.args[1], tc.args[2]).Return(tc.cert, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{issueCmd}, tc.args...)...) - - switch tc.logType { - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - 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 entityLog: - err := json.Unmarshal([]byte(out), &cs) - assert.Nil(t, err) - assert.Equal(t, tc.cert, cs, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.cert, cs)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/channels.go b/cli/channels.go deleted file mode 100644 index a033f1aa8..000000000 --- a/cli/channels.go +++ /dev/null @@ -1,376 +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/spf13/cobra" -) - -const all = "all" - -var cmdChannels = []cobra.Command{ - { - Use: "create ", - Short: "Create channel", - Long: `Creates new channel and generates it's UUID`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - var channel mgxsdk.Channel - if err := json.Unmarshal([]byte(args[0]), &channel); err != nil { - logErrorCmd(*cmd, err) - return - } - - channel, err := sdk.CreateChannel(channel, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, channel) - }, - }, - { - Use: "get [all | ] ", - 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 `, - - 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: "", - Offset: Offset, - Limit: Limit, - Metadata: metadata, - } - - if args[0] == all { - l, err := sdk.Channels(pageMetadata, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, l) - return - } - c, err := sdk.Channel(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, c) - }, - }, - { - Use: "delete ", - Short: "Delete channel", - Long: "Delete channel by id.\n" + - "Usage:\n" + - "\tmagistrala-cli channels delete $DOMAINID $USERTOKEN - delete the given channel ID\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - if err := sdk.DeleteChannel(args[0], args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, - { - Use: "update ", - Short: "Update channel", - Long: `Updates channel record`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - - var channel mgxsdk.Channel - if err := json.Unmarshal([]byte(args[1]), &channel); err != nil { - logErrorCmd(*cmd, err) - return - } - channel.ID = args[0] - channel, err := sdk.UpdateChannel(channel, args[2], args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, channel) - }, - }, - { - Use: "connections ", - Short: "Connections list", - Long: `List of Things connected to a Channel`, - 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.ThingsByChannel(args[0], pm, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, cl) - }, - }, - { - Use: "enable ", - Short: "Change channel status to enabled", - Long: `Change channel status to enabled`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - channel, err := sdk.EnableChannel(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, channel) - }, - }, - { - Use: "disable ", - Short: "Change channel status to disabled", - Long: `Change channel status to disabled`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - channel, err := sdk.DisableChannel(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, channel) - }, - }, - { - Use: "users ", - Short: "List users", - Long: "List users of a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels 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.ListChannelUsers(args[0], pm, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, ul) - }, - }, - { - Use: "groups ", - Short: "List groups", - Long: "List groups of a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels groups $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.ListChannelUserGroups(args[0], pm, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, ul) - }, - }, -} - -var channelAssignCmds = []cobra.Command{ - { - Use: "users ", - Short: "Assign users", - Long: "Assign users to a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels assign users '[\"\", \"\"]' $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsageCmd(*cmd, cmd.Use) - return - } - var userIDs []string - if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { - logErrorCmd(*cmd, err) - return - } - if err := sdk.AddUserToChannel(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3], args[4]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, - { - Use: "groups ", - Short: "Assign groups", - Long: "Assign groups to a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels assign groups '[\"\", \"\"]' $DOMAINID $USERTOKEN\n", - - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - var groupIDs []string - if err := json.Unmarshal([]byte(args[0]), &groupIDs); err != nil { - logErrorCmd(*cmd, err) - return - } - if err := sdk.AddUserGroupToChannel(args[1], mgxsdk.UserGroupsRequest{UserGroupIDs: groupIDs}, args[2], args[3]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, -} - -var channelUnassignCmds = []cobra.Command{ - { - Use: "groups ", - Short: "Unassign groups", - Long: "Unassign groups from a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels unassign groups '[\"\", \"\"]' $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - var groupIDs []string - if err := json.Unmarshal([]byte(args[0]), &groupIDs); err != nil { - logErrorCmd(*cmd, err) - return - } - if err := sdk.RemoveUserGroupFromChannel(args[1], mgxsdk.UserGroupsRequest{UserGroupIDs: groupIDs}, args[2], args[3]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, - - { - Use: "users ", - Short: "Unassign users", - Long: "Unassign users from a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels unassign users '[\"\", \"\"]' $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsageCmd(*cmd, cmd.Use) - return - } - var userIDs []string - if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { - logErrorCmd(*cmd, err) - return - } - if err := sdk.RemoveUserFromChannel(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3], args[4]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, -} - -func NewChannelAssignCmds() *cobra.Command { - cmd := cobra.Command{ - Use: "assign [users | groups]", - Short: "Assign users or groups to a channel", - Long: "Assign users or groups to a channel", - } - for i := range channelAssignCmds { - cmd.AddCommand(&channelAssignCmds[i]) - } - return &cmd -} - -func NewChannelUnassignCmds() *cobra.Command { - cmd := cobra.Command{ - Use: "unassign [users | groups]", - Short: "Unassign users or groups from a channel", - Long: "Unassign users or groups from a channel", - } - for i := range channelUnassignCmds { - cmd.AddCommand(&channelUnassignCmds[i]) - } - return &cmd -} - -// NewChannelsCmd returns channels command. -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`, - } - - for i := range cmdChannels { - cmd.AddCommand(&cmdChannels[i]) - } - - cmd.AddCommand(NewChannelAssignCmds()) - cmd.AddCommand(NewChannelUnassignCmds()) - return &cmd -} diff --git a/cli/channels_test.go b/cli/channels_test.go deleted file mode 100644 index 428144fec..000000000 --- a/cli/channels_test.go +++ /dev/null @@ -1,1137 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var channel = mgsdk.Channel{ - ID: testsutil.GenerateUUID(&testing.T{}), - Name: "testchannel", -} - -func TestCreateChannelCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelJson := "{\"name\":\"testchannel\", \"metadata\":{\"key1\":\"value1\"}}" - channelCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelCmd) - - cp := mgsdk.Channel{} - cases := []struct { - desc string - args []string - logType outputLog - channel mgsdk.Channel - sdkErr errors.SDKError - errLogMessage string - }{ - { - desc: "create channel successfully", - args: []string{ - channelJson, - domainID, - token, - }, - channel: channel, - logType: entityLog, - }, - { - desc: "create channel with invalid args", - args: []string{ - channelJson, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "create channel with invalid json", - args: []string{ - "{\"name\":\"testchannel\", \"metadata\":{\"key1\":\"value1\"}", - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "create channel with invalid token", - args: []string{ - channelJson, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("CreateChannel", mock.Anything, tc.args[1], tc.args[2]).Return(tc.channel, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &cp) - assert.Nil(t, err) - assert.Equal(t, tc.channel, cp, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.channel, cp)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestGetChannelsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelCmd) - - var ch mgsdk.Channel - var page mgsdk.ChannelsPage - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - page mgsdk.ChannelsPage - channel mgsdk.Channel - logType outputLog - errLogMessage string - }{ - { - desc: "get all channels successfully", - args: []string{ - all, - domainID, - token, - }, - page: mgsdk.ChannelsPage{ - Channels: []mgsdk.Channel{channel}, - }, - logType: entityLog, - }, - { - desc: "get channel with id", - args: []string{ - channel.ID, - domainID, - token, - }, - logType: entityLog, - channel: channel, - }, - { - desc: "get channels with invalid args", - args: []string{ - all, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get all channels with invalid token", - args: []string{ - all, - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "get channel with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Channel", tc.args[0], tc.args[1], tc.args[2]).Return(tc.channel, tc.sdkErr) - sdkCall1 := sdkMock.On("Channels", mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) - - out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - if tc.args[1] == all { - err := json.Unmarshal([]byte(out), &page) - assert.Nil(t, err) - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - } else { - err := json.Unmarshal([]byte(out), &ch) - assert.Nil(t, err) - assert.Equal(t, tc.channel, ch, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.channel, ch)) - } - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} - -func TestDeleteChannelCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelCmd) - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - logType outputLog - errLogMessage string - }{ - { - desc: "delete channel successfully", - args: []string{ - channel.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "delete channel with invalid args", - args: []string{ - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "delete channel with invalid channel id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "delete channel with invalid token", - args: []string{ - channel.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DeleteChannel", tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{delCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUpdateChannelCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelCmd) - - newChannelJson := "{\"name\" : \"channel1\"}" - cases := []struct { - desc string - args []string - channel mgsdk.Channel - sdkErr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "update channel successfully", - args: []string{ - channel.ID, - newChannelJson, - domainID, - token, - }, - channel: mgsdk.Channel{ - Name: "newchannel1", - ID: channel.ID, - }, - logType: entityLog, - }, - { - desc: "update channel with invalid args", - args: []string{ - channel.ID, - newChannelJson, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "update channel with invalid channel id", - args: []string{ - invalidID, - newChannelJson, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "update channel with invalid json syntax", - args: []string{ - channel.ID, - "{\"name\" : \"channel1\"", - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var ch mgsdk.Channel - sdkCall := sdkMock.On("UpdateChannel", mock.Anything, tc.args[2], tc.args[3]).Return(tc.channel, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{updCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &ch) - assert.Nil(t, err) - assert.Equal(t, tc.channel, ch, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.channel, ch)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestListConnectionsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelCmd) - - var tp mgsdk.ThingsPage - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - logType outputLog - page mgsdk.ThingsPage - }{ - { - desc: "list connections successfully", - args: []string{ - channel.ID, - domainID, - token, - }, - page: mgsdk.ThingsPage{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Things: []mgsdk.Thing{thing}, - }, - logType: entityLog, - }, - { - desc: "list connections with invalid args", - args: []string{ - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list connections with invalid channel id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - 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) - out := executeCommand(t, rootCmd, append([]string{connsCmd}, tc.args...)...) - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &tp) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, tp, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, tp)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestEnableChannelCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelCmd) - var ch mgsdk.Channel - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - channel mgsdk.Channel - logType outputLog - }{ - { - desc: "enable channel successfully", - args: []string{ - channel.ID, - domainID, - validToken, - }, - channel: channel, - logType: entityLog, - }, - { - desc: "delete channel with invalid token", - args: []string{ - channel.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "delete channel with invalid channel ID", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "enable channel with invalid args", - args: []string{ - channel.ID, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("EnableChannel", tc.args[0], tc.args[1], tc.args[2]).Return(tc.channel, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{enableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &ch) - assert.Nil(t, err) - assert.Equal(t, tc.channel, ch, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.channel, ch)) - } - - sdkCall.Unset() - }) - } -} - -func TestDisableChannelCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelsCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelsCmd) - - var ch mgsdk.Channel - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - channel mgsdk.Channel - logType outputLog - }{ - { - desc: "disable channel successfully", - args: []string{ - channel.ID, - domainID, - validToken, - }, - logType: entityLog, - channel: channel, - }, - { - desc: "disable channel with invalid token", - args: []string{ - channel.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "disable channel with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "disable thing with invalid args", - args: []string{ - channel.ID, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DisableChannel", tc.args[0], tc.args[1], tc.args[2]).Return(tc.channel, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{disableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &ch) - if err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) - } - assert.Equal(t, tc.channel, ch, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.channel, ch)) - } - - sdkCall.Unset() - }) - } -} - -func TestUsersChannelCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelsCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelsCmd) - - page := mgsdk.UsersPage{} - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - page mgsdk.UsersPage - sdkErr errors.SDKError - }{ - { - desc: "get channel's users successfully", - args: []string{ - channel.ID, - domainID, - token, - }, - page: mgsdk.UsersPage{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Users: []mgsdk.User{user}, - }, - logType: entityLog, - }, - { - desc: "list channel users with invalid args", - args: []string{ - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list channel users with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListChannelUsers", tc.args[0], mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{usrCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &page) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestListGroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelsCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelsCmd) - - var gp mgsdk.GroupsPage - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - logType outputLog - page mgsdk.GroupsPage - }{ - { - desc: "list groups successfully", - args: []string{ - channel.ID, - domainID, - token, - }, - page: mgsdk.GroupsPage{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Groups: []mgsdk.Group{group}, - }, - logType: entityLog, - }, - { - desc: "list groups with invalid args", - args: []string{ - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list groups with invalid channel id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListChannelUserGroups", tc.args[0], mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{grpCmd}, tc.args...)...) - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &gp) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, gp, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, gp)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestAssignUserCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelsCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelsCmd) - - userIds := fmt.Sprintf("[\"%s\"]", user.ID) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "assign user successfully", - args: []string{ - relation, - userIds, - channel.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "assign user with invalid args", - args: []string{ - relation, - userIds, - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "assign user with invalid json", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"", user.ID), - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "assign user with invalid channel id", - args: []string{ - relation, - userIds, - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "assign user with invalid user id", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"]", invalidID), - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("AddUserToChannel", tc.args[2], mock.Anything, tc.args[3], tc.args[4]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{assignCmd, usrCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestAssignGroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelsCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelsCmd) - - grpIds := fmt.Sprintf("[\"%s\"]", group.ID) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "assign group successfully", - args: []string{ - grpIds, - channel.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "assign group with invalid args", - args: []string{ - grpIds, - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "assign group with invalid json", - args: []string{ - fmt.Sprintf("[\"%s\"", group.ID), - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "assign group with invalid channel id", - args: []string{ - grpIds, - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "assign group with invalid user id", - args: []string{ - fmt.Sprintf("[\"%s\"]", invalidID), - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("AddUserGroupToChannel", tc.args[1], mock.Anything, tc.args[2], tc.args[3]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{assignCmd, grpCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUnassignUserCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelsCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelsCmd) - - userIds := fmt.Sprintf("[\"%s\"]", user.ID) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "unassign user successfully", - args: []string{ - relation, - userIds, - channel.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "unassign user with invalid args", - args: []string{ - relation, - userIds, - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "unassign user with invalid json", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"", user.ID), - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "unassign user with invalid channel id", - args: []string{ - relation, - userIds, - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "unassign user with invalid user id", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"]", invalidID), - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("RemoveUserFromChannel", tc.args[2], mock.Anything, tc.args[3], tc.args[4]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{unassignCmd, usrCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUnassignGroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - channelsCmd := cli.NewChannelsCmd() - rootCmd := setFlags(channelsCmd) - - grpIds := fmt.Sprintf("[\"%s\"]", group.ID) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "unassign group successfully", - args: []string{ - unassignCmd, - grpCmd, - grpIds, - channel.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "unassign group with invalid args", - args: []string{ - grpIds, - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "unassign group with invalid json", - args: []string{ - fmt.Sprintf("[\"%s\"", group.ID), - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "unassign group with invalid channel id", - args: []string{ - grpIds, - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "unassign group with invalid user id", - args: []string{ - fmt.Sprintf("[\"%s\"]", invalidID), - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("RemoveUserGroupFromChannel", tc.args[1], mock.Anything, tc.args[2], tc.args[3]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{unassignCmd, grpCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/commands_test.go b/cli/commands_test.go deleted file mode 100644 index 3e432f2fe..000000000 --- a/cli/commands_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -// CRUD and common commands -const ( - createCmd = "create" - updateCmd = "update" - getCmd = "get" - enableCmd = "enable" - disableCmd = "disable" - updCmd = "update" - delCmd = "delete" - rmCmd = "remove" -) - -// Users commands -const ( - tokCmd = "token" - refTokCmd = "refreshtoken" - profCmd = "profile" - resPassReqCmd = "resetpasswordrequest" - resPassCmd = "resetpassword" - passCmd = "password" - domsCmd = "domains" -) - -// Things commands -const ( - thsCmd = "things" - connsCmd = "connections" - connCmd = "connect" - disconnCmd = "disconnect" - shrCmd = "share" - unshrCmd = "unshare" -) - -// Groups and channels commands -const ( - chansCmd = "channels" - grpCmd = "groups" - childCmd = "children" - parentCmd = "parents" - usrCmd = "users" - assignCmd = "assign" - unassignCmd = "unassign" -) - -// Certs commands -const ( - revokeCmd = "revoke" - issueCmd = "issue" -) - -// Messages commands -const ( - sendCmd = "send" - readCmd = "read" -) - -// Bootstrap commands -const ( - whitelistCmd = "whitelist" - bootStrapCmd = "bootstrap" -) - -// Invitations commands -const ( - acceptCmd = "accept" - rejectCmd = "reject" -) diff --git a/cli/config.go b/cli/config.go deleted file mode 100644 index e3910aaa6..000000000 --- a/cli/config.go +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "io" - "net/url" - "os" - "reflect" - "strconv" - "strings" - - "github.com/absmach/magistrala/pkg/errors" - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/pelletier/go-toml" - "github.com/spf13/cobra" -) - -const ( - defURL string = "http://localhost" - defUsersURL string = defURL + ":9002" - defThingsURL string = defURL + ":9000" - defReaderURL string = defURL + ":9011" - defBootstrapURL string = defURL + ":9013" - defDomainsURL string = defURL + ":8189" - defCertsURL string = defURL + ":9019" - defInvitationsURL string = defURL + ":9020" - defHTTPURL string = defURL + ":8008" - defJournalURL string = defURL + ":9021" - defTLSVerification bool = false - defOffset string = "0" - defLimit string = "10" - defTopic string = "" - defRawOutput string = "false" -) - -type remotes struct { - ThingsURL string `toml:"things_url"` - UsersURL string `toml:"users_url"` - ReaderURL string `toml:"reader_url"` - DomainsURL string `toml:"domains_url"` - HTTPAdapterURL string `toml:"http_adapter_url"` - BootstrapURL string `toml:"bootstrap_url"` - CertsURL string `toml:"certs_url"` - InvitationsURL string `toml:"invitations_url"` - JournalURL string `toml:"journal_url"` - HostURL string `toml:"host_url"` - TLSVerification bool `toml:"tls_verification"` -} - -type filter struct { - Offset string `toml:"offset"` - Limit string `toml:"limit"` - Topic string `toml:"topic"` -} - -type config struct { - Remotes remotes `toml:"remotes"` - Filter filter `toml:"filter"` - UserToken string `toml:"user_token"` - RawOutput string `toml:"raw_output"` -} - -// Readable by all user groups but writeable by the user only. -const filePermission = 0o644 - -var ( - errReadFail = errors.New("failed to read config file") - errNoKey = errors.New("no such key") - errUnsupportedKeyValue = errors.New("unsupported data type for key") - errWritingConfig = errors.New("error in writing the updated config to file") - errInvalidURL = errors.New("invalid url") - errURLParseFail = errors.New("failed to parse url") - defaultConfigPath = "./config.toml" -) - -func read(file string) (config, error) { - c := config{} - data, err := os.Open(file) - if err != nil { - return c, errors.Wrap(errReadFail, err) - } - defer data.Close() - - buf, err := io.ReadAll(data) - if err != nil { - return c, errors.Wrap(errReadFail, err) - } - - if err := toml.Unmarshal(buf, &c); err != nil { - return config{}, err - } - - return c, nil -} - -// ParseConfig - parses the config file. -func ParseConfig(sdkConf mgxsdk.Config) (mgxsdk.Config, error) { - if ConfigPath == "" { - ConfigPath = defaultConfigPath - } - - _, err := os.Stat(ConfigPath) - switch { - // If the file does not exist, create it with default values. - case os.IsNotExist(err): - defaultConfig := config{ - Remotes: remotes{ - ThingsURL: defThingsURL, - UsersURL: defUsersURL, - ReaderURL: defReaderURL, - DomainsURL: defDomainsURL, - HTTPAdapterURL: defHTTPURL, - BootstrapURL: defBootstrapURL, - CertsURL: defCertsURL, - InvitationsURL: defInvitationsURL, - JournalURL: defJournalURL, - HostURL: defURL, - TLSVerification: defTLSVerification, - }, - Filter: filter{ - Offset: defOffset, - Limit: defLimit, - Topic: defTopic, - }, - RawOutput: defRawOutput, - } - buf, err := toml.Marshal(defaultConfig) - if err != nil { - return sdkConf, err - } - if err = os.WriteFile(ConfigPath, buf, filePermission); err != nil { - return sdkConf, errors.Wrap(errWritingConfig, err) - } - case err != nil: - return sdkConf, err - } - - config, err := read(ConfigPath) - if err != nil { - return sdkConf, err - } - - if config.Filter.Offset != "" && Offset == 0 { - offset, err := strconv.ParseUint(config.Filter.Offset, 10, 64) - if err != nil { - return sdkConf, err - } - Offset = offset - } - - if config.Filter.Limit != "" && Limit == 0 { - limit, err := strconv.ParseUint(config.Filter.Limit, 10, 64) - if err != nil { - return sdkConf, err - } - Limit = limit - } - - if config.Filter.Topic != "" && Topic == "" { - Topic = config.Filter.Topic - } - - if config.RawOutput != "" { - rawOutput, err := strconv.ParseBool(config.RawOutput) - if err != nil { - return sdkConf, err - } - // check for config file value or flag input value is true - RawOutput = rawOutput || RawOutput - } - - if sdkConf.ThingsURL == "" && config.Remotes.ThingsURL != "" { - sdkConf.ThingsURL = config.Remotes.ThingsURL - } - - if sdkConf.UsersURL == "" && config.Remotes.UsersURL != "" { - sdkConf.UsersURL = config.Remotes.UsersURL - } - - if sdkConf.ReaderURL == "" && config.Remotes.ReaderURL != "" { - sdkConf.ReaderURL = config.Remotes.ReaderURL - } - - if sdkConf.DomainsURL == "" && config.Remotes.DomainsURL != "" { - sdkConf.DomainsURL = config.Remotes.DomainsURL - } - - if sdkConf.HTTPAdapterURL == "" && config.Remotes.HTTPAdapterURL != "" { - sdkConf.HTTPAdapterURL = config.Remotes.HTTPAdapterURL - } - - if sdkConf.BootstrapURL == "" && config.Remotes.BootstrapURL != "" { - sdkConf.BootstrapURL = config.Remotes.BootstrapURL - } - - if sdkConf.CertsURL == "" && config.Remotes.CertsURL != "" { - sdkConf.CertsURL = config.Remotes.CertsURL - } - - if sdkConf.InvitationsURL == "" && config.Remotes.InvitationsURL != "" { - sdkConf.InvitationsURL = config.Remotes.InvitationsURL - } - - if sdkConf.JournalURL == "" && config.Remotes.JournalURL != "" { - sdkConf.JournalURL = config.Remotes.JournalURL - } - - if sdkConf.HostURL == "" && config.Remotes.HostURL != "" { - sdkConf.HostURL = config.Remotes.HostURL - } - - sdkConf.TLSVerification = config.Remotes.TLSVerification || sdkConf.TLSVerification - - return sdkConf, nil -} - -// New config command to store params to local TOML file. -func NewConfigCmd() *cobra.Command { - return &cobra.Command{ - Use: "config ", - Short: "CLI local config", - Long: "Local param storage to prevent repetitive passing of keys", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := setConfigValue(args[0], args[1]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - } -} - -func setConfigValue(key, value string) error { - config, err := read(ConfigPath) - if err != nil { - return err - } - - if strings.Contains(key, "url") { - u, err := url.Parse(value) - if err != nil { - return errors.Wrap(errInvalidURL, err) - } - if u.Scheme == "" || u.Host == "" { - return errors.Wrap(errInvalidURL, err) - } - if u.Scheme != "http" && u.Scheme != "https" { - return errors.Wrap(errURLParseFail, err) - } - } - - configKeyToField := map[string]interface{}{ - "things_url": &config.Remotes.ThingsURL, - "users_url": &config.Remotes.UsersURL, - "reader_url": &config.Remotes.ReaderURL, - "http_adapter_url": &config.Remotes.HTTPAdapterURL, - "bootstrap_url": &config.Remotes.BootstrapURL, - "certs_url": &config.Remotes.CertsURL, - "tls_verification": &config.Remotes.TLSVerification, - "offset": &config.Filter.Offset, - "limit": &config.Filter.Limit, - "topic": &config.Filter.Topic, - "raw_output": &config.RawOutput, - "user_token": &config.UserToken, - } - - fieldPtr, ok := configKeyToField[key] - if !ok { - return errNoKey - } - - fieldValue := reflect.ValueOf(fieldPtr).Elem() - - switch fieldValue.Kind() { - case reflect.String: - fieldValue.SetString(value) - case reflect.Int: - intValue, err := strconv.Atoi(value) - if err != nil { - return err - } - fieldValue.SetUint(uint64(intValue)) - case reflect.Bool: - boolValue, err := strconv.ParseBool(value) - if err != nil { - return err - } - fieldValue.SetBool(boolValue) - default: - return errors.Wrap(errUnsupportedKeyValue, err) - } - - buf, err := toml.Marshal(config) - if err != nil { - return err - } - - if err = os.WriteFile(ConfigPath, buf, filePermission); err != nil { - return errors.Wrap(errWritingConfig, err) - } - - return nil -} diff --git a/cli/consumers.go b/cli/consumers.go deleted file mode 100644 index d6b363e32..000000000 --- a/cli/consumers.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/spf13/cobra" -) - -var cmdSubscription = []cobra.Command{ - { - Use: "create ", - Short: "Create subscription", - Long: `Create new subscription`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - id, err := sdk.CreateSubscription(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logCreatedCmd(*cmd, id) - }, - }, - { - Use: "get [all | ] ", - Short: "Get subscription", - Long: `Get subscription. - all - lists all subscriptions - - view subscription of `, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - pageMetadata := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - Topic: Topic, - Contact: Contact, - } - if args[0] == "all" { - sub, err := sdk.ListSubscriptions(pageMetadata, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, sub) - return - } - - c, err := sdk.ViewSubscription(args[0], args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, c) - }, - }, - { - Use: "remove ", - Short: "Remove subscription", - Long: `Removes removes a subscription with the provided id`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.DeleteSubscription(args[0], args[1]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, -} - -// NewSubscriptionCmd returns subscription command. -func NewSubscriptionCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "subscription [create | get | remove ]", - Short: "Subscription management", - Long: `Subscription management: create, get, or delete subscription`, - } - - for i := range cmdSubscription { - cmd.AddCommand(&cmdSubscription[i]) - } - - return &cmd -} diff --git a/cli/consumers_test.go b/cli/consumers_test.go deleted file mode 100644 index 41f30b4b8..000000000 --- a/cli/consumers_test.go +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var subscription = mgsdk.Subscription{ - ID: testsutil.GenerateUUID(&testing.T{}), - OwnerID: user.ID, - Topic: "topic", - Contact: "identity@example.com", -} - -func TestCreateSubscriptionCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - subCmd := cli.NewSubscriptionCmd() - rootCmd := setFlags(subCmd) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - response string - id string - }{ - { - desc: "create subscription successfully", - args: []string{ - subscription.Topic, - subscription.Contact, - validToken, - }, - id: user.ID, - response: fmt.Sprintf("\ncreated: %s\n\n", user.ID), - logType: createLog, - }, - { - desc: "create subscription with invalid args", - args: []string{ - subscription.Topic, - subscription.Contact, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "create subscription with invalid token", - args: []string{ - subscription.Topic, - subscription.Contact, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("CreateSubscription", tc.args[0], tc.args[1], tc.args[2]).Return(tc.id, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...) - - switch tc.logType { - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - 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 createLog: - assert.Equal(t, tc.response, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.response, out)) - } - sdkCall.Unset() - }) - } -} - -func TestGetSubscriptionsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - subCmd := cli.NewSubscriptionCmd() - rootCmd := setFlags(subCmd) - - var sub mgsdk.Subscription - var page mgsdk.SubscriptionPage - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - page mgsdk.SubscriptionPage - subscription mgsdk.Subscription - logType outputLog - errLogMessage string - }{ - { - desc: "get all subscriptions successfully", - args: []string{ - all, - token, - }, - page: mgsdk.SubscriptionPage{ - Subscriptions: []mgsdk.Subscription{subscription}, - }, - logType: entityLog, - }, - { - desc: "get subscription with id", - args: []string{ - subscription.ID, - token, - }, - logType: entityLog, - subscription: subscription, - }, - { - desc: "get subscriptions with invalid args", - args: []string{ - all, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get all subscriptions with invalid token", - args: []string{ - all, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "get subscription without domain token", - args: []string{ - subscription.ID, - tokenWithoutDomain, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - }, - { - desc: "get subscription with invalid id", - args: []string{ - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ViewSubscription", tc.args[0], tc.args[1]).Return(tc.subscription, tc.sdkErr) - sdkCall1 := sdkMock.On("ListSubscriptions", mock.Anything, tc.args[1]).Return(tc.page, tc.sdkErr) - - out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - if tc.args[1] == all { - err := json.Unmarshal([]byte(out), &page) - assert.Nil(t, err) - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - } else { - err := json.Unmarshal([]byte(out), &sub) - assert.Nil(t, err) - assert.Equal(t, tc.subscription, sub, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.subscription, sub)) - } - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} - -func TestRemoveSubscriptionCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - subCmd := cli.NewSubscriptionCmd() - rootCmd := setFlags(subCmd) - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - logType outputLog - errLogMessage string - }{ - { - desc: "remove subscription successfully", - args: []string{ - subscription.ID, - token, - }, - logType: okLog, - }, - { - desc: "remove subscription with invalid args", - args: []string{ - subscription.ID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "remove subscription with invalid subscription id", - args: []string{ - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "remove subscription with invalid token", - args: []string{ - subscription.ID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DeleteSubscription", tc.args[0], tc.args[1]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{rmCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/doc.go b/cli/doc.go deleted file mode 100644 index 4045431e1..000000000 --- a/cli/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package cli contains the domain concept definitions needed to support -// Magistrala CLI functionality. -package cli diff --git a/cli/domains.go b/cli/domains.go deleted file mode 100644 index 5d66d25dc..000000000 --- a/cli/domains.go +++ /dev/null @@ -1,263 +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/spf13/cobra" -) - -var cmdDomains = []cobra.Command{ - { - Use: "create ", - Short: "Create Domain", - Long: "Create Domain with provided name and alias. \n" + - "For example:\n" + - "\tmagistrala-cli domains create domain_1 domain_1_alias $TOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - dom := mgxsdk.Domain{ - Name: args[0], - Alias: args[1], - } - d, err := sdk.CreateDomain(dom, args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, d) - }, - }, - { - Use: "get [all | ] ", - Short: "Get Domains", - Long: "Get all domains. Users can be filtered by name or metadata or status", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - 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, - Status: Status, - } - if args[0] == all { - l, err := sdk.Domains(pageMetadata, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - return - } - d, err := sdk.Domain(args[0], args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, d) - }, - }, - - { - Use: "users ", - Short: "List Domain users", - Long: "List Domain users", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - metadata, err := convertMetadata(Metadata) - if err != nil { - logErrorCmd(*cmd, err) - return - } - pageMetadata := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - Metadata: metadata, - Status: Status, - } - - l, err := sdk.ListDomainUsers(args[0], pageMetadata, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - }, - }, - - { - Use: "update ", - Short: "Update domains", - Long: "Updates domains name, alias and metadata \n" + - "Usage:\n" + - "\tmagistrala-cli domains update '{\"name\":\"new name\", \"alias\":\"new_alias\", \"metadata\":{\"key\": \"value\"}}' $TOKEN \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 && len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - var d mgxsdk.Domain - - if err := json.Unmarshal([]byte(args[1]), &d); err != nil { - logErrorCmd(*cmd, err) - return - } - d.ID = args[0] - d, err := sdk.UpdateDomain(d, args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, d) - }, - }, - - { - Use: "enable ", - Short: "Change domain status to enabled", - Long: "Change domain status to enabled\n" + - "Usage:\n" + - "\tmagistrala-cli domains enable \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.EnableDomain(args[0], args[1]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, - { - Use: "disable ", - Short: "Change domain status to disabled", - Long: "Change domain status to disabled\n" + - "Usage:\n" + - "\tmagistrala-cli domains disable \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.DisableDomain(args[0], args[1]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, -} - -var domainAssignCmds = []cobra.Command{ - { - Use: "users ", - Short: "Assign users", - Long: "Assign users to a domain\n" + - "Usage:\n" + - "\tmagistrala-cli domains assign users '[\"\", \"\"]' $TOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - var userIDs []string - if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { - logErrorCmd(*cmd, err) - return - } - if err := sdk.AddUserToDomain(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, -} - -var domainUnassignCmds = []cobra.Command{ - { - Use: "users ", - Short: "Unassign users", - Long: "Unassign users from a domain\n" + - "Usage:\n" + - "\tmagistrala-cli domains unassign users $TOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.RemoveUserFromDomain(args[1], args[0], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, -} - -func NewDomainAssignCmds() *cobra.Command { - cmd := cobra.Command{ - Use: "assign [users]", - Short: "Assign users to a domain", - Long: "Assign users to a domain", - } - for i := range domainAssignCmds { - cmd.AddCommand(&domainAssignCmds[i]) - } - return &cmd -} - -func NewDomainUnassignCmds() *cobra.Command { - cmd := cobra.Command{ - Use: "unassign [users]", - Short: "Unassign users from a domain", - Long: "Unassign users from a domain", - } - for i := range domainUnassignCmds { - cmd.AddCommand(&domainUnassignCmds[i]) - } - return &cmd -} - -// NewDomainsCmd returns domains command. -func NewDomainsCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "domains [create | get | update | enable | disable | enable | users | assign | unassign]", - Short: "Domains management", - Long: `Domains management: create, update, retrieve domains , assign/unassign users to domains and list users of domain"`, - } - - for i := range cmdDomains { - cmd.AddCommand(&cmdDomains[i]) - } - - cmd.AddCommand(NewDomainAssignCmds()) - cmd.AddCommand(NewDomainUnassignCmds()) - return &cmd -} diff --git a/cli/domains_test.go b/cli/domains_test.go deleted file mode 100644 index 3a4869003..000000000 --- a/cli/domains_test.go +++ /dev/null @@ -1,669 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var domain = mgsdk.Domain{ - ID: testsutil.GenerateUUID(&testing.T{}), - Name: "Test domain", - Alias: "alias", -} - -func TestCreateDomainsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - domainCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainCmd) - - var dom mgsdk.Domain - - cases := []struct { - desc string - args []string - domain mgsdk.Domain - errLogMessage string - sdkErr errors.SDKError - logType outputLog - }{ - { - desc: "create domain successfully", - args: []string{ - dom.Name, - dom.Alias, - validToken, - }, - logType: entityLog, - domain: domain, - }, - { - desc: "create domain with invalid args", - args: []string{ - dom.Name, - dom.Alias, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "create domain with invalid token", - args: []string{ - dom.Name, - dom.Alias, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("CreateDomain", mock.Anything, mock.Anything).Return(tc.domain, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &dom) - assert.Nil(t, err) - assert.Equal(t, tc.domain, dom, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.domain, dom)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestGetDomainsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - all := "all" - domainCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainCmd) - - var dom mgsdk.Domain - var page mgsdk.DomainsPage - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - page mgsdk.DomainsPage - domain mgsdk.Domain - logType outputLog - errLogMessage string - }{ - { - desc: "get all domains successfully", - args: []string{ - all, - validToken, - }, - page: mgsdk.DomainsPage{ - Domains: []mgsdk.Domain{domain}, - }, - logType: entityLog, - }, - { - desc: "get domain with id", - args: []string{ - domain.ID, - validToken, - }, - logType: entityLog, - domain: domain, - }, - { - desc: "get domains with invalid args", - args: []string{ - all, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get all domains with invalid token", - args: []string{ - all, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "get domain with invalid id", - args: []string{ - invalidID, - validToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Domain", tc.args[0], tc.args[1]).Return(tc.domain, tc.sdkErr) - sdkCall1 := sdkMock.On("Domains", mock.Anything, tc.args[1]).Return(tc.page, tc.sdkErr) - - out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - if tc.args[1] == all { - err := json.Unmarshal([]byte(out), &page) - assert.Nil(t, err) - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - } else { - err := json.Unmarshal([]byte(out), &dom) - assert.Nil(t, err) - assert.Equal(t, tc.domain, dom, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.domain, dom)) - } - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} - -func TestListDomainUsers(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - domainsCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainsCmd) - - page := mgsdk.UsersPage{} - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - page mgsdk.UsersPage - sdkErr errors.SDKError - }{ - { - desc: "list domain users successfully", - args: []string{ - domain.ID, - token, - }, - page: mgsdk.UsersPage{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Users: []mgsdk.User{user}, - }, - logType: entityLog, - }, - { - desc: "list domain users with invalid args", - args: []string{ - domain.ID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list domain users without domain token", - args: []string{ - domain.ID, - tokenWithoutDomain, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "list domain users with invalid id", - args: []string{ - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListDomainUsers", tc.args[0], mock.Anything, tc.args[1]).Return(tc.page, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{usrCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &page) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUpdateDomainCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - domainsCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainsCmd) - - newDomainJson := "{\"name\" : \"New domain\"}" - cases := []struct { - desc string - args []string - domain mgsdk.Domain - sdkErr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "update domain successfully", - args: []string{ - domain.ID, - newDomainJson, - token, - }, - domain: mgsdk.Domain{ - Name: "New domain", - ID: domain.ID, - }, - logType: entityLog, - }, - { - desc: "update domain with invalid args", - args: []string{ - domain.ID, - newDomainJson, - token, - extraArg, - extraArg, - }, - logType: usageLog, - }, - { - desc: "update domain with invalid id", - args: []string{ - invalidID, - newDomainJson, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "update domain with invalid json syntax", - args: []string{ - domain.ID, - "{\"name\" : \"New domain\"", - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var dom mgsdk.Domain - sdkCall := sdkMock.On("UpdateDomain", mock.Anything, tc.args[2]).Return(tc.domain, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{updCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &dom) - assert.Nil(t, err) - assert.Equal(t, tc.domain, dom, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.domain, dom)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestEnableDomainCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - domainsCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainsCmd) - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "enable domain successfully", - args: []string{ - domain.ID, - validToken, - }, - logType: entityLog, - }, - { - desc: "enable domain with invalid token", - args: []string{ - domain.ID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "enable domain with invalid domain id", - args: []string{ - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "enable domain with invalid args", - args: []string{ - domain.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("EnableDomain", tc.args[0], tc.args[1]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{enableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestDisableDomainCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - domainsCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainsCmd) - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "disable domain successfully", - args: []string{ - domain.ID, - validToken, - }, - logType: okLog, - }, - { - desc: "disable domain with invalid token", - args: []string{ - domain.ID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "disable domain with invalid id", - args: []string{ - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "disable domain with invalid args", - args: []string{ - domain.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DisableDomain", tc.args[0], tc.args[1]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{disableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestAssignUserToDomainCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - domainsCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainsCmd) - - userIds := fmt.Sprintf("[\"%s\"]", user.ID) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "assign user successfully", - args: []string{ - relation, - userIds, - domain.ID, - token, - }, - logType: okLog, - }, - { - desc: "assign user with invalid args", - args: []string{ - relation, - userIds, - domain.ID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "assign user with invalid json", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"", user.ID), - domain.ID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "assign user with invalid domain id", - args: []string{ - relation, - userIds, - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "assign user with invalid user id", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"]", invalidID), - domain.ID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("AddUserToDomain", tc.args[2], mock.Anything, tc.args[3]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{assignCmd, usrCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUnassignUserTodomainCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - domainsCmd := cli.NewDomainsCmd() - rootCmd := setFlags(domainsCmd) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "unassign user successfully", - args: []string{ - user.ID, - domain.ID, - token, - }, - logType: okLog, - }, - { - desc: "unassign user with invalid args", - args: []string{ - user.ID, - domain.ID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "unassign user with invalid domain id", - args: []string{ - user.ID, - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "unassign user with invalid user id", - args: []string{ - invalidID, - domain.ID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("RemoveUserFromDomain", tc.args[1], tc.args[0], tc.args[2]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{unassignCmd, usrCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/groups.go b/cli/groups.go deleted file mode 100644 index 867d1ec66..000000000 --- a/cli/groups.go +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "encoding/json" - - "github.com/absmach/magistrala/internal/groups" - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/spf13/cobra" -) - -var cmdGroups = []cobra.Command{ - { - Use: "create ", - Short: "Create group", - Long: "Creates new group\n" + - "Usage:\n" + - "\tmagistrala-cli groups create '{\"name\":\"new group\", \"description\":\"new group description\", \"metadata\":{\"key\": \"value\"}}' $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - var group mgxsdk.Group - if err := json.Unmarshal([]byte(args[0]), &group); err != nil { - logErrorCmd(*cmd, err) - return - } - group.Status = groups.EnabledStatus.String() - group, err := sdk.CreateGroup(group, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, group) - }, - }, - { - Use: "update ", - Short: "Update group", - Long: "Updates group\n" + - "Usage:\n" + - "\tmagistrala-cli groups update '{\"id\":\"\", \"name\":\"new group\", \"description\":\"new group description\", \"metadata\":{\"key\": \"value\"}}' $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - var group mgxsdk.Group - if err := json.Unmarshal([]byte(args[0]), &group); err != nil { - logErrorCmd(*cmd, err) - return - } - - group, err := sdk.UpdateGroup(group, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, group) - }, - }, - { - Use: "get [all | children | parents | members | ] ", - Short: "Get group", - Long: "Get all users groups, group children or group by id.\n" + - "Usage:\n" + - "\tmagistrala-cli groups get all $DOMAINID $USERTOKEN - lists all groups\n" + - "\tmagistrala-cli groups get children $DOMAINID $USERTOKEN - lists all children groups of \n" + - "\tmagistrala-cli groups get parents $DOMAINID $USERTOKEN - lists all parent groups of \n" + - "\tmagistrala-cli groups get $DOMAINID $USERTOKEN - shows group with provided group ID\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - if args[0] == all { - if len(args) > 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - l, err := sdk.Groups(pm, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - return - } - if args[0] == "children" { - if len(args) > 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - DomainID: args[2], - } - l, err := sdk.Children(args[1], pm, args[2], args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - return - } - if args[0] == "parents" { - if len(args) > 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - l, err := sdk.Parents(args[1], pm, args[2], args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - return - } - if len(args) > 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - t, err := sdk.Group(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, t) - }, - }, - { - Use: "delete ", - Short: "Delete group", - Long: "Delete group by id.\n" + - "Usage:\n" + - "\tmagistrala-cli groups delete $DOMAINID $USERTOKEN - delete the given group ID\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - if err := sdk.DeleteGroup(args[0], args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, - { - Use: "users ", - Short: "List users", - Long: "List users in a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups users $DOMAINID $USERTOKEN", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - Status: Status, - } - users, err := sdk.ListGroupUsers(args[0], pm, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, users) - }, - }, - { - Use: "channels ", - Short: "List channels", - Long: "List channels in a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups channels $DOMAINID $USERTOKEN", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - Status: Status, - } - channels, err := sdk.ListGroupChannels(args[0], pm, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, channels) - }, - }, - { - Use: "enable ", - Short: "Change group status to enabled", - Long: "Change group status to enabled\n" + - "Usage:\n" + - "\tmagistrala-cli groups enable $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - group, err := sdk.EnableGroup(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, group) - }, - }, - { - Use: "disable ", - Short: "Change group status to disabled", - Long: "Change group status to disabled\n" + - "Usage:\n" + - "\tmagistrala-cli groups disable $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - group, err := sdk.DisableGroup(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, group) - }, - }, -} - -var groupAssignCmds = []cobra.Command{ - { - Use: "users ", - Short: "Assign users", - Long: "Assign users to a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups assign users '[\"\", \"\"]' $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsageCmd(*cmd, cmd.Use) - return - } - var userIDs []string - if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { - logErrorCmd(*cmd, err) - return - } - if err := sdk.AddUserToGroup(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3], args[4]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, -} - -var groupUnassignCmds = []cobra.Command{ - { - Use: "users ", - Short: "Unassign users", - Long: "Unassign users from a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups unassign users '[\"\", \"\"]' $DOMAINID $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { - logUsageCmd(*cmd, cmd.Use) - return - } - var userIDs []string - if err := json.Unmarshal([]byte(args[1]), &userIDs); err != nil { - logErrorCmd(*cmd, err) - return - } - if err := sdk.RemoveUserFromGroup(args[2], mgxsdk.UsersRelationRequest{Relation: args[0], UserIDs: userIDs}, args[3], args[4]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, -} - -func NewGroupAssignCmds() *cobra.Command { - cmd := cobra.Command{ - Use: "assign [users]", - Short: "Assign users to a group", - Long: "Assign users to a group", - } - - for i := range groupAssignCmds { - cmd.AddCommand(&groupAssignCmds[i]) - } - return &cmd -} - -func NewGroupUnassignCmds() *cobra.Command { - cmd := cobra.Command{ - Use: "unassign [users]", - Short: "Unassign users from a group", - Long: "Unassign users from a group", - } - - for i := range groupUnassignCmds { - cmd.AddCommand(&groupUnassignCmds[i]) - } - return &cmd -} - -// NewGroupsCmd returns users command. -func NewGroupsCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "groups [create | get | update | delete | assign | unassign | users | channels ]", - Short: "Groups management", - Long: `Groups management: create, update, delete group and assign and unassign member to groups"`, - } - - for i := range cmdGroups { - cmd.AddCommand(&cmdGroups[i]) - } - - cmd.AddCommand(NewGroupAssignCmds()) - cmd.AddCommand(NewGroupUnassignCmds()) - return &cmd -} diff --git a/cli/groups_test.go b/cli/groups_test.go deleted file mode 100644 index 5f3daed86..000000000 --- a/cli/groups_test.go +++ /dev/null @@ -1,985 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var group = mgsdk.Group{ - ID: testsutil.GenerateUUID(&testing.T{}), - Name: "testgroup", -} - -func TestCreateGroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupJson := "{\"name\":\"testgroup\", \"metadata\":{\"key1\":\"value1\"}}" - groupCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupCmd) - - gp := mgsdk.Group{} - cases := []struct { - desc string - args []string - logType outputLog - group mgsdk.Group - sdkErr errors.SDKError - errLogMessage string - }{ - { - desc: "create group successfully", - args: []string{ - groupJson, - domainID, - token, - }, - group: group, - logType: entityLog, - }, - { - desc: "create group with invalid args", - args: []string{ - groupJson, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "create group with invalid json", - args: []string{ - "{\"name\":\"testgroup\", \"metadata\":{\"key1\":\"value1\"}", - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "create group with invalid token", - args: []string{ - groupJson, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - { - desc: "create group with invalid domain", - args: []string{ - groupJson, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("CreateGroup", mock.Anything, tc.args[1], tc.args[2]).Return(tc.group, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &gp) - assert.Nil(t, err) - assert.Equal(t, tc.group, gp, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.group, gp)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestGetGroupsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupCmd) - - var ch mgsdk.Group - var page mgsdk.GroupsPage - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - page mgsdk.GroupsPage - group mgsdk.Group - logType outputLog - errLogMessage string - }{ - { - desc: "get all groups successfully", - args: []string{ - all, - domainID, - token, - }, - page: mgsdk.GroupsPage{ - Groups: []mgsdk.Group{group}, - }, - logType: entityLog, - }, - { - desc: "get all groups with invalid args", - args: []string{ - all, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get children groups successfully", - args: []string{ - childCmd, - group.ID, - domainID, - token, - }, - page: mgsdk.GroupsPage{ - Groups: []mgsdk.Group{group}, - }, - logType: entityLog, - }, - { - desc: "get children groups with invalid args", - args: []string{ - childCmd, - group.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get children groups with invalid token", - args: []string{ - childCmd, - group.ID, - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "get parents groups successfully", - args: []string{ - parentCmd, - group.ID, - domainID, - token, - }, - page: mgsdk.GroupsPage{ - Groups: []mgsdk.Group{group}, - }, - logType: entityLog, - }, - { - desc: "get parents groups with invalid args", - args: []string{ - parentCmd, - group.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get parents groups with invalid token", - args: []string{ - parentCmd, - group.ID, - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "get group with id", - args: []string{ - group.ID, - domainID, - token, - }, - logType: entityLog, - group: group, - }, - { - desc: "get groups with invalid args", - args: []string{ - all, - }, - logType: usageLog, - }, - { - desc: "get all groups with invalid token", - args: []string{ - all, - domainID, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "get group with invalid domain", - args: []string{ - group.ID, - invalidID, - token, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - }, - { - desc: "get group with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "get group with invalid args", - args: []string{ - group.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Group", mock.Anything, mock.Anything, mock.Anything).Return(tc.group, tc.sdkErr) - sdkCall1 := sdkMock.On("Groups", mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr) - sdkCall2 := sdkMock.On("Parents", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr) - sdkCall3 := sdkMock.On("Children", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr) - - out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - if tc.args[1] == all { - err := json.Unmarshal([]byte(out), &page) - assert.Nil(t, err) - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - } else { - err := json.Unmarshal([]byte(out), &ch) - assert.Nil(t, err) - assert.Equal(t, tc.group, ch, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.group, ch)) - } - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - sdkCall2.Unset() - sdkCall3.Unset() - }) - } -} - -func TestDeletegroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupCmd) - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - logType outputLog - errLogMessage string - }{ - { - desc: "delete group successfully", - args: []string{ - group.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "delete group with invalid args", - args: []string{ - group.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "delete group with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "delete group with invalid token", - args: []string{ - group.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DeleteGroup", tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{delCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUpdategroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupCmd) - - newGroupJson := fmt.Sprintf("{\"id\":\"%s\",\"name\" : \"newgroup\"}", group.ID) - cases := []struct { - desc string - args []string - group mgsdk.Group - sdkErr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "update group successfully", - args: []string{ - newGroupJson, - domainID, - token, - }, - group: mgsdk.Group{ - Name: "newgroup1", - ID: group.ID, - }, - logType: entityLog, - }, - { - desc: "update group with invalid args", - args: []string{ - newGroupJson, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "update group with invalid group id", - args: []string{ - fmt.Sprintf("{\"id\":\"%s\",\"name\" : \"group1\"}", invalidID), - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "update group with invalid json syntax", - args: []string{ - fmt.Sprintf("{\"id\":\"%s\",\"name\" : \"group1\"", group.ID), - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var ch mgsdk.Group - sdkCall := sdkMock.On("UpdateGroup", mock.Anything, tc.args[1], tc.args[2]).Return(tc.group, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{updCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &ch) - assert.Nil(t, err) - assert.Equal(t, tc.group, ch, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.group, ch)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestListUsersCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupsCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupsCmd) - - var up mgsdk.UsersPage - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - logType outputLog - page mgsdk.UsersPage - }{ - { - desc: "list users successfully", - args: []string{ - group.ID, - domainID, - token, - }, - page: mgsdk.UsersPage{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Users: []mgsdk.User{user}, - }, - logType: entityLog, - }, - { - desc: "list users with invalid args", - args: []string{ - group.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list users with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListGroupUsers", tc.args[0], mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{usrCmd}, tc.args...)...) - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &up) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, up, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, up)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestListChannelsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupsCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupsCmd) - - var cp mgsdk.ChannelsPage - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - logType outputLog - page mgsdk.ChannelsPage - }{ - { - desc: "list channels successfully", - args: []string{ - group.ID, - domainID, - token, - }, - page: mgsdk.ChannelsPage{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Channels: []mgsdk.Channel{channel}, - }, - logType: entityLog, - }, - { - desc: "list channels with invalid args", - args: []string{ - group.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list channels with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListGroupChannels", tc.args[0], mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{chansCmd}, tc.args...)...) - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &cp) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, cp, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, cp)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestEnablegroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupCmd) - var ch mgsdk.Group - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - group mgsdk.Group - logType outputLog - }{ - { - desc: "enable group successfully", - args: []string{ - group.ID, - domainID, - validToken, - }, - group: group, - logType: entityLog, - }, - { - desc: "delete group with invalid token", - args: []string{ - group.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "delete group with invalid group ID", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "enable group with invalid args", - args: []string{ - group.ID, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("EnableGroup", tc.args[0], tc.args[1], tc.args[2]).Return(tc.group, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{enableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &ch) - assert.Nil(t, err) - assert.Equal(t, tc.group, ch, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.group, ch)) - } - - sdkCall.Unset() - }) - } -} - -func TestDisablegroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupsCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupsCmd) - - var ch mgsdk.Group - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - group mgsdk.Group - logType outputLog - }{ - { - desc: "disable group successfully", - args: []string{ - group.ID, - domainID, - validToken, - }, - logType: entityLog, - group: group, - }, - { - desc: "disable group with invalid token", - args: []string{ - group.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "disable group with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "disable thing with invalid args", - args: []string{ - group.ID, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DisableGroup", tc.args[0], tc.args[1], tc.args[2]).Return(tc.group, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{disableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &ch) - if err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) - } - assert.Equal(t, tc.group, ch, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.group, ch)) - } - - sdkCall.Unset() - }) - } -} - -func TestAssignUserToGroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupsCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupsCmd) - - userIds := fmt.Sprintf("[\"%s\"]", user.ID) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "assign user successfully", - args: []string{ - relation, - userIds, - group.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "assign user with invalid args", - args: []string{ - relation, - userIds, - group.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "assign user with invalid json", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"", user.ID), - group.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "assign user with invalid group id", - args: []string{ - relation, - userIds, - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "assign user with invalid user id", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"]", invalidID), - group.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("AddUserToGroup", tc.args[2], mock.Anything, tc.args[3], tc.args[4]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{assignCmd, usrCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUnassignUserToGroupCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - groupsCmd := cli.NewGroupsCmd() - rootCmd := setFlags(groupsCmd) - - userIds := fmt.Sprintf("[\"%s\"]", user.ID) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "unassign user successfully", - args: []string{ - relation, - userIds, - group.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "unassign user with invalid args", - args: []string{ - relation, - userIds, - group.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "unassign user with invalid json", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"", user.ID), - group.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "unassign user with invalid group id", - args: []string{ - relation, - userIds, - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "unassign user with invalid user id", - args: []string{ - relation, - fmt.Sprintf("[\"%s\"]", invalidID), - group.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("RemoveUserFromGroup", tc.args[2], mock.Anything, tc.args[3], tc.args[4]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{unassignCmd, usrCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/health.go b/cli/health.go deleted file mode 100644 index b66d8be33..000000000 --- a/cli/health.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import "github.com/spf13/cobra" - -// NewHealthCmd returns health check command. -func NewHealthCmd() *cobra.Command { - return &cobra.Command{ - Use: "health ", - Short: "Health Check", - Long: "Magistrala service Health Check\n" + - "usage:\n" + - "\tmagistrala-cli health ", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 1 { - logUsageCmd(*cmd, cmd.Use) - return - } - v, err := sdk.Health(args[0]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, v) - }, - } -} diff --git a/cli/health_test.go b/cli/health_test.go deleted file mode 100644 index 162732563..000000000 --- a/cli/health_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/pkg/errors" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestHealthCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - healthCmd := cli.NewHealthCmd() - rootCmd := setFlags(healthCmd) - service := "users" - - var health mgsdk.HealthInfo - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - health mgsdk.HealthInfo - sdkErr errors.SDKError - }{ - { - desc: "Check health successfully", - args: []string{ - service, - }, - logType: entityLog, - health: mgsdk.HealthInfo{ - Status: "pass", - Description: "users service", - }, - }, - { - desc: "Check health with invalid args", - args: []string{ - service, - extraArg, - }, - logType: usageLog, - }, - { - desc: "Check health with invalid service", - args: []string{ - "invalid", - }, - sdkErr: errors.NewSDKErrorWithStatus(errors.New("unsupported protocol scheme"), 306), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(errors.New("unsupported protocol scheme"), 306)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Health", mock.Anything).Return(tc.health, tc.sdkErr) - out := executeCommand(t, rootCmd, tc.args...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &health) - assert.Nil(t, err) - assert.Equal(t, tc.health, health, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.health, health)) - 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: - assert.True(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/invitations.go b/cli/invitations.go deleted file mode 100644 index 379187c8b..000000000 --- a/cli/invitations.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/spf13/cobra" -) - -var cmdInvitations = []cobra.Command{ - { - Use: "send ", - Short: "Send invitation", - Long: "Send invitation to user\n" + - "For example:\n" + - "\tmagistrala-cli invitations send 39f97daf-d6b6-40f4-b229-2697be8006ef 4ef09eff-d500-4d56-b04f-d23a512d6f2a administrator $USER_AUTH_TOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - inv := mgxsdk.Invitation{ - UserID: args[0], - DomainID: args[1], - Relation: args[2], - } - if err := sdk.SendInvitation(inv, args[3]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "get [all | ] ", - Short: "Get invitations", - Long: "Get invitations\n" + - "Usage:\n" + - "\tmagistrala-cli invitations get all - lists all invitations\n" + - "\tmagistrala-cli invitations get all --offset --limit - lists all invitations with provided offset and limit\n" + - "\tmagistrala-cli invitations get - shows invitation by user id and domain id\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 && len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - pageMetadata := mgxsdk.PageMetadata{ - Identity: Identity, - Offset: Offset, - Limit: Limit, - } - if args[0] == all { - l, err := sdk.Invitations(pageMetadata, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - return - } - u, err := sdk.Invitation(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, u) - }, - }, - { - Use: "accept ", - Short: "Accept invitation", - Long: "Accept invitation to domain\n" + - "Usage:\n" + - "\tmagistrala-cli invitations accept 39f97daf-d6b6-40f4-b229-2697be8006ef $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.AcceptInvitation(args[0], args[1]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "reject ", - Short: "Reject invitation", - Long: "Reject invitation to domain\n" + - "Usage:\n" + - "\tmagistrala-cli invitations reject 39f97daf-d6b6-40f4-b229-2697be8006ef $USER_AUTH_TOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.RejectInvitation(args[0], args[1]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "delete ", - Short: "Delete invitation", - Long: "Delete invitation\n" + - "Usage:\n" + - "\tmagistrala-cli invitations delete 39f97daf-d6b6-40f4-b229-2697be8006ef 4ef09eff-d500-4d56-b04f-d23a512d6f2a $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.DeleteInvitation(args[0], args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, -} - -// NewInvitationsCmd returns invitations command. -func NewInvitationsCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "invitations [send | get | accept | delete]", - Short: "Invitations management", - Long: `Invitations management to send, get, accept and delete invitations`, - } - - for i := range cmdInvitations { - cmd.AddCommand(&cmdInvitations[i]) - } - - return &cmd -} diff --git a/cli/invitations_test.go b/cli/invitations_test.go deleted file mode 100644 index 43b9bb86a..000000000 --- a/cli/invitations_test.go +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var invitation = mgsdk.Invitation{ - InvitedBy: testsutil.GenerateUUID(&testing.T{}), - UserID: user.ID, - DomainID: domain.ID, -} - -func TestSendUserInvitationCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - invCmd := cli.NewInvitationsCmd() - rootCmd := setFlags(invCmd) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "send invitation successfully", - args: []string{ - user.ID, - domain.ID, - relation, - validToken, - }, - logType: okLog, - }, - { - desc: "send invitation with invalid args", - args: []string{ - user.ID, - domain.ID, - relation, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "send invitation with invalid token", - args: []string{ - user.ID, - domain.ID, - relation, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("SendInvitation", mock.Anything, mock.Anything).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{sendCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestGetInvitationCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - invCmd := cli.NewInvitationsCmd() - rootCmd := setFlags(invCmd) - - var inv mgsdk.Invitation - var page mgsdk.InvitationPage - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - page mgsdk.InvitationPage - inv mgsdk.Invitation - logType outputLog - errLogMessage string - }{ - { - desc: "get all invitations successfully", - args: []string{ - all, - token, - }, - page: mgsdk.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []mgsdk.Invitation{invitation}, - }, - logType: entityLog, - }, - { - desc: "get invitation with user id", - args: []string{ - user.ID, - domain.ID, - token, - }, - logType: entityLog, - inv: invitation, - }, - { - desc: "get invitation with invalid args", - args: []string{ - all, - token, - extraArg, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get all invitations with invalid token", - args: []string{ - all, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "get invitation with invalid token", - args: []string{ - user.ID, - domain.ID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Invitation", tc.args[0], tc.args[1], mock.Anything).Return(tc.inv, tc.sdkErr) - sdkCall1 := sdkMock.On("Invitations", mock.Anything, tc.args[1]).Return(tc.page, tc.sdkErr) - - out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - if tc.args[0] == all { - err := json.Unmarshal([]byte(out), &page) - assert.Nil(t, err) - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - } else { - err := json.Unmarshal([]byte(out), &inv) - assert.Nil(t, err) - assert.Equal(t, tc.inv, inv, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.inv, inv)) - } - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} - -func TestAcceptInvitationCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - invCmd := cli.NewInvitationsCmd() - rootCmd := setFlags(invCmd) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "accept invitation successfully", - args: []string{ - domain.ID, - validToken, - }, - logType: okLog, - }, - { - desc: "accept invitation with invalid args", - args: []string{ - domain.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "accept invitation with invalid token", - args: []string{ - domain.ID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("AcceptInvitation", mock.Anything, mock.Anything).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{acceptCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestRejectInvitationCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - invCmd := cli.NewInvitationsCmd() - rootCmd := setFlags(invCmd) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "reject invitation successfully", - args: []string{ - domain.ID, - validToken, - }, - logType: okLog, - }, - { - desc: "reject invitation with invalid args", - args: []string{ - domain.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "reject invitation with invalid token", - args: []string{ - domain.ID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("RejectInvitation", mock.Anything, mock.Anything).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{rejectCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestDeleteInvitationCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - invCmd := cli.NewInvitationsCmd() - rootCmd := setFlags(invCmd) - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "delete invitation successfully", - args: []string{ - user.ID, - domain.ID, - validToken, - }, - logType: okLog, - }, - { - desc: "delete invitation with invalid args", - args: []string{ - user.ID, - domain.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "delete invitation with invalid token", - args: []string{ - user.ID, - domain.ID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DeleteInvitation", mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{delCmd}, tc.args...)...) - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/journal.go b/cli/journal.go deleted file mode 100644 index 90298da8b..000000000 --- a/cli/journal.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/spf13/cobra" -) - -var cmdJournal = cobra.Command{ - Use: "get ", - Short: "Get journal", - Long: "Get journal\n" + - "Usage:\n" + - "\tmagistrala-cli journal get user - lists user journal logs\n" + - "\tmagistrala-cli journal get - lists entity journal logs\n" + - "\tmagistrala-cli journal get --offset --limit - lists user journal logs with provided offset and limit\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 3 || len(args) > 4 { - logUsageCmd(*cmd, cmd.Use) - return - } - pageMetadata := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - - entityType, entityID, token := args[0], args[1], args[2] - domainID := "" - if len(args) == 4 { - entityType, entityID, domainID, token = args[0], args[1], args[2], args[3] - } - - journal, err := sdk.Journal(entityType, entityID, domainID, pageMetadata, token) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, journal) - }, -} - -// NewJournalCmd returns journal log command. -func NewJournalCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "journal get", - Short: "journal log", - Long: `journal to read journal log`, - } - - cmd.AddCommand(&cmdJournal) - - return &cmd -} diff --git a/cli/journal_test.go b/cli/journal_test.go deleted file mode 100644 index 5ff42ecb6..000000000 --- a/cli/journal_test.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var journal = mgsdk.Journal{ - ID: testsutil.GenerateUUID(&testing.T{}), -} - -func TestGetJournalCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - invCmd := cli.NewJournalCmd() - rootCmd := setFlags(invCmd) - - var page mgsdk.JournalsPage - entityType := "group" - entityId := testsutil.GenerateUUID(t) - domainId := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - page mgsdk.JournalsPage - logType outputLog - errLogMessage string - }{ - { - desc: "get user journal", - args: []string{ - "user", - entityId, - token, - }, - logType: entityLog, - page: mgsdk.JournalsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Journals: []mgsdk.Journal{journal}, - }, - }, - { - desc: "get group journal", - args: []string{ - entityType, - entityId, - domainId, - token, - }, - logType: entityLog, - page: mgsdk.JournalsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Journals: []mgsdk.Journal{journal}, - }, - }, - { - desc: "get journal with invalid args", - args: []string{ - entityType, - entityId, - token, - domainId, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get journal with invalid token", - args: []string{ - entityType, - entityId, - domainId, - invalidToken, - }, - logType: errLog, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Journal", tc.args[0], tc.args[1], "", mock.Anything, tc.args[2]).Return(tc.page, tc.sdkErr) - if tc.args[0] != "user" { - sdkCall = sdkMock.On("Journal", tc.args[0], tc.args[1], tc.args[2], mock.Anything, tc.args[3]).Return(tc.page, tc.sdkErr) - } - out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &page) - assert.Nil(t, err) - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/message.go b/cli/message.go deleted file mode 100644 index e4cfc0b27..000000000 --- a/cli/message.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/spf13/cobra" -) - -var cmdMessages = []cobra.Command{ - { - Use: "send ", - Short: "Send messages", - Long: `Sends message on the channel`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.SendMessage(args[0], args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "read ", - Short: "Read messages", - Long: "Reads all channel messages\n" + - "Usage:\n" + - "\tmagistrala-cli messages read --offset --limit - lists all messages with provided offset and limit\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - pageMetadata := mgxsdk.MessagePageMetadata{ - PageMetadata: mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - }, - } - - m, err := sdk.ReadMessages(pageMetadata, args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, m) - }, - }, -} - -// NewMessagesCmd returns messages command. -func NewMessagesCmd() *cobra.Command { - cmd := cobra.Command{ - Use: "messages [send | read]", - Short: "Send or read messages", - Long: `Send or read messages using the http-adapter and the configured database reader`, - } - - for i := range cmdMessages { - cmd.AddCommand(&cmdMessages[i]) - } - - return &cmd -} diff --git a/cli/message_test.go b/cli/message_test.go deleted file mode 100644 index a145fe602..000000000 --- a/cli/message_test.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/absmach/magistrala/pkg/transformers/senml" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestSendMesageCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - messageCmd := cli.NewMessagesCmd() - rootCmd := setFlags(messageCmd) - - message := "[{\"bn\":\"Dev1\",\"n\":\"temp\",\"v\":20}, {\"n\":\"hum\",\"v\":40}, {\"bn\":\"Dev2\", \"n\":\"temp\",\"v\":20}, {\"n\":\"hum\",\"v\":40}]" - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "send message successfully", - args: []string{ - channel.ID, - message, - thing.Credentials.Secret, - }, - logType: okLog, - }, - { - desc: "send message with invalid args", - args: []string{ - channel.ID, - message, - thing.Credentials.Secret, - extraArg, - }, - logType: usageLog, - }, - { - desc: "send message with invalid thing secret", - args: []string{ - channel.ID, - message, - "invalid_secret", - }, - sdkErr: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrNotFound)), http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthentication, errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrNotFound)), http.StatusBadRequest)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("SendMessage", tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{sendCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestReadMesageCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - messageCmd := cli.NewMessagesCmd() - rootCmd := setFlags(messageCmd) - - var mp mgsdk.MessagesPage - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - sdkErr errors.SDKError - page mgsdk.MessagesPage - }{ - { - desc: "read message successfully", - args: []string{ - channel.ID, - domainID, - validToken, - }, - page: mgsdk.MessagesPage{ - PageRes: mgsdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Messages: []senml.Message{ - { - Channel: channel.ID, - }, - }, - }, - logType: entityLog, - }, - { - desc: "read message with invalid args", - args: []string{ - channel.ID, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "read message with invalid token", - args: []string{ - channel.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ReadMessages", mock.Anything, tc.args[0], tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{readCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &mp) - assert.Nil(t, err) - assert.Equal(t, tc.page, mp, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.page, mp)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/provision.go b/cli/provision.go deleted file mode 100644 index 6811a290d..000000000 --- a/cli/provision.go +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "encoding/csv" - "encoding/json" - "errors" - "fmt" - "io" - "math/rand" - "os" - "path/filepath" - "time" - - "github.com/0x6flab/namegenerator" - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/spf13/cobra" -) - -const ( - jsonExt = ".json" - csvExt = ".csv" -) - -var ( - msgFormat = `[{"bn":"provision:", "bu":"V", "t": %d, "bver":5, "n":"voltage", "u":"V", "v":%d}]` - namesgenerator = namegenerator.NewGenerator() -) - -var cmdProvision = []cobra.Command{ - { - Use: "things ", - Short: "Provision things", - Long: `Bulk create things`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if _, err := os.Stat(args[0]); os.IsNotExist(err) { - logErrorCmd(*cmd, err) - return - } - - things, err := thingsFromFile(args[0]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - things, err = sdk.CreateThings(things, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, things) - }, - }, - { - Use: "channels ", - Short: "Provision channels", - Long: `Bulk create channels`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - channels, err := channelsFromFile(args[0]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - var chs []mgxsdk.Channel - for _, c := range channels { - c, err = sdk.CreateChannel(c, args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - chs = append(chs, c) - } - channels = chs - - logJSONCmd(*cmd, channels) - }, - }, - { - Use: "connect ", - Short: "Provision connections", - Long: `Bulk connect things to channels`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - connIDs, err := connectionsFromFile(args[0]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - for _, conn := range connIDs { - if err := sdk.Connect(conn, args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - } - - logOKCmd(*cmd) - }, - }, - { - 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.`, - Run: func(cmd *cobra.Command, args []string) { - numThings := 2 - numChan := 2 - things := []mgxsdk.Thing{} - channels := []mgxsdk.Channel{} - - if len(args) != 0 { - logUsageCmd(*cmd, cmd.Use) - return - } - - // Create test user - name := namesgenerator.Generate() - user := mgxsdk.User{ - FirstName: name, - Email: fmt.Sprintf("%s@email.com", name), - Credentials: mgxsdk.Credentials{ - Username: name, - Secret: "12345678", - }, - Status: mgxsdk.EnabledStatus, - } - user, err := sdk.CreateUser(user, "") - if err != nil { - logErrorCmd(*cmd, err) - return - } - - ut, err := sdk.CreateToken(mgxsdk.Login{Identity: user.Credentials.Username, Secret: user.Credentials.Secret}) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - // create domain - domain := mgxsdk.Domain{ - Name: fmt.Sprintf("%s-domain", name), - Status: mgxsdk.EnabledStatus, - } - domain, err = sdk.CreateDomain(domain, ut.AccessToken) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - ut, err = sdk.CreateToken(mgxsdk.Login{Identity: user.Email, Secret: user.Credentials.Secret}) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - // Create things - for i := 0; i < numThings; i++ { - t := mgxsdk.Thing{ - Name: fmt.Sprintf("%s-thing-%d", name, i), - Status: mgxsdk.EnabledStatus, - } - - things = append(things, t) - } - things, err = sdk.CreateThings(things, domain.ID, ut.AccessToken) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - // Create channels - for i := 0; i < numChan; i++ { - c := mgxsdk.Channel{ - Name: fmt.Sprintf("%s-channel-%d", name, i), - Status: mgxsdk.EnabledStatus, - } - c, err = sdk.CreateChannel(c, domain.ID, ut.AccessToken) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - channels = append(channels, c) - } - - // Connect things to channels - first thing to both channels, second only to first - conIDs := mgxsdk.Connection{ - ChannelID: channels[0].ID, - ThingID: things[0].ID, - } - if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil { - logErrorCmd(*cmd, err) - return - } - - conIDs = mgxsdk.Connection{ - ChannelID: channels[1].ID, - ThingID: things[0].ID, - } - if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil { - logErrorCmd(*cmd, err) - return - } - - conIDs = mgxsdk.Connection{ - ChannelID: channels[0].ID, - ThingID: things[1].ID, - } - if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil { - logErrorCmd(*cmd, err) - return - } - - // 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 { - 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 { - 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 { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user, ut, things, channels) - }, - }, -} - -// 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`, - } - - for i := range cmdProvision { - cmd.AddCommand(&cmdProvision[i]) - } - - return &cmd -} - -func thingsFromFile(path string) ([]mgxsdk.Thing, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return []mgxsdk.Thing{}, err - } - - file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm) - if err != nil { - return []mgxsdk.Thing{}, err - } - defer file.Close() - - things := []mgxsdk.Thing{} - switch filepath.Ext(path) { - case csvExt: - reader := csv.NewReader(file) - - for { - l, err := reader.Read() - if err == io.EOF { - break - } - if err != nil { - return []mgxsdk.Thing{}, err - } - - if len(l) < 1 { - return []mgxsdk.Thing{}, errors.New("empty line found in file") - } - - thing := mgxsdk.Thing{ - Name: l[0], - } - - things = append(things, thing) - } - case jsonExt: - err := json.NewDecoder(file).Decode(&things) - if err != nil { - return []mgxsdk.Thing{}, err - } - default: - return []mgxsdk.Thing{}, err - } - - return things, nil -} - -func channelsFromFile(path string) ([]mgxsdk.Channel, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return []mgxsdk.Channel{}, err - } - - file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm) - if err != nil { - return []mgxsdk.Channel{}, err - } - defer file.Close() - - channels := []mgxsdk.Channel{} - switch filepath.Ext(path) { - case csvExt: - reader := csv.NewReader(file) - - for { - l, err := reader.Read() - if err == io.EOF { - break - } - if err != nil { - return []mgxsdk.Channel{}, err - } - - if len(l) < 1 { - return []mgxsdk.Channel{}, errors.New("empty line found in file") - } - - channel := mgxsdk.Channel{ - Name: l[0], - } - - channels = append(channels, channel) - } - case jsonExt: - err := json.NewDecoder(file).Decode(&channels) - if err != nil { - return []mgxsdk.Channel{}, err - } - default: - return []mgxsdk.Channel{}, err - } - - return channels, nil -} - -func connectionsFromFile(path string) ([]mgxsdk.Connection, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return []mgxsdk.Connection{}, err - } - - file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm) - if err != nil { - return []mgxsdk.Connection{}, err - } - defer file.Close() - - connections := []mgxsdk.Connection{} - switch filepath.Ext(path) { - case csvExt: - reader := csv.NewReader(file) - - for { - l, err := reader.Read() - if err == io.EOF { - break - } - if err != nil { - return []mgxsdk.Connection{}, err - } - - if len(l) < 1 { - return []mgxsdk.Connection{}, errors.New("empty line found in file") - } - connections = append(connections, mgxsdk.Connection{ - ThingID: l[0], - ChannelID: l[1], - }) - } - case jsonExt: - err := json.NewDecoder(file).Decode(&connections) - if err != nil { - return []mgxsdk.Connection{}, err - } - default: - return []mgxsdk.Connection{}, err - } - - return connections, nil -} diff --git a/cli/sdk.go b/cli/sdk.go deleted file mode 100644 index 9f7e273cb..000000000 --- a/cli/sdk.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - -// Keep SDK handle in global var. -var sdk mgxsdk.SDK - -// SetSDK sets magistrala SDK instance. -func SetSDK(s mgxsdk.SDK) { - sdk = s -} diff --git a/cli/setup_test.go b/cli/setup_test.go deleted file mode 100644 index 71099fdfe..000000000 --- a/cli/setup_test.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "bytes" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" -) - -type outputLog uint8 - -const ( - usageLog outputLog = iota - errLog - entityLog - okLog - createLog - revokeLog -) - -func executeCommand(t *testing.T, root *cobra.Command, args ...string) string { - buffer := new(bytes.Buffer) - root.SetOut(buffer) - root.SetErr(buffer) - root.SetArgs(args) - err := root.Execute() - assert.NoError(t, err, "Error executing command") - return buffer.String() -} - -func setFlags(rootCmd *cobra.Command) *cobra.Command { - // Root Flags - rootCmd.PersistentFlags().BoolVarP( - &cli.RawOutput, - "raw", - "r", - cli.RawOutput, - "Enables raw output mode for easier parsing of output", - ) - - // Client and Channels Flags - rootCmd.PersistentFlags().Uint64VarP( - &cli.Limit, - "limit", - "l", - 10, - "Limit query parameter", - ) - - rootCmd.PersistentFlags().Uint64VarP( - &cli.Offset, - "offset", - "o", - 0, - "Offset query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Name, - "name", - "n", - "", - "Name query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Identity, - "identity", - "I", - "", - "User identity query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Metadata, - "metadata", - "m", - "", - "Metadata query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Status, - "status", - "S", - "", - "User status query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.State, - "state", - "z", - "", - "Bootstrap state query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Topic, - "topic", - "T", - "", - "Subscription topic query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Contact, - "contact", - "C", - "", - "Subscription contact query parameter", - ) - - return rootCmd -} diff --git a/cli/things.go b/cli/things.go deleted file mode 100644 index b5ec1ad46..000000000 --- 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/things_test.go b/cli/things_test.go deleted file mode 100644 index f9b403d95..000000000 --- a/cli/things_test.go +++ /dev/null @@ -1,1243 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "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" -) - -var ( - token = "valid" + "domaintoken" - domainID = "domain-id" - tokenWithoutDomain = "valid" - relation = "administrator" - all = "all" -) - -var thing = sdk.Thing{ - ID: testsutil.GenerateUUID(&testing.T{}), - Name: "testthing", - Credentials: sdk.ClientCredentials{ - Secret: "secret", - }, - DomainID: testsutil.GenerateUUID(&testing.T{}), - Status: things.EnabledStatus.String(), -} - -func TestCreateThingsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingJson := "{\"name\":\"testthing\", \"metadata\":{\"key1\":\"value1\"}}" - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - var tg sdk.Thing - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - thing sdk.Thing - logType outputLog - }{ - { - desc: "create thing successfully with token", - args: []string{ - thingJson, - domainID, - token, - }, - thing: thing, - logType: entityLog, - }, - { - desc: "create thing without token", - args: []string{ - thingJson, - domainID, - }, - logType: usageLog, - }, - { - desc: "create thing with invalid token", - args: []string{ - thingJson, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - { - desc: "failed to create thing", - args: []string{ - thingJson, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity)), - logType: errLog, - }, - { - desc: "create thing with invalid metadata", - args: []string{ - "{\"name\":\"testthing\", \"metadata\":{\"key1\":value1}}", - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(errors.New("invalid character 'v' looking for beginning of value"), 306), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("invalid character 'v' looking for beginning of value")), - logType: errLog, - }, - } - - 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) - 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)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestGetThingsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - var tg sdk.Thing - var page sdk.ThingsPage - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - thing sdk.Thing - page sdk.ThingsPage - logType outputLog - }{ - { - desc: "get all things successfully", - args: []string{ - all, - domainID, - token, - }, - logType: entityLog, - page: sdk.ThingsPage{ - Things: []sdk.Thing{thing}, - }, - }, - { - desc: "get thing successfully with id", - args: []string{ - thing.ID, - domainID, - token, - }, - logType: entityLog, - thing: thing, - }, - { - desc: "get things with invalid token", - args: []string{ - all, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - page: sdk.ThingsPage{}, - logType: errLog, - }, - { - desc: "get things with invalid args", - args: []string{ - all, - invalidToken, - all, - invalidToken, - all, - invalidToken, - all, - invalidToken, - }, - logType: usageLog, - }, - { - desc: "get thing without token", - args: []string{ - all, - domainID, - }, - logType: usageLog, - }, - { - desc: "get thing with invalid thing id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - 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) - - out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - if tc.logType == entityLog { - switch { - case tc.args[1] == all: - err := json.Unmarshal([]byte(out), &page) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - default: - err := json.Unmarshal([]byte(out), &tg) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - } - } - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - 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)) - } else { - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - } - } - - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} - -func TestUpdateThingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - tagUpdateType := "tags" - secretUpdateType := "secret" - newTagsJson := "[\"tag1\", \"tag2\"]" - newTagString := []string{"tag1", "tag2"} - newNameandMeta := "{\"name\": \"thingName\", \"metadata\": {\"role\": \"general\"}}" - newSecret := "secret" - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - thing sdk.Thing - logType outputLog - }{ - { - desc: "update thing name and metadata successfully", - args: []string{ - thing.ID, - newNameandMeta, - domainID, - token, - }, - thing: sdk.Thing{ - Name: "thingName", - Metadata: map[string]interface{}{ - "metadata": map[string]interface{}{ - "role": "general", - }, - }, - ID: thing.ID, - DomainID: thing.DomainID, - Status: thing.Status, - }, - logType: entityLog, - }, - { - desc: "update thing name and metadata with invalid json", - args: []string{ - thing.ID, - "{\"name\": \"thingName\", \"metadata\": {\"role\": \"general\"}", - domainID, - token, - }, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "update thing name and metadata with invalid thing id", - args: []string{ - invalidID, - newNameandMeta, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "update thing tags successfully", - args: []string{ - tagUpdateType, - thing.ID, - newTagsJson, - domainID, - token, - }, - thing: sdk.Thing{ - Name: thing.Name, - ID: thing.ID, - DomainID: thing.DomainID, - Status: thing.Status, - Tags: newTagString, - }, - logType: entityLog, - }, - { - desc: "update thing with invalid tags", - args: []string{ - tagUpdateType, - thing.ID, - "[\"tag1\", \"tag2\"", - domainID, - token, - }, - logType: errLog, - sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - }, - { - desc: "update thing tags with invalid thing id", - args: []string{ - tagUpdateType, - invalidID, - newTagsJson, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "update thing secret successfully", - args: []string{ - secretUpdateType, - thing.ID, - newSecret, - domainID, - token, - }, - thing: sdk.Thing{ - Name: thing.Name, - ID: thing.ID, - DomainID: thing.DomainID, - Status: thing.Status, - Credentials: sdk.ClientCredentials{ - Secret: newSecret, - }, - }, - logType: entityLog, - }, - { - desc: "update thing with invalid secret", - args: []string{ - secretUpdateType, - thing.ID, - "", - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingSecret), http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingSecret), http.StatusBadRequest)), - logType: errLog, - }, - { - desc: "update thing with invalid token", - args: []string{ - secretUpdateType, - thing.ID, - newSecret, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "update thing with invalid args", - args: []string{ - secretUpdateType, - thing.ID, - newSecret, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - } - - 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) - - switch { - case tc.args[0] == tagUpdateType: - var th sdk.Thing - th.Tags = []string{"tag1", "tag2"} - th.ID = tc.args[1] - - sdkCall1 = sdkMock.On("UpdateThingTags", th, tc.args[3]).Return(tc.thing, tc.sdkErr) - case tc.args[0] == secretUpdateType: - var th sdk.Thing - 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) - } - out := executeCommand(t, rootCmd, append([]string{updCmd}, 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)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - sdkCall.Unset() - sdkCall1.Unset() - sdkCall2.Unset() - }) - } -} - -func TestDeleteThingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "delete thing successfully", - args: []string{ - thing.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "delete thing with invalid token", - args: []string{ - thing.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "delete thing with invalid thing id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "delete thing with invalid args", - args: []string{ - thing.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - } - - 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) - out := executeCommand(t, rootCmd, append([]string{delCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestEnableThingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - var tg sdk.Thing - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - thing sdk.Thing - logType outputLog - }{ - { - desc: "enable thing successfully", - args: []string{ - thing.ID, - domainID, - validToken, - }, - sdkErr: nil, - thing: thing, - logType: entityLog, - }, - { - desc: "delete thing with invalid token", - args: []string{ - thing.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "delete thing with invalid thing ID", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "enable thing with invalid args", - args: []string{ - thing.ID, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - 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) - out := executeCommand(t, rootCmd, append([]string{enableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - 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)) - } - - sdkCall.Unset() - }) - } -} - -func TestDisablethingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - var tg sdk.Thing - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - errLogMessage string - thing sdk.Thing - logType outputLog - }{ - { - desc: "disable thing successfully", - args: []string{ - thing.ID, - domainID, - validToken, - }, - logType: entityLog, - thing: thing, - }, - { - desc: "delete thing with invalid token", - args: []string{ - thing.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "delete thing with invalid thing ID", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "disable thing with invalid args", - args: []string{ - thing.ID, - domainID, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - 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) - out := executeCommand(t, rootCmd, append([]string{disableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &tg) - 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)) - } - - sdkCall.Unset() - }) - } -} - -func TestUsersThingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - page := sdk.UsersPage{} - - cases := []struct { - desc string - args []string - logType outputLog - errLogMessage string - page sdk.UsersPage - sdkErr errors.SDKError - }{ - { - desc: "get thing's users successfully", - args: []string{ - thing.ID, - domainID, - token, - }, - page: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Users: []sdk.User{user}, - }, - logType: entityLog, - }, - { - desc: "list thing users' with invalid args", - args: []string{ - thing.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list thing users' with invalid domain", - args: []string{ - thing.ID, - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "list thing users with invalid id", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - 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) - out := executeCommand(t, rootCmd, append([]string{usrCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &page) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestConnectThingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - cases := []struct { - desc string - args []string - logType outputLog - sdkErr errors.SDKError - errLogMessage string - }{ - { - desc: "Connect thing to channel successfully", - args: []string{ - thing.ID, - channel.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "connect with invalid args", - args: []string{ - thing.ID, - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "connect with invalid thing id", - args: []string{ - invalidID, - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - { - desc: "connect with invalid channel id", - args: []string{ - thing.ID, - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "list thing users' with invalid domain", - args: []string{ - thing.ID, - channel.ID, - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Connect", mock.Anything, tc.args[2], tc.args[3]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{connCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestDisconnectThingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - cases := []struct { - desc string - args []string - logType outputLog - sdkErr errors.SDKError - errLogMessage string - }{ - { - desc: "Disconnect thing to channel successfully", - args: []string{ - thing.ID, - channel.ID, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "Disconnect with invalid args", - args: []string{ - thing.ID, - channel.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "disconnect with invalid thing id", - args: []string{ - invalidID, - channel.ID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - { - desc: "disconnect with invalid channel id", - args: []string{ - thing.ID, - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "disconnect thing with invalid domain", - args: []string{ - thing.ID, - channel.ID, - invalidID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrDomainAuthorization, http.StatusForbidden)), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Disconnect", mock.Anything, tc.args[2], tc.args[3]).Return(tc.sdkErr) - out := executeCommand(t, rootCmd, append([]string{disconnCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestListConnectionCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - cp := sdk.ChannelsPage{} - cases := []struct { - desc string - args []string - logType outputLog - page sdk.ChannelsPage - errLogMessage string - sdkErr errors.SDKError - }{ - { - desc: "list connections successfully", - args: []string{ - thing.ID, - domainID, - token, - }, - page: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Channels: []sdk.Channel{channel}, - }, - logType: entityLog, - }, - { - desc: "list connections with invalid args", - args: []string{ - thing.ID, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list connections with invalid thing ID", - args: []string{ - invalidID, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "list connections with invalid token", - args: []string{ - thing.ID, - domainID, - invalidToken, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)), - logType: errLog, - }, - } - 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) - out := executeCommand(t, rootCmd, append([]string{connsCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &cp) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - assert.Equal(t, tc.page, cp, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, cp)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestShareThingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - cases := []struct { - desc string - args []string - logType outputLog - sdkErr errors.SDKError - errLogMessage string - }{ - { - desc: "share thing successfully", - args: []string{ - thing.ID, - user.ID, - relation, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "share thing with invalid user id", - args: []string{ - thing.ID, - invalidID, - relation, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAddPolicies, http.StatusBadRequest)), - logType: errLog, - }, - { - desc: "share thing with invalid thing ID", - args: []string{ - invalidID, - user.ID, - relation, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "share thing with invalid args", - args: []string{ - thing.ID, - user.ID, - relation, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "share thing with invalid relation", - args: []string{ - thing.ID, - user.ID, - "invalid", - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusBadRequest)), - logType: errLog, - }, - } - 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) - out := executeCommand(t, rootCmd, append([]string{shrCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} - -func TestUnshareThingCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - thingsCmd := cli.NewThingsCmd() - rootCmd := setFlags(thingsCmd) - - cases := []struct { - desc string - args []string - logType outputLog - sdkErr errors.SDKError - errLogMessage string - }{ - { - desc: "unshare thing successfully", - args: []string{ - thing.ID, - user.ID, - relation, - domainID, - token, - }, - logType: okLog, - }, - { - desc: "unshare thing with invalid thing ID", - args: []string{ - invalidID, - user.ID, - relation, - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - logType: errLog, - }, - { - desc: "unshare thing with invalid args", - args: []string{ - thing.ID, - user.ID, - relation, - domainID, - token, - extraArg, - }, - logType: usageLog, - }, - { - desc: "unshare thing with invalid relation", - args: []string{ - thing.ID, - user.ID, - "invalid", - domainID, - token, - }, - sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusBadRequest)), - logType: errLog, - }, - } - 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) - out := executeCommand(t, rootCmd, append([]string{unshrCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - case usageLog: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case errLog: - assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out)) - } - sdkCall.Unset() - }) - } -} diff --git a/cli/users.go b/cli/users.go deleted file mode 100644 index 54b415857..000000000 --- a/cli/users.go +++ /dev/null @@ -1,537 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "encoding/json" - "fmt" - "net/url" - "strconv" - - mgxsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/absmach/magistrala/users" - "github.com/spf13/cobra" -) - -var cmdUsers = []cobra.Command{ - { - Use: "create ", - Short: "Create user", - Long: "Create user with provided firstname, lastname, email, username and password. Token is optional\n" + - "For example:\n" + - "\tmagistrala-cli users create jane doe janedoe@example.com jane_doe 12345678 $USER_AUTH_TOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 5 || len(args) > 6 { - logUsageCmd(*cmd, cmd.Use) - return - } - if len(args) == 5 { - args = append(args, "") - } - - user := mgxsdk.User{ - FirstName: args[0], - LastName: args[1], - Email: args[2], - Credentials: mgxsdk.Credentials{ - Username: args[3], - Secret: args[4], - }, - Status: users.EnabledStatus.String(), - } - user, err := sdk.CreateUser(user, args[5]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - }, - }, - { - Use: "get [all | ] ", - Short: "Get users", - Long: "Get all users or get user by id. Users can be filtered by name or metadata or status\n" + - "Usage:\n" + - "\tmagistrala-cli users get all - lists all users\n" + - "\tmagistrala-cli users get all --offset --limit - lists all users with provided offset and limit\n" + - "\tmagistrala-cli users get - shows user with provided \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - metadata, err := convertMetadata(Metadata) - if err != nil { - logErrorCmd(*cmd, err) - return - } - pageMetadata := mgxsdk.PageMetadata{ - Username: Username, - Identity: Identity, - Offset: Offset, - Limit: Limit, - Metadata: metadata, - Status: Status, - } - if args[0] == all { - l, err := sdk.Users(pageMetadata, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, l) - return - } - u, err := sdk.User(args[0], args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, u) - }, - }, - { - Use: "token ", - Short: "Get token", - Long: "Generate a new token with username and password\n" + - "For example:\n" + - "\tmagistrala-cli users token jane.doe 12345678\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - loginReq := mgxsdk.Login{ - Identity: args[0], - Secret: args[1], - } - - token, err := sdk.CreateToken(loginReq) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, token) - }, - }, - - { - Use: "refreshtoken ", - Short: "Get token", - Long: "Generate new token from refresh token\n" + - "For example:\n" + - "\tmagistrala-cli users refreshtoken \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 1 { - logUsageCmd(*cmd, cmd.Use) - return - } - - token, err := sdk.RefreshToken(args[0]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, token) - }, - }, - { - Use: "update [ | tags | username | email ] ", - Short: "Update user", - Long: "Updates either user name and metadata or user tags or user email\n" + - "Usage:\n" + - "\tmagistrala-cli users update '{\"first_name\":\"new first_name\", \"metadata\":{\"key\": \"value\"}}' $USERTOKEN - updates user first and lastname and metadata\n" + - "\tmagistrala-cli users update tags '[\"tag1\", \"tag2\"]' $USERTOKEN - updates user tags\n" + - "\tmagistrala-cli users update username newusername $USERTOKEN - updates user name\n" + - "\tmagistrala-cli users update email newemail@example.com $USERTOKEN - updates user email\n", - - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 4 && len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - var user mgxsdk.User - if args[0] == "tags" { - if err := json.Unmarshal([]byte(args[2]), &user.Tags); err != nil { - logErrorCmd(*cmd, err) - return - } - user.ID = args[1] - user, err := sdk.UpdateUserTags(user, args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - return - } - - if args[0] == "email" { - user.ID = args[1] - user.Email = args[2] - user, err := sdk.UpdateUserEmail(user, args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - logJSONCmd(*cmd, user) - return - } - - if args[0] == "username" { - user.ID = args[1] - user.Credentials.Username = args[2] - user, err := sdk.UpdateUsername(user, args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - return - - } - - if args[0] == "role" { - user.ID = args[1] - user.Role = args[2] - user, err := sdk.UpdateUserRole(user, args[3]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - return - - } - - if err := json.Unmarshal([]byte(args[1]), &user); err != nil { - logErrorCmd(*cmd, err) - return - } - user.ID = args[0] - user, err := sdk.UpdateUser(user, args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - }, - }, - { - Use: "profile ", - Short: "Get user profile", - Long: "Get user profile\n" + - "Usage:\n" + - "\tmagistrala-cli users profile $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 1 { - logUsageCmd(*cmd, cmd.Use) - return - } - - user, err := sdk.UserProfile(args[0]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - }, - }, - { - Use: "resetpasswordrequest ", - Short: "Send reset password request", - Long: "Send reset password request\n" + - "Usage:\n" + - "\tmagistrala-cli users resetpasswordrequest example@mail.com\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 1 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.ResetPasswordRequest(args[0]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "resetpassword ", - Short: "Reset password", - Long: "Reset password\n" + - "Usage:\n" + - "\tmagistrala-cli users resetpassword 12345678 12345678 $REQUESTTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - if err := sdk.ResetPassword(args[0], args[1], args[2]); err != nil { - logErrorCmd(*cmd, err) - return - } - - logOKCmd(*cmd) - }, - }, - { - Use: "password ", - Short: "Update password", - Long: "Update password\n" + - "Usage:\n" + - "\tmagistrala-cli users password old_password new_password $USERTOKEN\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - user, err := sdk.UpdatePassword(args[0], args[1], args[2]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - }, - }, - { - Use: "enable ", - Short: "Change user status to enabled", - Long: "Change user status to enabled\n" + - "Usage:\n" + - "\tmagistrala-cli users enable \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - user, err := sdk.EnableUser(args[0], args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - }, - }, - { - Use: "disable ", - Short: "Change user status to disabled", - Long: "Change user status to disabled\n" + - "Usage:\n" + - "\tmagistrala-cli users disable \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - user, err := sdk.DisableUser(args[0], args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, user) - }, - }, - { - Use: "delete ", - Short: "Delete user", - Long: "Delete user by id\n" + - "Usage:\n" + - "\tmagistrala-cli users delete $USERTOKEN - delete user with \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - if err := sdk.DeleteUser(args[0], args[1]); err != nil { - logErrorCmd(*cmd, err) - return - } - logOKCmd(*cmd) - }, - }, - { - Use: "channels ", - Short: "List channels", - Long: "List channels of user\n" + - "Usage:\n" + - "\tmagistrala-cli users channels \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - - cp, err := sdk.ListUserChannels(args[0], pm, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, cp) - }, - }, - - { - Use: "things ", - Short: "List things", - Long: "List things of user\n" + - "Usage:\n" + - "\tmagistrala-cli users things \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - - tp, err := sdk.ListUserThings(args[0], pm, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, tp) - }, - }, - - { - Use: "domains ", - Short: "List domains", - Long: "List user's domains\n" + - "Usage:\n" + - "\tmagistrala-cli users domains \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - - dp, err := sdk.ListUserDomains(args[0], pm, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, dp) - }, - }, - - { - Use: "groups ", - Short: "List groups", - Long: "List groups of user\n" + - "Usage:\n" + - "\tmagistrala-cli users groups \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - - users, err := sdk.ListUserGroups(args[0], pm, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, users) - }, - }, - - { - Use: "search ", - Short: "Search users", - Long: "Search users by query\n" + - "Usage:\n" + - "\tmagistrala-cli users search \n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { - logUsageCmd(*cmd, cmd.Use) - return - } - - values, err := url.ParseQuery(args[0]) - if err != nil { - logErrorCmd(*cmd, fmt.Errorf("failed to parse query: %s", err)) - } - - pm := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - Name: values.Get("name"), - ID: values.Get("id"), - } - - if off, err := strconv.Atoi(values.Get("offset")); err == nil { - pm.Offset = uint64(off) - } - - if lim, err := strconv.Atoi(values.Get("limit")); err == nil { - pm.Limit = uint64(lim) - } - - users, err := sdk.SearchUsers(pm, args[1]) - if err != nil { - logErrorCmd(*cmd, err) - return - } - - logJSONCmd(*cmd, users) - }, - }, -} - -// 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]", - Short: "Users management", - Long: `Users management: create accounts and tokens"`, - } - - for i := range cmdUsers { - cmd.AddCommand(&cmdUsers[i]) - } - - return &cmd -} diff --git a/cli/users_test.go b/cli/users_test.go deleted file mode 100644 index b78a89fdb..000000000 --- a/cli/users_test.go +++ /dev/null @@ -1,1446 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli_test - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - - "github.com/absmach/magistrala/cli" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/absmach/magistrala/users" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var user = mgsdk.User{ - ID: testsutil.GenerateUUID(&testing.T{}), - FirstName: "testuserfirstname", - LastName: "testuserfirstname", - Credentials: mgsdk.Credentials{ - Secret: "testpassword", - Username: "testusername", - }, - Status: users.EnabledStatus.String(), -} - -var ( - validToken = "valid" - invalidToken = "" - invalidID = "invalidID" - extraArg = "extra-arg" -) - -func TestCreateUsersCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - - var usr mgsdk.User - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - user mgsdk.User - logType outputLog - }{ - { - desc: "create user successfully with token", - args: []string{ - user.FirstName, - user.LastName, - user.Email, - user.Credentials.Secret, - user.Credentials.Username, - validToken, - }, - user: user, - logType: entityLog, - }, - { - desc: "create user successfully without token", - args: []string{ - user.FirstName, - user.LastName, - user.Email, - user.Credentials.Secret, - user.Credentials.Username, - }, - user: user, - logType: entityLog, - }, - { - desc: "failed to create user", - args: []string{ - user.FirstName, - user.LastName, - user.Email, - user.Credentials.Secret, - user.Credentials.Username, - validToken, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity).Error()), - logType: errLog, - }, - { - desc: "create user with invalid args", - args: []string{user.FirstName, user.Credentials.Username}, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("CreateUser", mock.Anything, mock.Anything).Return(tc.user, tc.sdkerr) - if len(tc.args) == 4 { - sdkUser := mgsdk.User{ - FirstName: tc.args[0], - LastName: tc.args[1], - Email: tc.args[2], - Credentials: mgsdk.Credentials{ - Secret: tc.args[3], - }, - } - sdkCall = sdkMock.On("CreateUser", mock.Anything, sdkUser).Return(tc.user, tc.sdkerr) - } - out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &usr) - assert.Nil(t, err) - assert.Equal(t, tc.user, usr, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.user, usr)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestGetUsersCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - - var page mgsdk.UsersPage - var usr mgsdk.User - out := "" - userID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - user mgsdk.User - page mgsdk.UsersPage - logType outputLog - }{ - { - desc: "get users successfully", - args: []string{ - all, - validToken, - }, - sdkerr: nil, - page: mgsdk.UsersPage{ - Users: []mgsdk.User{user}, - }, - logType: entityLog, - }, - { - desc: "get user successfully with id", - args: []string{ - userID, - validToken, - }, - sdkerr: nil, - user: user, - logType: entityLog, - }, - { - desc: "get user with invalid id", - args: []string{ - invalidID, - validToken, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest).Error()), - user: mgsdk.User{}, - logType: errLog, - }, - { - desc: "get users successfully with offset and limit", - args: []string{ - all, - validToken, - "--offset=2", - "--limit=5", - }, - sdkerr: nil, - page: mgsdk.UsersPage{ - Users: []mgsdk.User{user}, - }, - logType: entityLog, - }, - { - desc: "get users with invalid token", - args: []string{ - all, - invalidToken, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden).Error()), - page: mgsdk.UsersPage{}, - logType: errLog, - }, - { - desc: "get users with invalid args", - args: []string{ - all, - invalidToken, - all, - invalidToken, - all, - invalidToken, - all, - invalidToken, - }, - logType: usageLog, - }, - { - desc: "get user with failed get operation", - args: []string{ - userID, - validToken, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusInternalServerError), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusInternalServerError).Error()), - user: mgsdk.User{}, - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("Users", mock.Anything, mock.Anything).Return(tc.page, tc.sdkerr) - sdkCall1 := sdkMock.On("User", tc.args[0], tc.args[1]).Return(tc.user, tc.sdkerr) - - out = executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...) - - if tc.logType == entityLog { - switch { - case tc.args[0] == all: - err := json.Unmarshal([]byte(out), &page) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - default: - err := json.Unmarshal([]byte(out), &usr) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - } - } - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - if tc.logType == entityLog { - if tc.args[0] != all { - assert.Equal(t, tc.user, usr, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.user, usr)) - } else { - assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page)) - } - } - - sdkCall.Unset() - sdkCall1.Unset() - }) - } -} - -func TestIssueTokenCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - - var tkn mgsdk.Token - invalidPassword := "" - - token := mgsdk.Token{ - AccessToken: testsutil.GenerateUUID(t), - RefreshToken: testsutil.GenerateUUID(t), - } - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - token mgsdk.Token - logType outputLog - }{ - { - desc: "issue token successfully", - args: []string{ - user.Email, - user.Credentials.Secret, - }, - sdkerr: nil, - logType: entityLog, - token: token, - }, - { - desc: "issue token with failed authentication", - args: []string{ - user.Email, - invalidPassword, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden).Error()), - logType: errLog, - token: mgsdk.Token{}, - }, - { - desc: "issue token with invalid args", - args: []string{ - user.Email, - user.Credentials.Secret, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - lg := mgsdk.Login{ - Identity: tc.args[0], - Secret: tc.args[1], - } - sdkCall := sdkMock.On("CreateToken", lg).Return(tc.token, tc.sdkerr) - - out := executeCommand(t, rootCmd, append([]string{tokCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &tkn) - assert.Nil(t, err) - assert.Equal(t, tc.token, tkn, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.token, tkn)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestRefreshIssueTokenCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - - var tkn mgsdk.Token - - token := mgsdk.Token{ - AccessToken: testsutil.GenerateUUID(t), - RefreshToken: testsutil.GenerateUUID(t), - } - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - token mgsdk.Token - logType outputLog - }{ - { - desc: "issue refresh token successfully without domain id", - args: []string{ - "token", - }, - sdkerr: nil, - logType: entityLog, - token: token, - }, - { - desc: "issue refresh token with invalid args", - args: []string{ - "token", - extraArg, - }, - logType: usageLog, - }, - { - desc: "issue refresh token with invalid Username", - args: []string{ - "invalidToken", - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden).Error()), - logType: errLog, - token: mgsdk.Token{}, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("RefreshToken", mock.Anything).Return(tc.token, tc.sdkerr) - - out := executeCommand(t, rootCmd, append([]string{refTokCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &tkn) - assert.Nil(t, err) - assert.Equal(t, tc.token, tkn, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.token, tkn)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestUpdateUserCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - - var usr mgsdk.User - - userID := testsutil.GenerateUUID(t) - - tagUpdateType := "tags" - emailUpdateType := "email" - roleUpdateType := "role" - newEmail := "newemail@example.com" - newRole := "administrator" - newTagsJSON := "[\"tag1\", \"tag2\"]" - newNameMetadataJSON := "{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}" - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - user mgsdk.User - logType outputLog - }{ - { - desc: "update user tags successfully", - args: []string{ - tagUpdateType, - userID, - newTagsJSON, - validToken, - }, - sdkerr: nil, - logType: entityLog, - user: user, - }, - { - desc: "update user tags with invalid json", - args: []string{ - tagUpdateType, - userID, - "[\"tag1\", \"tag2\"", - validToken, - }, - sdkerr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "update user tags with invalid token", - args: []string{ - tagUpdateType, - userID, - newTagsJSON, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "update user email successfully", - args: []string{ - emailUpdateType, - userID, - newEmail, - validToken, - }, - logType: entityLog, - user: user, - }, - { - desc: "update user email with invalid token", - args: []string{ - emailUpdateType, - userID, - newEmail, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "update user successfully", - args: []string{ - userID, - newNameMetadataJSON, - validToken, - }, - logType: entityLog, - user: user, - }, - { - desc: "update user with invalid token", - args: []string{ - userID, - newNameMetadataJSON, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "update user with invalid json", - args: []string{ - userID, - "{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}", - validToken, - }, - sdkerr: errors.NewSDKError(errors.New("unexpected end of JSON input")), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")), - logType: errLog, - }, - { - desc: "update user role successfully", - args: []string{ - roleUpdateType, - userID, - newRole, - validToken, - }, - logType: entityLog, - user: user, - }, - { - desc: "update user role with invalid token", - args: []string{ - roleUpdateType, - userID, - newRole, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - { - desc: "update user with invalid args", - args: []string{ - roleUpdateType, - userID, - newRole, - validToken, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("UpdateUser", mock.Anything, mock.Anything).Return(tc.user, tc.sdkerr) - sdkCall1 := sdkMock.On("UpdateUserTags", mock.Anything, mock.Anything).Return(tc.user, tc.sdkerr) - sdkCall2 := sdkMock.On("UpdateUserIdentity", mock.Anything, mock.Anything).Return(tc.user, tc.sdkerr) - sdkCall3 := sdkMock.On("UpdateUserRole", mock.Anything, mock.Anything).Return(tc.user, tc.sdkerr) - switch { - case tc.args[0] == tagUpdateType: - var u mgsdk.User - u.Tags = []string{"tag1", "tag2"} - u.ID = tc.args[1] - - sdkCall1 = sdkMock.On("UpdateUserTags", u, tc.args[3]).Return(tc.user, tc.sdkerr) - case tc.args[0] == emailUpdateType: - var u mgsdk.User - u.Email = tc.args[2] - u.ID = tc.args[1] - - sdkCall2 = sdkMock.On("UpdateUserEmail", u, tc.args[3]).Return(tc.user, tc.sdkerr) - case tc.args[0] == roleUpdateType && len(tc.args) == 4: - sdkCall3 = sdkMock.On("UpdateUserRole", mgsdk.User{ - Role: tc.args[2], - }, tc.args[3]).Return(tc.user, tc.sdkerr) - case tc.args[0] == userID: - sdkCall = sdkMock.On("UpdateUser", mgsdk.User{ - FirstName: "new name", - Metadata: mgsdk.Metadata{ - "key": "value", - }, - }, tc.args[2]).Return(tc.user, tc.sdkerr) - } - out := executeCommand(t, rootCmd, append([]string{updCmd}, tc.args...)...) - - switch tc.logType { - case entityLog: - err := json.Unmarshal([]byte(out), &usr) - assert.Nil(t, err) - assert.Equal(t, tc.user, usr, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.user, usr)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - sdkCall.Unset() - sdkCall1.Unset() - sdkCall2.Unset() - sdkCall3.Unset() - }) - } -} - -func TestGetUserProfileCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - - var usr mgsdk.User - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - user mgsdk.User - logType outputLog - }{ - { - desc: "get user profile successfully", - args: []string{ - validToken, - }, - sdkerr: nil, - logType: entityLog, - }, - { - desc: "get user profile with invalid args", - args: []string{ - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get user profile with invalid token", - args: []string{ - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("UserProfile", tc.args[0]).Return(tc.user, tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{profCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &usr) - assert.Nil(t, err) - assert.Equal(t, tc.user, usr, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.user, usr)) - } - sdkCall.Unset() - }) - } -} - -func TestResetPasswordRequestCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - exampleEmail := "example@mail.com" - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "request password reset successfully", - args: []string{ - exampleEmail, - }, - sdkerr: nil, - logType: okLog, - }, - { - desc: "request password reset with invalid args", - args: []string{ - exampleEmail, - extraArg, - }, - logType: usageLog, - }, - { - desc: "failed request password reset", - args: []string{ - exampleEmail, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity).Error()), - logType: errLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ResetPasswordRequest", tc.args[0]).Return(tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{resPassReqCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - } - sdkCall.Unset() - }) - } -} - -func TestResetPasswordCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - newPassword := "new-password" - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "reset password successfully", - args: []string{ - newPassword, - newPassword, - validToken, - }, - sdkerr: nil, - logType: okLog, - }, - { - desc: "reset password with invalid args", - args: []string{ - newPassword, - newPassword, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "reset password with invalid token", - args: []string{ - newPassword, - newPassword, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ResetPassword", tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{resPassCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestUpdatePasswordCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - oldPassword := "old-password" - newPassword := "new-password" - - var usr mgsdk.User - var err error - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - user mgsdk.User - logType outputLog - }{ - { - desc: "update password successfully", - args: []string{ - oldPassword, - newPassword, - validToken, - }, - sdkerr: nil, - logType: entityLog, - user: user, - }, - { - desc: "reset password with invalid args", - args: []string{ - oldPassword, - newPassword, - validToken, - extraArg, - }, - sdkerr: nil, - logType: usageLog, - user: user, - }, - { - desc: "update password with invalid token", - args: []string{ - oldPassword, - newPassword, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("UpdatePassword", tc.args[0], tc.args[1], tc.args[2]).Return(tc.user, tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{passCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err = json.Unmarshal([]byte(out), &usr) - assert.Nil(t, err) - assert.Equal(t, tc.user, usr, fmt.Sprintf("%s user mismatch: expected %+v got %+v", tc.desc, tc.user, usr)) - } - - sdkCall.Unset() - }) - } -} - -func TestEnableUserCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - var usr mgsdk.User - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - user mgsdk.User - logType outputLog - }{ - { - desc: "enable user successfully", - args: []string{ - user.ID, - validToken, - }, - sdkerr: nil, - user: user, - logType: entityLog, - }, - { - desc: "enable user with invalid args", - args: []string{ - user.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "enable user with invalid token", - args: []string{ - user.ID, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("EnableUser", tc.args[0], tc.args[1]).Return(tc.user, tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{enableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &usr) - assert.Nil(t, err) - assert.Equal(t, tc.user, usr, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.user, usr)) - } - - sdkCall.Unset() - }) - } -} - -func TestDisableUserCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - - var usr mgsdk.User - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - user mgsdk.User - logType outputLog - }{ - { - desc: "disable user successfully", - args: []string{ - user.ID, - validToken, - }, - sdkerr: nil, - logType: entityLog, - user: user, - }, - { - desc: "disable user with invalid args", - args: []string{ - user.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "disable user with invalid token", - args: []string{ - user.ID, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DisableUser", tc.args[0], tc.args[1]).Return(tc.user, tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{disableCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &usr) - if err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) - } - assert.Equal(t, tc.user, usr, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.user, usr)) - } - - sdkCall.Unset() - }) - } -} - -func TestDeleteUserCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - logType outputLog - }{ - { - desc: "delete user successfully", - args: []string{ - user.ID, - validToken, - }, - logType: okLog, - }, - { - desc: "delete user with invalid args", - args: []string{ - user.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "delete user with invalid token", - args: []string{ - user.ID, - invalidToken, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden).Error()), - logType: errLog, - }, - { - desc: "delete user with invalid user ID", - args: []string{ - invalidID, - validToken, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden).Error()), - logType: errLog, - }, - { - desc: "delete user with failed to delete", - args: []string{ - user.ID, - validToken, - }, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity).Error()), - logType: errLog, - }, - { - desc: "delete user with invalid args", - args: []string{ - user.ID, - extraArg, - }, - logType: usageLog, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("DeleteUser", mock.Anything, mock.Anything).Return(tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{delCmd}, tc.args...)...) - - switch tc.logType { - case okLog: - assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out)) - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - } - - sdkCall.Unset() - }) - } -} - -func TestListUserChannelsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - ch := mgsdk.Channel{ - ID: testsutil.GenerateUUID(t), - Name: "testchannel", - } - - var pg mgsdk.ChannelsPage - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - channel mgsdk.Channel - page mgsdk.ChannelsPage - output bool - logType outputLog - }{ - { - desc: "list user channels successfully", - args: []string{ - user.ID, - validToken, - }, - sdkerr: nil, - logType: entityLog, - page: mgsdk.ChannelsPage{ - Channels: []mgsdk.Channel{ch}, - }, - }, - { - desc: "list user channels successfully with flags", - args: []string{ - user.ID, - validToken, - "--offset=0", - "--limit=5", - }, - sdkerr: nil, - logType: entityLog, - page: mgsdk.ChannelsPage{ - Channels: []mgsdk.Channel{ch}, - }, - }, - { - desc: "list user channels with invalid args", - args: []string{ - user.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list user channels with invalid token", - args: []string{ - user.ID, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ListUserChannels", tc.args[0], mock.Anything, tc.args[1]).Return(tc.page, tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{chansCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &pg) - if err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) - } - assert.Equal(t, tc.page, pg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.page, pg)) - } - - sdkCall.Unset() - }) - } -} - -func TestListUserThingsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - th := mgsdk.Thing{ - ID: testsutil.GenerateUUID(t), - Name: "testthing", - } - - var pg mgsdk.ThingsPage - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - thing mgsdk.Thing - page mgsdk.ThingsPage - logType outputLog - }{ - { - desc: "list user things successfully", - args: []string{ - user.ID, - validToken, - }, - sdkerr: nil, - logType: entityLog, - page: mgsdk.ThingsPage{ - Things: []mgsdk.Thing{th}, - }, - }, - { - desc: "list user things with invalid args", - args: []string{ - user.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list user things with invalid token", - args: []string{ - user.ID, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - 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...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &pg) - if err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) - } - assert.Equal(t, tc.page, pg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.page, pg)) - } - - sdkCall.Unset() - }) - } -} - -func TestListUserDomainsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - d := mgsdk.Domain{ - ID: testsutil.GenerateUUID(t), - Name: "testdomain", - } - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - logType outputLog - page mgsdk.DomainsPage - }{ - { - desc: "list user domains successfully", - args: []string{ - user.ID, - validToken, - }, - sdkerr: nil, - logType: entityLog, - page: mgsdk.DomainsPage{ - Domains: []mgsdk.Domain{d}, - }, - }, - { - desc: "list user domains with invalid args", - args: []string{ - user.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list user domains with invalid token", - args: []string{ - user.ID, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var pg mgsdk.DomainsPage - sdkCall := sdkMock.On("ListUserDomains", tc.args[0], mock.Anything, tc.args[1]).Return(tc.page, tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{domsCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &pg) - if err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) - } - assert.Equal(t, tc.page, pg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.page, pg)) - } - - sdkCall.Unset() - }) - } -} - -func TestListUserGroupsCmd(t *testing.T) { - sdkMock := new(sdkmocks.SDK) - cli.SetSDK(sdkMock) - usersCmd := cli.NewUsersCmd() - rootCmd := setFlags(usersCmd) - g := mgsdk.Group{ - ID: testsutil.GenerateUUID(t), - Name: "testgroup", - } - - cases := []struct { - desc string - args []string - sdkerr errors.SDKError - errLogMessage string - logType outputLog - page mgsdk.GroupsPage - }{ - { - desc: "list user groups successfully", - args: []string{ - user.ID, - validToken, - }, - sdkerr: nil, - logType: entityLog, - page: mgsdk.GroupsPage{ - Groups: []mgsdk.Group{g}, - }, - }, - { - desc: "list user groups with invalid args", - args: []string{ - user.ID, - validToken, - extraArg, - }, - logType: usageLog, - }, - { - desc: "list user groups with invalid token", - args: []string{ - user.ID, - invalidToken, - }, - logType: errLog, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var pg mgsdk.GroupsPage - sdkCall := sdkMock.On("ListUserGroups", tc.args[0], mock.Anything, tc.args[1]).Return(tc.page, tc.sdkerr) - out := executeCommand(t, rootCmd, append([]string{grpCmd}, tc.args...)...) - - switch tc.logType { - 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: - assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out)) - case entityLog: - err := json.Unmarshal([]byte(out), &pg) - if err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) - } - assert.Equal(t, tc.page, pg, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.page, pg)) - } - - sdkCall.Unset() - }) - } -} diff --git a/cli/utils.go b/cli/utils.go deleted file mode 100644 index 0809f69aa..000000000 --- a/cli/utils.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cli - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/fatih/color" - "github.com/hokaccha/go-prettyjson" - "github.com/spf13/cobra" -) - -var ( - // Limit query parameter. - Limit uint64 = 10 - // Offset query parameter. - Offset uint64 = 0 - // Name query parameter. - Name string = "" - // Identity query parameter. - Identity string = "" - // Metadata query parameter. - Metadata string = "" - // Status query parameter. - Status string = "" - // ConfigPath config path parameter. - ConfigPath string = "" - // State query parameter. - State string = "" - // Topic query parameter. - Topic string = "" - // Contact query parameter. - Contact string = "" - // RawOutput raw output mode. - RawOutput bool = false - // Username query parameter. - Username string = "" - // FirstName query parameter. - FirstName string = "" - // LastName query parameter. - LastName string = "" -) - -func logJSONCmd(cmd cobra.Command, iList ...interface{}) { - for _, i := range iList { - m, err := json.Marshal(i) - if err != nil { - logErrorCmd(cmd, err) - return - } - - pj, err := prettyjson.Format(m) - if err != nil { - logErrorCmd(cmd, err) - return - } - - fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", string(pj)) - } -} - -func logUsageCmd(cmd cobra.Command, u string) { - fmt.Fprintf(cmd.OutOrStdout(), color.YellowString("\nusage: %s\n\n"), u) -} - -func logErrorCmd(cmd cobra.Command, err error) { - boldRed := color.New(color.FgRed, color.Bold) - boldRed.Fprintf(cmd.ErrOrStderr(), "\nerror: ") - - fmt.Fprintf(cmd.ErrOrStderr(), "%s\n\n", color.RedString(err.Error())) -} - -func logOKCmd(cmd cobra.Command) { - fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", color.BlueString("ok")) -} - -func logCreatedCmd(cmd cobra.Command, e string) { - if RawOutput { - fmt.Fprintln(cmd.OutOrStdout(), e) - } else { - fmt.Fprintf(cmd.OutOrStdout(), color.BlueString("\ncreated: %s\n\n"), e) - } -} - -func logRevokedTimeCmd(cmd cobra.Command, t time.Time) { - if RawOutput { - fmt.Fprintln(cmd.OutOrStdout(), t) - } else { - fmt.Fprintf(cmd.OutOrStdout(), color.BlueString("\nrevoked: %v\n\n"), t) - } -} - -func convertMetadata(m string) (map[string]interface{}, error) { - var metadata map[string]interface{} - if m == "" { - return nil, nil - } - if err := json.Unmarshal([]byte(Metadata), &metadata); err != nil { - return nil, err - } - return nil, nil -} diff --git a/cmd/auth/main.go b/cmd/auth/main.go deleted file mode 100644 index a29477830..000000000 --- a/cmd/auth/main.go +++ /dev/null @@ -1,233 +0,0 @@ -// 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/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" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/jaeger" - "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/uuid" - v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" - "github.com/authzed/authzed-go/v1" - "github.com/authzed/grpcutil" - "github.com/caarlos0/env/v11" - "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 = "auth" - envPrefixHTTP = "MG_AUTH_HTTP_" - envPrefixGrpc = "MG_AUTH_GRPC_" - envPrefixDB = "MG_AUTH_DB_" - defDB = "auth" - defSvcHTTPPort = "8189" - defSvcGRPCPort = "8181" -) - -type config struct { - LogLevel string `env:"MG_AUTH_LOG_LEVEL" envDefault:"info"` - SecretKey string `env:"MG_AUTH_SECRET_KEY" envDefault:"secret"` - 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_AUTH_ADAPTER_INSTANCE_ID" envDefault:""` - AccessDuration time.Duration `env:"MG_AUTH_ACCESS_TOKEN_DURATION" envDefault:"1h"` - RefreshDuration time.Duration `env:"MG_AUTH_REFRESH_TOKEN_DURATION" envDefault:"24h"` - InvitationDuration time.Duration `env:"MG_AUTH_INVITATION_DURATION" envDefault:"168h"` - SpicedbHost string `env:"MG_SPICEDB_HOST" envDefault:"localhost"` - SpicedbPort string `env:"MG_SPICEDB_PORT" envDefault:"50051"` - SpicedbSchemaFile string `env:"MG_SPICEDB_SCHEMA_FILE" envDefault:"./docker/spicedb/schema.zed"` - 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()) - } - - db, err := pgclient.Setup(dbConfig, *apostgres.Migration()) - 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) - - spicedbclient, err := initSpiceDB(ctx, cfg) - if err != nil { - logger.Error(fmt.Sprintf("failed to init spicedb grpc client : %s\n", err.Error())) - 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())) - exitCode = 1 - return - } - 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)) - } - - gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerAuthServiceServer, logger) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - g.Go(func() error { - return hs.Start() - }) - g.Go(func() error { - return gs.Start() - }) - - g.Go(func() error { - return server.StopSignalHandler(ctx, cancel, logger, svcName, hs, gs) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("users service terminated: %s", err)) - } -} - -func initSpiceDB(ctx context.Context, cfg config) (*authzed.ClientWithExperimental, 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 client, err - } - - if err := initSchema(ctx, client, cfg.SpicedbSchemaFile); err != nil { - return client, err - } - - return client, nil -} - -func initSchema(ctx context.Context, client *authzed.ClientWithExperimental, schemaFilePath string) error { - schemaContent, err := os.ReadFile(schemaFilePath) - if err != nil { - return fmt.Errorf("failed to read spice db schema file : %w", err) - } - - if _, err = client.SchemaServiceClient.WriteSchema(ctx, &v1.WriteSchemaRequest{Schema: string(schemaContent)}); err != nil { - return fmt.Errorf("failed to create schema in spicedb : %w", err) - } - - 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 { - database := postgres.NewDatabase(db, dbConfig, tracer) - keysRepo := apostgres.New(database) - domainsRepo := apostgres.NewDomainRepository(database) - idProvider := uuid.New() - - pEvaluator := spicedb.NewPolicyEvaluator(spicedbClient, logger) - pService := spicedb.NewPolicyService(spicedbClient, logger) - - 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 = api.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics("groups", "api") - svc = api.MetricsMiddleware(svc, counter, latency) - svc = tracing.New(svc, tracer) - - return svc -} diff --git a/cmd/bootstrap/main.go b/cmd/bootstrap/main.go index cfe998b41..2476aab73 100644 --- a/cmd/bootstrap/main.go +++ b/cmd/bootstrap/main.go @@ -13,30 +13,31 @@ import ( "os" chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/bootstrap/api" - "github.com/absmach/magistrala/bootstrap/events/consumer" - "github.com/absmach/magistrala/bootstrap/events/producer" - "github.com/absmach/magistrala/bootstrap/middleware" - bootstrappg "github.com/absmach/magistrala/bootstrap/postgres" - "github.com/absmach/magistrala/bootstrap/tracing" - 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/events" - "github.com/absmach/magistrala/pkg/events/store" - "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" - pgclient "github.com/absmach/magistrala/pkg/postgres" - "github.com/absmach/magistrala/pkg/prometheus" - 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/supermq" + "github.com/absmach/supermq/bootstrap" + httpapi "github.com/absmach/supermq/bootstrap/api" + "github.com/absmach/supermq/bootstrap/events/consumer" + "github.com/absmach/supermq/bootstrap/events/producer" + "github.com/absmach/supermq/bootstrap/middleware" + bootstrappg "github.com/absmach/supermq/bootstrap/postgres" + "github.com/absmach/supermq/bootstrap/tracing" + smqlog "github.com/absmach/supermq/logger" + authsvcAuthn "github.com/absmach/supermq/pkg/authn/authsvc" + smqauthz "github.com/absmach/supermq/pkg/authz" + authsvcAuthz "github.com/absmach/supermq/pkg/authz/authsvc" + domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient" + "github.com/absmach/supermq/pkg/events" + "github.com/absmach/supermq/pkg/events/store" + "github.com/absmach/supermq/pkg/grpcclient" + "github.com/absmach/supermq/pkg/jaeger" + "github.com/absmach/supermq/pkg/policies" + "github.com/absmach/supermq/pkg/policies/spicedb" + pgclient "github.com/absmach/supermq/pkg/postgres" + "github.com/absmach/supermq/pkg/prometheus" + mgsdk "github.com/absmach/supermq/pkg/sdk" + "github.com/absmach/supermq/pkg/server" + httpserver "github.com/absmach/supermq/pkg/server/http" + "github.com/absmach/supermq/pkg/uuid" "github.com/authzed/authzed-go/v1" "github.com/authzed/grpcutil" "github.com/caarlos0/env/v11" @@ -48,30 +49,31 @@ import ( ) const ( - svcName = "bootstrap" - envPrefixDB = "MG_BOOTSTRAP_DB_" - envPrefixHTTP = "MG_BOOTSTRAP_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - defDB = "bootstrap" - defSvcHTTPPort = "9013" - - thingsStream = "events.magistrala.things" - streamID = "magistrala.bootstrap" + svcName = "bootstrap" + envPrefixDB = "SMQ_BOOTSTRAP_DB_" + envPrefixHTTP = "SMQ_BOOTSTRAP_HTTP_" + envPrefixAuth = "SMQ_AUTH_GRPC_" + envPrefixDomains = "SMQ_DOMAINS_GRPC_" + defDB = "bootstrap" + defSvcHTTPPort = "9013" + + stream = "events.supermq.clients" + streamID = "supermq.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"` - 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:""` - 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"` + LogLevel string `env:"SMQ_BOOTSTRAP_LOG_LEVEL" envDefault:"info"` + EncKey string `env:"SMQ_BOOTSTRAP_ENCRYPT_KEY" envDefault:"12345678910111213141516171819202"` + ESConsumerName string `env:"SMQ_BOOTSTRAP_EVENT_CONSUMER" envDefault:"bootstrap"` + ClientsURL string `env:"SMQ_CLIENTS_URL" envDefault:"http://localhost:9000"` + JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"SMQ_BOOTSTRAP_INSTANCE_ID" envDefault:""` + ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` + SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"` + SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"` + SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"` } func main() { @@ -83,13 +85,13 @@ func main() { log.Fatalf("failed to load %s configuration : %s", svcName, err) } - logger, err := mglog.New(os.Stdout, cfg.LogLevel) + logger, err := smqlog.New(os.Stdout, cfg.LogLevel) if err != nil { log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int - defer mglog.ExitWithError(&exitCode) + defer smqlog.ExitWithError(&exitCode) if cfg.InstanceID == "" { if cfg.InstanceID, err = uuid.New().ID(); err != nil { @@ -148,7 +150,21 @@ func main() { logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure()) defer authnClient.Close() - authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg) + domsGrpcCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil { + logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err)) + exitCode = 1 + return + } + domainsAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer domainsHandler.Close() + + authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg, domainsAuthz) if err != nil { logger.Error(err.Error()) exitCode = 1 @@ -165,8 +181,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 } @@ -179,10 +195,10 @@ func main() { exitCode = 1 return } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, authn, bootstrap.NewConfigReader([]byte(cfg.EncKey)), logger, cfg.InstanceID), logger) + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, bootstrap.NewConfigReader([]byte(cfg.EncKey)), logger, cfg.InstanceID), logger) if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) + chc := chclient.New(svcName, supermq.Version, logger, cancel) go chc.CallHome(ctx) } @@ -199,13 +215,13 @@ func main() { } } -func newService(ctx context.Context, authz mgauthz.Authorization, policySvc policies.Service, db *sqlx.DB, tracer trace.Tracer, logger *slog.Logger, cfg config, dbConfig pgclient.Config) (bootstrap.Service, error) { +func newService(ctx context.Context, authz smqauthz.Authorization, policySvc policies.Service, db *sqlx.DB, tracer trace.Tracer, logger *slog.Logger, cfg config, dbConfig pgclient.Config) (bootstrap.Service, error) { database := pgclient.NewDatabase(db, dbConfig, tracer) repoConfig := bootstrappg.NewConfigRepository(database, logger) config := mgsdk.Config{ - ThingsURL: cfg.ThingsURL, + ClientsURL: cfg.ClientsURL, } sdk := mgsdk.NewSDK(config) @@ -228,14 +244,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 deleted file mode 100644 index 1b75d80d6..000000000 --- a/cmd/certs/main.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains certs main function to start the certs 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/certs" - "github.com/absmach/magistrala/certs/api" - pki "github.com/absmach/magistrala/certs/pki/amcerts" - "github.com/absmach/magistrala/certs/tracing" - mglog "github.com/absmach/magistrala/logger" - authsvcAuthn "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/prometheus" - 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/caarlos0/env/v11" - "go.opentelemetry.io/otel/trace" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "certs" - envPrefixDB = "MG_CERTS_DB_" - envPrefixHTTP = "MG_CERTS_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - defDB = "certs" - defSvcHTTPPort = "9019" -) - -type config struct { - LogLevel string `env:"MG_CERTS_LOG_LEVEL" envDefault:"info"` - ThingsURL string `env:"MG_THINGS_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:""` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` - - // Sign and issue certificates without 3rd party PKI - SignCAPath string `env:"MG_CERTS_SIGN_CA_PATH" envDefault:"ca.crt"` - SignCAKeyPath string `env:"MG_CERTS_SIGN_CA_KEY_PATH" envDefault:"ca.key"` - - // Amcerts SDK settings - SDKHost string `env:"MG_CERTS_SDK_HOST" envDefault:""` - SDKCertsURL string `env:"MG_CERTS_SDK_CERTS_URL" envDefault:"http://localhost:9010"` - TLSVerification bool `env:"MG_CERTS_SDK_TLS_VERIFICATION" envDefault:"false"` -} - -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) - } - - 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 - } - } - - if cfg.SDKHost == "" { - logger.Error("No host specified for PKI engine") - exitCode = 1 - return - } - - pkiclient, err := pki.NewAgent(cfg.SDKHost, cfg.SDKCertsURL, cfg.TLSVerification) - if err != nil { - logger.Error("failed to configure client for PKI engine") - exitCode = 1 - return - } - - 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()) - - 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) - - svc := newService(tracer, logger, cfg, pkiclient) - - 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 - } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, authn, logger, cfg.InstanceID), logger) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - g.Go(func() error { - return hs.Start() - }) - - g.Go(func() error { - return server.StopSignalHandler(ctx, cancel, logger, svcName, hs) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("Certs service terminated: %s", err)) - } -} - -func newService(tracer trace.Tracer, logger *slog.Logger, cfg config, pkiAgent pki.Agent) certs.Service { - config := mgsdk.Config{ - ThingsURL: cfg.ThingsURL, - } - sdk := mgsdk.NewSDK(config) - svc := certs.New(sdk, pkiAgent) - svc = api.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics(svcName, "api") - svc = api.MetricsMiddleware(svc, counter, latency) - svc = tracing.New(svc, tracer) - - return svc -} diff --git a/cmd/cli/main.go b/cmd/cli/main.go deleted file mode 100644 index 7ed42dfbb..000000000 --- a/cmd/cli/main.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains cli main function to run the cli. -package main - -import ( - "log" - - "github.com/absmach/magistrala/cli" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/spf13/cobra" -) - -func main() { - msgContentType := string(sdk.CTJSONSenML) - sdkConf := sdk.Config{ - MsgContentType: sdk.ContentType(msgContentType), - } - - // Root - rootCmd := &cobra.Command{ - Use: "magistrala-cli", - PersistentPreRun: func(_ *cobra.Command, _ []string) { - cliConf, err := cli.ParseConfig(sdkConf) - if err != nil { - log.Fatalf("Failed to parse config: %s", err) - } - if cliConf.MsgContentType == "" { - cliConf.MsgContentType = sdk.ContentType(msgContentType) - } - s := sdk.NewSDK(cliConf) - cli.SetSDK(s) - }, - } - // API commands - healthCmd := cli.NewHealthCmd() - usersCmd := cli.NewUsersCmd() - domainsCmd := cli.NewDomainsCmd() - thingsCmd := cli.NewThingsCmd() - groupsCmd := cli.NewGroupsCmd() - channelsCmd := cli.NewChannelsCmd() - messagesCmd := cli.NewMessagesCmd() - provisionCmd := cli.NewProvisionCmd() - bootstrapCmd := cli.NewBootstrapCmd() - certsCmd := cli.NewCertsCmd() - subscriptionsCmd := cli.NewSubscriptionCmd() - configCmd := cli.NewConfigCmd() - invitationsCmd := cli.NewInvitationsCmd() - journalCmd := cli.NewJournalCmd() - - // Root Commands - rootCmd.AddCommand(healthCmd) - rootCmd.AddCommand(usersCmd) - rootCmd.AddCommand(domainsCmd) - rootCmd.AddCommand(groupsCmd) - rootCmd.AddCommand(thingsCmd) - rootCmd.AddCommand(channelsCmd) - rootCmd.AddCommand(messagesCmd) - rootCmd.AddCommand(provisionCmd) - rootCmd.AddCommand(bootstrapCmd) - rootCmd.AddCommand(certsCmd) - rootCmd.AddCommand(subscriptionsCmd) - rootCmd.AddCommand(configCmd) - rootCmd.AddCommand(invitationsCmd) - rootCmd.AddCommand(journalCmd) - - // Root Flags - rootCmd.PersistentFlags().StringVarP( - &sdkConf.BootstrapURL, - "bootstrap-url", - "b", - sdkConf.BootstrapURL, - "Bootstrap service URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.CertsURL, - "certs-url", - "s", - sdkConf.CertsURL, - "Certs service URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.ThingsURL, - "things-url", - "t", - sdkConf.ThingsURL, - "Things service URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.UsersURL, - "users-url", - "u", - sdkConf.UsersURL, - "Users service URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.DomainsURL, - "domains-url", - "d", - sdkConf.DomainsURL, - "Domains service URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.HTTPAdapterURL, - "http-url", - "p", - sdkConf.HTTPAdapterURL, - "HTTP adapter URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.ReaderURL, - "reader-url", - "R", - sdkConf.ReaderURL, - "Reader URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.InvitationsURL, - "invitations-url", - "v", - sdkConf.InvitationsURL, - "Inivitations URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.JournalURL, - "journal-url", - "a", - sdkConf.JournalURL, - "Journal Log URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &sdkConf.HostURL, - "host-url", - "H", - sdkConf.HostURL, - "Host URL", - ) - - rootCmd.PersistentFlags().StringVarP( - &msgContentType, - "content-type", - "y", - msgContentType, - "Message content type", - ) - - rootCmd.PersistentFlags().BoolVarP( - &sdkConf.TLSVerification, - "insecure", - "i", - sdkConf.TLSVerification, - "Do not check for TLS cert", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.ConfigPath, - "config", - "c", - cli.ConfigPath, - "Config path", - ) - - rootCmd.PersistentFlags().BoolVarP( - &cli.RawOutput, - "raw", - "r", - cli.RawOutput, - "Enables raw output mode for easier parsing of output", - ) - rootCmd.PersistentFlags().BoolVarP( - &sdkConf.CurlFlag, - "curl", - "x", - false, - "Convert HTTP request to cURL command", - ) - - // Client and Channels Flags - rootCmd.PersistentFlags().Uint64VarP( - &cli.Limit, - "limit", - "l", - 10, - "Limit query parameter", - ) - - rootCmd.PersistentFlags().Uint64VarP( - &cli.Offset, - "offset", - "o", - 0, - "Offset query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Name, - "name", - "n", - "", - "Name query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Identity, - "identity", - "I", - "", - "User identity query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Metadata, - "metadata", - "m", - "", - "Metadata query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Status, - "status", - "S", - "", - "User status query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.State, - "state", - "z", - "", - "Bootstrap state query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Topic, - "topic", - "T", - "", - "Subscription topic query parameter", - ) - - rootCmd.PersistentFlags().StringVarP( - &cli.Contact, - "contact", - "C", - "", - "Subscription contact query parameter", - ) - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/coap/main.go b/cmd/coap/main.go deleted file mode 100644 index ad16e992c..000000000 --- a/cmd/coap/main.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains coap-adapter main function to start the coap-adapter service. -package main - -import ( - "context" - "fmt" - "log" - "net/url" - "os" - - chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/coap" - "github.com/absmach/magistrala/coap/api" - "github.com/absmach/magistrala/coap/tracing" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/grpcclient" - jaegerclient "github.com/absmach/magistrala/pkg/jaeger" - "github.com/absmach/magistrala/pkg/messaging/brokers" - brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" - "github.com/absmach/magistrala/pkg/prometheus" - "github.com/absmach/magistrala/pkg/server" - coapserver "github.com/absmach/magistrala/pkg/server/coap" - httpserver "github.com/absmach/magistrala/pkg/server/http" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/caarlos0/env/v11" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "coap_adapter" - envPrefix = "MG_COAP_ADAPTER_" - envPrefixHTTP = "MG_COAP_ADAPTER_HTTP_" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - defSvcHTTPPort = "5683" - defSvcCoAPPort = "5683" -) - -type config struct { - LogLevel string `env:"MG_COAP_ADAPTER_LOG_LEVEL" envDefault:"info"` - BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` - 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_COAP_ADAPTER_INSTANCE_ID" envDefault:""` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` -} - -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) - } - - 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 - } - } - - 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 - } - - coapServerConfig := server.Config{Port: defSvcCoAPPort} - if err := env.ParseWithOptions(&coapServerConfig, env.Options{Prefix: envPrefix}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s CoAP server configuration : %s", svcName, err)) - exitCode = 1 - 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)) - exitCode = 1 - return - } - - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) - 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()) - - 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) - - nps, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger) - if err != nil { - logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err)) - exitCode = 1 - return - } - defer nps.Close() - nps = brokerstracing.NewPubSub(coapServerConfig, tracer, nps) - - svc := coap.New(thingsClient, nps) - - svc = tracing.New(tracer, svc) - - svc = api.LoggingMiddleware(svc, logger) - - counter, latency := prometheus.MakeMetrics(svcName, "api") - svc = api.MetricsMiddleware(svc, counter, latency) - - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(cfg.InstanceID), logger) - - cs := coapserver.NewServer(ctx, cancel, svcName, coapServerConfig, api.MakeCoAPHandler(svc, logger), logger) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - g.Go(func() error { - return hs.Start() - }) - g.Go(func() error { - return cs.Start() - }) - g.Go(func() error { - return server.StopSignalHandler(ctx, cancel, logger, svcName, hs, cs) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("CoAP adapter service terminated: %s", err)) - } -} diff --git a/cmd/http/main.go b/cmd/http/main.go deleted file mode 100644 index 4bf25efa9..000000000 --- a/cmd/http/main.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains http-adapter main function to start the http-adapter service. -package main - -import ( - "context" - "crypto/tls" - "fmt" - "log" - "log/slog" - "net/http" - "net/url" - "os" - - chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - adapter "github.com/absmach/magistrala/http" - "github.com/absmach/magistrala/http/api" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/grpcclient" - jaegerclient "github.com/absmach/magistrala/pkg/jaeger" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/brokers" - brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" - "github.com/absmach/magistrala/pkg/messaging/handler" - "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/mgate" - mgatehttp "github.com/absmach/mgate/pkg/http" - "github.com/absmach/mgate/pkg/session" - "github.com/caarlos0/env/v11" - "go.opentelemetry.io/otel/trace" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "http_adapter" - envPrefix = "MG_HTTP_ADAPTER_" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - defSvcHTTPPort = "80" - targetHTTPPort = "81" - targetHTTPHost = "http://localhost" -) - -type config struct { - LogLevel string `env:"MG_HTTP_ADAPTER_LOG_LEVEL" envDefault:"info"` - BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` - 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_HTTP_ADAPTER_INSTANCE_ID" envDefault:""` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` -} - -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) - } - - 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 - } - } - - httpServerConfig := server.Config{Port: defSvcHTTPPort} - if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefix}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err)) - exitCode = 1 - 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)) - exitCode = 1 - return - } - - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) - 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()) - - 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) - - pub, err := brokers.NewPublisher(ctx, cfg.BrokerURL) - if err != nil { - logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err)) - exitCode = 1 - return - } - defer pub.Close() - pub = brokerstracing.NewPublisher(httpServerConfig, tracer, pub) - - svc := newService(pub, thingsClient, logger, tracer) - targetServerCfg := server.Config{Port: targetHTTPPort} - - hs := httpserver.NewServer(ctx, cancel, svcName, targetServerCfg, api.MakeHandler(logger, cfg.InstanceID), logger) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - g.Go(func() error { - return hs.Start() - }) - - g.Go(func() error { - return proxyHTTP(ctx, httpServerConfig, logger, svc) - }) - - g.Go(func() error { - return server.StopSignalHandler(ctx, cancel, logger, svcName, hs) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("HTTP adapter service terminated: %s", err)) - } -} - -func newService(pub messaging.Publisher, tc magistrala.ThingsServiceClient, logger *slog.Logger, tracer trace.Tracer) session.Handler { - svc := adapter.NewHandler(pub, logger, tc) - svc = handler.NewTracing(tracer, svc) - svc = handler.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics(svcName, "api") - svc = handler.MetricsMiddleware(svc, counter, latency) - return svc -} - -func proxyHTTP(ctx context.Context, cfg server.Config, logger *slog.Logger, sessionHandler session.Handler) error { - config := mgate.Config{ - Address: fmt.Sprintf("%s:%s", "", cfg.Port), - Target: fmt.Sprintf("%s:%s", targetHTTPHost, targetHTTPPort), - PathPrefix: "/", - } - if cfg.CertFile != "" || cfg.KeyFile != "" { - tlsCert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) - if err != nil { - return err - } - config.TLSConfig = &tls.Config{ - Certificates: []tls.Certificate{tlsCert}, - } - } - mp, err := mgatehttp.NewProxy(config, sessionHandler, logger) - if err != nil { - return err - } - http.HandleFunc("/", mp.ServeHTTP) - - errCh := make(chan error) - switch { - case cfg.CertFile != "" || cfg.KeyFile != "": - go func() { - errCh <- mp.Listen(ctx) - }() - logger.Info(fmt.Sprintf("%s service https server listening at %s:%s with TLS cert %s and key %s", svcName, cfg.Host, cfg.Port, cfg.CertFile, cfg.KeyFile)) - default: - go func() { - errCh <- mp.Listen(ctx) - }() - logger.Info(fmt.Sprintf("%s service http server listening at %s:%s without TLS", svcName, cfg.Host, cfg.Port)) - } - - select { - case <-ctx.Done(): - logger.Info(fmt.Sprintf("proxy HTTP shutdown at %s", config.Target)) - return nil - case err := <-errCh: - return err - } -} diff --git a/cmd/invitations/main.go b/cmd/invitations/main.go deleted file mode 100644 index 8f79da391..000000000 --- a/cmd/invitations/main.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains invitations main function to start the invitations 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/invitations" - "github.com/absmach/magistrala/invitations/api" - "github.com/absmach/magistrala/invitations/middleware" - invitationspg "github.com/absmach/magistrala/invitations/postgres" - 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" - "github.com/absmach/magistrala/pkg/jaeger" - "github.com/absmach/magistrala/pkg/postgres" - clientspg "github.com/absmach/magistrala/pkg/postgres" - "github.com/absmach/magistrala/pkg/prometheus" - mgsdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/absmach/magistrala/pkg/server" - "github.com/absmach/magistrala/pkg/server/http" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/caarlos0/env/v11" - "github.com/jmoiron/sqlx" - "go.opentelemetry.io/otel/trace" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "invitations" - envPrefixDB = "MG_INVITATIONS_DB_" - envPrefixHTTP = "MG_INVITATIONS_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - defDB = "invitations" - defSvcHTTPPort = "9020" -) - -type config struct { - LogLevel string `env:"MG_INVITATIONS_LOG_LEVEL" envDefault:"info"` - UsersURL string `env:"MG_USERS_URL" envDefault:"http://localhost:9002"` - DomainsURL string `env:"MG_DOMAINS_URL" envDefault:"http://localhost:8189"` - InstanceID string `env:"MG_INVITATIONS_INSTANCE_ID" envDefault:""` - JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` - SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` -} - -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) - } - - 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 := clientspg.Config{Name: defDB} - if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s database configuration : %s", svcName, err)) - exitCode = 1 - return - } - db, err := clientspg.Setup(dbConfig, *invitationspg.Migration()) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer db.Close() - - authClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&authClientCfg, env.Options{Prefix: envPrefixAuth}); err != nil { - logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err.Error())) - exitCode = 1 - return - } - tokenClient, tokenHandler, err := grpcclient.SetupTokenClient(ctx, authClientCfg) - if err != nil { - logger.Error(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, authClientCfg) - if err != nil { - logger.Error(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, authClientCfg) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer authzHandler.Close() - logger.Info("Authz successfully connected to auth gRPC server " + authzHandler.Secure()) - - 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) - - svc, err := newService(db, dbConfig, authz, tokenClient, tracer, cfg, logger) - if err != nil { - logger.Error(fmt.Sprintf("failed to create %s service: %s", svcName, 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)) - exitCode = 1 - return - } - - httpSvr := http.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, logger, authn, cfg.InstanceID), logger) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - g.Go(func() error { - return httpSvr.Start() - }) - - g.Go(func() error { - return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSvr) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err)) - } -} - -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) { - database := postgres.NewDatabase(db, dbConfig, tracer) - repo := invitationspg.NewRepository(database) - - config := mgsdk.Config{ - UsersURL: conf.UsersURL, - DomainsURL: conf.DomainsURL, - } - sdk := mgsdk.NewSDK(config) - - svc := invitations.NewService(token, repo, sdk) - svc = middleware.AuthorizationMiddleware(authz, svc) - svc = middleware.Tracing(svc, tracer) - svc = middleware.Logging(logger, svc) - counter, latency := prometheus.MakeMetrics(svcName, "api") - svc = middleware.Metrics(counter, latency, svc) - - return svc, nil -} diff --git a/cmd/journal/main.go b/cmd/journal/main.go deleted file mode 100644 index 8d7b6b349..000000000 --- a/cmd/journal/main.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains journal main function to start the journal 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/journal" - "github.com/absmach/magistrala/journal/api" - "github.com/absmach/magistrala/journal/events" - "github.com/absmach/magistrala/journal/middleware" - journalpg "github.com/absmach/magistrala/journal/postgres" - 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/events/store" - "github.com/absmach/magistrala/pkg/grpcclient" - jaegerclient "github.com/absmach/magistrala/pkg/jaeger" - "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" - "github.com/absmach/magistrala/pkg/server/http" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/caarlos0/env/v11" - "github.com/jmoiron/sqlx" - "go.opentelemetry.io/otel/trace" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "journal" - envPrefixDB = "MG_JOURNAL_DB_" - envPrefixHTTP = "MG_JOURNAL_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - defDB = "journal" - defSvcHTTPPort = "9021" -) - -type config struct { - LogLevel string `env:"MG_JOURNAL_LOG_LEVEL" envDefault:"info"` - ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` - 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_JOURNAL_INSTANCE_ID" envDefault:""` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` -} - -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) - } - - logger, err := mglog.New(os.Stdout, cfg.LogLevel) - if err != nil { - log.Fatalf("failed to init logger: %s", err) - } - - 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 - } - db, err := pgclient.Setup(dbConfig, *journalpg.Migration()) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer db.Close() - - authClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&authClientCfg, 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 := authsvcAuthn.NewAuthentication(ctx, authClientCfg) - if err != nil { - logger.Error(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, authClientCfg) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer authzHandler.Close() - logger.Info("AuthZ successfully connected to auth gRPC server " + authzHandler.Secure()) - - 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: %s", err)) - } - }() - tracer := tp.Tracer(svcName) - - svc := newService(db, dbConfig, authz, logger, tracer) - - subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, logger) - if err != nil { - logger.Error(fmt.Sprintf("failed to create subscriber: %s", err)) - exitCode = 1 - return - } - - logger.Info("Subscribed to Event Store") - - if err := events.Start(ctx, svcName, subscriber, svc); err != nil { - logger.Error("failed to start %s service: %s", svcName, 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 - } - - hs := http.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, authn, logger, svcName, cfg.InstanceID), logger) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - g.Go(func() error { - return hs.Start() - }) - - g.Go(func() error { - return server.StopSignalHandler(ctx, cancel, logger, svcName, hs) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err)) - } -} - -func newService(db *sqlx.DB, dbConfig pgclient.Config, authz mgauthz.Authorization, logger *slog.Logger, tracer trace.Tracer) journal.Service { - database := postgres.NewDatabase(db, dbConfig, tracer) - repo := journalpg.NewRepository(database) - idp := uuid.New() - - svc := journal.NewService(idp, repo) - svc = middleware.AuthorizationMiddleware(svc, authz) - svc = middleware.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics("journal", "journal_writer") - svc = middleware.MetricsMiddleware(svc, counter, latency) - svc = middleware.Tracing(svc, tracer) - - return svc -} diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go deleted file mode 100644 index 1d226543d..000000000 --- a/cmd/mqtt/main.go +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains mqtt-adapter main function to start the mqtt-adapter service. -package main - -import ( - "context" - "fmt" - "io" - "log" - "log/slog" - "net/http" - "net/url" - "os" - "os/signal" - "syscall" - "time" - - chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/mqtt" - "github.com/absmach/magistrala/mqtt/events" - mqtttracing "github.com/absmach/magistrala/mqtt/tracing" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/grpcclient" - jaegerclient "github.com/absmach/magistrala/pkg/jaeger" - "github.com/absmach/magistrala/pkg/messaging/brokers" - brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" - "github.com/absmach/magistrala/pkg/messaging/handler" - mqttpub "github.com/absmach/magistrala/pkg/messaging/mqtt" - "github.com/absmach/magistrala/pkg/server" - "github.com/absmach/magistrala/pkg/uuid" - mgate "github.com/absmach/mgate" - mgatemqtt "github.com/absmach/mgate/pkg/mqtt" - "github.com/absmach/mgate/pkg/mqtt/websocket" - "github.com/absmach/mgate/pkg/session" - "github.com/caarlos0/env/v11" - "github.com/cenkalti/backoff/v4" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "mqtt" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - wsPathPrefix = "/mqtt" -) - -type config struct { - LogLevel string `env:"MG_MQTT_ADAPTER_LOG_LEVEL" envDefault:"info"` - MQTTPort string `env:"MG_MQTT_ADAPTER_MQTT_PORT" envDefault:"1883"` - MQTTTargetHost string `env:"MG_MQTT_ADAPTER_MQTT_TARGET_HOST" envDefault:"localhost"` - MQTTTargetPort string `env:"MG_MQTT_ADAPTER_MQTT_TARGET_PORT" envDefault:"1883"` - MQTTForwarderTimeout time.Duration `env:"MG_MQTT_ADAPTER_FORWARDER_TIMEOUT" envDefault:"30s"` - MQTTTargetHealthCheck string `env:"MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK" envDefault:""` - MQTTQoS uint8 `env:"MG_MQTT_ADAPTER_MQTT_QOS" envDefault:"1"` - HTTPPort string `env:"MG_MQTT_ADAPTER_WS_PORT" envDefault:"8080"` - HTTPTargetHost string `env:"MG_MQTT_ADAPTER_WS_TARGET_HOST" envDefault:"localhost"` - HTTPTargetPort string `env:"MG_MQTT_ADAPTER_WS_TARGET_PORT" envDefault:"8080"` - HTTPTargetPath string `env:"MG_MQTT_ADAPTER_WS_TARGET_PATH" envDefault:"/mqtt"` - Instance string `env:"MG_MQTT_ADAPTER_INSTANCE" envDefault:""` - JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` - BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` - SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"MG_MQTT_ADAPTER_INSTANCE_ID" envDefault:""` - ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` -} - -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) - } - - 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 - } - } - - if cfg.MQTTTargetHealthCheck != "" { - notify := func(e error, next time.Duration) { - logger.Info(fmt.Sprintf("Broker not ready: %s, next try in %s", e.Error(), next)) - } - - err := backoff.RetryNotify(healthcheck(cfg), backoff.NewExponentialBackOff(), notify) - if err != nil { - logger.Error(fmt.Sprintf("MQTT healthcheck limit exceeded, exiting. %s ", err)) - exitCode = 1 - return - } - } - - serverConfig := server.Config{ - Host: cfg.HTTPTargetHost, - Port: cfg.HTTPTargetPort, - } - - 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) - - bsub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger) - if err != nil { - logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err)) - exitCode = 1 - return - } - defer bsub.Close() - bsub = brokerstracing.NewPubSub(serverConfig, tracer, bsub) - - mpub, err := mqttpub.NewPublisher(fmt.Sprintf("mqtt://%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), cfg.MQTTQoS, cfg.MQTTForwarderTimeout) - if err != nil { - logger.Error(fmt.Sprintf("failed to create MQTT publisher: %s", err)) - exitCode = 1 - return - } - defer mpub.Close() - - fwd := mqtt.NewForwarder(brokers.SubjectAllChannels, logger) - fwd = mqtttracing.New(serverConfig, tracer, fwd, brokers.SubjectAllChannels) - if err := fwd.Forward(ctx, svcName, bsub, mpub); err != nil { - logger.Error(fmt.Sprintf("failed to forward message broker messages: %s", err)) - exitCode = 1 - return - } - - np, err := brokers.NewPublisher(ctx, cfg.BrokerURL) - if err != nil { - logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err)) - exitCode = 1 - return - } - defer np.Close() - np = brokerstracing.NewPublisher(serverConfig, tracer, np) - - es, err := events.NewEventStore(ctx, cfg.ESURL, cfg.Instance) - if err != nil { - logger.Error(fmt.Sprintf("failed to create %s event store : %s", svcName, err)) - exitCode = 1 - 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)) - exitCode = 1 - return - } - - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) - 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()) - - h := mqtt.NewHandler(np, es, logger, thingsClient) - h = handler.NewTracing(tracer, h) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - var interceptor session.Interceptor - logger.Info(fmt.Sprintf("Starting MQTT proxy on port %s", cfg.MQTTPort)) - g.Go(func() error { - return proxyMQTT(ctx, cfg, logger, h, interceptor) - }) - - logger.Info(fmt.Sprintf("Starting MQTT over WS proxy on port %s", cfg.HTTPPort)) - g.Go(func() error { - return proxyWS(ctx, cfg, logger, h, interceptor) - }) - - g.Go(func() error { - return stopSignalHandler(ctx, cancel, logger) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("mProxy terminated: %s", err)) - } -} - -func proxyMQTT(ctx context.Context, cfg config, logger *slog.Logger, sessionHandler session.Handler, interceptor session.Interceptor) error { - config := mgate.Config{ - Address: fmt.Sprintf(":%s", cfg.MQTTPort), - Target: fmt.Sprintf("%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), - } - mproxy := mgatemqtt.New(config, sessionHandler, interceptor, logger) - - errCh := make(chan error) - go func() { - errCh <- mproxy.Listen(ctx) - }() - - select { - case <-ctx.Done(): - logger.Info(fmt.Sprintf("proxy MQTT shutdown at %s", config.Target)) - return nil - case err := <-errCh: - return err - } -} - -func proxyWS(ctx context.Context, cfg config, logger *slog.Logger, sessionHandler session.Handler, interceptor session.Interceptor) error { - config := mgate.Config{ - Address: fmt.Sprintf("%s:%s", "", cfg.HTTPPort), - Target: fmt.Sprintf("ws://%s:%s%s", cfg.HTTPTargetHost, cfg.HTTPTargetPort, wsPathPrefix), - PathPrefix: wsPathPrefix, - } - - wp := websocket.New(config, sessionHandler, interceptor, logger) - http.HandleFunc(wsPathPrefix, wp.ServeHTTP) - - errCh := make(chan error) - - go func() { - errCh <- wp.Listen(ctx) - }() - - select { - case <-ctx.Done(): - logger.Info(fmt.Sprintf("proxy MQTT WS shutdown at %s", config.Target)) - return nil - case err := <-errCh: - return err - } -} - -func healthcheck(cfg config) func() error { - return func() error { - res, err := http.Get(cfg.MQTTTargetHealthCheck) - if err != nil { - return err - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - return errors.New(string(body)) - } - return nil - } -} - -func stopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *slog.Logger) error { - c := make(chan os.Signal, 2) - signal.Notify(c, syscall.SIGINT, syscall.SIGABRT) - select { - case sig := <-c: - defer cancel() - logger.Info(fmt.Sprintf("%s service shutdown by signal: %s", svcName, sig)) - return nil - case <-ctx.Done(): - return nil - } -} diff --git a/cmd/postgres-reader/main.go b/cmd/postgres-reader/main.go index 0bbb62554..5157c9270 100644 --- a/cmd/postgres-reader/main.go +++ b/cmd/postgres-reader/main.go @@ -12,38 +12,38 @@ import ( "os" 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/grpcclient" - 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/readers" - "github.com/absmach/magistrala/readers/api" - "github.com/absmach/magistrala/readers/postgres" + "github.com/absmach/supermq" + smqlog "github.com/absmach/supermq/logger" + "github.com/absmach/supermq/pkg/authn/authsvc" + "github.com/absmach/supermq/pkg/grpcclient" + pgclient "github.com/absmach/supermq/pkg/postgres" + "github.com/absmach/supermq/pkg/prometheus" + "github.com/absmach/supermq/pkg/server" + httpserver "github.com/absmach/supermq/pkg/server/http" + "github.com/absmach/supermq/pkg/uuid" + "github.com/absmach/supermq/readers" + httpapi "github.com/absmach/supermq/readers/api" + "github.com/absmach/supermq/readers/postgres" "github.com/caarlos0/env/v11" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" ) 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 = "SMQ_POSTGRES_" + envPrefixHTTP = "SMQ_POSTGRES_READER_HTTP_" + envPrefixAuth = "SMQ_AUTH_GRPC_" + envPrefixClients = "SMQ_CLIENTS_AUTH_GRPC_" + envPrefixChannels = "SMQ_CHANNELS_GRPC_" + defDB = "supermq" + defSvcHTTPPort = "9009" ) type config struct { - LogLevel string `env:"MG_POSTGRES_READER_LOG_LEVEL" envDefault:"info"` - SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"MG_POSTGRES_READER_INSTANCE_ID" envDefault:""` + LogLevel string `env:"SMQ_POSTGRES_READER_LOG_LEVEL" envDefault:"info"` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"SMQ_POSTGRES_READER_INSTANCE_ID" envDefault:""` } func main() { @@ -55,13 +55,13 @@ func main() { log.Fatalf("failed to load %s configuration : %s", svcName, err) } - logger, err := mglog.New(os.Stdout, cfg.LogLevel) + logger, err := smqlog.New(os.Stdout, cfg.LogLevel) if err != nil { log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int - defer mglog.ExitWithError(&exitCode) + defer smqlog.ExitWithError(&exitCode) if cfg.InstanceID == "" { if cfg.InstanceID, err = uuid.New().ID(); err != nil { @@ -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,10 +141,10 @@ 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, httpapi.MakeHandler(repo, authn, clientsClient, channelsClient, svcName, cfg.InstanceID), logger) if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) + chc := chclient.New(svcName, supermq.Version, logger, cancel) go chc.CallHome(ctx) } @@ -157,9 +163,9 @@ func main() { func newService(db *sqlx.DB, logger *slog.Logger) readers.MessageRepository { svc := postgres.New(db) - svc = api.LoggingMiddleware(svc, logger) + svc = httpapi.LoggingMiddleware(svc, logger) counter, latency := prometheus.MakeMetrics("postgres", "message_reader") - svc = api.MetricsMiddleware(svc, counter, latency) + svc = httpapi.MetricsMiddleware(svc, counter, latency) return svc } diff --git a/cmd/postgres-writer/main.go b/cmd/postgres-writer/main.go index d5b258e09..c6923b217 100644 --- a/cmd/postgres-writer/main.go +++ b/cmd/postgres-writer/main.go @@ -13,20 +13,20 @@ import ( "os" chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/consumers" - consumertracing "github.com/absmach/magistrala/consumers/tracing" - "github.com/absmach/magistrala/consumers/writers/api" - writerpg "github.com/absmach/magistrala/consumers/writers/postgres" - mglog "github.com/absmach/magistrala/logger" - jaegerclient "github.com/absmach/magistrala/pkg/jaeger" - "github.com/absmach/magistrala/pkg/messaging/brokers" - brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" - 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/supermq" + "github.com/absmach/supermq/consumers" + consumertracing "github.com/absmach/supermq/consumers/tracing" + httpapi "github.com/absmach/supermq/consumers/writers/api" + writerpg "github.com/absmach/supermq/consumers/writers/postgres" + smqlog "github.com/absmach/supermq/logger" + jaegerclient "github.com/absmach/supermq/pkg/jaeger" + "github.com/absmach/supermq/pkg/messaging/brokers" + brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing" + pgclient "github.com/absmach/supermq/pkg/postgres" + "github.com/absmach/supermq/pkg/prometheus" + "github.com/absmach/supermq/pkg/server" + httpserver "github.com/absmach/supermq/pkg/server/http" + "github.com/absmach/supermq/pkg/uuid" "github.com/caarlos0/env/v11" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" @@ -34,20 +34,20 @@ import ( const ( svcName = "postgres-writer" - envPrefixDB = "MG_POSTGRES_" - envPrefixHTTP = "MG_POSTGRES_WRITER_HTTP_" + envPrefixDB = "SMQ_POSTGRES_" + envPrefixHTTP = "SMQ_POSTGRES_WRITER_HTTP_" defDB = "messages" defSvcHTTPPort = "9010" ) type config struct { - LogLevel string `env:"MG_POSTGRES_WRITER_LOG_LEVEL" envDefault:"info"` - ConfigPath string `env:"MG_POSTGRES_WRITER_CONFIG_PATH" envDefault:"/config.toml"` - BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` - 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_POSTGRES_WRITER_INSTANCE_ID" envDefault:""` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` + LogLevel string `env:"SMQ_POSTGRES_WRITER_LOG_LEVEL" envDefault:"info"` + ConfigPath string `env:"SMQ_POSTGRES_WRITER_CONFIG_PATH" envDefault:"/config.toml"` + BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` + JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"SMQ_POSTGRES_WRITER_INSTANCE_ID" envDefault:""` + TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` } func main() { @@ -59,13 +59,13 @@ func main() { log.Fatalf("failed to load %s configuration : %s", svcName, err) } - logger, err := mglog.New(os.Stdout, cfg.LogLevel) + logger, err := smqlog.New(os.Stdout, cfg.LogLevel) if err != nil { log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int - defer mglog.ExitWithError(&exitCode) + defer smqlog.ExitWithError(&exitCode) if cfg.InstanceID == "" { if cfg.InstanceID, err = uuid.New().ID(); err != nil { @@ -125,10 +125,10 @@ func main() { return } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svcName, cfg.InstanceID), logger) + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svcName, cfg.InstanceID), logger) if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) + chc := chclient.New(svcName, supermq.Version, logger, cancel) go chc.CallHome(ctx) } @@ -147,8 +147,8 @@ func main() { func newService(db *sqlx.DB, logger *slog.Logger) consumers.BlockingConsumer { svc := writerpg.New(db) - svc = api.LoggingMiddleware(svc, logger) + svc = httpapi.LoggingMiddleware(svc, logger) counter, latency := prometheus.MakeMetrics("postgres", "message_writer") - svc = api.MetricsMiddleware(svc, counter, latency) + svc = httpapi.MetricsMiddleware(svc, counter, latency) return svc } diff --git a/cmd/provision/main.go b/cmd/provision/main.go index 986f7acf7..c4beb7cbb 100644 --- a/cmd/provision/main.go +++ b/cmd/provision/main.go @@ -13,17 +13,17 @@ import ( "reflect" chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - 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" + httpapi "github.com/absmach/magistrala/provision/api" + "github.com/absmach/supermq" + "github.com/absmach/supermq/channels" + "github.com/absmach/supermq/clients" + smqlog "github.com/absmach/supermq/logger" + "github.com/absmach/supermq/pkg/errors" + mgsdk "github.com/absmach/supermq/pkg/sdk" + "github.com/absmach/supermq/pkg/server" + httpserver "github.com/absmach/supermq/pkg/server/http" + "github.com/absmach/supermq/pkg/uuid" "github.com/caarlos0/env/v11" "golang.org/x/sync/errgroup" ) @@ -48,13 +48,13 @@ func main() { log.Fatalf("failed to load %s configuration : %s", svcName, err) } - logger, err := mglog.New(os.Stdout, cfg.Server.LogLevel) + logger, err := smqlog.New(os.Stdout, cfg.Server.LogLevel) if err != nil { log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int - defer mglog.ExitWithError(&exitCode) + defer smqlog.ExitWithError(&exitCode) if cfg.InstanceID == "" { if cfg.InstanceID, err = uuid.New().ID(); err != nil { @@ -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, @@ -84,13 +84,13 @@ func main() { SDK := mgsdk.NewSDK(SDKCfg) svc := provision.New(cfg, SDK, logger) - svc = api.NewLoggingMiddleware(svc, logger) + svc = httpapi.NewLoggingMiddleware(svc, logger) httpServerConfig := server.Config{Host: "", Port: cfg.Server.HTTPPort, KeyFile: cfg.Server.ServerKey, CertFile: cfg.Server.ServerCert} - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, logger, cfg.InstanceID), logger) + 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) + chc := chclient.New(svcName, supermq.Version, logger, cancel) go chc.CallHome(ctx) } @@ -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/re/main.go b/cmd/re/main.go index 4ce170b67..14196874e 100644 --- a/cmd/re/main.go +++ b/cmd/re/main.go @@ -14,51 +14,49 @@ import ( "time" chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/consumers" - redisclient "github.com/absmach/magistrala/internal/clients/redis" - mglog "github.com/absmach/magistrala/logger" - authnsvc "github.com/absmach/magistrala/pkg/authn/authsvc" - mgauthz "github.com/absmach/magistrala/pkg/authz" - authzsvc "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/messaging/brokers" - brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" - pgclient "github.com/absmach/magistrala/pkg/postgres" - "github.com/absmach/magistrala/pkg/server" - httpserver "github.com/absmach/magistrala/pkg/server/http" - "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/re" httpapi "github.com/absmach/magistrala/re/api" repg "github.com/absmach/magistrala/re/postgres" + "github.com/absmach/supermq" + "github.com/absmach/supermq/consumers" + smqlog "github.com/absmach/supermq/logger" + authnsvc "github.com/absmach/supermq/pkg/authn/authsvc" + mgauthz "github.com/absmach/supermq/pkg/authz" + authzsvc "github.com/absmach/supermq/pkg/authz/authsvc" + "github.com/absmach/supermq/pkg/grpcclient" + jaegerclient "github.com/absmach/supermq/pkg/jaeger" + "github.com/absmach/supermq/pkg/messaging/brokers" + brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing" + pgclient "github.com/absmach/supermq/pkg/postgres" + "github.com/absmach/supermq/pkg/server" + httpserver "github.com/absmach/supermq/pkg/server/http" + "github.com/absmach/supermq/pkg/uuid" "github.com/caarlos0/env/v11" "github.com/jmoiron/sqlx" - "github.com/redis/go-redis/v9" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) const ( svcName = "rules_engine" - envPrefixDB = "MG_RE_DB_" - envPrefixHTTP = "MG_RE_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" + envPrefixDB = "SMQ_RE_DB_" + envPrefixHTTP = "SMQ_RE_HTTP_" + envPrefixAuth = "SMQ_AUTH_GRPC_" defDB = "r" defSvcHTTPPort = "9008" ) type config struct { - LogLevel string `env:"MG_RE_LOG_LEVEL" envDefault:"info"` - InstanceID string `env:"MG_RE_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"` - CacheURL string `env:"MG_RE_CACHE_URL" envDefault:"redis://localhost:6379/0"` - CacheKeyDuration time.Duration `env:"MG_RE_CACHE_KEY_DURATION" envDefault:"10m"` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` - ConfigPath string `env:"MG_RE_CONFIG_PATH" envDefault:"/config.toml"` - BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` + LogLevel string `env:"SMQ_RE_LOG_LEVEL" envDefault:"info"` + InstanceID string `env:"SMQ_RE_INSTANCE_ID" envDefault:""` + JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + CacheURL string `env:"SMQ_RE_CACHE_URL" envDefault:"redis://localhost:6379/0"` + CacheKeyDuration time.Duration `env:"SMQ_RE_CACHE_KEY_DURATION" envDefault:"10m"` + TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` + ConfigPath string `env:"SMQ_RE_CONFIG_PATH" envDefault:"/config.toml"` + BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` } func main() { @@ -72,13 +70,13 @@ func main() { } var logger *slog.Logger - logger, err := mglog.New(os.Stdout, cfg.LogLevel) + logger, err := smqlog.New(os.Stdout, cfg.LogLevel) if err != nil { log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int - defer mglog.ExitWithError(&exitCode) + defer smqlog.ExitWithError(&exitCode) if cfg.InstanceID == "" { if cfg.InstanceID, err = uuid.New().ID(); err != nil { @@ -115,7 +113,6 @@ func main() { } }() tracer := tp.Tracer(svcName) - pubSub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger) if err != nil { logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err)) @@ -133,13 +130,13 @@ func main() { pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub) // Setup new redis cache client - cacheclient, err := redisclient.Connect(cfg.CacheURL) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer cacheclient.Close() + // cacheclient, err := redisclient.Connect(cfg.CacheURL) + // if err != nil { + // logger.Error(err.Error()) + // exitCode = 1 + // return + // } + // defer cacheclient.Close() grpcCfg := grpcclient.Config{} if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil { @@ -156,7 +153,7 @@ func main() { defer authnClient.Close() logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure()) - authz, authzClient, err := authzsvc.NewAuthorization(ctx, grpcCfg) + authz, authzClient, err := authzsvc.NewAuthorization(ctx, grpcCfg, nil) if err != nil { logger.Error(err.Error()) exitCode = 1 @@ -165,7 +162,7 @@ func main() { defer authzClient.Close() logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure()) - svc, err := newService(ctx, db, dbConfig, authz, cacheclient, cfg.CacheKeyDuration, cfg.ESURL, tracer, logger) + svc, err := newService(ctx, db, dbConfig, authz, cfg.ESURL, tracer, logger) if err != nil { logger.Error(fmt.Sprintf("failed to create services: %s", err)) exitCode = 1 @@ -180,7 +177,7 @@ func main() { httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, logger, cfg.InstanceID), logger) if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) + chc := chclient.New(svcName, supermq.Version, logger, cancel) go chc.CallHome(ctx) } @@ -198,7 +195,7 @@ func main() { } } -func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authz mgauthz.Authorization, cacheClient *redis.Client, keyDuration time.Duration, esURL string, tracer trace.Tracer, logger *slog.Logger) (re.Service, error) { +func newService(ctx context.Context, db *sqlx.DB, dbConfig pgclient.Config, authz mgauthz.Authorization, esURL string, tracer trace.Tracer, logger *slog.Logger) (re.Service, error) { database := pgclient.NewDatabase(db, dbConfig, tracer) repo := repg.NewRepository(database) idp := uuid.New() diff --git a/cmd/things/main.go b/cmd/things/main.go deleted file mode 100644 index 6410e6abc..000000000 --- a/cmd/things/main.go +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains things main function to start the things service. -package main - -import ( - "context" - "fmt" - "log" - "log/slog" - "net/url" - "os" - "time" - - chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - 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" - 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" - 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/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" - "github.com/go-chi/chi/v5" - "github.com/jmoiron/sqlx" - "github.com/redis/go-redis/v9" - "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 = "things" - envPrefixDB = "MG_THINGS_DB_" - envPrefixHTTP = "MG_THINGS_HTTP_" - envPrefixGRPC = "MG_THINGS_AUTH_GRPC_" - envPrefixAuth = "MG_AUTH_GRPC_" - defDB = "things" - 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:""` - 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"` - 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 things 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 things - 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...) - db, err := pgclient.Setup(dbConfig, *tm) - 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) - - // Setup new redis cache client - cacheclient, err := redisclient.Connect(cfg.CacheURL) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer cacheclient.Close() - - policyEvaluator, policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - logger.Info("Policy evaluator and Policy manager 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 " + authnClient.Secure()) - - csvc, gsvc, err := newService(ctx, db, dbConfig, authz, policyEvaluator, policyService, cacheclient, cfg.CacheKeyDuration, cfg.ESURL, tracer, logger) - if err != nil { - logger.Error(fmt.Sprintf("failed to create services: %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)) - exitCode = 1 - return - } - mux := chi.NewRouter() - httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(csvc, gsvc, authn, mux, logger, cfg.InstanceID), logger) - - grpcServerConfig := server.Config{Port: defSvcAuthGRPCPort} - 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 - } - registerThingsServer := func(srv *grpc.Server) { - reflection.Register(srv) - magistrala.RegisterThingsServiceServer(srv, grpcapi.NewServer(csvc)) - } - gs := grpcserver.NewServer(ctx, cancel, svcName, grpcServerConfig, registerThingsServer, 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, 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) - - idp := uuid.New() - - thingCache := thcache.NewCache(cacheClient, keyDuration) - - csvc := things.NewService(pe, ps, cRepo, thingCache, idp) - gsvc := mggroups.NewService(gRepo, idp, ps) - - csvc, err := thevents.NewEventStoreMiddleware(ctx, csvc, esURL) - if err != nil { - return nil, nil, err - } - - gsvc, err = gevents.NewEventStoreMiddleware(ctx, gsvc, esURL, streamID) - if err != nil { - return nil, nil, err - } - - csvc = tmiddleware.AuthorizationMiddleware(csvc, authz) - gsvc = gmiddleware.AuthorizationMiddleware(gsvc, authz) - - csvc = ctracing.New(csvc, tracer) - csvc = tmiddleware.LoggingMiddleware(csvc, logger) - counter, latency := prometheus.MakeMetrics(svcName, "api") - csvc = tmiddleware.MetricsMiddleware(csvc, counter, latency) - - 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) - - return csvc, gsvc, 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 - } - pe := spicedb.NewPolicyEvaluator(client, logger) - ps := spicedb.NewPolicyService(client, logger) - - return pe, ps, nil -} diff --git a/cmd/timescale-reader/main.go b/cmd/timescale-reader/main.go index 066291a7f..ed5e22913 100644 --- a/cmd/timescale-reader/main.go +++ b/cmd/timescale-reader/main.go @@ -12,38 +12,38 @@ import ( "os" 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/grpcclient" - 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/readers" - "github.com/absmach/magistrala/readers/api" - "github.com/absmach/magistrala/readers/timescale" + "github.com/absmach/supermq" + smqlog "github.com/absmach/supermq/logger" + "github.com/absmach/supermq/pkg/authn/authsvc" + "github.com/absmach/supermq/pkg/grpcclient" + pgclient "github.com/absmach/supermq/pkg/postgres" + "github.com/absmach/supermq/pkg/prometheus" + "github.com/absmach/supermq/pkg/server" + httpserver "github.com/absmach/supermq/pkg/server/http" + "github.com/absmach/supermq/pkg/uuid" + "github.com/absmach/supermq/readers" + httpapi "github.com/absmach/supermq/readers/api" + "github.com/absmach/supermq/readers/timescale" "github.com/caarlos0/env/v11" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" ) 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 = "SMQ_TIMESCALE_" + envPrefixHTTP = "SMQ_TIMESCALE_READER_HTTP_" + envPrefixAuth = "SMQ_AUTH_GRPC_" + envPrefixClients = "SMQ_CLIENTS_AUTH_GRPC_" + envPrefixChannels = "SMQ_CHANNELS_GRPC_" + defDB = "messages" + defSvcHTTPPort = "9011" ) type config struct { - LogLevel string `env:"MG_TIMESCALE_READER_LOG_LEVEL" envDefault:"info"` - SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"MG_TIMESCALE_READER_INSTANCE_ID" envDefault:""` + LogLevel string `env:"SMQ_TIMESCALE_READER_LOG_LEVEL" envDefault:"info"` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"SMQ_TIMESCALE_READER_INSTANCE_ID" envDefault:""` } func main() { @@ -55,13 +55,13 @@ func main() { log.Fatalf("failed to load %s configuration : %s", svcName, err) } - logger, err := mglog.New(os.Stdout, cfg.LogLevel) + logger, err := smqlog.New(os.Stdout, cfg.LogLevel) if err != nil { log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int - defer mglog.ExitWithError(&exitCode) + defer smqlog.ExitWithError(&exitCode) if cfg.InstanceID == "" { if cfg.InstanceID, err = uuid.New().ID(); err != nil { @@ -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,10 +140,10 @@ 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, httpapi.MakeHandler(repo, authn, clientsClient, channelsClient, svcName, cfg.InstanceID), logger) if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) + chc := chclient.New(svcName, supermq.Version, logger, cancel) go chc.CallHome(ctx) } @@ -155,9 +162,9 @@ func main() { func newService(db *sqlx.DB, logger *slog.Logger) readers.MessageRepository { svc := timescale.New(db) - svc = api.LoggingMiddleware(svc, logger) + svc = httpapi.LoggingMiddleware(svc, logger) counter, latency := prometheus.MakeMetrics("timescale", "message_reader") - svc = api.MetricsMiddleware(svc, counter, latency) + svc = httpapi.MetricsMiddleware(svc, counter, latency) return svc } diff --git a/cmd/timescale-writer/main.go b/cmd/timescale-writer/main.go index 1b26fcda7..394abca9f 100644 --- a/cmd/timescale-writer/main.go +++ b/cmd/timescale-writer/main.go @@ -13,20 +13,20 @@ import ( "os" chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/consumers" - consumertracing "github.com/absmach/magistrala/consumers/tracing" - "github.com/absmach/magistrala/consumers/writers/api" - "github.com/absmach/magistrala/consumers/writers/timescale" - mglog "github.com/absmach/magistrala/logger" - jaegerclient "github.com/absmach/magistrala/pkg/jaeger" - "github.com/absmach/magistrala/pkg/messaging/brokers" - brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" - 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/supermq" + "github.com/absmach/supermq/consumers" + consumertracing "github.com/absmach/supermq/consumers/tracing" + httpapi "github.com/absmach/supermq/consumers/writers/api" + "github.com/absmach/supermq/consumers/writers/timescale" + smqlog "github.com/absmach/supermq/logger" + jaegerclient "github.com/absmach/supermq/pkg/jaeger" + "github.com/absmach/supermq/pkg/messaging/brokers" + brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing" + pgclient "github.com/absmach/supermq/pkg/postgres" + "github.com/absmach/supermq/pkg/prometheus" + "github.com/absmach/supermq/pkg/server" + httpserver "github.com/absmach/supermq/pkg/server/http" + "github.com/absmach/supermq/pkg/uuid" "github.com/caarlos0/env/v11" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" @@ -34,20 +34,20 @@ import ( const ( svcName = "timescaledb-writer" - envPrefixDB = "MG_TIMESCALE_" - envPrefixHTTP = "MG_TIMESCALE_WRITER_HTTP_" + envPrefixDB = "SMQ_TIMESCALE_" + envPrefixHTTP = "SMQ_TIMESCALE_WRITER_HTTP_" defDB = "messages" defSvcHTTPPort = "9012" ) type config struct { - LogLevel string `env:"MG_TIMESCALE_WRITER_LOG_LEVEL" envDefault:"info"` - ConfigPath string `env:"MG_TIMESCALE_WRITER_CONFIG_PATH" envDefault:"/config.toml"` - BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` - 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_TIMESCALE_WRITER_INSTANCE_ID" envDefault:""` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` + LogLevel string `env:"SMQ_TIMESCALE_WRITER_LOG_LEVEL" envDefault:"info"` + ConfigPath string `env:"SMQ_TIMESCALE_WRITER_CONFIG_PATH" envDefault:"/config.toml"` + BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` + JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"SMQ_TIMESCALE_WRITER_INSTANCE_ID" envDefault:""` + TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` } func main() { @@ -59,13 +59,13 @@ func main() { log.Fatalf("failed to load %s service configuration : %s", svcName, err) } - logger, err := mglog.New(os.Stdout, cfg.LogLevel) + logger, err := smqlog.New(os.Stdout, cfg.LogLevel) if err != nil { log.Fatalf("failed to init logger: %s", err.Error()) } var exitCode int - defer mglog.ExitWithError(&exitCode) + defer smqlog.ExitWithError(&exitCode) if cfg.InstanceID == "" { if cfg.InstanceID, err = uuid.New().ID(); err != nil { @@ -127,10 +127,10 @@ func main() { return } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svcName, cfg.InstanceID), logger) + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svcName, cfg.InstanceID), logger) if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) + chc := chclient.New(svcName, supermq.Version, logger, cancel) go chc.CallHome(ctx) } @@ -149,8 +149,8 @@ func main() { func newService(db *sqlx.DB, logger *slog.Logger) consumers.BlockingConsumer { svc := timescale.New(db) - svc = api.LoggingMiddleware(svc, logger) + svc = httpapi.LoggingMiddleware(svc, logger) counter, latency := prometheus.MakeMetrics("timescale", "message_writer") - svc = api.MetricsMiddleware(svc, counter, latency) + svc = httpapi.MetricsMiddleware(svc, counter, latency) return svc } diff --git a/cmd/users/main.go b/cmd/users/main.go deleted file mode 100644 index 3d285b9af..000000000 --- a/cmd/users/main.go +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains users main function to start the users service. -package main - -import ( - "context" - "fmt" - "log" - "log/slog" - "net/url" - "os" - "regexp" - "time" - - 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" - 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" - 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/emailer" - uevents "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/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" -) - -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" -) - -type config struct { - LogLevel string `env:"MG_USERS_LOG_LEVEL" envDefault:"info"` - AdminEmail string `env:"MG_USERS_ADMIN_EMAIL" envDefault:"admin@example.com"` - AdminPassword string `env:"MG_USERS_ADMIN_PASSWORD" envDefault:"12345678"` - AdminUsername string `env:"MG_USERS_ADMIN_USERNAME" envDefault:"admin"` - AdminFirstName string `env:"MG_USERS_ADMIN_FIRST_NAME" envDefault:"super"` - AdminLastName string `env:"MG_USERS_ADMIN_LAST_NAME" envDefault:"admin"` - PassRegexText string `env:"MG_USERS_PASS_REGEX" envDefault:"^.{8,}$"` - ResetURL string `env:"MG_TOKEN_RESET_ENDPOINT" envDefault:"/reset-request"` - 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_USERS_INSTANCE_ID" envDefault:""` - ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` - SelfRegister bool `env:"MG_USERS_ALLOW_SELF_REGISTER" envDefault:"false"` - OAuthUIRedirectURL string `env:"MG_OAUTH_UI_REDIRECT_URL" envDefault:"http://localhost:9095/domains"` - OAuthUIErrorURL string `env:"MG_OAUTH_UI_ERROR_URL" envDefault:"http://localhost:9095/error"` - DeleteInterval time.Duration `env:"MG_USERS_DELETE_INTERVAL" envDefault:"24h"` - DeleteAfter time.Duration `env:"MG_USERS_DELETE_AFTER" envDefault:"720h"` - 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"` - PassRegex *regexp.Regexp -} - -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()) - } - passRegex, err := regexp.Compile(cfg.PassRegexText) - if err != nil { - log.Fatalf("invalid password validation rules %s\n", cfg.PassRegexText) - } - cfg.PassRegex = passRegex - - 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 - } - } - - ec := email.Config{} - if err := env.Parse(&ec); err != nil { - logger.Error(fmt.Sprintf("failed to load email configuration : %s", err.Error())) - 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 - } - cm := clientspg.Migration() - gm := gpostgres.Migration() - cm.Migrations = append(cm.Migrations, gm.Migrations...) - db, err := pgclient.Setup(dbConfig, *cm) - 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) - - clientConfig := grpcclient.Config{} - if err := env.ParseWithOptions(&clientConfig, 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) - if err != nil { - logger.Error(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) - if err != nil { - logger.Error(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(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) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer domainsHandler.Close() - logger.Info("DomainsService gRPC client successfully connected to auth gRPC server " + domainsHandler.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") - - csvc, gsvc, 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 - 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 - } - - oauthConfig := oauth2.Config{} - if err := env.ParseWithOptions(&oauthConfig, env.Options{Prefix: envPrefixGoogle}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s Google configuration : %s", svcName, err.Error())) - exitCode = 1 - return - } - 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) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - 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("users service terminated: %s", err)) - } -} - -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) - - idp := uuid.New() - hsr := hasher.New() - - 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) - - csvc, err = uevents.NewEventStoreMiddleware(ctx, csvc, c.ESURL) - if err != nil { - return nil, nil, err - } - gsvc, err = gevents.NewEventStoreMiddleware(ctx, gsvc, c.ESURL, streamID) - if err != nil { - return nil, nil, err - } - - csvc = cmiddleware.AuthorizationMiddleware(csvc, authz, c.SelfRegister) - gsvc = gmiddleware.AuthorizationMiddleware(gsvc, authz) - - csvc = ctracing.New(csvc, tracer) - csvc = cmiddleware.LoggingMiddleware(csvc, logger) - counter, latency := prometheus.MakeMetrics(svcName, "api") - csvc = cmiddleware.MetricsMiddleware(csvc, 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) - 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 - } - - users.NewDeleteHandler(ctx, cRepo, policyService, domainsClient, c.DeleteInterval, c.DeleteAfter, logger) - - return csvc, gsvc, err -} - -func createAdmin(ctx context.Context, c config, urepo users.Repository, hsr users.Hasher, svc users.Service) (string, error) { - id, err := uuid.New().ID() - if err != nil { - return "", err - } - hash, err := hsr.Hash(c.AdminPassword) - if err != nil { - return "", err - } - - user := users.User{ - ID: id, - Email: c.AdminEmail, - FirstName: c.AdminFirstName, - LastName: c.AdminLastName, - Credentials: users.Credentials{ - Username: "admin", - Secret: hash, - }, - Metadata: users.Metadata{ - "role": "admin", - }, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Role: users.AdminRole, - Status: users.EnabledStatus, - } - - if u, err := urepo.RetrieveByEmail(ctx, user.Email); err == nil { - return u.ID, nil - } - - // Create an admin - if _, err = urepo.Save(ctx, user); err != nil { - return "", err - } - if _, err = svc.IssueToken(ctx, c.AdminUsername, c.AdminPassword); err != nil { - return "", err - } - return user.ID, nil -} - -func createAdminPolicy(ctx context.Context, userID string, authz mgauthz.Authorization, policyService policies.Service) error { - if err := authz.Authorize(ctx, mgauthz.PolicyReq{ - SubjectType: policies.UserType, - Subject: userID, - Permission: policies.AdministratorRelation, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err != nil { - err := policyService.AddPolicy(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.AdministratorRelation, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }) - if err != nil { - return err - } - } - return 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/ws/main.go b/cmd/ws/main.go deleted file mode 100644 index a2f1e57d5..000000000 --- a/cmd/ws/main.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains websocket-adapter main function to start the websocket-adapter service. -package main - -import ( - "context" - "fmt" - "log" - "log/slog" - "net/url" - "os" - - chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/grpcclient" - jaegerclient "github.com/absmach/magistrala/pkg/jaeger" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/brokers" - brokerstracing "github.com/absmach/magistrala/pkg/messaging/brokers/tracing" - "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/ws" - "github.com/absmach/magistrala/ws/api" - "github.com/absmach/magistrala/ws/tracing" - "github.com/absmach/mgate/pkg/session" - "github.com/absmach/mgate/pkg/websockets" - "github.com/caarlos0/env/v11" - "go.opentelemetry.io/otel/trace" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "ws-adapter" - envPrefixHTTP = "MG_WS_ADAPTER_HTTP_" - envPrefixThings = "MG_THINGS_AUTH_GRPC_" - defSvcHTTPPort = "8190" - targetWSPort = "8191" - targetWSHost = "localhost" -) - -type config struct { - LogLevel string `env:"MG_WS_ADAPTER_LOG_LEVEL" envDefault:"info"` - BrokerURL string `env:"MG_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` - 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_WS_ADAPTER_INSTANCE_ID" envDefault:""` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` -} - -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) - } - - 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 - } - } - - 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 - } - - targetServerConfig := server.Config{ - Port: targetWSPort, - Host: targetWSHost, - } - - 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)) - exitCode = 1 - return - } - - thingsClient, thingsHandler, err := grpcclient.SetupThingsClient(ctx, thingsClientCfg) - 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()) - - 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) - - nps, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger) - if err != nil { - logger.Error(fmt.Sprintf("Failed to connect to message broker: %s", err)) - exitCode = 1 - return - } - defer nps.Close() - nps = brokerstracing.NewPubSub(targetServerConfig, tracer, nps) - - svc := newService(thingsClient, nps, logger, tracer) - - hs := httpserver.NewServer(ctx, cancel, svcName, targetServerConfig, api.MakeHandler(ctx, svc, logger, cfg.InstanceID), logger) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - g.Go(func() error { - g.Go(func() error { - return hs.Start() - }) - handler := ws.NewHandler(nps, logger, thingsClient) - return proxyWS(ctx, httpServerConfig, targetServerConfig, logger, handler) - }) - - g.Go(func() error { - return server.StopSignalHandler(ctx, cancel, logger, svcName, hs) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("WS adapter service terminated: %s", err)) - } -} - -func newService(thingsClient magistrala.ThingsServiceClient, nps messaging.PubSub, logger *slog.Logger, tracer trace.Tracer) ws.Service { - svc := ws.New(thingsClient, nps) - svc = tracing.New(tracer, svc) - svc = api.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics("ws_adapter", "api") - svc = api.MetricsMiddleware(svc, counter, latency) - return svc -} - -func proxyWS(ctx context.Context, hostConfig, targetConfig server.Config, logger *slog.Logger, handler session.Handler) error { - target := fmt.Sprintf("ws://%s:%s", targetConfig.Host, targetConfig.Port) - address := fmt.Sprintf("%s:%s", hostConfig.Host, hostConfig.Port) - wp, err := websockets.NewProxy(address, target, logger, handler) - if err != nil { - return err - } - - errCh := make(chan error) - - go func() { - if hostConfig.CertFile != "" && hostConfig.KeyFile != "" { - logger.Info(fmt.Sprintf("ws-adapter service http server listening at %s:%s with TLS", hostConfig.Host, hostConfig.Port)) - errCh <- wp.ListenTLS(hostConfig.CertFile, hostConfig.KeyFile) - } else { - logger.Info(fmt.Sprintf("ws-adapter service http server listening at %s:%s without TLS", hostConfig.Host, hostConfig.Port)) - errCh <- wp.Listen() - } - }() - - select { - case <-ctx.Done(): - logger.Info(fmt.Sprintf("proxy MQTT WS shutdown at %s", target)) - return nil - case err := <-errCh: - return err - } -} diff --git a/coap/README.md b/coap/README.md deleted file mode 100644 index 373bd866a..000000000 --- a/coap/README.md +++ /dev/null @@ -1,80 +0,0 @@ -# Magistrala CoAP Adapter - -Magistrala CoAP adapter provides an [CoAP](http://coap.technology/) API for sending messages through the platform. - -## 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_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 | "" | - -## 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. -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 http -make coap - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_COAP_ADAPTER_LOG_LEVEL=info \ -MG_COAP_ADAPTER_HOST=localhost \ -MG_COAP_ADAPTER_PORT=5683 \ -MG_COAP_ADAPTER_SERVER_CERT="" \ -MG_COAP_ADAPTER_SERVER_KEY="" \ -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_MESSAGE_BROKER_URL=nats://localhost:4222 \ -MG_JAEGER_URL=http://localhost:14268/api/traces \ -MG_JAEGER_TRACE_RATIO=1.0 \ -MG_SEND_TELEMETRY=true \ -MG_COAP_ADAPTER_INSTANCE_ID="" \ -$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. - -## 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. diff --git a/coap/adapter.go b/coap/adapter.go deleted file mode 100644 index 2d25b3c0c..000000000 --- a/coap/adapter.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package coap contains the domain concept definitions needed to support -// Magistrala CoAP adapter service functionality. All constant values are taken -// from RFC, and could be adjusted based on specific use case. -package coap - -import ( - "context" - "fmt" - - "github.com/absmach/magistrala" - "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" -) - -const chansPrefix = "channels" - -// Service specifies CoAP service API. -type Service interface { - // Publish publishes message to specified channel. - // Key is used to authorize publisher. - Publish(ctx context.Context, key string, msg *messaging.Message) error - - // Subscribes to channel with specified id, subtopic and adds subscription to - // service map of subscriptions under given ID. - Subscribe(ctx context.Context, key, chanID, subtopic string, c Client) error - - // Unsubscribe method is used to stop observing resource. - Unsubscribe(ctx context.Context, key, chanID, subptopic, token string) error -} - -var _ Service = (*adapterService)(nil) - -// Observers is a map of maps,. -type adapterService struct { - things magistrala.ThingsServiceClient - pubsub messaging.PubSub -} - -// New instantiates the CoAP adapter implementation. -func New(thingsClient magistrala.ThingsServiceClient, pubsub messaging.PubSub) Service { - as := &adapterService{ - things: thingsClient, - 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(), - } - res, err := svc.things.Authorize(ctx, ar) - if err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - if !res.GetAuthorized() { - return svcerr.ErrAuthorization - } - msg.Publisher = res.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, - } - res, err := svc.things.Authorize(ctx, ar) - if err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - if !res.GetAuthorized() { - return svcerr.ErrAuthorization - } - subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) - if subtopic != "" { - subject = fmt.Sprintf("%s.%s", subject, subtopic) - } - subCfg := messaging.SubscriberConfig{ - ID: c.Token(), - Topic: subject, - Handler: c, - } - 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, - } - res, err := svc.things.Authorize(ctx, ar) - if err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - if !res.GetAuthorized() { - return svcerr.ErrAuthorization - } - subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) - if subtopic != "" { - subject = fmt.Sprintf("%s.%s", subject, subtopic) - } - - return svc.pubsub.Unsubscribe(ctx, token, subject) -} diff --git a/coap/api/doc.go b/coap/api/doc.go deleted file mode 100644 index 2424852cc..000000000 --- a/coap/api/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains API-related concerns: endpoint definitions, middlewares -// and all resource representations. -package api diff --git a/coap/api/logging.go b/coap/api/logging.go deleted file mode 100644 index 2f81f77f9..000000000 --- a/coap/api/logging.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/coap" - "github.com/absmach/magistrala/pkg/messaging" -) - -var _ coap.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc coap.Service -} - -// LoggingMiddleware adds logging facilities to the adapter. -func LoggingMiddleware(svc coap.Service, logger *slog.Logger) coap.Service { - return &loggingMiddleware{logger, svc} -} - -// Publish logs the publish request. 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) Publish(ctx context.Context, key string, msg *messaging.Message) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("channel_id", msg.GetChannel()), - } - if msg.GetSubtopic() != "" { - args = append(args, slog.String("subtopic", msg.GetSubtopic())) - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Publish message failed", args...) - return - } - lm.logger.Info("Publish message completed successfully", args...) - }(time.Now()) - - return lm.svc.Publish(ctx, key, msg) -} - -// Subscribe logs the subscribe request. 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) Subscribe(ctx context.Context, key, chanID, subtopic string, c coap.Client) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("channel_id", chanID), - } - if subtopic != "" { - args = append(args, slog.String("subtopic", subtopic)) - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Subscribe failed", args...) - return - } - lm.logger.Info("Subscribe completed successfully", args...) - }(time.Now()) - - return lm.svc.Subscribe(ctx, key, chanID, subtopic, c) -} - -// Unsubscribe logs the unsubscribe request. 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) Unsubscribe(ctx context.Context, key, 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), - } - 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.Unsubscribe(ctx, key, chanID, subtopic, token) -} diff --git a/coap/api/metrics.go b/coap/api/metrics.go deleted file mode 100644 index e6bca3292..000000000 --- a/coap/api/metrics.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "context" - "time" - - "github.com/absmach/magistrala/coap" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/go-kit/kit/metrics" -) - -var _ coap.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc coap.Service -} - -// MetricsMiddleware instruments adapter by tracking request count and latency. -func MetricsMiddleware(svc coap.Service, counter metrics.Counter, latency metrics.Histogram) coap.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -// Publish instruments Publish method with metrics. -func (mm *metricsMiddleware) Publish(ctx context.Context, key string, msg *messaging.Message) error { - defer func(begin time.Time) { - mm.counter.With("method", "publish").Add(1) - mm.latency.With("method", "publish").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.Publish(ctx, key, msg) -} - -// Subscribe instruments Subscribe method with metrics. -func (mm *metricsMiddleware) Subscribe(ctx context.Context, key, chanID, subtopic string, c coap.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, key, chanID, subtopic, c) -} - -// Unsubscribe instruments Unsubscribe method with metrics. -func (mm *metricsMiddleware) Unsubscribe(ctx context.Context, key, chanID, subtopic, token string) error { - defer func(begin time.Time) { - mm.counter.With("method", "unsubscribe").Add(1) - mm.latency.With("method", "unsubscribe").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.Unsubscribe(ctx, key, chanID, subtopic, token) -} diff --git a/coap/api/transport.go b/coap/api/transport.go deleted file mode 100644 index a2bbc8d1b..000000000 --- a/coap/api/transport.go +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "fmt" - "io" - "log/slog" - "net/http" - "net/url" - "regexp" - "strings" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/coap" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/go-chi/chi/v5" - "github.com/plgd-dev/go-coap/v3/message" - "github.com/plgd-dev/go-coap/v3/message/codes" - "github.com/plgd-dev/go-coap/v3/message/pool" - "github.com/plgd-dev/go-coap/v3/mux" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -const ( - protocol = "coap" - authQuery = "auth" - startObserve = 0 // observe option value that indicates start of observation -) - -var channelPartRegExp = regexp.MustCompile(`^/channels/([\w\-]+)/messages(/[^?]*)?(\?.*)?$`) - -const ( - numGroups = 3 // entire expression + channel group + subtopic group - channelGroup = 2 // channel group is second in channel regexp -) - -var ( - errMalformedSubtopic = errors.New("malformed subtopic") - errBadOptions = errors.New("bad options") - errMethodNotAllowed = errors.New("method not allowed") -) - -var ( - logger *slog.Logger - service coap.Service -) - -// MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(instanceID string) http.Handler { - b := chi.NewRouter() - b.Get("/health", magistrala.Health(protocol, instanceID)) - b.Handle("/metrics", promhttp.Handler()) - - return b -} - -// MakeCoAPHandler creates handler for CoAP messages. -func MakeCoAPHandler(svc coap.Service, l *slog.Logger) mux.HandlerFunc { - logger = l - service = svc - - return handler -} - -func sendResp(w mux.ResponseWriter, resp *pool.Message) { - if err := w.Conn().WriteMessage(resp); err != nil { - logger.Warn(fmt.Sprintf("Can't set response: %s", err)) - } -} - -func handler(w mux.ResponseWriter, m *mux.Message) { - resp := pool.NewMessage(w.Conn().Context()) - resp.SetToken(m.Token()) - for _, opt := range m.Options() { - resp.AddOptionBytes(opt.ID, opt.Value) - } - defer sendResp(w, resp) - - msg, err := decodeMessage(m) - if err != nil { - logger.Warn(fmt.Sprintf("Error decoding message: %s", err)) - resp.SetCode(codes.BadRequest) - return - } - key, err := parseKey(m) - if err != nil { - logger.Warn(fmt.Sprintf("Error parsing auth: %s", err)) - resp.SetCode(codes.Unauthorized) - return - } - - switch m.Code() { - case codes.GET: - resp.SetCode(codes.Content) - err = handleGet(m, w, msg, key) - case codes.POST: - resp.SetCode(codes.Created) - err = service.Publish(m.Context(), key, msg) - default: - err = errMethodNotAllowed - } - - if err != nil { - switch { - case err == errBadOptions: - resp.SetCode(codes.BadOption) - case err == errMethodNotAllowed: - resp.SetCode(codes.MethodNotAllowed) - case errors.Contains(err, svcerr.ErrAuthorization): - resp.SetCode(codes.Forbidden) - case errors.Contains(err, svcerr.ErrAuthentication): - resp.SetCode(codes.Unauthorized) - default: - resp.SetCode(codes.InternalServerError) - } - } -} - -func handleGet(m *mux.Message, w mux.ResponseWriter, msg *messaging.Message, key string) error { - var obs uint32 - obs, err := m.Options().Observe() - if err != nil { - logger.Warn(fmt.Sprintf("Error reading observe option: %s", err)) - return errBadOptions - } - 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...) - }) - return service.Subscribe(w.Conn().Context(), key, msg.GetChannel(), msg.GetSubtopic(), c) - } - return service.Unsubscribe(w.Conn().Context(), key, msg.GetChannel(), msg.GetSubtopic(), m.Token().String()) -} - -func decodeMessage(msg *mux.Message) (*messaging.Message, error) { - if msg.Options() == nil { - return &messaging.Message{}, errBadOptions - } - path, err := msg.Path() - if err != nil { - return &messaging.Message{}, err - } - channelParts := channelPartRegExp.FindStringSubmatch(path) - if len(channelParts) < numGroups { - return &messaging.Message{}, errMalformedSubtopic - } - - st, err := parseSubtopic(channelParts[channelGroup]) - if err != nil { - return &messaging.Message{}, err - } - ret := &messaging.Message{ - Protocol: protocol, - Channel: channelParts[1], - Subtopic: st, - Payload: []byte{}, - Created: time.Now().UnixNano(), - } - - if msg.Body() != nil { - buff, err := io.ReadAll(msg.Body()) - if err != nil { - return ret, err - } - ret.Payload = buff - } - return ret, nil -} - -func parseKey(msg *mux.Message) (string, error) { - authKey, err := msg.Options().GetString(message.URIQuery) - if err != nil { - return "", err - } - vars := strings.Split(authKey, "=") - if len(vars) != 2 || vars[0] != authQuery { - return "", svcerr.ErrAuthorization - } - return vars[1], nil -} - -func parseSubtopic(subtopic string) (string, error) { - if subtopic == "" { - return subtopic, nil - } - - subtopic, err := url.QueryUnescape(subtopic) - if err != nil { - return "", errMalformedSubtopic - } - subtopic = strings.ReplaceAll(subtopic, "/", ".") - - elems := strings.Split(subtopic, ".") - filteredElems := []string{} - for _, elem := range elems { - if elem == "" { - continue - } - - if len(elem) > 1 && (strings.Contains(elem, "*") || strings.Contains(elem, ">")) { - return "", errMalformedSubtopic - } - - filteredElems = append(filteredElems, elem) - } - - subtopic = strings.Join(filteredElems, ".") - return subtopic, nil -} diff --git a/coap/client.go b/coap/client.go deleted file mode 100644 index 6b278ce0f..000000000 --- a/coap/client.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package coap - -import ( - "bytes" - "fmt" - "log/slog" - "sync/atomic" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/plgd-dev/go-coap/v3/message" - "github.com/plgd-dev/go-coap/v3/message/codes" - mux "github.com/plgd-dev/go-coap/v3/mux" -) - -// Client wraps CoAP client. -type Client interface { - // In CoAP terminology, Token similar to the Session ID. - Token() string - - // Handle handles incoming messages. - Handle(m *messaging.Message) error - - // Cancel cancels the client. - Cancel() error - - // Done returns a channel that's closed when the client is done. - Done() <-chan struct{} -} - -// ErrOption indicates an error when adding an option. -var ErrOption = errors.New("unable to set option") - -type client struct { - conn mux.Conn - token message.Token - observe uint32 - logger *slog.Logger -} - -// NewClient instantiates a new Observer. -func NewClient(conn mux.Conn, tkn message.Token, l *slog.Logger) Client { - return &client{ - conn: conn, - token: tkn, - logger: l, - observe: 0, - } -} - -func (c *client) Done() <-chan struct{} { - return c.conn.Done() -} - -func (c *client) Cancel() error { - pm := c.conn.AcquireMessage(c.conn.Context()) - pm.SetCode(codes.Content) - pm.SetToken(c.token) - if err := c.conn.WriteMessage(pm); err != nil { - c.logger.Error(fmt.Sprintf("Error sending message: %s.", err)) - } - c.conn.ReleaseMessage(pm) - return c.conn.Close() -} - -func (c *client) Token() string { - return c.token.String() -} - -func (c *client) Handle(msg *messaging.Message) error { - pm := c.conn.AcquireMessage(c.conn.Context()) - defer c.conn.ReleaseMessage(pm) - pm.SetCode(codes.Content) - pm.SetToken(c.token) - pm.SetBody(bytes.NewReader(msg.GetPayload())) - - atomic.AddUint32(&c.observe, 1) - var opts message.Options - var buff []byte - opts, n, err := opts.SetContentFormat(buff, message.TextPlain) - if err == message.ErrTooSmall { - buff = append(buff, make([]byte, n)...) - _, _, err = opts.SetContentFormat(buff, message.TextPlain) - } - if err != nil { - c.logger.Error(fmt.Sprintf("Can't set content format: %s.", err)) - return errors.Wrap(ErrOption, err) - } - opts, n, err = opts.SetObserve(buff, c.observe) - if err == message.ErrTooSmall { - buff = append(buff, make([]byte, n)...) - opts, _, err = opts.SetObserve(buff, uint32(c.observe)) - } - if err != nil { - return fmt.Errorf("cannot set options to response: %w", err) - } - - for _, option := range opts { - pm.SetOptionBytes(option.ID, option.Value) - } - return c.conn.WriteMessage(pm) -} diff --git a/coap/tracing/adapter.go b/coap/tracing/adapter.go deleted file mode 100644 index f2d3e92a4..000000000 --- a/coap/tracing/adapter.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/coap" - "github.com/absmach/magistrala/pkg/messaging" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ coap.Service = (*tracingServiceMiddleware)(nil) - -// Operation names for tracing CoAP operations. -const ( - publishOP = "publish_op" - subscribeOP = "subscribe_op" - unsubscribeOP = "unsubscribe_op" -) - -// tracingServiceMiddleware is a middleware implementation for tracing CoAP service operations using OpenTelemetry. -type tracingServiceMiddleware struct { - tracer trace.Tracer - svc coap.Service -} - -// New creates a new instance of TracingServiceMiddleware that wraps an existing CoAP service with tracing capabilities. -func New(tracer trace.Tracer, svc coap.Service) coap.Service { - return &tracingServiceMiddleware{ - tracer: tracer, - svc: svc, - } -} - -// Publish traces a CoAP publish operation. -func (tm *tracingServiceMiddleware) Publish(ctx context.Context, key string, msg *messaging.Message) error { - ctx, span := tm.tracer.Start(ctx, publishOP) - defer span.End() - return tm.svc.Publish(ctx, key, msg) -} - -// Subscribe traces a CoAP subscribe operation. -func (tm *tracingServiceMiddleware) Subscribe(ctx context.Context, key, chanID, subtopic string, c coap.Client) error { - ctx, span := tm.tracer.Start(ctx, subscribeOP, trace.WithAttributes( - attribute.String("channel_id", chanID), - attribute.String("subtopic", subtopic), - )) - defer span.End() - return tm.svc.Subscribe(ctx, key, chanID, subtopic, c) -} - -// Unsubscribe traces a CoAP unsubscribe operation. -func (tm *tracingServiceMiddleware) Unsubscribe(ctx context.Context, key, chanID, subptopic, token string) error { - ctx, span := tm.tracer.Start(ctx, unsubscribeOP, trace.WithAttributes( - attribute.String("channel_id", chanID), - attribute.String("subtopic", subptopic), - )) - defer span.End() - return tm.svc.Unsubscribe(ctx, key, chanID, subptopic, token) -} diff --git a/coap/tracing/doc.go b/coap/tracing/doc.go deleted file mode 100644 index 2d65dbe4e..000000000 --- a/coap/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala WebSocket adapter service. -// -// This package provides tracing middleware for Magistrala WebSocket adapter service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala WebSocket adapter 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/consumers/README.md b/consumers/README.md index f4e2f28bb..5e2f6cac8 100644 --- a/consumers/README.md +++ b/consumers/README.md @@ -1,8 +1,8 @@ # Consumers -Consumers provide an abstraction of various `Magistrala consumers`. -Magistrala consumer is a generic service that can handle received messages - consume them. -The message is not necessarily a Magistrala message - before consuming, Magistrala message can +Consumers provide an abstraction of various `SuperMQ consumers`. +SuperMQ consumer is a generic service that can handle received messages - consume them. +The message is not necessarily a SuperMQ message - before consuming, SuperMQ message can be transformed into any valid format that specific consumer can understand. For example, writers are consumers that can take a SenML or JSON message and store it. @@ -10,9 +10,9 @@ Consumers are optional services and are treated as plugins. In order to run consumer services, core services must be up and running. For an in-depth explanation of the usage of `consumers`, as well as thorough -understanding of Magistrala, please check out the [official documentation][doc]. +understanding of SuperMQ, please check out the [official documentation][doc]. For more information about service capabilities and its usage, please check out -the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=consumers-notifiers-openapi.yml). +the [API documentation](https://docs.api.supermq.abstractmachines.fr/?urls.primaryName=consumers-notifiers-openapi.yml). -[doc]: https://docs.magistrala.abstractmachines.fr +[doc]: https://docs.supermq.abstractmachines.fr diff --git a/consumers/consumer.go b/consumers/consumer.go deleted file mode 100644 index 403f9a3fe..000000000 --- a/consumers/consumer.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package consumers - -import "context" - -// AsyncConsumer specifies a non-blocking message-consuming API, -// which can be used for writing data to the DB, publishing messages -// to broker, sending notifications, or any other asynchronous job. -type AsyncConsumer interface { - // ConsumeAsync method is used to asynchronously consume received messages. - ConsumeAsync(ctx context.Context, messages interface{}) - - // Errors method returns a channel for reading errors which occur during async writes. - // Must be called before performing any writes for errors to be collected. - // The channel is buffered(1) so it allows only 1 error without blocking if not drained. - // The channel may receive nil error to indicate success. - Errors() <-chan error -} - -// BlockingConsumer specifies a blocking message-consuming API, -// which can be used for writing data to the DB, publishing messages -// to broker, sending notifications... BlockingConsumer implementations -// might also support concurrent use, but consult implementation for more details. -type BlockingConsumer interface { - // ConsumeBlocking method is used to consume received messages synchronously. - // A non-nil error is returned to indicate operation failure. - ConsumeBlocking(ctx context.Context, messages interface{}) error -} diff --git a/consumers/doc.go b/consumers/doc.go index 6280125e1..d2f2564e6 100644 --- a/consumers/doc.go +++ b/consumers/doc.go @@ -2,5 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 // Package consumers contain the domain concept definitions needed to -// support Magistrala consumer services functionality. +// support SuperMQ consumer services functionality. package consumers diff --git a/consumers/messages.go b/consumers/messages.go deleted file mode 100644 index 6b37f081a..000000000 --- a/consumers/messages.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package consumers - -import ( - "context" - "fmt" - "log/slog" - "os" - "strings" - - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/brokers" - "github.com/absmach/magistrala/pkg/transformers" - "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/absmach/magistrala/pkg/transformers/senml" - "github.com/pelletier/go-toml" -) - -const ( - defContentType = "application/senml+json" - defFormat = "senml" -) - -var ( - errOpenConfFile = errors.New("unable to open configuration file") - errParseConfFile = errors.New("unable to parse configuration file") -) - -// Start method starts consuming messages received from Message broker. -// This method transforms messages to SenML format before -// using MessageRepository to store them. -func Start(ctx context.Context, id string, sub messaging.Subscriber, consumer interface{}, configPath string, logger *slog.Logger) error { - cfg, err := loadConfig(configPath) - if err != nil { - logger.Warn(fmt.Sprintf("Failed to load consumer config: %s", err)) - } - - transformer := makeTransformer(cfg.TransformerCfg, logger) - - for _, subject := range cfg.SubscriberCfg.Subjects { - subCfg := messaging.SubscriberConfig{ - ID: id, - Topic: subject, - DeliveryPolicy: messaging.DeliverAllPolicy, - } - switch c := consumer.(type) { - case AsyncConsumer: - subCfg.Handler = handleAsync(ctx, transformer, c) - if err := sub.Subscribe(ctx, subCfg); err != nil { - return err - } - case BlockingConsumer: - subCfg.Handler = handleSync(ctx, transformer, c) - if err := sub.Subscribe(ctx, subCfg); err != nil { - return err - } - default: - return apiutil.ErrInvalidQueryParams - } - } - return nil -} - -func handleSync(ctx context.Context, t transformers.Transformer, sc BlockingConsumer) handleFunc { - return func(msg *messaging.Message) error { - m := interface{}(msg) - var err error - if t != nil { - m, err = t.Transform(msg) - if err != nil { - return err - } - } - return sc.ConsumeBlocking(ctx, m) - } -} - -func handleAsync(ctx context.Context, t transformers.Transformer, ac AsyncConsumer) handleFunc { - return func(msg *messaging.Message) error { - m := interface{}(msg) - var err error - if t != nil { - m, err = t.Transform(msg) - if err != nil { - return err - } - } - - ac.ConsumeAsync(ctx, m) - return nil - } -} - -type handleFunc func(msg *messaging.Message) error - -func (h handleFunc) Handle(msg *messaging.Message) error { - return h(msg) -} - -func (h handleFunc) Cancel() error { - return nil -} - -type subscriberConfig struct { - Subjects []string `toml:"subjects"` -} - -type transformerConfig struct { - Format string `toml:"format"` - ContentType string `toml:"content_type"` - TimeFields []json.TimeField `toml:"time_fields"` -} - -type config struct { - SubscriberCfg subscriberConfig `toml:"subscriber"` - TransformerCfg transformerConfig `toml:"transformer"` -} - -func loadConfig(configPath string) (config, error) { - cfg := config{ - SubscriberCfg: subscriberConfig{ - Subjects: []string{brokers.SubjectAllChannels}, - }, - TransformerCfg: transformerConfig{}, - } - - data, err := os.ReadFile(configPath) - if err != nil { - return cfg, errors.Wrap(errOpenConfFile, err) - } - - if err := toml.Unmarshal(data, &cfg); err != nil { - return cfg, errors.Wrap(errParseConfFile, err) - } - - return cfg, nil -} - -func makeTransformer(cfg transformerConfig, logger *slog.Logger) transformers.Transformer { - switch strings.ToUpper(cfg.Format) { - case "SENML": - logger.Info("Using SenML transformer") - return senml.New(cfg.ContentType) - case "JSON": - logger.Info("Using JSON transformer") - return json.New(cfg.TimeFields) - default: - logger.Error(fmt.Sprintf("Can't create transformer: unknown transformer type %s; continuing without transformer", cfg.Format)) - return nil - } -} diff --git a/consumers/notifiers/README.md b/consumers/notifiers/README.md index 186671967..f64a2213e 100644 --- a/consumers/notifiers/README.md +++ b/consumers/notifiers/README.md @@ -20,4 +20,4 @@ default values. Subscriptions service will start consuming messages and sending notifications when a message is received. -[doc]: https://docs.magistrala.abstractmachines.fr +[doc]: https://docs.supermq.abstractmachines.fr diff --git a/consumers/notifiers/api/endpoint.go b/consumers/notifiers/api/endpoint.go index 4b411eaf0..42b8055ea 100644 --- a/consumers/notifiers/api/endpoint.go +++ b/consumers/notifiers/api/endpoint.go @@ -6,9 +6,9 @@ package api import ( "context" - notifiers "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" + apiutil "github.com/absmach/supermq/api/http/util" + notifiers "github.com/absmach/supermq/consumers/notifiers" + "github.com/absmach/supermq/pkg/errors" "github.com/go-kit/kit/endpoint" ) diff --git a/consumers/notifiers/api/endpoint_test.go b/consumers/notifiers/api/endpoint_test.go index ec9e7842c..f91635b14 100644 --- a/consumers/notifiers/api/endpoint_test.go +++ b/consumers/notifiers/api/endpoint_test.go @@ -13,14 +13,14 @@ import ( "strings" "testing" - "github.com/absmach/magistrala/consumers/notifiers" - httpapi "github.com/absmach/magistrala/consumers/notifiers/api" - "github.com/absmach/magistrala/consumers/notifiers/mocks" "github.com/absmach/magistrala/internal/testsutil" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/apiutil" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/uuid" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/consumers/notifiers" + "github.com/absmach/supermq/consumers/notifiers/api" + "github.com/absmach/supermq/consumers/notifiers/mocks" + smqlog "github.com/absmach/supermq/logger" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -68,9 +68,9 @@ func (tr testRequest) make() (*http.Response, error) { } func newServer() (*httptest.Server, *mocks.Service) { - logger := mglog.NewMock() + logger := smqlog.NewMock() svc := new(mocks.Service) - mux := httpapi.MakeHandler(svc, logger, instanceID) + mux := api.MakeHandler(svc, logger, instanceID) return httptest.NewServer(mux), svc } diff --git a/consumers/notifiers/api/logging.go b/consumers/notifiers/api/logging.go index e327d9226..ae779b1aa 100644 --- a/consumers/notifiers/api/logging.go +++ b/consumers/notifiers/api/logging.go @@ -10,7 +10,7 @@ import ( "log/slog" "time" - "github.com/absmach/magistrala/consumers/notifiers" + "github.com/absmach/supermq/consumers/notifiers" ) var _ notifiers.Service = (*loggingMiddleware)(nil) diff --git a/consumers/notifiers/api/metrics.go b/consumers/notifiers/api/metrics.go index 209730288..ce312c35c 100644 --- a/consumers/notifiers/api/metrics.go +++ b/consumers/notifiers/api/metrics.go @@ -9,7 +9,7 @@ import ( "context" "time" - "github.com/absmach/magistrala/consumers/notifiers" + "github.com/absmach/supermq/consumers/notifiers" "github.com/go-kit/kit/metrics" ) diff --git a/consumers/notifiers/api/requests.go b/consumers/notifiers/api/requests.go index 9285f4d7f..9b133aead 100644 --- a/consumers/notifiers/api/requests.go +++ b/consumers/notifiers/api/requests.go @@ -3,7 +3,7 @@ package api -import "github.com/absmach/magistrala/pkg/apiutil" +import apiutil "github.com/absmach/supermq/api/http/util" type createSubReq struct { token string diff --git a/consumers/notifiers/api/responses.go b/consumers/notifiers/api/responses.go index 7d3100629..c4732213c 100644 --- a/consumers/notifiers/api/responses.go +++ b/consumers/notifiers/api/responses.go @@ -7,14 +7,14 @@ import ( "fmt" "net/http" - "github.com/absmach/magistrala" + "github.com/absmach/supermq" ) var ( - _ magistrala.Response = (*createSubRes)(nil) - _ magistrala.Response = (*viewSubRes)(nil) - _ magistrala.Response = (*listSubsRes)(nil) - _ magistrala.Response = (*removeSubRes)(nil) + _ supermq.Response = (*createSubRes)(nil) + _ supermq.Response = (*viewSubRes)(nil) + _ supermq.Response = (*listSubsRes)(nil) + _ supermq.Response = (*removeSubRes)(nil) ) type createSubRes struct { diff --git a/consumers/notifiers/api/transport.go b/consumers/notifiers/api/transport.go index 2f6e258b4..605833fe0 100644 --- a/consumers/notifiers/api/transport.go +++ b/consumers/notifiers/api/transport.go @@ -10,11 +10,11 @@ import ( "net/http" "strings" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/supermq" + api "github.com/absmach/supermq/api/http" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/consumers/notifiers" + "github.com/absmach/supermq/pkg/errors" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -75,7 +75,7 @@ func MakeHandler(svc notifiers.Service, logger *slog.Logger, instanceID string) opts..., ), "delete").ServeHTTP) }) - mux.Get("/health", magistrala.Health("notifier", instanceID)) + mux.Get("/health", supermq.Health("notifier", instanceID)) mux.Handle("/metrics", promhttp.Handler()) return mux diff --git a/consumers/notifiers/doc.go b/consumers/notifiers/doc.go index e90c58c1d..ee084f198 100644 --- a/consumers/notifiers/doc.go +++ b/consumers/notifiers/doc.go @@ -2,5 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 // Package notifiers contain the domain concept definitions needed to -// support Magistrala notifications functionality. +// support SuperMQ notifications functionality. package notifiers diff --git a/consumers/notifiers/mocks/notifier.go b/consumers/notifiers/mocks/notifier.go index a3dcc56f8..edd18a0d2 100644 --- a/consumers/notifiers/mocks/notifier.go +++ b/consumers/notifiers/mocks/notifier.go @@ -5,7 +5,7 @@ package mocks import ( - messaging "github.com/absmach/magistrala/pkg/messaging" + messaging "github.com/absmach/supermq/pkg/messaging" mock "github.com/stretchr/testify/mock" ) diff --git a/consumers/notifiers/mocks/repository.go b/consumers/notifiers/mocks/repository.go index 49e572762..b815d6f11 100644 --- a/consumers/notifiers/mocks/repository.go +++ b/consumers/notifiers/mocks/repository.go @@ -7,7 +7,7 @@ package mocks import ( context "context" - notifiers "github.com/absmach/magistrala/consumers/notifiers" + notifiers "github.com/absmach/supermq/consumers/notifiers" mock "github.com/stretchr/testify/mock" ) diff --git a/consumers/notifiers/mocks/service.go b/consumers/notifiers/mocks/service.go index 9fe9494f0..50f463e18 100644 --- a/consumers/notifiers/mocks/service.go +++ b/consumers/notifiers/mocks/service.go @@ -7,7 +7,7 @@ package mocks import ( context "context" - notifiers "github.com/absmach/magistrala/consumers/notifiers" + notifiers "github.com/absmach/supermq/consumers/notifiers" mock "github.com/stretchr/testify/mock" ) diff --git a/consumers/notifiers/notifier.go b/consumers/notifiers/notifier.go index 2c23bc9e5..0337176d4 100644 --- a/consumers/notifiers/notifier.go +++ b/consumers/notifiers/notifier.go @@ -6,7 +6,7 @@ package notifiers import ( "errors" - "github.com/absmach/magistrala/pkg/messaging" + "github.com/absmach/supermq/pkg/messaging" ) // ErrNotify wraps sending notification errors. diff --git a/consumers/notifiers/postgres/setup_test.go b/consumers/notifiers/postgres/setup_test.go index b6033780d..523bf92de 100644 --- a/consumers/notifiers/postgres/setup_test.go +++ b/consumers/notifiers/postgres/setup_test.go @@ -11,9 +11,9 @@ import ( "os" "testing" - "github.com/absmach/magistrala/consumers/notifiers/postgres" - pgclient "github.com/absmach/magistrala/pkg/postgres" - "github.com/absmach/magistrala/pkg/ulid" + "github.com/absmach/supermq/consumers/notifiers/postgres" + pgclient "github.com/absmach/supermq/pkg/postgres" + "github.com/absmach/supermq/pkg/ulid" _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" diff --git a/consumers/notifiers/postgres/subscriptions.go b/consumers/notifiers/postgres/subscriptions.go index 1d445d93b..71e7ab495 100644 --- a/consumers/notifiers/postgres/subscriptions.go +++ b/consumers/notifiers/postgres/subscriptions.go @@ -9,9 +9,9 @@ import ( "fmt" "strings" - "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" + "github.com/absmach/supermq/consumers/notifiers" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" ) diff --git a/consumers/notifiers/postgres/subscriptions_test.go b/consumers/notifiers/postgres/subscriptions_test.go index 507de0405..3eac5badc 100644 --- a/consumers/notifiers/postgres/subscriptions_test.go +++ b/consumers/notifiers/postgres/subscriptions_test.go @@ -8,10 +8,10 @@ import ( "fmt" "testing" - "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/consumers/notifiers/postgres" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" + "github.com/absmach/supermq/consumers/notifiers" + "github.com/absmach/supermq/consumers/notifiers/postgres" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" diff --git a/consumers/notifiers/service.go b/consumers/notifiers/service.go index 1207a011b..02d02503f 100644 --- a/consumers/notifiers/service.go +++ b/consumers/notifiers/service.go @@ -7,16 +7,16 @@ import ( "context" "fmt" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/consumers" - 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/messaging" + "github.com/absmach/supermq" + "github.com/absmach/supermq/consumers" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/pkg/messaging" ) -// ErrMessage indicates an error converting a message to Magistrala message. -var ErrMessage = errors.New("failed to convert to Magistrala message") +// ErrMessage indicates an error converting a message to SuperMQ message. +var ErrMessage = errors.New("failed to convert to SuperMQ message") var _ consumers.AsyncConsumer = (*notifierService)(nil) @@ -43,16 +43,16 @@ type Service interface { var _ Service = (*notifierService)(nil) type notifierService struct { - authn mgauthn.Authentication + authn smqauthn.Authentication subs SubscriptionsRepository - idp magistrala.IDProvider + idp supermq.IDProvider notifier Notifier errCh chan error from string } // New instantiates the subscriptions service implementation. -func New(authn mgauthn.Authentication, subs SubscriptionsRepository, idp magistrala.IDProvider, notifier Notifier, from string) Service { +func New(authn smqauthn.Authentication, subs SubscriptionsRepository, idp supermq.IDProvider, notifier Notifier, from string) Service { return ¬ifierService{ authn: authn, subs: subs, diff --git a/consumers/notifiers/service_test.go b/consumers/notifiers/service_test.go index 28c0092b8..234359681 100644 --- a/consumers/notifiers/service_test.go +++ b/consumers/notifiers/service_test.go @@ -8,16 +8,16 @@ import ( "fmt" "testing" - "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/consumers/notifiers/mocks" "github.com/absmach/magistrala/internal/testsutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" - "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/messaging" - "github.com/absmach/magistrala/pkg/uuid" + "github.com/absmach/supermq/consumers/notifiers" + "github.com/absmach/supermq/consumers/notifiers/mocks" + smqauthn "github.com/absmach/supermq/pkg/authn" + authnmocks "github.com/absmach/supermq/pkg/authn/mocks" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/pkg/messaging" + "github.com/absmach/supermq/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -79,7 +79,7 @@ func TestCreateSubscription(t *testing.T) { } for _, tc := range cases { - repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(mgauthn.Session{UserID: tc.userID}, tc.authenticateErr) + repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(smqauthn.Session{UserID: tc.userID}, tc.authenticateErr) repoCall1 := repo.On("Save", context.Background(), mock.Anything).Return(tc.id, tc.err) id, err := svc.CreateSubscription(context.Background(), tc.token, tc.sub) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -136,7 +136,7 @@ func TestViewSubscription(t *testing.T) { } for _, tc := range cases { - repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(mgauthn.Session{UserID: tc.userID}, tc.authenticateErr) + repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(smqauthn.Session{UserID: tc.userID}, tc.authenticateErr) repoCall1 := repo.On("Retrieve", context.Background(), tc.id).Return(tc.sub, tc.err) sub, err := svc.ViewSubscription(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)) @@ -263,7 +263,7 @@ func TestListSubscriptions(t *testing.T) { } for _, tc := range cases { - repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(mgauthn.Session{UserID: tc.userID}, tc.authenticateErr) + repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(smqauthn.Session{UserID: tc.userID}, tc.authenticateErr) repoCall1 := repo.On("RetrieveAll", context.Background(), tc.pageMeta).Return(tc.page, tc.err) page, err := svc.ListSubscriptions(context.Background(), tc.token, tc.pageMeta) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -313,7 +313,7 @@ func TestRemoveSubscription(t *testing.T) { } for _, tc := range cases { - repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(mgauthn.Session{UserID: tc.userID}, tc.authenticateErr) + repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(smqauthn.Session{UserID: tc.userID}, tc.authenticateErr) repoCall1 := repo.On("Remove", context.Background(), tc.id).Return(tc.err) err := svc.RemoveSubscription(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)) diff --git a/consumers/notifiers/smtp/notifier.go b/consumers/notifiers/smtp/notifier.go index fb8d618e0..a45d0f1b9 100644 --- a/consumers/notifiers/smtp/notifier.go +++ b/consumers/notifiers/smtp/notifier.go @@ -6,13 +6,13 @@ package smtp import ( "fmt" - "github.com/absmach/magistrala/consumers/notifiers" "github.com/absmach/magistrala/internal/email" - "github.com/absmach/magistrala/pkg/messaging" + "github.com/absmach/supermq/consumers/notifiers" + "github.com/absmach/supermq/pkg/messaging" ) const ( - footer = "Sent by Magistrala SMTP Notification" + footer = "Sent by SuperMQ SMTP Notification" contentTemplate = "A publisher with an id %s sent the message over %s with the following values \n %s" ) diff --git a/consumers/notifiers/tracing/doc.go b/consumers/notifiers/tracing/doc.go index 2d65dbe4e..aadb62fe7 100644 --- a/consumers/notifiers/tracing/doc.go +++ b/consumers/notifiers/tracing/doc.go @@ -1,12 +1,12 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package tracing provides tracing instrumentation for Magistrala WebSocket adapter service. +// Package tracing provides tracing instrumentation for SuperMQ WebSocket adapter service. // -// This package provides tracing middleware for Magistrala WebSocket adapter service. +// This package provides tracing middleware for SuperMQ WebSocket adapter service. // It can be used to trace incoming requests and add tracing capabilities to -// Magistrala WebSocket adapter service. +// SuperMQ WebSocket adapter service. // -// For more details about tracing instrumentation for Magistrala messaging refer -// to the documentation at https://docs.magistrala.abstractmachines.fr/tracing/. +// For more details about tracing instrumentation for SuperMQ messaging refer +// to the documentation at https://docs.supermq.abstractmachines.fr/tracing/. package tracing diff --git a/consumers/notifiers/tracing/subscriptions.go b/consumers/notifiers/tracing/subscriptions.go index c8c292012..8236e7635 100644 --- a/consumers/notifiers/tracing/subscriptions.go +++ b/consumers/notifiers/tracing/subscriptions.go @@ -8,7 +8,7 @@ package tracing import ( "context" - "github.com/absmach/magistrala/consumers/notifiers" + "github.com/absmach/supermq/consumers/notifiers" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) diff --git a/consumers/tracing/consumers.go b/consumers/tracing/consumers.go index c9cb362bd..195d5512c 100644 --- a/consumers/tracing/consumers.go +++ b/consumers/tracing/consumers.go @@ -7,10 +7,10 @@ import ( "context" "fmt" - "github.com/absmach/magistrala/consumers" - "github.com/absmach/magistrala/pkg/server" - mgjson "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/absmach/magistrala/pkg/transformers/senml" + "github.com/absmach/supermq/consumers" + "github.com/absmach/supermq/pkg/server" + smqjson "github.com/absmach/supermq/pkg/transformers/json" + "github.com/absmach/supermq/pkg/transformers/senml" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) @@ -69,7 +69,7 @@ func NewBlocking(tracer trace.Tracer, consumerBlock consumers.BlockingConsumer, func (tm *tracingMiddlewareBlock) ConsumeBlocking(ctx context.Context, messages interface{}) error { var span trace.Span switch m := messages.(type) { - case mgjson.Messages: + case smqjson.Messages: if len(m.Data) > 0 { firstMsg := m.Data[0] ctx, span = createSpan(ctx, consumeBlockingOP, firstMsg.Publisher, firstMsg.Channel, firstMsg.Subtopic, len(m.Data), tm.host, trace.SpanKindConsumer, tm.tracer) @@ -89,7 +89,7 @@ func (tm *tracingMiddlewareBlock) ConsumeBlocking(ctx context.Context, messages func (tm *tracingMiddlewareAsync) ConsumeAsync(ctx context.Context, messages interface{}) { var span trace.Span switch m := messages.(type) { - case mgjson.Messages: + case smqjson.Messages: if len(m.Data) > 0 { firstMsg := m.Data[0] ctx, span = createSpan(ctx, consumeAsyncOP, firstMsg.Publisher, firstMsg.Channel, firstMsg.Subtopic, len(m.Data), tm.host, trace.SpanKindConsumer, tm.tracer) diff --git a/consumers/writers/README.md b/consumers/writers/README.md index 3bfd0e6b6..d8b831ecd 100644 --- a/consumers/writers/README.md +++ b/consumers/writers/README.md @@ -2,7 +2,7 @@ Writers provide an implementation of various `message writers`. Message writers are services that normalize (in `SenML` format) -Magistrala messages and store them in specific data store. +SuperMQ messages and store them in specific data store. Writers are optional services and are treated as plugins. In order to run writer services, core services must be up and running. For more info @@ -10,7 +10,7 @@ on the platform core services with its dependencies, please check out the [Docker Compose][compose] file. For an in-depth explanation of the usage of `writers`, as well as thorough -understanding of Magistrala, please check out the [official documentation][doc]. +understanding of SuperMQ, please check out the [official documentation][doc]. -[doc]: https://docs.magistrala.abstractmachines.fr +[doc]: https://docs.supermq.abstractmachines.fr [compose]: ../docker/docker-compose.yml diff --git a/consumers/writers/api/logging.go b/consumers/writers/api/logging.go index 77e5f9144..323b1d94c 100644 --- a/consumers/writers/api/logging.go +++ b/consumers/writers/api/logging.go @@ -10,7 +10,7 @@ import ( "log/slog" "time" - "github.com/absmach/magistrala/consumers" + "github.com/absmach/supermq/consumers" ) var _ consumers.BlockingConsumer = (*loggingMiddleware)(nil) diff --git a/consumers/writers/api/metrics.go b/consumers/writers/api/metrics.go index 29dfb2f47..ac407c547 100644 --- a/consumers/writers/api/metrics.go +++ b/consumers/writers/api/metrics.go @@ -9,7 +9,7 @@ import ( "context" "time" - "github.com/absmach/magistrala/consumers" + "github.com/absmach/supermq/consumers" "github.com/go-kit/kit/metrics" ) diff --git a/consumers/writers/api/transport.go b/consumers/writers/api/transport.go index 3c2fa5d5e..7ae45b4c1 100644 --- a/consumers/writers/api/transport.go +++ b/consumers/writers/api/transport.go @@ -6,7 +6,7 @@ package api import ( "net/http" - "github.com/absmach/magistrala" + "github.com/absmach/supermq" "github.com/go-chi/chi/v5" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -14,7 +14,7 @@ import ( // MakeHandler returns a HTTP API handler with health check and metrics. func MakeHandler(svcName, instanceID string) http.Handler { r := chi.NewRouter() - r.Get("/health", magistrala.Health(svcName, instanceID)) + r.Get("/health", supermq.Health(svcName, instanceID)) r.Handle("/metrics", promhttp.Handler()) return r diff --git a/consumers/writers/doc.go b/consumers/writers/doc.go index 59e88b652..644079245 100644 --- a/consumers/writers/doc.go +++ b/consumers/writers/doc.go @@ -2,5 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 // Package writers contain the domain concept definitions needed to -// support Magistrala writer services functionality. +// support SuperMQ writer services functionality. package writers diff --git a/consumers/writers/postgres/README.md b/consumers/writers/postgres/README.md index 26898d4bc..e41be0b6f 100644 --- a/consumers/writers/postgres/README.md +++ b/consumers/writers/postgres/README.md @@ -8,39 +8,39 @@ 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_WRITER_LOG_LEVEL | Service log level | info | -| MG_POSTGRES_WRITER_CONFIG_PATH | Config file path with Message broker subjects list, payload type and content-type | /config.toml | -| MG_POSTGRES_WRITER_HTTP_HOST | Service HTTP host | localhost | -| MG_POSTGRES_WRITER_HTTP_PORT | Service HTTP port | 9010 | -| MG_POSTGRES_WRITER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" | -| MG_POSTGRES_WRITER_HTTP_SERVER_KEY | Service HTTP server key | "" | -| MG_POSTGRES_HOST | Postgres DB host | postgres | -| 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_MESSAGE_BROKER_URL | Message broker instance URL | nats://localhost:4222 | -| MG_JAEGER_URL | Jaeger server URL | http://jaeger:4318/v1/traces | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_POSTGRES_WRITER_INSTANCE_ID | Service instance ID | "" | +| Variable | Description | Default | +| ------------------------------------ | --------------------------------------------------------------------------------- | ---------------------------- | +| SMQ_POSTGRES_WRITER_LOG_LEVEL | Service log level | info | +| SMQ_POSTGRES_WRITER_CONFIG_PATH | Config file path with Message broker subjects list, payload type and content-type | /config.toml | +| SMQ_POSTGRES_WRITER_HTTP_HOST | Service HTTP host | localhost | +| SMQ_POSTGRES_WRITER_HTTP_PORT | Service HTTP port | 9010 | +| SMQ_POSTGRES_WRITER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" | +| SMQ_POSTGRES_WRITER_HTTP_SERVER_KEY | Service HTTP server key | "" | +| SMQ_POSTGRES_HOST | Postgres DB host | postgres | +| SMQ_POSTGRES_PORT | Postgres DB port | 5432 | +| SMQ_POSTGRES_USER | Postgres user | supermq | +| SMQ_POSTGRES_PASS | Postgres password | supermq | +| SMQ_POSTGRES_NAME | Postgres database name | messages | +| SMQ_POSTGRES_SSL_MODE | Postgres SSL mode | disabled | +| SMQ_POSTGRES_SSL_CERT | Postgres SSL certificate path | "" | +| SMQ_POSTGRES_SSL_KEY | Postgres SSL key | "" | +| SMQ_POSTGRES_SSL_ROOT_CERT | Postgres SSL root certificate path | "" | +| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | nats://localhost:4222 | +| SMQ_JAEGER_URL | Jaeger server URL | http://jaeger:4318/v1/traces | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | +| SMQ_POSTGRES_WRITER_INSTANCE_ID | Service instance ID | "" | ## Deployment -The service itself is distributed as Docker container. Check the [`postgres-writer`](https://github.com/absmach/magistrala/blob/main/docker/addons/postgres-writer/docker-compose.yml#L34-L59) service section in docker-compose file to see how service is deployed. +The service itself is distributed as Docker container. Check the [`postgres-writer`](https://github.com/absmach/supermq/blob/main/docker/addons/postgres-writer/docker-compose.yml#L34-L59) service section in docker-compose file to see how service is deployed. To start the service, execute the following shell script: ```bash # download the latest version of the service -git clone https://github.com/absmach/magistrala +git clone https://github.com/absmach/supermq -cd magistrala +cd supermq # compile the postgres writer make postgres-writer @@ -49,27 +49,27 @@ make postgres-writer make install # Set the environment variables and run the service -MG_POSTGRES_WRITER_LOG_LEVEL=[Service log level] \ -MG_POSTGRES_WRITER_CONFIG_PATH=[Config file path with Message broker subjects list, payload type and content-type] \ -MG_POSTGRES_WRITER_HTTP_HOST=[Service HTTP host] \ -MG_POSTGRES_WRITER_HTTP_PORT=[Service HTTP port] \ -MG_POSTGRES_WRITER_HTTP_SERVER_CERT=[Service HTTP server cert] \ -MG_POSTGRES_WRITER_HTTP_SERVER_KEY=[Service HTTP server key] \ -MG_POSTGRES_HOST=[Postgres host] \ -MG_POSTGRES_PORT=[Postgres port] \ -MG_POSTGRES_USER=[Postgres user] \ -MG_POSTGRES_PASS=[Postgres password] \ -MG_POSTGRES_NAME=[Postgres database name] \ -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_MESSAGE_BROKER_URL=[Message broker instance URL] \ -MG_JAEGER_URL=[Jaeger server URL] \ -MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \ -MG_POSTGRES_WRITER_INSTANCE_ID=[Service instance ID] \ +SMQ_POSTGRES_WRITER_LOG_LEVEL=[Service log level] \ +SMQ_POSTGRES_WRITER_CONFIG_PATH=[Config file path with Message broker subjects list, payload type and content-type] \ +SMQ_POSTGRES_WRITER_HTTP_HOST=[Service HTTP host] \ +SMQ_POSTGRES_WRITER_HTTP_PORT=[Service HTTP port] \ +SMQ_POSTGRES_WRITER_HTTP_SERVER_CERT=[Service HTTP server cert] \ +SMQ_POSTGRES_WRITER_HTTP_SERVER_KEY=[Service HTTP server key] \ +SMQ_POSTGRES_HOST=[Postgres host] \ +SMQ_POSTGRES_PORT=[Postgres port] \ +SMQ_POSTGRES_USER=[Postgres user] \ +SMQ_POSTGRES_PASS=[Postgres password] \ +SMQ_POSTGRES_NAME=[Postgres database name] \ +SMQ_POSTGRES_SSL_MODE=[Postgres SSL mode] \ +SMQ_POSTGRES_SSL_CERT=[Postgres SSL cert] \ +SMQ_POSTGRES_SSL_KEY=[Postgres SSL key] \ +SMQ_POSTGRES_SSL_ROOT_CERT=[Postgres SSL Root cert] \ +SMQ_MESSAGE_BROKER_URL=[Message broker instance URL] \ +SMQ_JAEGER_URL=[Jaeger server URL] \ +SMQ_SEND_TELEMETRY=[Send telemetry to supermq call home server] \ +SMQ_POSTGRES_WRITER_INSTANCE_ID=[Service instance ID] \ -$GOBIN/magistrala-postgres-writer +$GOBIN/supermq-postgres-writer ``` ## Usage diff --git a/consumers/writers/postgres/consumer.go b/consumers/writers/postgres/consumer.go index e78408e41..dad5b21c3 100644 --- a/consumers/writers/postgres/consumer.go +++ b/consumers/writers/postgres/consumer.go @@ -8,10 +8,10 @@ import ( "encoding/json" "fmt" - "github.com/absmach/magistrala/consumers" - "github.com/absmach/magistrala/pkg/errors" - mgjson "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/absmach/magistrala/pkg/transformers/senml" + "github.com/absmach/supermq/consumers" + "github.com/absmach/supermq/pkg/errors" + smqjson "github.com/absmach/supermq/pkg/transformers/json" + "github.com/absmach/supermq/pkg/transformers/senml" "github.com/gofrs/uuid/v5" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" @@ -38,7 +38,7 @@ func New(db *sqlx.DB) consumers.BlockingConsumer { func (pr postgresRepo) ConsumeBlocking(ctx context.Context, message interface{}) (err error) { switch m := message.(type) { - case mgjson.Messages: + case smqjson.Messages: return pr.saveJSON(ctx, m) default: return pr.saveSenml(ctx, m) @@ -94,7 +94,7 @@ func (pr postgresRepo) saveSenml(ctx context.Context, messages interface{}) (err return err } -func (pr postgresRepo) saveJSON(ctx context.Context, msgs mgjson.Messages) error { +func (pr postgresRepo) saveJSON(ctx context.Context, msgs smqjson.Messages) error { if err := pr.insertJSON(ctx, msgs); err != nil { if err == errNoTable { if err := pr.createTable(msgs.Format); err != nil { @@ -107,7 +107,7 @@ func (pr postgresRepo) saveJSON(ctx context.Context, msgs mgjson.Messages) error return nil } -func (pr postgresRepo) insertJSON(ctx context.Context, msgs mgjson.Messages) error { +func (pr postgresRepo) insertJSON(ctx context.Context, msgs smqjson.Messages) error { tx, err := pr.db.BeginTxx(ctx, nil) if err != nil { return errors.Wrap(errSaveMessage, err) @@ -184,7 +184,7 @@ type jsonMessage struct { Payload []byte `db:"payload"` } -func toJSONMessage(msg mgjson.Message) (jsonMessage, error) { +func toJSONMessage(msg smqjson.Message) (jsonMessage, error) { id, err := uuid.NewV4() if err != nil { return jsonMessage{}, err diff --git a/consumers/writers/postgres/consumer_test.go b/consumers/writers/postgres/consumer_test.go index bbaee8454..1f1b4ab4e 100644 --- a/consumers/writers/postgres/consumer_test.go +++ b/consumers/writers/postgres/consumer_test.go @@ -9,9 +9,9 @@ import ( "testing" "time" - "github.com/absmach/magistrala/consumers/writers/postgres" - "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/absmach/magistrala/pkg/transformers/senml" + "github.com/absmach/supermq/consumers/writers/postgres" + "github.com/absmach/supermq/pkg/transformers/json" + "github.com/absmach/supermq/pkg/transformers/senml" "github.com/gofrs/uuid/v5" "github.com/stretchr/testify/assert" ) diff --git a/consumers/writers/postgres/setup_test.go b/consumers/writers/postgres/setup_test.go index a046f8dfb..ecb2ab34c 100644 --- a/consumers/writers/postgres/setup_test.go +++ b/consumers/writers/postgres/setup_test.go @@ -11,8 +11,8 @@ import ( "os" "testing" - "github.com/absmach/magistrala/consumers/writers/postgres" - pgclient "github.com/absmach/magistrala/pkg/postgres" + "github.com/absmach/supermq/consumers/writers/postgres" + pgclient "github.com/absmach/supermq/pkg/postgres" "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" diff --git a/consumers/writers/timescale/README.md b/consumers/writers/timescale/README.md index 5554d32f4..8e5480078 100644 --- a/consumers/writers/timescale/README.md +++ b/consumers/writers/timescale/README.md @@ -8,39 +8,39 @@ 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_WRITER_LOG_LEVEL | Service log level | info | -| MG_TIMESCALE_WRITER_CONFIG_PATH | Configuration file path with Message broker subjects list | /config.toml | -| MG_TIMESCALE_WRITER_HTTP_HOST | Service HTTP host | localhost | -| MG_TIMESCALE_WRITER_HTTP_PORT | Service HTTP port | 9012 | -| MG_TIMESCALE_WRITER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" | -| MG_TIMESCALE_WRITER_HTTP_SERVER_KEY | Service HTTP server key | "" | -| MG_TIMESCALE_HOST | Timescale DB host | timescale | -| 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_MESSAGE_BROKER_URL | Message broker instance URL | nats://localhost:4222 | -| MG_JAEGER_URL | Jaeger server URL | http://jaeger:4318/v1/traces | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_TIMESCALE_WRITER_INSTANCE_ID | Timescale writer instance ID | "" | +| Variable | Description | Default | +| ------------------------------------- | --------------------------------------------------------- | ---------------------------- | +| SMQ_TIMESCALE_WRITER_LOG_LEVEL | Service log level | info | +| SMQ_TIMESCALE_WRITER_CONFIG_PATH | Configuration file path with Message broker subjects list | /config.toml | +| SMQ_TIMESCALE_WRITER_HTTP_HOST | Service HTTP host | localhost | +| SMQ_TIMESCALE_WRITER_HTTP_PORT | Service HTTP port | 9012 | +| SMQ_TIMESCALE_WRITER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" | +| SMQ_TIMESCALE_WRITER_HTTP_SERVER_KEY | Service HTTP server key | "" | +| SMQ_TIMESCALE_HOST | Timescale DB host | timescale | +| SMQ_TIMESCALE_PORT | Timescale DB port | 5432 | +| SMQ_TIMESCALE_USER | Timescale user | supermq | +| SMQ_TIMESCALE_PASS | Timescale password | supermq | +| SMQ_TIMESCALE_NAME | Timescale database name | messages | +| SMQ_TIMESCALE_SSL_MODE | Timescale SSL mode | disabled | +| SMQ_TIMESCALE_SSL_CERT | Timescale SSL certificate path | "" | +| SMQ_TIMESCALE_SSL_KEY | Timescale SSL key | "" | +| SMQ_TIMESCALE_SSL_ROOT_CERT | Timescale SSL root certificate path | "" | +| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | nats://localhost:4222 | +| SMQ_JAEGER_URL | Jaeger server URL | http://jaeger:4318/v1/traces | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | +| SMQ_TIMESCALE_WRITER_INSTANCE_ID | Timescale writer instance ID | "" | ## Deployment -The service itself is distributed as Docker container. Check the [`timescale-writer`](https://github.com/absmach/magistrala/blob/main/docker/addons/timescale-writer/docker-compose.yml#L34-L59) service section in docker-compose file to see how service is deployed. +The service itself is distributed as Docker container. Check the [`timescale-writer`](https://github.com/absmach/supermq/blob/main/docker/addons/timescale-writer/docker-compose.yml#L34-L59) service section in docker-compose file to see how service is deployed. To start the service, execute the following shell script: ```bash # download the latest version of the service -git clone https://github.com/absmach/magistrala +git clone https://github.com/absmach/supermq -cd magistrala +cd supermq # compile the timescale writer make timescale-writer @@ -49,26 +49,26 @@ make timescale-writer make install # Set the environment variables and run the service -MG_TIMESCALE_WRITER_LOG_LEVEL=[Service log level] \ -MG_TIMESCALE_WRITER_CONFIG_PATH=[Configuration file path with Message broker subjects list] \ -MG_TIMESCALE_WRITER_HTTP_HOST=[Service HTTP host] \ -MG_TIMESCALE_WRITER_HTTP_PORT=[Service HTTP port] \ -MG_TIMESCALE_WRITER_HTTP_SERVER_CERT=[Service HTTP server cert] \ -MG_TIMESCALE_WRITER_HTTP_SERVER_KEY=[Service HTTP server key] \ -MG_TIMESCALE_HOST=[Timescale host] \ -MG_TIMESCALE_PORT=[Timescale port] \ -MG_TIMESCALE_USER=[Timescale user] \ -MG_TIMESCALE_PASS=[Timescale password] \ -MG_TIMESCALE_NAME=[Timescale database name] \ -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_MESSAGE_BROKER_URL=[Message broker instance URL] \ -MG_JAEGER_URL=[Jaeger server URL] \ -MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \ -MG_TIMESCALE_WRITER_INSTANCE_ID=[Timescale writer instance ID] \ -$GOBIN/magistrala-timescale-writer +SMQ_TIMESCALE_WRITER_LOG_LEVEL=[Service log level] \ +SMQ_TIMESCALE_WRITER_CONFIG_PATH=[Configuration file path with Message broker subjects list] \ +SMQ_TIMESCALE_WRITER_HTTP_HOST=[Service HTTP host] \ +SMQ_TIMESCALE_WRITER_HTTP_PORT=[Service HTTP port] \ +SMQ_TIMESCALE_WRITER_HTTP_SERVER_CERT=[Service HTTP server cert] \ +SMQ_TIMESCALE_WRITER_HTTP_SERVER_KEY=[Service HTTP server key] \ +SMQ_TIMESCALE_HOST=[Timescale host] \ +SMQ_TIMESCALE_PORT=[Timescale port] \ +SMQ_TIMESCALE_USER=[Timescale user] \ +SMQ_TIMESCALE_PASS=[Timescale password] \ +SMQ_TIMESCALE_NAME=[Timescale database name] \ +SMQ_TIMESCALE_SSL_MODE=[Timescale SSL mode] \ +SMQ_TIMESCALE_SSL_CERT=[Timescale SSL cert] \ +SMQ_TIMESCALE_SSL_KEY=[Timescale SSL key] \ +SMQ_TIMESCALE_SSL_ROOT_CERT=[Timescale SSL Root cert] \ +SMQ_MESSAGE_BROKER_URL=[Message broker instance URL] \ +SMQ_JAEGER_URL=[Jaeger server URL] \ +SMQ_SEND_TELEMETRY=[Send telemetry to supermq call home server] \ +SMQ_TIMESCALE_WRITER_INSTANCE_ID=[Timescale writer instance ID] \ +$GOBIN/supermq-timescale-writer ``` ## Usage diff --git a/consumers/writers/timescale/consumer.go b/consumers/writers/timescale/consumer.go index 070fe5d79..4901ad0d1 100644 --- a/consumers/writers/timescale/consumer.go +++ b/consumers/writers/timescale/consumer.go @@ -8,10 +8,10 @@ import ( "encoding/json" "fmt" - "github.com/absmach/magistrala/consumers" - "github.com/absmach/magistrala/pkg/errors" - mgjson "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/absmach/magistrala/pkg/transformers/senml" + "github.com/absmach/supermq/consumers" + "github.com/absmach/supermq/pkg/errors" + smqjson "github.com/absmach/supermq/pkg/transformers/json" + "github.com/absmach/supermq/pkg/transformers/senml" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" "github.com/jmoiron/sqlx" // required for DB access @@ -37,7 +37,7 @@ func New(db *sqlx.DB) consumers.BlockingConsumer { func (tr *timescaleRepo) ConsumeBlocking(ctx context.Context, message interface{}) (err error) { switch m := message.(type) { - case mgjson.Messages: + case smqjson.Messages: return tr.saveJSON(ctx, m) default: return tr.saveSenml(ctx, m) @@ -89,7 +89,7 @@ func (tr timescaleRepo) saveSenml(ctx context.Context, messages interface{}) (er return err } -func (tr timescaleRepo) saveJSON(ctx context.Context, msgs mgjson.Messages) error { +func (tr timescaleRepo) saveJSON(ctx context.Context, msgs smqjson.Messages) error { if err := tr.insertJSON(ctx, msgs); err != nil { if err == errNoTable { if err := tr.createTable(msgs.Format); err != nil { @@ -102,7 +102,7 @@ func (tr timescaleRepo) saveJSON(ctx context.Context, msgs mgjson.Messages) erro return nil } -func (tr timescaleRepo) insertJSON(ctx context.Context, msgs mgjson.Messages) error { +func (tr timescaleRepo) insertJSON(ctx context.Context, msgs smqjson.Messages) error { tx, err := tr.db.BeginTxx(ctx, nil) if err != nil { return errors.Wrap(errSaveMessage, err) @@ -175,7 +175,7 @@ type jsonMessage struct { Payload []byte `db:"payload"` } -func toJSONMessage(msg mgjson.Message) (jsonMessage, error) { +func toJSONMessage(msg smqjson.Message) (jsonMessage, error) { data := []byte("{}") if msg.Payload != nil { b, err := json.Marshal(msg.Payload) diff --git a/consumers/writers/timescale/consumer_test.go b/consumers/writers/timescale/consumer_test.go index a8c36f1f8..1704d1b68 100644 --- a/consumers/writers/timescale/consumer_test.go +++ b/consumers/writers/timescale/consumer_test.go @@ -9,9 +9,9 @@ import ( "testing" "time" - "github.com/absmach/magistrala/consumers/writers/timescale" - "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/absmach/magistrala/pkg/transformers/senml" + "github.com/absmach/supermq/consumers/writers/timescale" + "github.com/absmach/supermq/pkg/transformers/json" + "github.com/absmach/supermq/pkg/transformers/senml" "github.com/gofrs/uuid/v5" "github.com/stretchr/testify/assert" ) diff --git a/consumers/writers/timescale/setup_test.go b/consumers/writers/timescale/setup_test.go index d3d9064fa..aacf15c05 100644 --- a/consumers/writers/timescale/setup_test.go +++ b/consumers/writers/timescale/setup_test.go @@ -11,8 +11,8 @@ import ( "os" "testing" - "github.com/absmach/magistrala/consumers/writers/timescale" - pgclient "github.com/absmach/magistrala/pkg/postgres" + "github.com/absmach/supermq/consumers/writers/timescale" + pgclient "github.com/absmach/supermq/pkg/postgres" "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" diff --git a/docker/.env b/docker/.env index 614a11fd6..aa4e14436 100644 --- a/docker/.env +++ b/docker/.env @@ -3,496 +3,591 @@ # Docker: Environment variables in Compose ## NginX -MG_NGINX_HTTP_PORT=80 -MG_NGINX_SSL_PORT=443 -MG_NGINX_MQTT_PORT=1883 -MG_NGINX_MQTTS_PORT=8883 +SMQ_NGINX_HTTP_PORT=80 +SMQ_NGINX_SSL_PORT=443 +SMQ_NGINX_MQTT_PORT=1883 +SMQ_NGINX_MQTTS_PORT=8883 ## Nats -MG_NATS_PORT=4222 -MG_NATS_HTTP_PORT=8222 -MG_NATS_JETSTREAM_KEY=u7wFoAPgXpDueXOFldBnXDh4xjnSOyEJ2Cb8Z5SZvGLzIZ3U4exWhhoIBZHzuNvh -MG_NATS_URL=nats://nats:${MG_NATS_PORT} +SMQ_NATS_PORT=4222 +SMQ_NATS_HTTP_PORT=8222 +SMQ_NATS_JETSTREAM_KEY=u7wFoAPgXpDueXOFldBnXDh4xjnSOyEJ2Cb8Z5SZvGLzIZ3U4exWhhoIBZHzuNvh +SMQ_NATS_URL=nats://nats:${SMQ_NATS_PORT} # Configs for nats as MQTT broker -MG_NATS_HEALTH_CHECK=http://nats:${MG_NATS_HTTP_PORT}/healthz -MG_NATS_WS_TARGET_PATH= -MG_NATS_MQTT_QOS=1 +SMQ_NATS_HEALTH_CHECK=http://nats:${SMQ_NATS_HTTP_PORT}/healthz +SMQ_NATS_WS_TARGET_PATH= +SMQ_NATS_MQTT_QOS=1 ## RabbitMQ -MG_RABBITMQ_PORT=5672 -MG_RABBITMQ_HTTP_PORT=15672 -MG_RABBITMQ_USER=magistrala -MG_RABBITMQ_PASS=magistrala -MG_RABBITMQ_COOKIE=magistrala -MG_RABBITMQ_VHOST=/ -MG_RABBITMQ_URL=amqp://${MG_RABBITMQ_USER}:${MG_RABBITMQ_PASS}@rabbitmq:${MG_RABBITMQ_PORT}${MG_RABBITMQ_VHOST} +SMQ_RABBITMQ_PORT=5672 +SMQ_RABBITMQ_HTTP_PORT=15672 +SMQ_RABBITMQ_USER=supermq +SMQ_RABBITMQ_PASS=supermq +SMQ_RABBITMQ_COOKIE=supermq +SMQ_RABBITMQ_VHOST=/ +SMQ_RABBITMQ_URL=amqp://${SMQ_RABBITMQ_USER}:${SMQ_RABBITMQ_PASS}@rabbitmq:${SMQ_RABBITMQ_PORT}${SMQ_RABBITMQ_VHOST} ## Message Broker -MG_MESSAGE_BROKER_TYPE=nats -MG_MESSAGE_BROKER_URL=${MG_NATS_URL} +SMQ_MESSAGE_BROKER_TYPE=nats +SMQ_MESSAGE_BROKER_URL=${SMQ_NATS_URL} ## VERNEMQ -MG_DOCKER_VERNEMQ_ALLOW_ANONYMOUS=on -MG_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL=error -MG_VERNEMQ_HEALTH_CHECK=http://vernemq:8888/health -MG_VERNEMQ_WS_TARGET_PATH=/mqtt -MG_VERNEMQ_MQTT_QOS=2 +SMQ_DOCKER_VERNEMQ_ALLOW_ANONYMOUS=on +SMQ_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL=error +SMQ_VERNEMQ_HEALTH_CHECK=http://vernemq:8888/health +SMQ_VERNEMQ_WS_TARGET_PATH=/mqtt +SMQ_VERNEMQ_MQTT_QOS=2 ## MQTT Broker -MG_MQTT_BROKER_TYPE=vernemq -MG_MQTT_BROKER_HEALTH_CHECK=${MG_VERNEMQ_HEALTH_CHECK} -MG_MQTT_ADAPTER_MQTT_QOS=${MG_VERNEMQ_MQTT_QOS} -MG_MQTT_ADAPTER_MQTT_TARGET_HOST=${MG_MQTT_BROKER_TYPE} -MG_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 -MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK=${MG_MQTT_BROKER_HEALTH_CHECK} -MG_MQTT_ADAPTER_WS_TARGET_HOST=${MG_MQTT_BROKER_TYPE} -MG_MQTT_ADAPTER_WS_TARGET_PORT=8080 -MG_MQTT_ADAPTER_WS_TARGET_PATH=${MG_VERNEMQ_WS_TARGET_PATH} +SMQ_MQTT_BROKER_TYPE=vernemq +SMQ_MQTT_BROKER_HEALTH_CHECK=${SMQ_VERNEMQ_HEALTH_CHECK} +SMQ_MQTT_ADAPTER_MQTT_QOS=${SMQ_VERNEMQ_MQTT_QOS} +SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST=${SMQ_MQTT_BROKER_TYPE} +SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 +SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK=${SMQ_MQTT_BROKER_HEALTH_CHECK} +SMQ_MQTT_ADAPTER_WS_TARGET_HOST=${SMQ_MQTT_BROKER_TYPE} +SMQ_MQTT_ADAPTER_WS_TARGET_PORT=8080 +SMQ_MQTT_ADAPTER_WS_TARGET_PATH=${SMQ_VERNEMQ_WS_TARGET_PATH} ## Redis -MG_REDIS_TCP_PORT=6379 -MG_REDIS_URL=redis://es-redis:${MG_REDIS_TCP_PORT}/0 +SMQ_REDIS_TCP_PORT=6379 +SMQ_REDIS_URL=redis://es-redis:${SMQ_REDIS_TCP_PORT}/0 ## Event Store -MG_ES_TYPE=${MG_MESSAGE_BROKER_TYPE} -MG_ES_URL=${MG_MESSAGE_BROKER_URL} +SMQ_ES_TYPE=${SMQ_MESSAGE_BROKER_TYPE} +SMQ_ES_URL=${SMQ_MESSAGE_BROKER_URL} ## Jaeger -MG_JAEGER_COLLECTOR_OTLP_ENABLED=true -MG_JAEGER_FRONTEND=16686 -MG_JAEGER_OLTP_HTTP=4318 -MG_JAEGER_URL=http://jaeger:4318/v1/traces -MG_JAEGER_TRACE_RATIO=1.0 -MG_JAEGER_MEMORY_MAX_TRACES=5000 +SMQ_JAEGER_COLLECTOR_OTLP_ENABLED=true +SMQ_JAEGER_FRONTEND=16686 +SMQ_JAEGER_OLTP_HTTP=4318 +SMQ_JAEGER_URL=http://jaeger:4318/v1/traces +SMQ_JAEGER_TRACE_RATIO=1.0 +SMQ_JAEGER_MEMORY_MAX_TRACES=5000 ## Call home -MG_SEND_TELEMETRY=true +SMQ_SEND_TELEMETRY=true ## Postgres -MG_POSTGRES_MAX_CONNECTIONS=100 +SMQ_POSTGRES_MAX_CONNECTIONS=100 ## Core Services ### Auth -MG_AUTH_LOG_LEVEL=debug -MG_AUTH_HTTP_HOST=auth -MG_AUTH_HTTP_PORT=8189 -MG_AUTH_HTTP_SERVER_CERT= -MG_AUTH_HTTP_SERVER_KEY= -MG_AUTH_GRPC_HOST=auth -MG_AUTH_GRPC_PORT=8181 -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} -MG_AUTH_DB_HOST=auth-db -MG_AUTH_DB_PORT=5432 -MG_AUTH_DB_USER=magistrala -MG_AUTH_DB_PASS=magistrala -MG_AUTH_DB_NAME=auth -MG_AUTH_DB_SSL_MODE=disable -MG_AUTH_DB_SSL_CERT= -MG_AUTH_DB_SSL_KEY= -MG_AUTH_DB_SSL_ROOT_CERT= -MG_AUTH_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH -MG_AUTH_ACCESS_TOKEN_DURATION="1h" -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 -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} +SMQ_AUTH_LOG_LEVEL=debug +SMQ_AUTH_HTTP_HOST=auth +SMQ_AUTH_HTTP_PORT=9001 +SMQ_AUTH_HTTP_SERVER_CERT= +SMQ_AUTH_HTTP_SERVER_KEY= +SMQ_AUTH_GRPC_HOST=auth +SMQ_AUTH_GRPC_PORT=7001 +SMQ_AUTH_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-server.crt}${GRPC_TLS:+./ssl/certs/auth-grpc-server.crt} +SMQ_AUTH_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/auth-grpc-server.key}${GRPC_TLS:+./ssl/certs/auth-grpc-server.key} +SMQ_AUTH_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} +SMQ_AUTH_DB_HOST=auth-db +SMQ_AUTH_DB_PORT=5432 +SMQ_AUTH_DB_USER=supermq +SMQ_AUTH_DB_PASS=supermq +SMQ_AUTH_DB_NAME=auth +SMQ_AUTH_DB_SSL_MODE=disable +SMQ_AUTH_DB_SSL_CERT= +SMQ_AUTH_DB_SSL_KEY= +SMQ_AUTH_DB_SSL_ROOT_CERT= +SMQ_AUTH_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH +SMQ_AUTH_ACCESS_TOKEN_DURATION="1h" +SMQ_AUTH_REFRESH_TOKEN_DURATION="24h" +SMQ_AUTH_INVITATION_DURATION="168h" +SMQ_AUTH_ADAPTER_INSTANCE_ID= + +#### Auth Client Config +SMQ_AUTH_URL=auth:9001 +SMQ_AUTH_GRPC_URL=auth:7001 +SMQ_AUTH_GRPC_TIMEOUT=300s +SMQ_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.crt} +SMQ_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/auth-grpc-client.key} +SMQ_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} + +### Domains +SMQ_DOMAINS_LOG_LEVEL=debug +SMQ_DOMAINS_HTTP_HOST=domains +SMQ_DOMAINS_HTTP_PORT=9003 +SMQ_DOMAINS_HTTP_SERVER_KEY= +SMQ_DOMAINS_HTTP_SERVER_CERT= +SMQ_DOMAINS_GRPC_HOST=domains +SMQ_DOMAINS_GRPC_PORT=7003 +SMQ_DOMAINS_DB_HOST=domains-db +SMQ_DOMAINS_DB_PORT=5432 +SMQ_DOMAINS_DB_NAME=domains +SMQ_DOMAINS_DB_USER=supermq +SMQ_DOMAINS_DB_PASS=supermq +SMQ_DOMAINS_DB_SSL_MODE= +SMQ_DOMAINS_DB_SSL_KEY= +SMQ_DOMAINS_DB_SSL_CERT= +SMQ_DOMAINS_DB_SSL_ROOT_CERT= +SMQ_DOMAINS_INSTANCE_ID= +SMQ_DOMAINS_CACHE_URL=redis://domains-redis:${SMQ_REDIS_TCP_PORT}/0 +SMQ_DOMAINS_CACHE_KEY_DURATION=10m #### Domains Client Config -MG_DOMAINS_URL=http://auth:8189 +SMQ_DOMAINS_URL=http://domains:9003 +SMQ_DOMAINS_GRPC_URL=domains:7003 +SMQ_DOMAINS_GRPC_TIMEOUT=300s +SMQ_DOMAINS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/domains-grpc-client.crt} +SMQ_DOMAINS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/domains-grpc-client.key} +SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} ### SpiceDB Datastore config -MG_SPICEDB_DB_USER=magistrala -MG_SPICEDB_DB_PASS=magistrala -MG_SPICEDB_DB_NAME=spicedb -MG_SPICEDB_DB_PORT=5432 +SMQ_SPICEDB_DB_USER=supermq +SMQ_SPICEDB_DB_PASS=supermq +SMQ_SPICEDB_DB_NAME=spicedb +SMQ_SPICEDB_DB_PORT=5432 ### SpiceDB config -MG_SPICEDB_PRE_SHARED_KEY="12345678" -MG_SPICEDB_SCHEMA_FILE="/schema.zed" -MG_SPICEDB_HOST=magistrala-spicedb -MG_SPICEDB_PORT=50051 -MG_SPICEDB_DATASTORE_ENGINE=postgres +SMQ_SPICEDB_PRE_SHARED_KEY="12345678" +SMQ_SPICEDB_SCHEMA_FILE="/schema.zed" +SMQ_SPICEDB_HOST=magistrala-spicedb +SMQ_SPICEDB_PORT=50051 +SMQ_SPICEDB_DATASTORE_ENGINE=postgres ### Invitations -MG_INVITATIONS_LOG_LEVEL=info -MG_INVITATIONS_HTTP_HOST=invitations -MG_INVITATIONS_HTTP_PORT=9020 -MG_INVITATIONS_HTTP_SERVER_CERT= -MG_INVITATIONS_HTTP_SERVER_KEY= -MG_INVITATIONS_DB_HOST=invitations-db -MG_INVITATIONS_DB_PORT=5432 -MG_INVITATIONS_DB_USER=magistrala -MG_INVITATIONS_DB_PASS=magistrala -MG_INVITATIONS_DB_NAME=invitations -MG_INVITATIONS_DB_SSL_MODE=disable -MG_INVITATIONS_DB_SSL_CERT= -MG_INVITATIONS_DB_SSL_KEY= -MG_INVITATIONS_DB_SSL_ROOT_CERT= -MG_INVITATIONS_INSTANCE_ID= +SMQ_INVITATIONS_LOG_LEVEL=info +SMQ_INVITATIONS_HTTP_HOST=invitations +SMQ_INVITATIONS_HTTP_PORT=9020 +SMQ_INVITATIONS_HTTP_SERVER_CERT= +SMQ_INVITATIONS_HTTP_SERVER_KEY= +SMQ_INVITATIONS_DB_HOST=invitations-db +SMQ_INVITATIONS_DB_PORT=5432 +SMQ_INVITATIONS_DB_USER=supermq +SMQ_INVITATIONS_DB_PASS=supermq +SMQ_INVITATIONS_DB_NAME=invitations +SMQ_INVITATIONS_DB_SSL_MODE=disable +SMQ_INVITATIONS_DB_SSL_CERT= +SMQ_INVITATIONS_DB_SSL_KEY= +SMQ_INVITATIONS_DB_SSL_ROOT_CERT= +SMQ_INVITATIONS_INSTANCE_ID= ### UI -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_USERS_URL=http://users:9002 -MG_INVITATIONS_URL=http://invitations:9020 -MG_DOMAINS_URL=http://auth:8189 -MG_BOOTSTRAP_URL=http://bootstrap:9013 -MG_UI_HOST_URL=http://localhost:9095 -MG_UI_VERIFICATION_TLS=false -MG_UI_CONTENT_TYPE=application/senml+json -MG_UI_INSTANCE_ID= -MG_UI_DB_HOST=ui-db -MG_UI_DB_PORT=5432 -MG_UI_DB_USER=magistrala -MG_UI_DB_PASS=magistrala -MG_UI_DB_NAME=ui -MG_UI_DB_SSL_MODE=disable -MG_UI_DB_SSL_CERT= -MG_UI_DB_SSL_KEY= -MG_UI_DB_SSL_ROOT_CERT= -MG_UI_HASH_KEY=5jx4x2Qg9OUmzpP5dbveWQ -MG_UI_BLOCK_KEY=UtgZjr92jwRY6SPUndHXiyl9QY8qTUyZ -MG_UI_PATH_PREFIX=/ui +SMQ_UI_LOG_LEVEL=debug +SMQ_UI_PORT=9095 +SMQ_HTTP_ADAPTER_URL=http://http-adapter:8008 +SMQ_READER_URL=http://timescale-reader:9011 +SMQ_CLIENTS_URL=http://clients:9006 +SMQ_USERS_URL=http://users:9002 +SMQ_INVITATIONS_URL=http://invitations:9020 +SMQ_DOMAINS_URL=http://domains:9003 +SMQ_BOOTSTRAP_URL=http://bootstrap:9013 +SMQ_UI_HOST_URL=http://localhost:9095 +SMQ_UI_VERIFICATION_TLS=false +SMQ_UI_CONTENT_TYPE=application/senml+json +SMQ_UI_INSTANCE_ID= +SMQ_UI_DB_HOST=ui-db +SMQ_UI_DB_PORT=5432 +SMQ_UI_DB_USER=supermq +SMQ_UI_DB_PASS=supermq +SMQ_UI_DB_NAME=ui +SMQ_UI_DB_SSL_MODE=disable +SMQ_UI_DB_SSL_CERT= +SMQ_UI_DB_SSL_KEY= +SMQ_UI_DB_SSL_ROOT_CERT= +SMQ_UI_HASH_KEY=5jx4x2Qg9OUmzpP5dbveWQ +SMQ_UI_BLOCK_KEY=UtgZjr92jwRY6SPUndHXiyl9QY8qTUyZ +SMQ_UI_PATH_PREFIX=/ui ### Users -MG_USERS_LOG_LEVEL=debug -MG_USERS_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH -MG_USERS_ADMIN_EMAIL=admin@example.com -MG_USERS_ADMIN_PASSWORD=12345678 -MG_USERS_ADMIN_USERNAME=admin -MG_USERS_ADMIN_FIRST_NAME=super -MG_USERS_ADMIN_LAST_NAME=admin -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_HTTP_HOST=users -MG_USERS_HTTP_PORT=9002 -MG_USERS_HTTP_SERVER_CERT= -MG_USERS_HTTP_SERVER_KEY= -MG_USERS_DB_HOST=users-db -MG_USERS_DB_PORT=5432 -MG_USERS_DB_USER=magistrala -MG_USERS_DB_PASS=magistrala -MG_USERS_DB_NAME=users -MG_USERS_DB_SSL_MODE=disable -MG_USERS_DB_SSL_CERT= -MG_USERS_DB_SSL_KEY= -MG_USERS_DB_SSL_ROOT_CERT= -MG_USERS_RESET_PWD_TEMPLATE=users.tmpl -MG_USERS_INSTANCE_ID= -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 +SMQ_USERS_LOG_LEVEL=debug +SMQ_USERS_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH +SMQ_USERS_ADMIN_EMAIL=admin@example.com +SMQ_USERS_ADMIN_PASSWORD=12345678 +SMQ_USERS_ADMIN_USERNAME=admin +SMQ_USERS_ADMIN_FIRST_NAME=super +SMQ_USERS_ADMIN_LAST_NAME=admin +SMQ_USERS_PASS_REGEX=^.{8,}$ +SMQ_USERS_ACCESS_TOKEN_DURATION=15m +SMQ_USERS_REFRESH_TOKEN_DURATION=24h +SMQ_TOKEN_RESET_ENDPOINT=/reset-request +SMQ_USERS_HTTP_HOST=users +SMQ_USERS_HTTP_PORT=9002 +SMQ_USERS_HTTP_SERVER_CERT= +SMQ_USERS_HTTP_SERVER_KEY= +SMQ_USERS_DB_HOST=users-db +SMQ_USERS_DB_PORT=5432 +SMQ_USERS_DB_USER=supermq +SMQ_USERS_DB_PASS=supermq +SMQ_USERS_DB_NAME=users +SMQ_USERS_DB_SSL_MODE=disable +SMQ_USERS_DB_SSL_CERT= +SMQ_USERS_DB_SSL_KEY= +SMQ_USERS_DB_SSL_ROOT_CERT= +SMQ_USERS_RESET_PWD_TEMPLATE=users.tmpl +SMQ_USERS_INSTANCE_ID= +SMQ_USERS_SECRET_KEY=HyE2D4RUt9nnKG6v8zKEqAp6g6ka8hhZsqUpzgKvnwpXrNVQSH +SMQ_USERS_ADMIN_EMAIL=admin@example.com +SMQ_USERS_ADMIN_PASSWORD=12345678 +SMQ_USERS_PASS_REGEX=^.{8,}$ +SMQ_USERS_ACCESS_TOKEN_DURATION=15m +SMQ_USERS_REFRESH_TOKEN_DURATION=24h +SMQ_TOKEN_RESET_ENDPOINT=/reset-request +SMQ_USERS_ALLOW_SELF_REGISTER=true +SMQ_OAUTH_UI_REDIRECT_URL=http://localhost:9095${SMQ_UI_PATH_PREFIX}/tokens/secure +SMQ_OAUTH_UI_ERROR_URL=http://localhost:9095${SMQ_UI_PATH_PREFIX}/error +SMQ_USERS_DELETE_INTERVAL=24h +SMQ_USERS_DELETE_AFTER=720h + +#### Users Client Config +SMQ_USERS_URL=users:9002 ### Email utility -MG_EMAIL_HOST=smtp.mailtrap.io -MG_EMAIL_PORT=2525 -MG_EMAIL_USERNAME=18bf7f70705139 -MG_EMAIL_PASSWORD=2b0d302e775b1e -MG_EMAIL_FROM_ADDRESS=from@example.com -MG_EMAIL_FROM_NAME=Example -MG_EMAIL_TEMPLATE=email.tmpl +SMQ_EMAIL_HOST=smtp.mailtrap.io +SMQ_EMAIL_PORT=2525 +SMQ_EMAIL_USERNAME=18bf7f70705139 +SMQ_EMAIL_PASSWORD=2b0d302e775b1e +SMQ_EMAIL_FROM_ADDRESS=from@example.com +SMQ_EMAIL_FROM_NAME=Example +SMQ_EMAIL_TEMPLATE=email.tmpl ### Google OAuth2 -MG_GOOGLE_CLIENT_ID= -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} +SMQ_GOOGLE_CLIENT_ID= +SMQ_GOOGLE_CLIENT_SECRET= +SMQ_GOOGLE_REDIRECT_URL= +SMQ_GOOGLE_STATE= + +### Groups +SMQ_GROUPS_LOG_LEVEL=debug +SMQ_GROUPS_HTTP_HOST=groups +SMQ_GROUPS_HTTP_PORT=9004 +SMQ_GROUPS_HTTP_SERVER_CERT= +SMQ_GROUPS_HTTP_SERVER_KEY= +SMQ_GROUPS_GRPC_HOST=groups +SMQ_GROUPS_GRPC_PORT=7004 +SMQ_GROUPS_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/groups-grpc-server.crt}${GRPC_TLS:+./ssl/certs/groups-grpc-server.crt} +SMQ_GROUPS_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/groups-grpc-server.key}${GRPC_TLS:+./ssl/certs/groups-grpc-server.key} +SMQ_GROUPS_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} +SMQ_GROUPS_DB_HOST=groups-db +SMQ_GROUPS_DB_PORT=5432 +SMQ_GROUPS_DB_USER=supermq +SMQ_GROUPS_DB_PASS=supermq +SMQ_GROUPS_DB_NAME=groups +SMQ_GROUPS_DB_SSL_MODE=disable +SMQ_GROUPS_DB_SSL_CERT= +SMQ_GROUPS_DB_SSL_KEY= +SMQ_GROUPS_DB_SSL_ROOT_CERT= +SMQ_GROUPS_INSTANCE_ID= + +#### Groups Client Config +SMQ_GROUPS_URL=groups:9004 +SMQ_GROUPS_GRPC_URL=groups:7004 +SMQ_GROUPS_GRPC_TIMEOUT=300s +SMQ_GROUPS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/groups-grpc-client.crt} +SMQ_GROUPS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/groups-grpc-client.key} +SMQ_GROUPS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} + +### Clients +SMQ_CLIENTS_LOG_LEVEL=debug +SMQ_CLIENTS_STANDALONE_ID= +SMQ_CLIENTS_STANDALONE_TOKEN= +SMQ_CLIENTS_CACHE_KEY_DURATION=10m +SMQ_CLIENTS_HTTP_HOST=clients +SMQ_CLIENTS_HTTP_PORT=9006 +SMQ_CLIENTS_AUTH_GRPC_HOST=clients +SMQ_CLIENTS_AUTH_GRPC_PORT=7006 +SMQ_CLIENTS_AUTH_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/clients-grpc-server.crt}${GRPC_TLS:+./ssl/certs/clients-grpc-server.crt} +SMQ_CLIENTS_AUTH_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/clients-grpc-server.key}${GRPC_TLS:+./ssl/certs/clients-grpc-server.key} +SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} +SMQ_CLIENTS_CACHE_URL=redis://clients-redis:${SMQ_REDIS_TCP_PORT}/0 +SMQ_CLIENTS_DB_HOST=clients-db +SMQ_CLIENTS_DB_PORT=5432 +SMQ_CLIENTS_DB_USER=supermq +SMQ_CLIENTS_DB_PASS=supermq +SMQ_CLIENTS_DB_NAME=clients +SMQ_CLIENTS_DB_SSL_MODE=disable +SMQ_CLIENTS_DB_SSL_CERT= +SMQ_CLIENTS_DB_SSL_KEY= +SMQ_CLIENTS_DB_SSL_ROOT_CERT= +SMQ_CLIENTS_INSTANCE_ID= + +#### Clients Client Config +SMQ_CLIENTS_URL=http://clients:9006 +SMQ_CLIENTS_AUTH_GRPC_URL=clients:7006 +SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=1s +SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/clients-grpc-client.crt} +SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/clients-grpc-client.key} +SMQ_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} + +### Channels +SMQ_CHANNELS_LOG_LEVEL=debug +SMQ_CHANNELS_HTTP_HOST=channels +SMQ_CHANNELS_HTTP_PORT=9005 +SMQ_CHANNELS_GRPC_HOST=channels +SMQ_CHANNELS_GRPC_PORT=7005 +SMQ_CHANNELS_GRPC_SERVER_CERT=${GRPC_MTLS:+./ssl/certs/channels-grpc-server.crt}${GRPC_TLS:+./ssl/certs/channels-grpc-server.crt} +SMQ_CHANNELS_GRPC_SERVER_KEY=${GRPC_MTLS:+./ssl/certs/channels-grpc-server.key}${GRPC_TLS:+./ssl/certs/channels-grpc-server.key} +SMQ_CHANNELS_GRPC_SERVER_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt}${GRPC_TLS:+./ssl/certs/ca.crt} +SMQ_CHANNELS_DB_HOST=channels-db +SMQ_CHANNELS_DB_PORT=5432 +SMQ_CHANNELS_DB_USER=supermq +SMQ_CHANNELS_DB_PASS=supermq +SMQ_CHANNELS_DB_NAME=channels +SMQ_CHANNELS_DB_SSL_MODE=disable +SMQ_CHANNELS_DB_SSL_CERT= +SMQ_CHANNELS_DB_SSL_KEY= +SMQ_CHANNELS_DB_SSL_ROOT_CERT= +SMQ_CHANNELS_INSTANCE_ID= ### RE -MG_RE_LOG_LEVEL=debug -MG_RE_HTTP_HOST=re -MG_RE_HTTP_PORT=9008 -MG_RE_HTTP_SERVER_CERT= -MG_RE_HTTP_SERVER_KEY= -MG_RE_DB_HOST=re-db -MG_RE_DB_PORT=5432 -MG_RE_DB_USER=magistrala -MG_RE_DB_PASS=magistrala -MG_RE_DB_NAME=rule_engine -MG_RE_DB_SSL_MODE=disable -MG_RE_DB_SSL_CERT= -MG_RE_DB_SSL_KEY= -MG_RE_DB_SSL_ROOT_CERT= -MG_RE_INSTANCE_ID= +SMQ_RE_LOG_LEVEL=debug +SMQ_RE_HTTP_HOST=re +SMQ_RE_HTTP_PORT=9008 +SMQ_RE_HTTP_SERVER_CERT= +SMQ_RE_HTTP_SERVER_KEY= +SMQ_RE_DB_HOST=re-db +SMQ_RE_DB_PORT=5432 +SMQ_RE_DB_USER=magistrala +SMQ_RE_DB_PASS=magistrala +SMQ_RE_DB_NAME=rule_engine +SMQ_RE_DB_SSL_MODE=disable +SMQ_RE_DB_SSL_CERT= +SMQ_RE_DB_SSL_KEY= +SMQ_RE_DB_SSL_ROOT_CERT= +SMQ_RE_INSTANCE_ID= + +#### Channels Client Config +SMQ_CHANNELS_URL=http://channels:9005 +SMQ_CHANNELS_GRPC_URL=channels:7005 +SMQ_CHANNELS_GRPC_TIMEOUT=1s +SMQ_CHANNELS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/channels-grpc-client.crt} +SMQ_CHANNELS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/channels-grpc-client.key} +SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} ### HTTP -MG_HTTP_ADAPTER_LOG_LEVEL=debug -MG_HTTP_ADAPTER_HOST=http-adapter -MG_HTTP_ADAPTER_PORT=8008 -MG_HTTP_ADAPTER_SERVER_CERT= -MG_HTTP_ADAPTER_SERVER_KEY= -MG_HTTP_ADAPTER_INSTANCE_ID= +SMQ_HTTP_ADAPTER_LOG_LEVEL=debug +SMQ_HTTP_ADAPTER_HOST=http-adapter +SMQ_HTTP_ADAPTER_PORT=8008 +SMQ_HTTP_ADAPTER_SERVER_CERT= +SMQ_HTTP_ADAPTER_SERVER_KEY= +SMQ_HTTP_ADAPTER_INSTANCE_ID= ### MQTT -MG_MQTT_ADAPTER_LOG_LEVEL=debug -MG_MQTT_ADAPTER_MQTT_PORT=1883 -MG_MQTT_ADAPTER_FORWARDER_TIMEOUT=30s -MG_MQTT_ADAPTER_WS_PORT=8080 -MG_MQTT_ADAPTER_INSTANCE= -MG_MQTT_ADAPTER_INSTANCE_ID= -MG_MQTT_ADAPTER_ES_DB=0 +SMQ_MQTT_ADAPTER_LOG_LEVEL=debug +SMQ_MQTT_ADAPTER_MQTT_PORT=1883 +SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT=30s +SMQ_MQTT_ADAPTER_WS_PORT=8080 +SMQ_MQTT_ADAPTER_INSTANCE= +SMQ_MQTT_ADAPTER_INSTANCE_ID= +SMQ_MQTT_ADAPTER_ES_DB=0 ### CoAP -MG_COAP_ADAPTER_LOG_LEVEL=debug -MG_COAP_ADAPTER_HOST=coap-adapter -MG_COAP_ADAPTER_PORT=5683 -MG_COAP_ADAPTER_SERVER_CERT= -MG_COAP_ADAPTER_SERVER_KEY= -MG_COAP_ADAPTER_HTTP_HOST=coap-adapter -MG_COAP_ADAPTER_HTTP_PORT=5683 -MG_COAP_ADAPTER_HTTP_SERVER_CERT= -MG_COAP_ADAPTER_HTTP_SERVER_KEY= -MG_COAP_ADAPTER_INSTANCE_ID= +SMQ_COAP_ADAPTER_LOG_LEVEL=debug +SMQ_COAP_ADAPTER_HOST=coap-adapter +SMQ_COAP_ADAPTER_PORT=5683 +SMQ_COAP_ADAPTER_SERVER_CERT= +SMQ_COAP_ADAPTER_SERVER_KEY= +SMQ_COAP_ADAPTER_HTTP_HOST=coap-adapter +SMQ_COAP_ADAPTER_HTTP_PORT=5683 +SMQ_COAP_ADAPTER_HTTP_SERVER_CERT= +SMQ_COAP_ADAPTER_HTTP_SERVER_KEY= +SMQ_COAP_ADAPTER_INSTANCE_ID= ### WS -MG_WS_ADAPTER_LOG_LEVEL=debug -MG_WS_ADAPTER_HTTP_HOST=ws-adapter -MG_WS_ADAPTER_HTTP_PORT=8186 -MG_WS_ADAPTER_HTTP_SERVER_CERT= -MG_WS_ADAPTER_HTTP_SERVER_KEY= -MG_WS_ADAPTER_INSTANCE_ID= +SMQ_WS_ADAPTER_LOG_LEVEL=debug +SMQ_WS_ADAPTER_HTTP_HOST=ws-adapter +SMQ_WS_ADAPTER_HTTP_PORT=8186 +SMQ_WS_ADAPTER_HTTP_SERVER_CERT= +SMQ_WS_ADAPTER_HTTP_SERVER_KEY= +SMQ_WS_ADAPTER_INSTANCE_ID= ## Addons Services ### Bootstrap -MG_BOOTSTRAP_LOG_LEVEL=debug -MG_BOOTSTRAP_ENCRYPT_KEY=v7aT0HGxJxt2gULzr3RHwf4WIf6DusPp -MG_BOOTSTRAP_EVENT_CONSUMER=bootstrap -MG_BOOTSTRAP_HTTP_HOST=bootstrap -MG_BOOTSTRAP_HTTP_PORT=9013 -MG_BOOTSTRAP_HTTP_SERVER_CERT= -MG_BOOTSTRAP_HTTP_SERVER_KEY= -MG_BOOTSTRAP_DB_HOST=bootstrap-db -MG_BOOTSTRAP_DB_PORT=5432 -MG_BOOTSTRAP_DB_USER=magistrala -MG_BOOTSTRAP_DB_PASS=magistrala -MG_BOOTSTRAP_DB_NAME=bootstrap -MG_BOOTSTRAP_DB_SSL_MODE=disable -MG_BOOTSTRAP_DB_SSL_CERT= -MG_BOOTSTRAP_DB_SSL_KEY= -MG_BOOTSTRAP_DB_SSL_ROOT_CERT= -MG_BOOTSTRAP_INSTANCE_ID= +SMQ_BOOTSTRAP_LOG_LEVEL=debug +SMQ_BOOTSTRAP_ENCRYPT_KEY=v7aT0HGxJxt2gULzr3RHwf4WIf6DusPp +SMQ_BOOTSTRAP_EVENT_CONSUMER=bootstrap +SMQ_BOOTSTRAP_HTTP_HOST=bootstrap +SMQ_BOOTSTRAP_HTTP_PORT=9013 +SMQ_BOOTSTRAP_HTTP_SERVER_CERT= +SMQ_BOOTSTRAP_HTTP_SERVER_KEY= +SMQ_BOOTSTRAP_DB_HOST=bootstrap-db +SMQ_BOOTSTRAP_DB_PORT=5432 +SMQ_BOOTSTRAP_DB_USER=supermq +SMQ_BOOTSTRAP_DB_PASS=supermq +SMQ_BOOTSTRAP_DB_NAME=bootstrap +SMQ_BOOTSTRAP_DB_SSL_MODE=disable +SMQ_BOOTSTRAP_DB_SSL_CERT= +SMQ_BOOTSTRAP_DB_SSL_KEY= +SMQ_BOOTSTRAP_DB_SSL_ROOT_CERT= +SMQ_BOOTSTRAP_INSTANCE_ID= ### Provision -MG_PROVISION_CONFIG_FILE=/configs/config.toml -MG_PROVISION_LOG_LEVEL=debug -MG_PROVISION_HTTP_PORT=9016 -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_USER= -MG_PROVISION_USERNAME= -MG_PROVISION_PASS= -MG_PROVISION_API_KEY= -MG_PROVISION_CERTS_SVC_URL=http://certs:9019 -MG_PROVISION_X509_PROVISIONING=false -MG_PROVISION_BS_SVC_URL=http://bootstrap:9013 -MG_PROVISION_BS_CONFIG_PROVISIONING=true -MG_PROVISION_BS_AUTO_WHITELIST=true -MG_PROVISION_BS_CONTENT= -MG_PROVISION_CERTS_HOURS_VALID=2400h -MG_PROVISION_CERTS_RSA_BITS=2048 -MG_PROVISION_INSTANCE_ID= +SMQ_PROVISION_CONFIG_FILE=/configs/config.toml +SMQ_PROVISION_LOG_LEVEL=debug +SMQ_PROVISION_HTTP_PORT=9016 +SMQ_PROVISION_ENV_CLIENTS_TLS=false +SMQ_PROVISION_SERVER_CERT= +SMQ_PROVISION_SERVER_KEY= +SMQ_PROVISION_USERS_LOCATION=http://users:9002 +SMQ_PROVISION_CLIENTS_LOCATION=http://clients:9006 +SMQ_PROVISION_USER= +SMQ_PROVISION_USERNAME= +SMQ_PROVISION_PASS= +SMQ_PROVISION_API_KEY= +SMQ_PROVISION_CERTS_SVC_URL=http://certs:9019 +SMQ_PROVISION_X509_PROVISIONING=false +SMQ_PROVISION_BS_SVC_URL=http://bootstrap:9013 +SMQ_PROVISION_BS_CONFIG_PROVISIONING=true +SMQ_PROVISION_BS_AUTO_WHITELIST=true +SMQ_PROVISION_BS_CONTENT= +SMQ_PROVISION_CERTS_HOURS_VALID=2400h +SMQ_PROVISION_CERTS_RSA_BITS=2048 +SMQ_PROVISION_INSTANCE_ID= ### Vault -MG_VAULT_HOST=vault -MG_VAULT_PORT=8200 -MG_VAULT_ADDR=http://vault:8200 -MG_VAULT_NAMESPACE=magistrala -MG_VAULT_UNSEAL_KEY_1= -MG_VAULT_UNSEAL_KEY_2= -MG_VAULT_UNSEAL_KEY_3= -MG_VAULT_TOKEN= - -MG_VAULT_PKI_PATH=pki -MG_VAULT_PKI_ROLE_NAME=magistrala_int_ca -MG_VAULT_PKI_FILE_NAME=mg_root -MG_VAULT_PKI_CA_CN='Magistrala Root Certificate Authority' -MG_VAULT_PKI_CA_OU='Magistrala' -MG_VAULT_PKI_CA_O='Magistrala' -MG_VAULT_PKI_CA_C='FRANCE' -MG_VAULT_PKI_CA_L='PARIS' -MG_VAULT_PKI_CA_ST='PARIS' -MG_VAULT_PKI_CA_ADDR='5 Av. Anatole' -MG_VAULT_PKI_CA_PO='75007' -MG_VAULT_PKI_CLUSTER_PATH=http://localhost -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_FILE_NAME=mg_int -MG_VAULT_PKI_INT_CA_CN='Magistrala Intermediate Certificate Authority' -MG_VAULT_PKI_INT_CA_OU='Magistrala' -MG_VAULT_PKI_INT_CA_O='Magistrala' -MG_VAULT_PKI_INT_CA_C='FRANCE' -MG_VAULT_PKI_INT_CA_L='PARIS' -MG_VAULT_PKI_INT_CA_ST='PARIS' -MG_VAULT_PKI_INT_CA_ADDR='5 Av. Anatole' -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 +SMQ_VAULT_HOST=vault +SMQ_VAULT_PORT=8200 +SMQ_VAULT_ADDR=http://vault:8200 +SMQ_VAULT_NAMESPACE=supermq +SMQ_VAULT_UNSEAL_KEY_1= +SMQ_VAULT_UNSEAL_KEY_2= +SMQ_VAULT_UNSEAL_KEY_3= +SMQ_VAULT_TOKEN= + +SMQ_VAULT_PKI_PATH=pki +SMQ_VAULT_PKI_ROLE_NAME=supermq_int_ca +SMQ_VAULT_PKI_FILE_NAME=mg_root +SMQ_VAULT_PKI_CA_CN='SuperMQ Root Certificate Authority' +SMQ_VAULT_PKI_CA_OU='SuperMQ' +SMQ_VAULT_PKI_CA_O='SuperMQ' +SMQ_VAULT_PKI_CA_C='FRANCE' +SMQ_VAULT_PKI_CA_L='PARIS' +SMQ_VAULT_PKI_CA_ST='PARIS' +SMQ_VAULT_PKI_CA_ADDR='5 Av. Anatole' +SMQ_VAULT_PKI_CA_PO='75007' +SMQ_VAULT_PKI_CLUSTER_PATH=http://localhost +SMQ_VAULT_PKI_CLUSTER_AIA_PATH=http://localhost + +SMQ_VAULT_PKI_INT_PATH=pki_int +SMQ_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME=supermq_server_certs +SMQ_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME=supermq_clients_certs +SMQ_VAULT_PKI_INT_FILE_NAME=mg_int +SMQ_VAULT_PKI_INT_CA_CN='SuperMQ Intermediate Certificate Authority' +SMQ_VAULT_PKI_INT_CA_OU='SuperMQ' +SMQ_VAULT_PKI_INT_CA_O='SuperMQ' +SMQ_VAULT_PKI_INT_CA_C='FRANCE' +SMQ_VAULT_PKI_INT_CA_L='PARIS' +SMQ_VAULT_PKI_INT_CA_ST='PARIS' +SMQ_VAULT_PKI_INT_CA_ADDR='5 Av. Anatole' +SMQ_VAULT_PKI_INT_CA_PO='75007' +SMQ_VAULT_PKI_INT_CLUSTER_PATH=http://localhost +SMQ_VAULT_PKI_INT_CLUSTER_AIA_PATH=http://localhost + +SMQ_VAULT_CLIENTS_CERTS_ISSUER_ROLEID=supermq +SMQ_VAULT_CLIENTS_CERTS_ISSUER_SECRET=supermq # Certs -MG_CERTS_LOG_LEVEL=debug -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_HTTP_HOST=certs -MG_CERTS_HTTP_PORT=9019 -MG_CERTS_HTTP_SERVER_CERT= -MG_CERTS_HTTP_SERVER_KEY= -MG_CERTS_GRPC_HOST= -MG_CERTS_GRPC_PORT= -MG_CERTS_DB_HOST=am-certs-db -MG_CERTS_DB_PORT=5432 -MG_CERTS_DB_USER=magistrala -MG_CERTS_DB_PASS=magistrala -MG_CERTS_DB_NAME=certs -MG_CERTS_DB_SSL_MODE= -MG_CERTS_DB_SSL_CERT= -MG_CERTS_DB_SSL_KEY= -MG_CERTS_DB_SSL_ROOT_CERT= -MG_CERTS_INSTANCE_ID= -MG_CERTS_SDK_HOST=http://magistrala-am-certs -MG_CERTS_SDK_CERTS_URL=${MG_CERTS_SDK_HOST}:9010 -MG_CERTS_SDK_TLS_VERIFICATION=false +SMQ_CERTS_LOG_LEVEL=debug +SMQ_CERTS_SIGN_CA_PATH=/etc/ssl/certs/ca.crt +SMQ_CERTS_SIGN_CA_KEY_PATH=/etc/ssl/certs/ca.key +SMQ_CERTS_VAULT_HOST=${SMQ_VAULT_ADDR} +SMQ_CERTS_VAULT_NAMESPACE=${SMQ_VAULT_NAMESPACE} +SMQ_CERTS_VAULT_APPROLE_ROLEID=${SMQ_VAULT_CLIENTS_CERTS_ISSUER_ROLEID} +SMQ_CERTS_VAULT_APPROLE_SECRET=${SMQ_VAULT_CLIENTS_CERTS_ISSUER_SECRET} +SMQ_CERTS_VAULT_CLIENTS_CERTS_PKI_PATH=${SMQ_VAULT_PKI_INT_PATH} +SMQ_CERTS_VAULT_CLIENTS_CERTS_PKI_ROLE_NAME=${SMQ_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME} +SMQ_CERTS_HTTP_HOST=certs +SMQ_CERTS_HTTP_PORT=9019 +SMQ_CERTS_HTTP_SERVER_CERT= +SMQ_CERTS_HTTP_SERVER_KEY= +SMQ_CERTS_GRPC_HOST= +SMQ_CERTS_GRPC_PORT= +SMQ_CERTS_DB_HOST=am-certs-db +SMQ_CERTS_DB_PORT=5432 +SMQ_CERTS_DB_USER=supermq +SMQ_CERTS_DB_PASS=supermq +SMQ_CERTS_DB_NAME=certs +SMQ_CERTS_DB_SSL_MODE= +SMQ_CERTS_DB_SSL_CERT= +SMQ_CERTS_DB_SSL_KEY= +SMQ_CERTS_DB_SSL_ROOT_CERT= +SMQ_CERTS_INSTANCE_ID= +SMQ_CERTS_SDK_HOST=http://supermq-am-certs +SMQ_CERTS_SDK_CERTS_URL=${SMQ_CERTS_SDK_HOST}:9010 +SMQ_CERTS_SDK_TLS_VERIFICATION=false ### Postgres -MG_POSTGRES_HOST=magistrala-postgres -MG_POSTGRES_PORT=5432 -MG_POSTGRES_USER=magistrala -MG_POSTGRES_PASS=magistrala -MG_POSTGRES_NAME=messages -MG_POSTGRES_SSL_MODE=disable -MG_POSTGRES_SSL_CERT= -MG_POSTGRES_SSL_KEY= -MG_POSTGRES_SSL_ROOT_CERT= +SMQ_POSTGRES_HOST=supermq-postgres +SMQ_POSTGRES_PORT=5432 +SMQ_POSTGRES_USER=supermq +SMQ_POSTGRES_PASS=supermq +SMQ_POSTGRES_NAME=messages +SMQ_POSTGRES_SSL_MODE=disable +SMQ_POSTGRES_SSL_CERT= +SMQ_POSTGRES_SSL_KEY= +SMQ_POSTGRES_SSL_ROOT_CERT= ### Postgres Writer -MG_POSTGRES_WRITER_LOG_LEVEL=debug -MG_POSTGRES_WRITER_CONFIG_PATH=/config.toml -MG_POSTGRES_WRITER_HTTP_HOST=postgres-writer -MG_POSTGRES_WRITER_HTTP_PORT=9010 -MG_POSTGRES_WRITER_HTTP_SERVER_CERT= -MG_POSTGRES_WRITER_HTTP_SERVER_KEY= -MG_POSTGRES_WRITER_INSTANCE_ID= +SMQ_POSTGRES_WRITER_LOG_LEVEL=debug +SMQ_POSTGRES_WRITER_CONFIG_PATH=/config.toml +SMQ_POSTGRES_WRITER_HTTP_HOST=postgres-writer +SMQ_POSTGRES_WRITER_HTTP_PORT=9010 +SMQ_POSTGRES_WRITER_HTTP_SERVER_CERT= +SMQ_POSTGRES_WRITER_HTTP_SERVER_KEY= +SMQ_POSTGRES_WRITER_INSTANCE_ID= ### Postgres Reader -MG_POSTGRES_READER_LOG_LEVEL=debug -MG_POSTGRES_READER_HTTP_HOST=postgres-reader -MG_POSTGRES_READER_HTTP_PORT=9009 -MG_POSTGRES_READER_HTTP_SERVER_CERT= -MG_POSTGRES_READER_HTTP_SERVER_KEY= -MG_POSTGRES_READER_INSTANCE_ID= +SMQ_POSTGRES_READER_LOG_LEVEL=debug +SMQ_POSTGRES_READER_HTTP_HOST=postgres-reader +SMQ_POSTGRES_READER_HTTP_PORT=9009 +SMQ_POSTGRES_READER_HTTP_SERVER_CERT= +SMQ_POSTGRES_READER_HTTP_SERVER_KEY= +SMQ_POSTGRES_READER_INSTANCE_ID= ### Timescale -MG_TIMESCALE_HOST=magistrala-timescale -MG_TIMESCALE_PORT=5432 -MG_TIMESCALE_USER=magistrala -MG_TIMESCALE_PASS=magistrala -MG_TIMESCALE_NAME=magistrala -MG_TIMESCALE_SSL_MODE=disable -MG_TIMESCALE_SSL_CERT= -MG_TIMESCALE_SSL_KEY= -MG_TIMESCALE_SSL_ROOT_CERT= +SMQ_TIMESCALE_HOST=supermq-timescale +SMQ_TIMESCALE_PORT=5432 +SMQ_TIMESCALE_USER=supermq +SMQ_TIMESCALE_PASS=supermq +SMQ_TIMESCALE_NAME=supermq +SMQ_TIMESCALE_SSL_MODE=disable +SMQ_TIMESCALE_SSL_CERT= +SMQ_TIMESCALE_SSL_KEY= +SMQ_TIMESCALE_SSL_ROOT_CERT= ### Timescale Writer -MG_TIMESCALE_WRITER_LOG_LEVEL=debug -MG_TIMESCALE_WRITER_CONFIG_PATH=/config.toml -MG_TIMESCALE_WRITER_HTTP_HOST=timescale-writer -MG_TIMESCALE_WRITER_HTTP_PORT=9012 -MG_TIMESCALE_WRITER_HTTP_SERVER_CERT= -MG_TIMESCALE_WRITER_HTTP_SERVER_KEY= -MG_TIMESCALE_WRITER_INSTANCE_ID= +SMQ_TIMESCALE_WRITER_LOG_LEVEL=debug +SMQ_TIMESCALE_WRITER_CONFIG_PATH=/config.toml +SMQ_TIMESCALE_WRITER_HTTP_HOST=timescale-writer +SMQ_TIMESCALE_WRITER_HTTP_PORT=9012 +SMQ_TIMESCALE_WRITER_HTTP_SERVER_CERT= +SMQ_TIMESCALE_WRITER_HTTP_SERVER_KEY= +SMQ_TIMESCALE_WRITER_INSTANCE_ID= ### Timescale Reader -MG_TIMESCALE_READER_LOG_LEVEL=debug -MG_TIMESCALE_READER_HTTP_HOST=timescale-reader -MG_TIMESCALE_READER_HTTP_PORT=9011 -MG_TIMESCALE_READER_HTTP_SERVER_CERT= -MG_TIMESCALE_READER_HTTP_SERVER_KEY= -MG_TIMESCALE_READER_INSTANCE_ID= +SMQ_TIMESCALE_READER_LOG_LEVEL=debug +SMQ_TIMESCALE_READER_HTTP_HOST=timescale-reader +SMQ_TIMESCALE_READER_HTTP_PORT=9011 +SMQ_TIMESCALE_READER_HTTP_SERVER_CERT= +SMQ_TIMESCALE_READER_HTTP_SERVER_KEY= +SMQ_TIMESCALE_READER_INSTANCE_ID= ### Journal -MG_JOURNAL_LOG_LEVEL=info -MG_JOURNAL_HTTP_HOST=journal -MG_JOURNAL_HTTP_PORT=9021 -MG_JOURNAL_HTTP_SERVER_CERT= -MG_JOURNAL_HTTP_SERVER_KEY= -MG_JOURNAL_DB_HOST=journal-db -MG_JOURNAL_DB_PORT=5432 -MG_JOURNAL_DB_USER=magistrala -MG_JOURNAL_DB_PASS=magistrala -MG_JOURNAL_DB_NAME=journal -MG_JOURNAL_DB_SSL_MODE=disable -MG_JOURNAL_DB_SSL_CERT= -MG_JOURNAL_DB_SSL_KEY= -MG_JOURNAL_DB_SSL_ROOT_CERT= -MG_JOURNAL_INSTANCE_ID= +SMQ_JOURNAL_LOG_LEVEL=info +SMQ_JOURNAL_HTTP_HOST=journal +SMQ_JOURNAL_HTTP_PORT=9021 +SMQ_JOURNAL_HTTP_SERVER_CERT= +SMQ_JOURNAL_HTTP_SERVER_KEY= +SMQ_JOURNAL_DB_HOST=journal-db +SMQ_JOURNAL_DB_PORT=5432 +SMQ_JOURNAL_DB_USER=supermq +SMQ_JOURNAL_DB_PASS=supermq +SMQ_JOURNAL_DB_NAME=journal +SMQ_JOURNAL_DB_SSL_MODE=disable +SMQ_JOURNAL_DB_SSL_CERT= +SMQ_JOURNAL_DB_SSL_KEY= +SMQ_JOURNAL_DB_SSL_ROOT_CERT= +SMQ_JOURNAL_INSTANCE_ID= ### GRAFANA and PROMETHEUS -MG_PROMETHEUS_PORT=9090 -MG_GRAFANA_PORT=3000 -MG_GRAFANA_ADMIN_USER=magistrala -MG_GRAFANA_ADMIN_PASSWORD=magistrala +SMQ_PROMETHEUS_PORT=9090 +SMQ_GRAFANA_PORT=3000 +SMQ_GRAFANA_ADMIN_USER=supermq +SMQ_GRAFANA_ADMIN_PASSWORD=supermq # Docker image tag -MG_RELEASE_TAG=latest +SMQ_RELEASE_TAG=latest diff --git a/docker/README.md b/docker/README.md index c21e20d4a..dca67ea7d 100644 --- a/docker/README.md +++ b/docker/README.md @@ -74,14 +74,14 @@ For MQTT broker other than VerneMQ, you would need to change the `docker/.env`. ```env MG_MQTT_BROKER_TYPE=nats -MG_MQTT_BROKER_HEALTH_CHECK=${MG_NATS_HEALTH_CHECK} -MG_MQTT_ADAPTER_MQTT_QOS=${MG_NATS_MQTT_QOS} +SMQ_MQTT_BROKER_HEALTH_CHECK=${SMQ_NATS_HEALTH_CHECK} +SMQ_MQTT_ADAPTER_MQTT_QOS=${SMQ_NATS_MQTT_QOS} MG_MQTT_ADAPTER_MQTT_TARGET_HOST=${MG_MQTT_BROKER_TYPE} MG_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK=${MG_MQTT_BROKER_HEALTH_CHECK} MG_MQTT_ADAPTER_WS_TARGET_HOST=${MG_MQTT_BROKER_TYPE} MG_MQTT_ADAPTER_WS_TARGET_PORT=8080 -MG_MQTT_ADAPTER_WS_TARGET_PATH=${MG_NATS_WS_TARGET_PATH} +SMQ_MQTT_ADAPTER_WS_TARGET_PATH=${SMQ_NATS_WS_TARGET_PATH} ``` ### RabbitMQ configuration diff --git a/docker/addons/re/docker-compose.yml b/docker/addons/re/docker-compose.yml index d54e34eb7..2b7bf1d56 100644 --- a/docker/addons/re/docker-compose.yml +++ b/docker/addons/re/docker-compose.yml @@ -20,12 +20,11 @@ services: image: postgres:16.2-alpine container_name: magistrala-re-db restart: on-failure - command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" + command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}" environment: - POSTGRES_USER: ${MG_RE_DB_USER} - POSTGRES_PASSWORD: ${MG_RE_DB_PASS} - POSTGRES_DB: ${MG_RE_DB_NAME} - MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} + POSTGRES_USER: ${SMQ_RE_DB_USER} + POSTGRES_PASSWORD: ${SMQ_RE_DB_PASS} + POSTGRES_DB: ${SMQ_RE_DB_NAME} ports: - 6008:5432 networks: @@ -34,59 +33,58 @@ services: - magistrala-re-db-volume:/var/lib/postgresql/data re: - image: ghcr.io/absmach/magistrala/re:${MG_RELEASE_TAG} + image: ghcr.io/absmach/magistrala/re:${SMQ_RELEASE_TAG} container_name: magistrala-re depends_on: - re-db restart: on-failure environment: - MG_RE_LOG_LEVEL: ${MG_RE_LOG_LEVEL} - MG_RE_HTTP_PORT: ${MG_RE_HTTP_PORT} - MG_RE_HTTP_HOST: ${MG_RE_HTTP_HOST} - MG_RE_HTTP_SERVER_CERT: ${MG_RE_HTTP_SERVER_CERT} - MG_RE_HTTP_SERVER_KEY: ${MG_RE_HTTP_SERVER_KEY} - MG_RE_DB_HOST: ${MG_RE_DB_HOST} - MG_RE_DB_PORT: ${MG_RE_DB_PORT} - MG_RE_DB_USER: ${MG_RE_DB_USER} - MG_RE_DB_PASS: ${MG_RE_DB_PASS} - MG_RE_DB_NAME: ${MG_RE_DB_NAME} - MG_RE_DB_SSL_MODE: ${MG_RE_DB_SSL_MODE} - MG_RE_DB_SSL_CERT: ${MG_RE_DB_SSL_CERT} - MG_RE_DB_SSL_KEY: ${MG_RE_DB_SSL_KEY} - MG_RE_DB_SSL_ROOT_CERT: ${MG_RE_DB_SSL_ROOT_CERT} - MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} - 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} - MG_RE_INSTANCE_ID: ${MG_RE_INSTANCE_ID} + SMQ_RE_LOG_LEVEL: ${SMQ_RE_LOG_LEVEL} + SMQ_RE_HTTP_PORT: ${SMQ_RE_HTTP_PORT} + SMQ_RE_HTTP_HOST: ${SMQ_RE_HTTP_HOST} + SMQ_RE_HTTP_SERVER_CERT: ${SMQ_RE_HTTP_SERVER_CERT} + SMQ_RE_HTTP_SERVER_KEY: ${SMQ_RE_HTTP_SERVER_KEY} + SMQ_RE_DB_HOST: ${SMQ_RE_DB_HOST} + SMQ_RE_DB_PORT: ${SMQ_RE_DB_PORT} + SMQ_RE_DB_USER: ${SMQ_RE_DB_USER} + SMQ_RE_DB_PASS: ${SMQ_RE_DB_PASS} + SMQ_RE_DB_NAME: ${SMQ_RE_DB_NAME} + SMQ_RE_DB_SSL_MODE: ${SMQ_RE_DB_SSL_MODE} + SMQ_RE_DB_SSL_CERT: ${SMQ_RE_DB_SSL_CERT} + SMQ_RE_DB_SSL_KEY: ${SMQ_RE_DB_SSL_KEY} + SMQ_RE_DB_SSL_ROOT_CERT: ${SMQ_RE_DB_SSL_ROOT_CERT} + SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY} + SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST} + SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT} + SMQ_RE_INSTANCE_ID: ${SMQ_RE_INSTANCE_ID} ports: - - ${MG_RE_HTTP_PORT}:${MG_RE_HTTP_PORT} + - ${SMQ_RE_HTTP_PORT}:${SMQ_RE_HTTP_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} + source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${SMQ_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} + source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${SMQ_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} + source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true - ./config.toml:/config.toml diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 804389ea8..5a2a8f470 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,18 +9,23 @@ 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-pat-db-volume: + magistrala-domains-db-volume: + magistrala-domains-redis-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" @@ -31,22 +36,22 @@ services: - "9091:9090" - "50051:50051" environment: - SPICEDB_GRPC_PRESHARED_KEY: ${MG_SPICEDB_PRE_SHARED_KEY} - SPICEDB_DATASTORE_ENGINE: ${MG_SPICEDB_DATASTORE_ENGINE} - SPICEDB_DATASTORE_CONN_URI: "${MG_SPICEDB_DATASTORE_ENGINE}://${MG_SPICEDB_DB_USER}:${MG_SPICEDB_DB_PASS}@spicedb-db:${MG_SPICEDB_DB_PORT}/${MG_SPICEDB_DB_NAME}?sslmode=disable" + SPICEDB_GRPC_PRESHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY} + SPICEDB_DATASTORE_ENGINE: ${SMQ_SPICEDB_DATASTORE_ENGINE} + SPICEDB_DATASTORE_CONN_URI: "${SMQ_SPICEDB_DATASTORE_ENGINE}://${SMQ_SPICEDB_DB_USER}:${SMQ_SPICEDB_DB_PASS}@spicedb-db:${SMQ_SPICEDB_DB_PORT}/${SMQ_SPICEDB_DB_NAME}?sslmode=disable" depends_on: - 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" networks: - magistrala-base-net environment: - SPICEDB_DATASTORE_ENGINE: ${MG_SPICEDB_DATASTORE_ENGINE} - SPICEDB_DATASTORE_CONN_URI: "${MG_SPICEDB_DATASTORE_ENGINE}://${MG_SPICEDB_DB_USER}:${MG_SPICEDB_DB_PASS}@spicedb-db:${MG_SPICEDB_DB_PORT}/${MG_SPICEDB_DB_NAME}?sslmode=disable" + SPICEDB_DATASTORE_ENGINE: ${SMQ_SPICEDB_DATASTORE_ENGINE} + SPICEDB_DATASTORE_CONN_URI: "${SMQ_SPICEDB_DATASTORE_ENGINE}://${SMQ_SPICEDB_DB_USER}:${SMQ_SPICEDB_DB_PASS}@spicedb-db:${SMQ_SPICEDB_DB_PORT}/${SMQ_SPICEDB_DB_NAME}?sslmode=disable" depends_on: - spicedb-db @@ -58,99 +63,219 @@ services: ports: - "6010:5432" environment: - POSTGRES_USER: ${MG_SPICEDB_DB_USER} - POSTGRES_PASSWORD: ${MG_SPICEDB_DB_PASS} - POSTGRES_DB: ${MG_SPICEDB_DB_NAME} + POSTGRES_USER: ${SMQ_SPICEDB_DB_USER} + POSTGRES_PASSWORD: ${SMQ_SPICEDB_DB_PASS} + POSTGRES_DB: ${SMQ_SPICEDB_DB_NAME} volumes: - magistrala-spicedb-db-volume:/var/lib/postgresql/data + command: ["postgres", "-c", "track_commit_timestamp=on"] auth-db: image: postgres:16.2-alpine 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} - POSTGRES_DB: ${MG_AUTH_DB_NAME} + POSTGRES_USER: ${SMQ_AUTH_DB_USER} + POSTGRES_PASSWORD: ${SMQ_AUTH_DB_PASS} + POSTGRES_DB: ${SMQ_AUTH_DB_NAME} networks: - magistrala-base-net volumes: - magistrala-auth-db-volume:/var/lib/postgresql/data auth: - image: magistrala/auth:${MG_RELEASE_TAG} + image: supermq/auth:${SMQ_RELEASE_TAG} container_name: magistrala-auth depends_on: - auth-db - spicedb expose: - - ${MG_AUTH_GRPC_PORT} + - ${SMQ_AUTH_GRPC_PORT} restart: on-failure environment: - MG_AUTH_LOG_LEVEL: ${MG_AUTH_LOG_LEVEL} - MG_SPICEDB_SCHEMA_FILE: ${MG_SPICEDB_SCHEMA_FILE} - MG_SPICEDB_PRE_SHARED_KEY: ${MG_SPICEDB_PRE_SHARED_KEY} - MG_SPICEDB_HOST: ${MG_SPICEDB_HOST} - MG_SPICEDB_PORT: ${MG_SPICEDB_PORT} - MG_AUTH_ACCESS_TOKEN_DURATION: ${MG_AUTH_ACCESS_TOKEN_DURATION} - MG_AUTH_REFRESH_TOKEN_DURATION: ${MG_AUTH_REFRESH_TOKEN_DURATION} - MG_AUTH_INVITATION_DURATION: ${MG_AUTH_INVITATION_DURATION} - MG_AUTH_SECRET_KEY: ${MG_AUTH_SECRET_KEY} - MG_AUTH_HTTP_HOST: ${MG_AUTH_HTTP_HOST} - MG_AUTH_HTTP_PORT: ${MG_AUTH_HTTP_PORT} - MG_AUTH_HTTP_SERVER_CERT: ${MG_AUTH_HTTP_SERVER_CERT} - MG_AUTH_HTTP_SERVER_KEY: ${MG_AUTH_HTTP_SERVER_KEY} - MG_AUTH_GRPC_HOST: ${MG_AUTH_GRPC_HOST} - MG_AUTH_GRPC_PORT: ${MG_AUTH_GRPC_PORT} + SMQ_AUTH_LOG_LEVEL: ${SMQ_AUTH_LOG_LEVEL} + SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE} + SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY} + SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST} + SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT} + SMQ_AUTH_ACCESS_TOKEN_DURATION: ${SMQ_AUTH_ACCESS_TOKEN_DURATION} + SMQ_AUTH_REFRESH_TOKEN_DURATION: ${SMQ_AUTH_REFRESH_TOKEN_DURATION} + SMQ_AUTH_INVITATION_DURATION: ${SMQ_AUTH_INVITATION_DURATION} + SMQ_AUTH_SECRET_KEY: ${SMQ_AUTH_SECRET_KEY} + SMQ_AUTH_HTTP_HOST: ${SMQ_AUTH_HTTP_HOST} + SMQ_AUTH_HTTP_PORT: ${SMQ_AUTH_HTTP_PORT} + SMQ_AUTH_HTTP_SERVER_CERT: ${SMQ_AUTH_HTTP_SERVER_CERT} + SMQ_AUTH_HTTP_SERVER_KEY: ${SMQ_AUTH_HTTP_SERVER_KEY} + SMQ_AUTH_GRPC_HOST: ${SMQ_AUTH_GRPC_HOST} + SMQ_AUTH_GRPC_PORT: ${SMQ_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_AUTH_GRPC_SERVER_CERT: ${MG_AUTH_GRPC_SERVER_CERT:+/auth-grpc-server.crt} - MG_AUTH_GRPC_SERVER_KEY: ${MG_AUTH_GRPC_SERVER_KEY:+/auth-grpc-server.key} - MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} - MG_AUTH_GRPC_CLIENT_CA_CERTS: ${MG_AUTH_GRPC_CLIENT_CA_CERTS:+/auth-grpc-client-ca.crt} - MG_AUTH_DB_HOST: ${MG_AUTH_DB_HOST} - MG_AUTH_DB_PORT: ${MG_AUTH_DB_PORT} - MG_AUTH_DB_USER: ${MG_AUTH_DB_USER} - MG_AUTH_DB_PASS: ${MG_AUTH_DB_PASS} - MG_AUTH_DB_NAME: ${MG_AUTH_DB_NAME} - MG_AUTH_DB_SSL_MODE: ${MG_AUTH_DB_SSL_MODE} - MG_AUTH_DB_SSL_CERT: ${MG_AUTH_DB_SSL_CERT} - MG_AUTH_DB_SSL_KEY: ${MG_AUTH_DB_SSL_KEY} - MG_AUTH_DB_SSL_ROOT_CERT: ${MG_AUTH_DB_SSL_ROOT_CERT} - MG_JAEGER_URL: ${MG_JAEGER_URL} - MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_AUTH_ADAPTER_INSTANCE_ID: ${MG_AUTH_ADAPTER_INSTANCE_ID} - MG_ES_URL: ${MG_ES_URL} + SMQ_AUTH_GRPC_SERVER_CERT: ${SMQ_AUTH_GRPC_SERVER_CERT:+/auth-grpc-server.crt} + SMQ_AUTH_GRPC_SERVER_KEY: ${SMQ_AUTH_GRPC_SERVER_KEY:+/auth-grpc-server.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_AUTH_GRPC_CLIENT_CA_CERTS: ${SMQ_AUTH_GRPC_CLIENT_CA_CERTS:+/auth-grpc-client-ca.crt} + SMQ_AUTH_DB_HOST: ${SMQ_AUTH_DB_HOST} + SMQ_AUTH_DB_PORT: ${SMQ_AUTH_DB_PORT} + SMQ_AUTH_DB_USER: ${SMQ_AUTH_DB_USER} + SMQ_AUTH_DB_PASS: ${SMQ_AUTH_DB_PASS} + SMQ_AUTH_DB_NAME: ${SMQ_AUTH_DB_NAME} + SMQ_AUTH_DB_SSL_MODE: ${SMQ_AUTH_DB_SSL_MODE} + SMQ_AUTH_DB_SSL_CERT: ${SMQ_AUTH_DB_SSL_CERT} + SMQ_AUTH_DB_SSL_KEY: ${SMQ_AUTH_DB_SSL_KEY} + SMQ_AUTH_DB_SSL_ROOT_CERT: ${SMQ_AUTH_DB_SSL_ROOT_CERT} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_AUTH_ADAPTER_INSTANCE_ID: ${SMQ_AUTH_ADAPTER_INSTANCE_ID} + SMQ_ES_URL: ${SMQ_ES_URL} ports: - - ${MG_AUTH_HTTP_PORT}:${MG_AUTH_HTTP_PORT} - - ${MG_AUTH_GRPC_PORT}:${MG_AUTH_GRPC_PORT} + - ${SMQ_AUTH_HTTP_PORT}:${SMQ_AUTH_HTTP_PORT} + - ${SMQ_AUTH_GRPC_PORT}:${SMQ_AUTH_GRPC_PORT} networks: - magistrala-base-net volumes: - - ./spicedb/schema.zed:${MG_SPICEDB_SCHEMA_FILE} + - ./spicedb/schema.zed:${SMQ_SPICEDB_SCHEMA_FILE} + - magistrala-pat-db-volume:/supermq-data # Auth gRPC mTLS server certificates - type: bind - source: ${MG_AUTH_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert} - target: /auth-grpc-server${MG_AUTH_GRPC_SERVER_CERT:+.crt} + source: ${SMQ_AUTH_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert} + target: /auth-grpc-server${SMQ_AUTH_GRPC_SERVER_CERT:+.crt} bind: create_host_path: true - type: bind - source: ${MG_AUTH_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key} - target: /auth-grpc-server${MG_AUTH_GRPC_SERVER_KEY:+.key} + source: ${SMQ_AUTH_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key} + target: /auth-grpc-server${SMQ_AUTH_GRPC_SERVER_KEY:+.key} bind: create_host_path: true - type: bind - source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs} - target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true - type: bind - source: ${MG_AUTH_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs} - target: /auth-grpc-client-ca${MG_AUTH_GRPC_CLIENT_CA_CERTS:+.crt} + source: ${SMQ_AUTH_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs} + target: /auth-grpc-client-ca${SMQ_AUTH_GRPC_CLIENT_CA_CERTS:+.crt} + 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: ${SMQ_DOMAINS_DB_USER} + POSTGRES_PASSWORD: ${SMQ_DOMAINS_DB_PASS} + POSTGRES_DB: ${SMQ_DOMAINS_DB_NAME} + networks: + - magistrala-base-net + volumes: + - magistrala-domains-db-volume:/var/lib/postgresql/data + + domains-redis: + image: redis:7.2.4-alpine + container_name: magistrala-domains-redis + restart: on-failure + networks: + - magistrala-base-net + volumes: + - magistrala-domains-redis-volume:/data + + domains: + image: supermq/domains:${SMQ_RELEASE_TAG} + container_name: magistrala-domains + depends_on: + - domains-db + - spicedb + expose: + - ${SMQ_DOMAINS_GRPC_PORT} + restart: on-failure + environment: + SMQ_DOMAINS_LOG_LEVEL: ${SMQ_DOMAINS_LOG_LEVEL} + SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY} + SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST} + SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT} + SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE} + SMQ_DOMAINS_HTTP_HOST: ${SMQ_DOMAINS_HTTP_HOST} + SMQ_DOMAINS_HTTP_PORT: ${SMQ_DOMAINS_HTTP_PORT} + SMQ_DOMAINS_HTTP_SERVER_CERT: ${SMQ_DOMAINS_HTTP_SERVER_CERT} + SMQ_DOMAINS_HTTP_SERVER_KEY: ${SMQ_DOMAINS_HTTP_SERVER_KEY} + SMQ_DOMAINS_GRPC_HOST: ${SMQ_DOMAINS_GRPC_HOST} + SMQ_DOMAINS_GRPC_PORT: ${SMQ_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 + SMQ_DOMAINS_GRPC_SERVER_CERT: ${SMQ_DOMAINS_GRPC_SERVER_CERT:+/auth-grpc-server.crt} + SMQ_DOMAINS_GRPC_SERVER_KEY: ${SMQ_DOMAINS_GRPC_SERVER_KEY:+/auth-grpc-server.key} + SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS: ${SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS:+/auth-grpc-client-ca.crt} + SMQ_DOMAINS_DB_HOST: ${SMQ_DOMAINS_DB_HOST} + SMQ_DOMAINS_DB_PORT: ${SMQ_DOMAINS_DB_PORT} + SMQ_DOMAINS_DB_USER: ${SMQ_DOMAINS_DB_USER} + SMQ_DOMAINS_DB_PASS: ${SMQ_DOMAINS_DB_PASS} + SMQ_DOMAINS_DB_NAME: ${SMQ_DOMAINS_DB_NAME} + SMQ_DOMAINS_DB_SSL_MODE: ${SMQ_DOMAINS_DB_SSL_MODE} + SMQ_DOMAINS_DB_SSL_CERT: ${SMQ_DOMAINS_DB_SSL_CERT} + SMQ_DOMAINS_DB_SSL_KEY: ${SMQ_DOMAINS_DB_SSL_KEY} + SMQ_DOMAINS_DB_SSL_ROOT_CERT: ${SMQ_DOMAINS_DB_SSL_ROOT_CERT} + SMQ_DOMAINS_INSTANCE_ID: ${SMQ_DOMAINS_INSTANCE_ID} + SMQ_ES_URL: ${SMQ_ES_URL} + SMQ_DOMAINS_CACHE_URL: ${SMQ_DOMAINS_CACHE_URL} + SMQ_DOMAINS_CACHE_KEY_DURATION: ${SMQ_DOMAINS_CACHE_KEY_DURATION} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_GROUPS_GRPC_URL: ${SMQ_GROUPS_GRPC_URL} + SMQ_GROUPS_GRPC_TIMEOUT: ${SMQ_GROUPS_GRPC_TIMEOUT} + SMQ_GROUPS_GRPC_CLIENT_CERT: ${SMQ_GROUPS_GRPC_CLIENT_CERT:+/groups-grpc-client.crt} + SMQ_GROUPS_GRPC_CLIENT_KEY: ${SMQ_GROUPS_GRPC_CLIENT_KEY:+/groups-grpc-client.key} + SMQ_GROUPS_GRPC_SERVER_CA_CERTS: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt} + SMQ_CHANNELS_URL: ${SMQ_CHANNELS_URL} + SMQ_CHANNELS_GRPC_URL: ${SMQ_CHANNELS_GRPC_URL} + SMQ_CHANNELS_GRPC_TIMEOUT: ${SMQ_CHANNELS_GRPC_TIMEOUT} + SMQ_CHANNELS_GRPC_CLIENT_CERT: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + SMQ_CHANNELS_GRPC_CLIENT_KEY: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + SMQ_CHANNELS_GRPC_SERVER_CA_CERTS: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} + SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + ports: + - ${SMQ_DOMAINS_HTTP_PORT}:${SMQ_DOMAINS_HTTP_PORT} + - ${SMQ_DOMAINS_GRPC_PORT}:${SMQ_DOMAINS_GRPC_PORT} + networks: + - magistrala-base-net + volumes: + - ./spicedb/schema.zed:${SMQ_SPICEDB_SCHEMA_FILE} + # Auth gRPC mTLS server certificates + - type: bind + source: ${SMQ_DOMAINS_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert} + target: /auth-grpc-server${SMQ_DOMAINS_GRPC_SERVER_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_DOMAINS_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key} + target: /auth-grpc-server${SMQ_DOMAINS_GRPC_SERVER_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs} + target: /auth-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs} + target: /auth-grpc-client-ca${SMQ_DOMAINS_GRPC_CLIENT_CA_CERTS:+.crt} bind: create_host_path: true @@ -158,12 +283,12 @@ services: image: postgres:16.2-alpine container_name: magistrala-invitations-db restart: on-failure - command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" + command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}" environment: - POSTGRES_USER: ${MG_INVITATIONS_DB_USER} - POSTGRES_PASSWORD: ${MG_INVITATIONS_DB_PASS} - POSTGRES_DB: ${MG_INVITATIONS_DB_NAME} - MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} + POSTGRES_USER: ${SMQ_INVITATIONS_DB_USER} + POSTGRES_PASSWORD: ${SMQ_INVITATIONS_DB_PASS} + POSTGRES_DB: ${SMQ_INVITATIONS_DB_NAME} + SMQ_POSTGRES_MAX_CONNECTIONS: ${SMQ_POSTGRES_MAX_CONNECTIONS} ports: - 6021:5432 networks: @@ -172,57 +297,62 @@ services: - magistrala-invitations-db-volume:/var/lib/postgresql/data invitations: - image: magistrala/invitations:${MG_RELEASE_TAG} + image: supermq/invitations:${SMQ_RELEASE_TAG} container_name: magistrala-invitations restart: on-failure depends_on: - auth - invitations-db environment: - MG_INVITATIONS_LOG_LEVEL: ${MG_INVITATIONS_LOG_LEVEL} - MG_USERS_URL: ${MG_USERS_URL} - MG_DOMAINS_URL: ${MG_DOMAINS_URL} - MG_INVITATIONS_HTTP_HOST: ${MG_INVITATIONS_HTTP_HOST} - MG_INVITATIONS_HTTP_PORT: ${MG_INVITATIONS_HTTP_PORT} - MG_INVITATIONS_HTTP_SERVER_CERT: ${MG_INVITATIONS_HTTP_SERVER_CERT} - MG_INVITATIONS_HTTP_SERVER_KEY: ${MG_INVITATIONS_HTTP_SERVER_KEY} - MG_INVITATIONS_DB_HOST: ${MG_INVITATIONS_DB_HOST} - MG_INVITATIONS_DB_USER: ${MG_INVITATIONS_DB_USER} - MG_INVITATIONS_DB_PASS: ${MG_INVITATIONS_DB_PASS} - MG_INVITATIONS_DB_PORT: ${MG_INVITATIONS_DB_PORT} - MG_INVITATIONS_DB_NAME: ${MG_INVITATIONS_DB_NAME} - MG_INVITATIONS_DB_SSL_MODE: ${MG_INVITATIONS_DB_SSL_MODE} - MG_INVITATIONS_DB_SSL_CERT: ${MG_INVITATIONS_DB_SSL_CERT} - MG_INVITATIONS_DB_SSL_KEY: ${MG_INVITATIONS_DB_SSL_KEY} - MG_INVITATIONS_DB_SSL_ROOT_CERT: ${MG_INVITATIONS_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_JAEGER_URL: ${MG_JAEGER_URL} - MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_INVITATIONS_INSTANCE_ID: ${MG_INVITATIONS_INSTANCE_ID} + SMQ_INVITATIONS_LOG_LEVEL: ${SMQ_INVITATIONS_LOG_LEVEL} + SMQ_USERS_URL: ${SMQ_USERS_URL} + SMQ_DOMAINS_URL: ${SMQ_DOMAINS_URL} + SMQ_INVITATIONS_HTTP_HOST: ${SMQ_INVITATIONS_HTTP_HOST} + SMQ_INVITATIONS_HTTP_PORT: ${SMQ_INVITATIONS_HTTP_PORT} + SMQ_INVITATIONS_HTTP_SERVER_CERT: ${SMQ_INVITATIONS_HTTP_SERVER_CERT} + SMQ_INVITATIONS_HTTP_SERVER_KEY: ${SMQ_INVITATIONS_HTTP_SERVER_KEY} + SMQ_INVITATIONS_DB_HOST: ${SMQ_INVITATIONS_DB_HOST} + SMQ_INVITATIONS_DB_USER: ${SMQ_INVITATIONS_DB_USER} + SMQ_INVITATIONS_DB_PASS: ${SMQ_INVITATIONS_DB_PASS} + SMQ_INVITATIONS_DB_PORT: ${SMQ_INVITATIONS_DB_PORT} + SMQ_INVITATIONS_DB_NAME: ${SMQ_INVITATIONS_DB_NAME} + SMQ_INVITATIONS_DB_SSL_MODE: ${SMQ_INVITATIONS_DB_SSL_MODE} + SMQ_INVITATIONS_DB_SSL_CERT: ${SMQ_INVITATIONS_DB_SSL_CERT} + SMQ_INVITATIONS_DB_SSL_KEY: ${SMQ_INVITATIONS_DB_SSL_KEY} + SMQ_INVITATIONS_DB_SSL_ROOT_CERT: ${SMQ_INVITATIONS_DB_SSL_ROOT_CERT} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL} + SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT} + SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt} + SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key} + SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_INVITATIONS_INSTANCE_ID: ${SMQ_INVITATIONS_INSTANCE_ID} ports: - - ${MG_INVITATIONS_HTTP_PORT}:${MG_INVITATIONS_HTTP_PORT} + - ${SMQ_INVITATIONS_HTTP_PORT}:${SMQ_INVITATIONS_HTTP_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} + source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${SMQ_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} + source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${SMQ_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} + source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true @@ -236,149 +366,281 @@ services: - ./nginx/snippets:/etc/nginx/snippets - ./ssl/authorization.js:/etc/nginx/authorization.js - type: bind - source: ${MG_NGINX_SERVER_CERT:-./ssl/certs/magistrala-server.crt} - target: /etc/ssl/certs/magistrala-server.crt + source: ${SMQ_NGINX_SERVER_CERT:-./ssl/certs/supermq-server.crt} + target: /etc/ssl/certs/supermq-server.crt - type: bind - source: ${MG_NGINX_SERVER_KEY:-./ssl/certs/magistrala-server.key} - target: /etc/ssl/private/magistrala-server.key + source: ${SMQ_NGINX_SERVER_KEY:-./ssl/certs/supermq-server.key} + target: /etc/ssl/private/supermq-server.key - type: bind - source: ${MG_NGINX_SERVER_CLIENT_CA:-./ssl/certs/ca.crt} + source: ${SMQ_NGINX_SERVER_CLIENT_CA:-./ssl/certs/ca.crt} target: /etc/ssl/certs/ca.crt - type: bind - source: ${MG_NGINX_SERVER_DHPARAM:-./ssl/dhparam.pem} + source: ${SMQ_NGINX_SERVER_DHPARAM:-./ssl/dhparam.pem} target: /etc/ssl/certs/dhparam.pem ports: - - ${MG_NGINX_HTTP_PORT}:${MG_NGINX_HTTP_PORT} - - ${MG_NGINX_SSL_PORT}:${MG_NGINX_SSL_PORT} - - ${MG_NGINX_MQTT_PORT}:${MG_NGINX_MQTT_PORT} - - ${MG_NGINX_MQTTS_PORT}:${MG_NGINX_MQTTS_PORT} + - ${SMQ_NGINX_HTTP_PORT}:${SMQ_NGINX_HTTP_PORT} + - ${SMQ_NGINX_SSL_PORT}:${SMQ_NGINX_SSL_PORT} + - ${SMQ_NGINX_MQTT_PORT}:${SMQ_NGINX_MQTT_PORT} + - ${SMQ_NGINX_MQTTS_PORT}:${SMQ_NGINX_MQTTS_PORT} networks: - magistrala-base-net env_file: - .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}" + command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}" environment: - POSTGRES_USER: ${MG_THINGS_DB_USER} - POSTGRES_PASSWORD: ${MG_THINGS_DB_PASS} - POSTGRES_DB: ${MG_THINGS_DB_NAME} - MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} + POSTGRES_USER: ${SMQ_CLIENTS_DB_USER} + POSTGRES_PASSWORD: ${SMQ_CLIENTS_DB_PASS} + POSTGRES_DB: ${SMQ_CLIENTS_DB_NAME} + SMQ_POSTGRES_MAX_CONNECTIONS: ${SMQ_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: supermq/clients:${SMQ_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} + SMQ_CLIENTS_LOG_LEVEL: ${SMQ_CLIENTS_LOG_LEVEL} + SMQ_CLIENTS_STANDALONE_ID: ${SMQ_CLIENTS_STANDALONE_ID} + SMQ_CLIENTS_STANDALONE_TOKEN: ${SMQ_CLIENTS_STANDALONE_TOKEN} + SMQ_CLIENTS_CACHE_KEY_DURATION: ${SMQ_CLIENTS_CACHE_KEY_DURATION} + SMQ_CLIENTS_HTTP_HOST: ${SMQ_CLIENTS_HTTP_HOST} + SMQ_CLIENTS_HTTP_PORT: ${SMQ_CLIENTS_HTTP_PORT} + SMQ_CLIENTS_AUTH_GRPC_HOST: ${SMQ_CLIENTS_AUTH_GRPC_HOST} + SMQ_CLIENTS_AUTH_GRPC_PORT: ${SMQ_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_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_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_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} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CERT: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CERT:+/clients-grpc-server.crt} + SMQ_CLIENTS_AUTH_GRPC_SERVER_KEY: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_KEY:+/clients-grpc-server.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS:+/clients-grpc-client-ca.crt} + SMQ_ES_URL: ${SMQ_ES_URL} + SMQ_CLIENTS_CACHE_URL: ${SMQ_CLIENTS_CACHE_URL} + SMQ_CLIENTS_DB_HOST: ${SMQ_CLIENTS_DB_HOST} + SMQ_CLIENTS_DB_PORT: ${SMQ_CLIENTS_DB_PORT} + SMQ_CLIENTS_DB_USER: ${SMQ_CLIENTS_DB_USER} + SMQ_CLIENTS_DB_PASS: ${SMQ_CLIENTS_DB_PASS} + SMQ_CLIENTS_DB_NAME: ${SMQ_CLIENTS_DB_NAME} + SMQ_CLIENTS_DB_SSL_MODE: ${SMQ_CLIENTS_DB_SSL_MODE} + SMQ_CLIENTS_DB_SSL_CERT: ${SMQ_CLIENTS_DB_SSL_CERT} + SMQ_CLIENTS_DB_SSL_KEY: ${SMQ_CLIENTS_DB_SSL_KEY} + SMQ_CLIENTS_DB_SSL_ROOT_CERT: ${SMQ_CLIENTS_DB_SSL_ROOT_CERT} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_CHANNELS_URL: ${SMQ_CHANNELS_URL} + SMQ_CHANNELS_GRPC_URL: ${SMQ_CHANNELS_GRPC_URL} + SMQ_CHANNELS_GRPC_TIMEOUT: ${SMQ_CHANNELS_GRPC_TIMEOUT} + SMQ_CHANNELS_GRPC_CLIENT_CERT: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + SMQ_CHANNELS_GRPC_CLIENT_KEY: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + SMQ_CHANNELS_GRPC_SERVER_CA_CERTS: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + SMQ_GROUPS_URL: ${SMQ_GROUPS_URL} + SMQ_GROUPS_GRPC_URL: ${SMQ_GROUPS_GRPC_URL} + SMQ_GROUPS_GRPC_TIMEOUT: ${SMQ_GROUPS_GRPC_TIMEOUT} + SMQ_GROUPS_GRPC_CLIENT_CERT: ${SMQ_GROUPS_GRPC_CLIENT_CERT:+/groups-grpc-client.crt} + SMQ_GROUPS_GRPC_CLIENT_KEY: ${SMQ_GROUPS_GRPC_CLIENT_KEY:+/groups-grpc-client.key} + SMQ_GROUPS_GRPC_SERVER_CA_CERTS: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt} + SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL} + SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT} + SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt} + SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key} + SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY} + SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST} + SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT} ports: - - ${MG_THINGS_HTTP_PORT}:${MG_THINGS_HTTP_PORT} - - ${MG_THINGS_AUTH_GRPC_PORT}:${MG_THINGS_AUTH_GRPC_PORT} + - ${SMQ_CLIENTS_HTTP_PORT}:${SMQ_CLIENTS_HTTP_PORT} + - ${SMQ_CLIENTS_AUTH_GRPC_PORT}:${SMQ_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: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CERT:-ssl/certs/dummy/server_cert} + target: /clients-grpc-server${SMQ_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: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_KEY:-ssl/certs/dummy/server_key} + target: /clients-grpc-server${SMQ_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: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca_certs} + target: /clients-grpc-server-ca${SMQ_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: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS:-ssl/certs/dummy/client_ca_certs} + target: /clients-grpc-client-ca${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS:+.crt} bind: create_host_path: true # 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} + source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key} 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} + source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true + # Channel gRPC client certificates - 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} + source: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${SMQ_CHANNELS_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${SMQ_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=${SMQ_POSTGRES_MAX_CONNECTIONS}" + environment: + POSTGRES_USER: ${SMQ_CHANNELS_DB_USER} + POSTGRES_PASSWORD: ${SMQ_CHANNELS_DB_PASS} + POSTGRES_DB: ${SMQ_CHANNELS_DB_NAME} + SMQ_POSTGRES_MAX_CONNECTIONS: ${SMQ_POSTGRES_MAX_CONNECTIONS} + networks: + - magistrala-base-net + ports: + - 6005:5432 + volumes: + - magistrala-channels-db-volume:/var/lib/postgresql/data + + channels: + image: supermq/channels:${SMQ_RELEASE_TAG} + container_name: magistrala-channels + depends_on: + - channels-db + - users + - auth + - nats + restart: on-failure + environment: + SMQ_CHANNELS_LOG_LEVEL: ${SMQ_CHANNELS_LOG_LEVEL} + SMQ_CHANNELS_INSTANCE_ID: ${SMQ_CHANNELS_INSTANCE_ID} + SMQ_CHANNELS_HTTP_HOST: ${SMQ_CHANNELS_HTTP_HOST} + SMQ_CHANNELS_HTTP_PORT: ${SMQ_CHANNELS_HTTP_PORT} + SMQ_CHANNELS_GRPC_HOST: ${SMQ_CHANNELS_GRPC_HOST} + SMQ_CHANNELS_GRPC_PORT: ${SMQ_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 + SMQ_CHANNELS_GRPC_SERVER_CERT: ${SMQ_CHANNELS_GRPC_SERVER_CERT:+/channels-grpc-server.crt} + SMQ_CHANNELS_GRPC_SERVER_KEY: ${SMQ_CHANNELS_GRPC_SERVER_KEY:+/channels-grpc-server.key} + SMQ_CHANNELS_GRPC_SERVER_CA_CERTS: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS: ${SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS:+/channels-grpc-client-ca.crt} + SMQ_CHANNELS_DB_HOST: ${SMQ_CHANNELS_DB_HOST} + SMQ_CHANNELS_DB_PORT: ${SMQ_CHANNELS_DB_PORT} + SMQ_CHANNELS_DB_USER: ${SMQ_CHANNELS_DB_USER} + SMQ_CHANNELS_DB_PASS: ${SMQ_CHANNELS_DB_PASS} + SMQ_CHANNELS_DB_NAME: ${SMQ_CHANNELS_DB_NAME} + SMQ_CHANNELS_DB_SSL_MODE: ${SMQ_CHANNELS_DB_SSL_MODE} + SMQ_CHANNELS_DB_SSL_CERT: ${SMQ_CHANNELS_DB_SSL_CERT} + SMQ_CHANNELS_DB_SSL_KEY: ${SMQ_CHANNELS_DB_SSL_KEY} + SMQ_CHANNELS_DB_SSL_ROOT_CERT: ${SMQ_CHANNELS_DB_SSL_ROOT_CERT} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} + SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_GROUPS_GRPC_URL: ${SMQ_GROUPS_GRPC_URL} + SMQ_GROUPS_GRPC_TIMEOUT: ${SMQ_GROUPS_GRPC_TIMEOUT} + SMQ_GROUPS_GRPC_CLIENT_CERT: ${SMQ_GROUPS_GRPC_CLIENT_CERT:+/groups-grpc-client.crt} + SMQ_GROUPS_GRPC_CLIENT_KEY: ${SMQ_GROUPS_GRPC_CLIENT_KEY:+/groups-grpc-client.key} + SMQ_GROUPS_GRPC_SERVER_CA_CERTS: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt} + SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL} + SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT} + SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt} + SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key} + SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt} + SMQ_ES_URL: ${SMQ_ES_URL} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY} + SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST} + SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT} + ports: + - ${SMQ_CHANNELS_HTTP_PORT}:${SMQ_CHANNELS_HTTP_PORT} + - ${SMQ_CHANNELS_GRPC_PORT}:${SMQ_CHANNELS_GRPC_PORT} + networks: + - magistrala-base-net + volumes: + # Auth gRPC client certificates + - type: bind + source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true @@ -386,21 +648,21 @@ services: image: postgres:16.2-alpine container_name: magistrala-users-db restart: on-failure - command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" + command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}" environment: - POSTGRES_USER: ${MG_USERS_DB_USER} - POSTGRES_PASSWORD: ${MG_USERS_DB_PASS} - POSTGRES_DB: ${MG_USERS_DB_NAME} - MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} + POSTGRES_USER: ${SMQ_USERS_DB_USER} + POSTGRES_PASSWORD: ${SMQ_USERS_DB_PASS} + POSTGRES_DB: ${SMQ_USERS_DB_NAME} + SMQ_POSTGRES_MAX_CONNECTIONS: ${SMQ_POSTGRES_MAX_CONNECTIONS} ports: - - 6000:5432 + - 6002:5432 networks: - magistrala-base-net volumes: - magistrala-users-db-volume:/var/lib/postgresql/data users: - image: magistrala/users:${MG_RELEASE_TAG} + image: supermq/users:${SMQ_RELEASE_TAG} container_name: magistrala-users depends_on: - users-db @@ -408,292 +670,527 @@ services: - nats restart: on-failure environment: - MG_USERS_LOG_LEVEL: ${MG_USERS_LOG_LEVEL} - MG_USERS_SECRET_KEY: ${MG_USERS_SECRET_KEY} - MG_USERS_ADMIN_EMAIL: ${MG_USERS_ADMIN_EMAIL} - MG_USERS_ADMIN_PASSWORD: ${MG_USERS_ADMIN_PASSWORD} - MG_USERS_ADMIN_USERNAME: ${MG_USERS_ADMIN_USERNAME} - MG_USERS_ADMIN_FIRST_NAME: ${MG_USERS_ADMIN_FIRST_NAME} - MG_USERS_ADMIN_LAST_NAME: ${MG_USERS_ADMIN_LAST_NAME} - MG_USERS_PASS_REGEX: ${MG_USERS_PASS_REGEX} - MG_USERS_ACCESS_TOKEN_DURATION: ${MG_USERS_ACCESS_TOKEN_DURATION} - MG_USERS_REFRESH_TOKEN_DURATION: ${MG_USERS_REFRESH_TOKEN_DURATION} - MG_TOKEN_RESET_ENDPOINT: ${MG_TOKEN_RESET_ENDPOINT} - MG_USERS_HTTP_HOST: ${MG_USERS_HTTP_HOST} - MG_USERS_HTTP_PORT: ${MG_USERS_HTTP_PORT} - MG_USERS_HTTP_SERVER_CERT: ${MG_USERS_HTTP_SERVER_CERT} - MG_USERS_HTTP_SERVER_KEY: ${MG_USERS_HTTP_SERVER_KEY} - MG_USERS_DB_HOST: ${MG_USERS_DB_HOST} - MG_USERS_DB_PORT: ${MG_USERS_DB_PORT} - MG_USERS_DB_USER: ${MG_USERS_DB_USER} - MG_USERS_DB_PASS: ${MG_USERS_DB_PASS} - MG_USERS_DB_NAME: ${MG_USERS_DB_NAME} - MG_USERS_DB_SSL_MODE: ${MG_USERS_DB_SSL_MODE} - MG_USERS_DB_SSL_CERT: ${MG_USERS_DB_SSL_CERT} - MG_USERS_DB_SSL_KEY: ${MG_USERS_DB_SSL_KEY} - MG_USERS_DB_SSL_ROOT_CERT: ${MG_USERS_DB_SSL_ROOT_CERT} - MG_USERS_ALLOW_SELF_REGISTER: ${MG_USERS_ALLOW_SELF_REGISTER} - MG_EMAIL_HOST: ${MG_EMAIL_HOST} - MG_EMAIL_PORT: ${MG_EMAIL_PORT} - MG_EMAIL_USERNAME: ${MG_EMAIL_USERNAME} - MG_EMAIL_PASSWORD: ${MG_EMAIL_PASSWORD} - MG_EMAIL_FROM_ADDRESS: ${MG_EMAIL_FROM_ADDRESS} - MG_EMAIL_FROM_NAME: ${MG_EMAIL_FROM_NAME} - MG_EMAIL_TEMPLATE: ${MG_EMAIL_TEMPLATE} - 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_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} - MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} - MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} - MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} - MG_OAUTH_UI_REDIRECT_URL: ${MG_OAUTH_UI_REDIRECT_URL} - MG_OAUTH_UI_ERROR_URL: ${MG_OAUTH_UI_ERROR_URL} - MG_USERS_DELETE_INTERVAL: ${MG_USERS_DELETE_INTERVAL} - MG_USERS_DELETE_AFTER: ${MG_USERS_DELETE_AFTER} - MG_SPICEDB_PRE_SHARED_KEY: ${MG_SPICEDB_PRE_SHARED_KEY} - MG_SPICEDB_HOST: ${MG_SPICEDB_HOST} - MG_SPICEDB_PORT: ${MG_SPICEDB_PORT} + SMQ_USERS_LOG_LEVEL: ${SMQ_USERS_LOG_LEVEL} + SMQ_USERS_SECRET_KEY: ${SMQ_USERS_SECRET_KEY} + SMQ_USERS_ADMIN_EMAIL: ${SMQ_USERS_ADMIN_EMAIL} + SMQ_USERS_ADMIN_PASSWORD: ${SMQ_USERS_ADMIN_PASSWORD} + SMQ_USERS_ADMIN_USERNAME: ${SMQ_USERS_ADMIN_USERNAME} + SMQ_USERS_ADMIN_FIRST_NAME: ${SMQ_USERS_ADMIN_FIRST_NAME} + SMQ_USERS_ADMIN_LAST_NAME: ${SMQ_USERS_ADMIN_LAST_NAME} + SMQ_USERS_PASS_REGEX: ${SMQ_USERS_PASS_REGEX} + SMQ_USERS_ACCESS_TOKEN_DURATION: ${SMQ_USERS_ACCESS_TOKEN_DURATION} + SMQ_USERS_REFRESH_TOKEN_DURATION: ${SMQ_USERS_REFRESH_TOKEN_DURATION} + SMQ_TOKEN_RESET_ENDPOINT: ${SMQ_TOKEN_RESET_ENDPOINT} + SMQ_USERS_HTTP_HOST: ${SMQ_USERS_HTTP_HOST} + SMQ_USERS_HTTP_PORT: ${SMQ_USERS_HTTP_PORT} + SMQ_USERS_HTTP_SERVER_CERT: ${SMQ_USERS_HTTP_SERVER_CERT} + SMQ_USERS_HTTP_SERVER_KEY: ${SMQ_USERS_HTTP_SERVER_KEY} + SMQ_USERS_DB_HOST: ${SMQ_USERS_DB_HOST} + SMQ_USERS_DB_PORT: ${SMQ_USERS_DB_PORT} + SMQ_USERS_DB_USER: ${SMQ_USERS_DB_USER} + SMQ_USERS_DB_PASS: ${SMQ_USERS_DB_PASS} + SMQ_USERS_DB_NAME: ${SMQ_USERS_DB_NAME} + SMQ_USERS_DB_SSL_MODE: ${SMQ_USERS_DB_SSL_MODE} + SMQ_USERS_DB_SSL_CERT: ${SMQ_USERS_DB_SSL_CERT} + SMQ_USERS_DB_SSL_KEY: ${SMQ_USERS_DB_SSL_KEY} + SMQ_USERS_DB_SSL_ROOT_CERT: ${SMQ_USERS_DB_SSL_ROOT_CERT} + SMQ_USERS_ALLOW_SELF_REGISTER: ${SMQ_USERS_ALLOW_SELF_REGISTER} + SMQ_EMAIL_HOST: ${SMQ_EMAIL_HOST} + SMQ_EMAIL_PORT: ${SMQ_EMAIL_PORT} + SMQ_EMAIL_USERNAME: ${SMQ_EMAIL_USERNAME} + SMQ_EMAIL_PASSWORD: ${SMQ_EMAIL_PASSWORD} + SMQ_EMAIL_FROM_ADDRESS: ${SMQ_EMAIL_FROM_ADDRESS} + SMQ_EMAIL_FROM_NAME: ${SMQ_EMAIL_FROM_NAME} + SMQ_EMAIL_TEMPLATE: ${SMQ_EMAIL_TEMPLATE} + SMQ_ES_URL: ${SMQ_ES_URL} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL} + SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT} + SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt} + SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key} + SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt} + SMQ_GOOGLE_CLIENT_ID: ${SMQ_GOOGLE_CLIENT_ID} + SMQ_GOOGLE_CLIENT_SECRET: ${SMQ_GOOGLE_CLIENT_SECRET} + SMQ_GOOGLE_REDIRECT_URL: ${SMQ_GOOGLE_REDIRECT_URL} + SMQ_GOOGLE_STATE: ${SMQ_GOOGLE_STATE} + SMQ_OAUTH_UI_REDIRECT_URL: ${SMQ_OAUTH_UI_REDIRECT_URL} + SMQ_OAUTH_UI_ERROR_URL: ${SMQ_OAUTH_UI_ERROR_URL} + SMQ_USERS_DELETE_INTERVAL: ${SMQ_USERS_DELETE_INTERVAL} + SMQ_USERS_DELETE_AFTER: ${SMQ_USERS_DELETE_AFTER} + SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY} + SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST} + SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT} ports: - - ${MG_USERS_HTTP_PORT}:${MG_USERS_HTTP_PORT} + - ${SMQ_USERS_HTTP_PORT}:${SMQ_USERS_HTTP_PORT} networks: - magistrala-base-net volumes: - - ./templates/${MG_USERS_RESET_PWD_TEMPLATE}:/email.tmpl + - ./templates/${SMQ_USERS_RESET_PWD_TEMPLATE}:/email.tmpl # 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} + source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${SMQ_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} + source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${SMQ_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} + source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} 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=${SMQ_POSTGRES_MAX_CONNECTIONS}" + environment: + POSTGRES_USER: ${SMQ_GROUPS_DB_USER} + POSTGRES_PASSWORD: ${SMQ_GROUPS_DB_PASS} + POSTGRES_DB: ${SMQ_GROUPS_DB_NAME} + SMQ_POSTGRES_MAX_CONNECTIONS: ${SMQ_POSTGRES_MAX_CONNECTIONS} + ports: + - 6004:5432 + networks: + - magistrala-base-net + volumes: + - magistrala-groups-db-volume:/var/lib/postgresql/data + + groups: + image: supermq/groups:${SMQ_RELEASE_TAG} + container_name: magistrala-groups + depends_on: + - groups-db + - auth + - nats + restart: on-failure + environment: + SMQ_GROUPS_LOG_LEVEL: ${SMQ_GROUPS_LOG_LEVEL} + SMQ_GROUPS_HTTP_HOST: ${SMQ_GROUPS_HTTP_HOST} + SMQ_GROUPS_HTTP_PORT: ${SMQ_GROUPS_HTTP_PORT} + SMQ_GROUPS_HTTP_SERVER_CERT: ${SMQ_GROUPS_HTTP_SERVER_CERT} + SMQ_GROUPS_HTTP_SERVER_KEY: ${SMQ_GROUPS_HTTP_SERVER_KEY} + SMQ_GROUPS_GRPC_HOST: ${SMQ_GROUPS_GRPC_HOST} + SMQ_GROUPS_GRPC_PORT: ${SMQ_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 + SMQ_GROUPS_GRPC_SERVER_CERT: ${SMQ_GROUPS_GRPC_SERVER_CERT:+/groups-grpc-server.crt} + SMQ_GROUPS_GRPC_SERVER_KEY: ${SMQ_GROUPS_GRPC_SERVER_KEY:+/groups-grpc-server.key} + SMQ_GROUPS_GRPC_SERVER_CA_CERTS: ${SMQ_GROUPS_GRPC_SERVER_CA_CERTS:+/groups-grpc-server-ca.crt} + SMQ_GROUPS_GRPC_CLIENT_CA_CERTS: ${SMQ_GROUPS_GRPC_CLIENT_CA_CERTS:+/groups-grpc-client-ca.crt} + SMQ_GROUPS_DB_HOST: ${SMQ_GROUPS_DB_HOST} + SMQ_GROUPS_DB_PORT: ${SMQ_GROUPS_DB_PORT} + SMQ_GROUPS_DB_USER: ${SMQ_GROUPS_DB_USER} + SMQ_GROUPS_DB_PASS: ${SMQ_GROUPS_DB_PASS} + SMQ_GROUPS_DB_NAME: ${SMQ_GROUPS_DB_NAME} + SMQ_GROUPS_DB_SSL_MODE: ${SMQ_GROUPS_DB_SSL_MODE} + SMQ_GROUPS_DB_SSL_CERT: ${SMQ_GROUPS_DB_SSL_CERT} + SMQ_GROUPS_DB_SSL_KEY: ${SMQ_GROUPS_DB_SSL_KEY} + SMQ_GROUPS_DB_SSL_ROOT_CERT: ${SMQ_GROUPS_DB_SSL_ROOT_CERT} + SMQ_CHANNELS_URL: ${SMQ_CHANNELS_URL} + SMQ_CHANNELS_GRPC_URL: ${SMQ_CHANNELS_GRPC_URL} + SMQ_CHANNELS_GRPC_TIMEOUT: ${SMQ_CHANNELS_GRPC_TIMEOUT} + SMQ_CHANNELS_GRPC_CLIENT_CERT: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + SMQ_CHANNELS_GRPC_CLIENT_KEY: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + SMQ_CHANNELS_GRPC_SERVER_CA_CERTS: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} + SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL} + SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT} + SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt} + SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key} + SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt} + SMQ_ES_URL: ${SMQ_ES_URL} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY} + SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST} + SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT} + SMQ_SPICEDB_SCHEMA_FILE: ${SMQ_SPICEDB_SCHEMA_FILE} + ports: + - ${SMQ_GROUPS_HTTP_PORT}:${SMQ_GROUPS_HTTP_PORT} + - ${SMQ_GROUPS_GRPC_PORT}:${SMQ_GROUPS_GRPC_PORT} + networks: + - magistrala-base-net + volumes: + - ./spicedb/schema.zed:${SMQ_SPICEDB_SCHEMA_FILE} + # Auth gRPC client certificates + - type: bind + source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + + jaeger: image: jaegertracing/all-in-one:1.60 container_name: magistrala-jaeger environment: - COLLECTOR_OTLP_ENABLED: ${MG_JAEGER_COLLECTOR_OTLP_ENABLED} - command: --memory.max-traces ${MG_JAEGER_MEMORY_MAX_TRACES} + COLLECTOR_OTLP_ENABLED: ${SMQ_JAEGER_COLLECTOR_OTLP_ENABLED} + command: --memory.max-traces ${SMQ_JAEGER_MEMORY_MAX_TRACES} ports: - - ${MG_JAEGER_FRONTEND}:${MG_JAEGER_FRONTEND} - - ${MG_JAEGER_OLTP_HTTP}:${MG_JAEGER_OLTP_HTTP} + - ${SMQ_JAEGER_FRONTEND}:${SMQ_JAEGER_FRONTEND} + - ${SMQ_JAEGER_OLTP_HTTP}:${SMQ_JAEGER_OLTP_HTTP} networks: - magistrala-base-net mqtt-adapter: - image: magistrala/mqtt:${MG_RELEASE_TAG} + image: supermq/mqtt:${SMQ_RELEASE_TAG} container_name: magistrala-mqtt depends_on: - - things + - clients - vernemq - nats restart: on-failure environment: - MG_MQTT_ADAPTER_LOG_LEVEL: ${MG_MQTT_ADAPTER_LOG_LEVEL} - MG_MQTT_ADAPTER_MQTT_PORT: ${MG_MQTT_ADAPTER_MQTT_PORT} - MG_MQTT_ADAPTER_MQTT_TARGET_HOST: ${MG_MQTT_ADAPTER_MQTT_TARGET_HOST} - MG_MQTT_ADAPTER_MQTT_TARGET_PORT: ${MG_MQTT_ADAPTER_MQTT_TARGET_PORT} - MG_MQTT_ADAPTER_FORWARDER_TIMEOUT: ${MG_MQTT_ADAPTER_FORWARDER_TIMEOUT} - MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK: ${MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK} - MG_MQTT_ADAPTER_MQTT_QOS: ${MG_MQTT_ADAPTER_MQTT_QOS} - MG_MQTT_ADAPTER_WS_PORT: ${MG_MQTT_ADAPTER_WS_PORT} - MG_MQTT_ADAPTER_INSTANCE_ID: ${MG_MQTT_ADAPTER_INSTANCE_ID} - MG_MQTT_ADAPTER_WS_TARGET_HOST: ${MG_MQTT_ADAPTER_WS_TARGET_HOST} - MG_MQTT_ADAPTER_WS_TARGET_PORT: ${MG_MQTT_ADAPTER_WS_TARGET_PORT} - 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_JAEGER_URL: ${MG_JAEGER_URL} - MG_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} - MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} + SMQ_MQTT_ADAPTER_LOG_LEVEL: ${SMQ_MQTT_ADAPTER_LOG_LEVEL} + SMQ_MQTT_ADAPTER_MQTT_PORT: ${SMQ_MQTT_ADAPTER_MQTT_PORT} + SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST} + SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT} + SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT: ${SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT} + SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK} + SMQ_MQTT_ADAPTER_MQTT_QOS: ${SMQ_MQTT_ADAPTER_MQTT_QOS} + SMQ_MQTT_ADAPTER_WS_PORT: ${SMQ_MQTT_ADAPTER_WS_PORT} + SMQ_MQTT_ADAPTER_INSTANCE_ID: ${SMQ_MQTT_ADAPTER_INSTANCE_ID} + SMQ_MQTT_ADAPTER_WS_TARGET_HOST: ${SMQ_MQTT_ADAPTER_WS_TARGET_HOST} + SMQ_MQTT_ADAPTER_WS_TARGET_PORT: ${SMQ_MQTT_ADAPTER_WS_TARGET_PORT} + SMQ_MQTT_ADAPTER_WS_TARGET_PATH: ${SMQ_MQTT_ADAPTER_WS_TARGET_PATH} + SMQ_MQTT_ADAPTER_INSTANCE: ${SMQ_MQTT_ADAPTER_INSTANCE} + SMQ_ES_URL: ${SMQ_ES_URL} + SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} + SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_CHANNELS_GRPC_URL: ${SMQ_CHANNELS_GRPC_URL} + SMQ_CHANNELS_GRPC_TIMEOUT: ${SMQ_CHANNELS_GRPC_TIMEOUT} + SMQ_CHANNELS_GRPC_CLIENT_CERT: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + SMQ_CHANNELS_GRPC_CLIENT_KEY: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + SMQ_CHANNELS_GRPC_SERVER_CA_CERTS: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} networks: - magistrala-base-net volumes: - # Things gRPC mTLS client certificates + # Clients 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: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${SMQ_CLIENTS_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: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${SMQ_CLIENTS_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: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Channels gRPC mTLS client certificates + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${SMQ_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${SMQ_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${SMQ_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true http-adapter: - image: magistrala/http:${MG_RELEASE_TAG} + image: supermq/http:${SMQ_RELEASE_TAG} container_name: magistrala-http depends_on: - - things + - clients - nats restart: on-failure environment: - MG_HTTP_ADAPTER_LOG_LEVEL: ${MG_HTTP_ADAPTER_LOG_LEVEL} - MG_HTTP_ADAPTER_HOST: ${MG_HTTP_ADAPTER_HOST} - 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_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} - MG_JAEGER_URL: ${MG_JAEGER_URL} - MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_HTTP_ADAPTER_INSTANCE_ID: ${MG_HTTP_ADAPTER_INSTANCE_ID} + SMQ_HTTP_ADAPTER_LOG_LEVEL: ${SMQ_HTTP_ADAPTER_LOG_LEVEL} + SMQ_HTTP_ADAPTER_HOST: ${SMQ_HTTP_ADAPTER_HOST} + SMQ_HTTP_ADAPTER_PORT: ${SMQ_HTTP_ADAPTER_PORT} + SMQ_HTTP_ADAPTER_SERVER_CERT: ${SMQ_HTTP_ADAPTER_SERVER_CERT} + SMQ_HTTP_ADAPTER_SERVER_KEY: ${SMQ_HTTP_ADAPTER_SERVER_KEY} + SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} + SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_CHANNELS_GRPC_URL: ${SMQ_CHANNELS_GRPC_URL} + SMQ_CHANNELS_GRPC_TIMEOUT: ${SMQ_CHANNELS_GRPC_TIMEOUT} + SMQ_CHANNELS_GRPC_CLIENT_CERT: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + SMQ_CHANNELS_GRPC_CLIENT_KEY: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + SMQ_CHANNELS_GRPC_SERVER_CA_CERTS: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_HTTP_ADAPTER_INSTANCE_ID: ${SMQ_HTTP_ADAPTER_INSTANCE_ID} ports: - - ${MG_HTTP_ADAPTER_PORT}:${MG_HTTP_ADAPTER_PORT} + - ${SMQ_HTTP_ADAPTER_PORT}:${SMQ_HTTP_ADAPTER_PORT} networks: - magistrala-base-net volumes: - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${SMQ_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: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${SMQ_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: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${SMQ_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: ${SMQ_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${SMQ_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${SMQ_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${SMQ_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Auth gRPC mTLS client certificates + - type: bind + source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true coap-adapter: - image: magistrala/coap:${MG_RELEASE_TAG} + image: supermq/coap:${SMQ_RELEASE_TAG} container_name: magistrala-coap depends_on: - - things + - clients - nats restart: on-failure environment: - MG_COAP_ADAPTER_LOG_LEVEL: ${MG_COAP_ADAPTER_LOG_LEVEL} - MG_COAP_ADAPTER_HOST: ${MG_COAP_ADAPTER_HOST} - MG_COAP_ADAPTER_PORT: ${MG_COAP_ADAPTER_PORT} - MG_COAP_ADAPTER_SERVER_CERT: ${MG_COAP_ADAPTER_SERVER_CERT} - MG_COAP_ADAPTER_SERVER_KEY: ${MG_COAP_ADAPTER_SERVER_KEY} - MG_COAP_ADAPTER_HTTP_HOST: ${MG_COAP_ADAPTER_HTTP_HOST} - 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_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} - MG_JAEGER_URL: ${MG_JAEGER_URL} - MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_COAP_ADAPTER_INSTANCE_ID: ${MG_COAP_ADAPTER_INSTANCE_ID} + SMQ_COAP_ADAPTER_LOG_LEVEL: ${SMQ_COAP_ADAPTER_LOG_LEVEL} + SMQ_COAP_ADAPTER_HOST: ${SMQ_COAP_ADAPTER_HOST} + SMQ_COAP_ADAPTER_PORT: ${SMQ_COAP_ADAPTER_PORT} + SMQ_COAP_ADAPTER_SERVER_CERT: ${SMQ_COAP_ADAPTER_SERVER_CERT} + SMQ_COAP_ADAPTER_SERVER_KEY: ${SMQ_COAP_ADAPTER_SERVER_KEY} + SMQ_COAP_ADAPTER_HTTP_HOST: ${SMQ_COAP_ADAPTER_HTTP_HOST} + SMQ_COAP_ADAPTER_HTTP_PORT: ${SMQ_COAP_ADAPTER_HTTP_PORT} + SMQ_COAP_ADAPTER_HTTP_SERVER_CERT: ${SMQ_COAP_ADAPTER_HTTP_SERVER_CERT} + SMQ_COAP_ADAPTER_HTTP_SERVER_KEY: ${SMQ_COAP_ADAPTER_HTTP_SERVER_KEY} + SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} + SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_CHANNELS_GRPC_URL: ${SMQ_CHANNELS_GRPC_URL} + SMQ_CHANNELS_GRPC_TIMEOUT: ${SMQ_CHANNELS_GRPC_TIMEOUT} + SMQ_CHANNELS_GRPC_CLIENT_CERT: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + SMQ_CHANNELS_GRPC_CLIENT_KEY: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + SMQ_CHANNELS_GRPC_SERVER_CA_CERTS: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_COAP_ADAPTER_INSTANCE_ID: ${SMQ_COAP_ADAPTER_INSTANCE_ID} ports: - - ${MG_COAP_ADAPTER_PORT}:${MG_COAP_ADAPTER_PORT}/udp - - ${MG_COAP_ADAPTER_HTTP_PORT}:${MG_COAP_ADAPTER_HTTP_PORT}/tcp + - ${SMQ_COAP_ADAPTER_PORT}:${SMQ_COAP_ADAPTER_PORT}/udp + - ${SMQ_COAP_ADAPTER_HTTP_PORT}:${SMQ_COAP_ADAPTER_HTTP_PORT}/tcp networks: - magistrala-base-net volumes: - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${SMQ_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: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${SMQ_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: ${SMQ_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${SMQ_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: ${SMQ_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${SMQ_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${SMQ_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true ws-adapter: - image: magistrala/ws:${MG_RELEASE_TAG} + image: supermq/ws:${SMQ_RELEASE_TAG} container_name: magistrala-ws depends_on: - - things + - clients - nats restart: on-failure environment: - MG_WS_ADAPTER_LOG_LEVEL: ${MG_WS_ADAPTER_LOG_LEVEL} - MG_WS_ADAPTER_HTTP_HOST: ${MG_WS_ADAPTER_HTTP_HOST} - 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_MESSAGE_BROKER_URL: ${MG_MESSAGE_BROKER_URL} - MG_JAEGER_URL: ${MG_JAEGER_URL} - MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_WS_ADAPTER_INSTANCE_ID: ${MG_WS_ADAPTER_INSTANCE_ID} + SMQ_WS_ADAPTER_LOG_LEVEL: ${SMQ_WS_ADAPTER_LOG_LEVEL} + SMQ_WS_ADAPTER_HTTP_HOST: ${SMQ_WS_ADAPTER_HTTP_HOST} + SMQ_WS_ADAPTER_HTTP_PORT: ${SMQ_WS_ADAPTER_HTTP_PORT} + SMQ_WS_ADAPTER_HTTP_SERVER_CERT: ${SMQ_WS_ADAPTER_HTTP_SERVER_CERT} + SMQ_WS_ADAPTER_HTTP_SERVER_KEY: ${SMQ_WS_ADAPTER_HTTP_SERVER_KEY} + SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} + SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_CHANNELS_GRPC_URL: ${SMQ_CHANNELS_GRPC_URL} + SMQ_CHANNELS_GRPC_TIMEOUT: ${SMQ_CHANNELS_GRPC_TIMEOUT} + SMQ_CHANNELS_GRPC_CLIENT_CERT: ${SMQ_CHANNELS_GRPC_CLIENT_CERT:+/channels-grpc-client.crt} + SMQ_CHANNELS_GRPC_CLIENT_KEY: ${SMQ_CHANNELS_GRPC_CLIENT_KEY:+/channels-grpc-client.key} + SMQ_CHANNELS_GRPC_SERVER_CA_CERTS: ${SMQ_CHANNELS_GRPC_SERVER_CA_CERTS:+/channels-grpc-server-ca.crt} + SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL} + SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT} + SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} + SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} + SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + SMQ_WS_ADAPTER_INSTANCE_ID: ${SMQ_WS_ADAPTER_INSTANCE_ID} ports: - - ${MG_WS_ADAPTER_HTTP_PORT}:${MG_WS_ADAPTER_HTTP_PORT} + - ${SMQ_WS_ADAPTER_HTTP_PORT}:${SMQ_WS_ADAPTER_HTTP_PORT} networks: - magistrala-base-net volumes: - # Things gRPC mTLS client certificates + # Clients gRPC mTLS client certificates + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Channels gRPC mTLS client certificates + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /channels-grpc-client${SMQ_CHANNELS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /channels-grpc-client${SMQ_CHANNELS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /channels-grpc-server-ca${SMQ_CHANNELS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + # Auth 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: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /auth-grpc-client${SMQ_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: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /auth-grpc-client${SMQ_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: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt} bind: create_host_path: true vernemq: - image: magistrala/vernemq:${MG_RELEASE_TAG} + image: supermq/vernemq:${SMQ_RELEASE_TAG} container_name: magistrala-vernemq restart: on-failure environment: - DOCKER_VERNEMQ_ALLOW_ANONYMOUS: ${MG_DOCKER_VERNEMQ_ALLOW_ANONYMOUS} - DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL: ${MG_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL} + DOCKER_VERNEMQ_ALLOW_ANONYMOUS: ${SMQ_DOCKER_VERNEMQ_ALLOW_ANONYMOUS} + DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL: ${SMQ_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL} networks: - magistrala-base-net volumes: @@ -705,12 +1202,12 @@ services: restart: on-failure command: "--config=/etc/nats/nats.conf" environment: - - MG_NATS_PORT=${MG_NATS_PORT} - - MG_NATS_HTTP_PORT=${MG_NATS_HTTP_PORT} - - MG_NATS_JETSTREAM_KEY=${MG_NATS_JETSTREAM_KEY} + - SMQ_NATS_PORT=${SMQ_NATS_PORT} + - SMQ_NATS_HTTP_PORT=${SMQ_NATS_HTTP_PORT} + - SMQ_NATS_JETSTREAM_KEY=${SMQ_NATS_JETSTREAM_KEY} ports: - - ${MG_NATS_PORT}:${MG_NATS_PORT} - - ${MG_NATS_HTTP_PORT}:${MG_NATS_HTTP_PORT} + - ${SMQ_NATS_PORT}:${SMQ_NATS_PORT} + - ${SMQ_NATS_HTTP_PORT}:${SMQ_NATS_HTTP_PORT} volumes: - magistrala-broker-volume:/data - ./nats:/etc/nats @@ -718,41 +1215,41 @@ services: - magistrala-base-net ui: - image: magistrala/ui:${MG_RELEASE_TAG} + image: magistrala/ui:${SMQ_RELEASE_TAG} container_name: magistrala-ui restart: on-failure environment: - MG_UI_LOG_LEVEL: ${MG_UI_LOG_LEVEL} - 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_USERS_URL: ${MG_USERS_URL} - MG_INVITATIONS_URL: ${MG_INVITATIONS_URL} - MG_DOMAINS_URL: ${MG_DOMAINS_URL} - MG_BOOTSTRAP_URL: ${MG_BOOTSTRAP_URL} - MG_UI_HOST_URL: ${MG_UI_HOST_URL} - MG_UI_VERIFICATION_TLS: ${MG_UI_VERIFICATION_TLS} - MG_UI_CONTENT_TYPE: ${MG_UI_CONTENT_TYPE} - MG_UI_INSTANCE_ID: ${MG_UI_INSTANCE_ID} - MG_UI_DB_HOST: ${MG_UI_DB_HOST} - MG_UI_DB_PORT: ${MG_UI_DB_PORT} - MG_UI_DB_USER: ${MG_UI_DB_USER} - MG_UI_DB_PASS: ${MG_UI_DB_PASS} - MG_UI_DB_NAME: ${MG_UI_DB_NAME} - MG_UI_DB_SSL_MODE: ${MG_UI_DB_SSL_MODE} - MG_UI_DB_SSL_CERT: ${MG_UI_DB_SSL_CERT} - MG_UI_DB_SSL_KEY: ${MG_UI_DB_SSL_KEY} - MG_UI_DB_SSL_ROOT_CERT: ${MG_UI_DB_SSL_ROOT_CERT} - MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} - MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} - MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} - MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} - MG_UI_HASH_KEY: ${MG_UI_HASH_KEY} - MG_UI_BLOCK_KEY: ${MG_UI_BLOCK_KEY} - MG_UI_PATH_PREFIX: ${MG_UI_PATH_PREFIX} + SMQ_UI_LOG_LEVEL: ${SMQ_UI_LOG_LEVEL} + SMQ_UI_PORT: ${SMQ_UI_PORT} + SMQ_HTTP_ADAPTER_URL: ${SMQ_HTTP_ADAPTER_URL} + SMQ_READER_URL: ${SMQ_READER_URL} + SMQ_CLIENTS_URL: ${SMQ_CLIENTS_URL} + SMQ_USERS_URL: ${SMQ_USERS_URL} + SMQ_INVITATIONS_URL: ${SMQ_INVITATIONS_URL} + SMQ_DOMAINS_URL: ${SMQ_DOMAINS_URL} + SMQ_BOOTSTRAP_URL: ${SMQ_BOOTSTRAP_URL} + SMQ_UI_HOST_URL: ${SMQ_UI_HOST_URL} + SMQ_UI_VERIFICATION_TLS: ${SMQ_UI_VERIFICATION_TLS} + SMQ_UI_CONTENT_TYPE: ${SMQ_UI_CONTENT_TYPE} + SMQ_UI_INSTANCE_ID: ${SMQ_UI_INSTANCE_ID} + SMQ_UI_DB_HOST: ${SMQ_UI_DB_HOST} + SMQ_UI_DB_PORT: ${SMQ_UI_DB_PORT} + SMQ_UI_DB_USER: ${SMQ_UI_DB_USER} + SMQ_UI_DB_PASS: ${SMQ_UI_DB_PASS} + SMQ_UI_DB_NAME: ${SMQ_UI_DB_NAME} + SMQ_UI_DB_SSL_MODE: ${SMQ_UI_DB_SSL_MODE} + SMQ_UI_DB_SSL_CERT: ${SMQ_UI_DB_SSL_CERT} + SMQ_UI_DB_SSL_KEY: ${SMQ_UI_DB_SSL_KEY} + SMQ_UI_DB_SSL_ROOT_CERT: ${SMQ_UI_DB_SSL_ROOT_CERT} + SMQ_GOOGLE_CLIENT_ID: ${SMQ_GOOGLE_CLIENT_ID} + SMQ_GOOGLE_CLIENT_SECRET: ${SMQ_GOOGLE_CLIENT_SECRET} + SMQ_GOOGLE_REDIRECT_URL: ${SMQ_GOOGLE_REDIRECT_URL} + SMQ_GOOGLE_STATE: ${SMQ_GOOGLE_STATE} + SMQ_UI_HASH_KEY: ${SMQ_UI_HASH_KEY} + SMQ_UI_BLOCK_KEY: ${SMQ_UI_BLOCK_KEY} + SMQ_UI_PATH_PREFIX: ${SMQ_UI_PATH_PREFIX} ports: - - ${MG_UI_PORT}:${MG_UI_PORT} + - ${SMQ_UI_PORT}:${SMQ_UI_PORT} networks: - magistrala-base-net @@ -760,12 +1257,12 @@ services: image: postgres:16.2-alpine container_name: magistrala-ui-db restart: on-failure - command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" + command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}" environment: - POSTGRES_USER: ${MG_UI_DB_USER} - POSTGRES_PASSWORD: ${MG_UI_DB_PASS} - POSTGRES_DB: ${MG_UI_DB_NAME} - MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} + POSTGRES_USER: ${SMQ_UI_DB_USER} + POSTGRES_PASSWORD: ${SMQ_UI_DB_PASS} + POSTGRES_DB: ${SMQ_UI_DB_NAME} + SMQ_POSTGRES_MAX_CONNECTIONS: ${SMQ_POSTGRES_MAX_CONNECTIONS} ports: - 6007:5432 networks: diff --git a/docker/nats/nats.conf b/docker/nats/nats.conf index 688a58d29..db380989b 100644 --- a/docker/nats/nats.conf +++ b/docker/nats/nats.conf @@ -4,14 +4,14 @@ server_name: "nats_internal_broker" max_payload: 1MB max_connections: 1M -port: $MG_NATS_PORT -http_port: $MG_NATS_HTTP_PORT +port: $SMQ_NATS_PORT +http_port: $SMQ_NATS_HTTP_PORT trace: true jetstream { store_dir: "/data" cipher: "aes" - key: $MG_NATS_JETSTREAM_KEY + key: $SMQ_NATS_JETSTREAM_KEY max_mem: 1G } diff --git a/docker/spicedb/schema.zed b/docker/spicedb/schema.zed index 215797a99..c52099d1d 100644 --- a/docker/spicedb/schema.zed +++ b/docker/spicedb/schema.zed @@ -1,74 +1,510 @@ 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 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 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 membership = read + update + enable + disable + delete + manage_role + add_role_users + remove_role_users + view_role_users + permission admin = read & update & enable & disable & delete & manage_role & add_role_users & remove_role_users & view_role_users + + 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/go.mod b/go.mod index a2ba581d6..5c8f5e8a2 100644 --- a/go.mod +++ b/go.mod @@ -1,158 +1,114 @@ module github.com/absmach/magistrala -go 1.23.0 - -toolchain go1.23.1 +go 1.23.4 require ( github.com/0x6flab/namegenerator v1.4.0 github.com/absmach/callhome v0.14.0 - github.com/absmach/certs v0.0.0-20241014135535-3f118b801054 - github.com/absmach/mgate v0.4.5 - github.com/absmach/senml v1.0.6 + github.com/absmach/supermq v0.16.1-0.20241227183413-f12aacd1da31 github.com/authzed/authzed-go v1.2.0 github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b - github.com/caarlos0/env/v11 v11.2.2 - github.com/cenkalti/backoff/v4 v4.3.0 + github.com/caarlos0/env/v11 v11.3.1 github.com/eclipse/paho.mqtt.golang v1.5.0 - github.com/fatih/color v1.18.0 github.com/go-chi/chi v1.5.5 - github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/chi/v5 v5.2.0 github.com/go-kit/kit v0.13.0 github.com/gofrs/uuid/v5 v5.3.0 github.com/gookit/color v1.5.4 github.com/gorilla/websocket v1.5.3 - github.com/hashicorp/vault/api v1.15.0 - github.com/hashicorp/vault/api/auth/approle v0.8.0 - github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f github.com/ivanpirog/coloredcobra v1.0.1 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 github.com/jackc/pgtype v1.14.4 - github.com/jackc/pgx/v5 v5.7.1 + github.com/jackc/pgx/v5 v5.7.2 github.com/jmoiron/sqlx v1.4.0 - github.com/lestrrat-go/jwx/v2 v2.1.3 - github.com/mitchellh/mapstructure v1.5.0 - github.com/nats-io/nats.go v1.37.0 - github.com/oklog/ulid/v2 v2.1.0 github.com/ory/dockertest/v3 v3.11.0 github.com/pelletier/go-toml v1.9.5 - github.com/plgd-dev/go-coap/v3 v3.3.6 github.com/prometheus/client_golang v1.20.5 - github.com/rabbitmq/amqp091-go v1.10.0 github.com/redis/go-redis/v9 v9.7.0 - github.com/rubenv/sql-migrate v1.7.0 + github.com/rubenv/sql-migrate v1.7.1 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 github.com/yuin/gopher-lua v1.1.1 - 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/contrib/instrumentation/net/http/otelhttp v0.58.0 go.opentelemetry.io/otel v1.33.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 - go.opentelemetry.io/otel/sdk v1.33.0 go.opentelemetry.io/otel/trace v1.33.0 - golang.org/x/crypto v0.31.0 - golang.org/x/oauth2 v0.24.0 golang.org/x/sync v0.10.0 gonum.org/v1/gonum v0.15.1 - google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 google.golang.org/grpc v1.69.2 - google.golang.org/protobuf v1.35.2 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df - moul.io/http2curl v1.0.0 ) require ( - cloud.google.com/go/compute/metadata v0.5.2 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/absmach/certs v0.0.0-20241209153600-91270de67b5a // indirect + github.com/absmach/senml v1.0.6 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/continuity v0.4.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/cli v26.1.4+incompatible // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dsnet/golib/memfile v1.0.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect - github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v4 v4.18.3 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/jzelinskie/stringz v0.0.3 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/lestrrat-go/blackmagic v1.0.2 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc v1.0.6 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/option v1.0.1 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/magiconair/properties v1.8.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect + github.com/nats-io/nats.go v1.38.0 // indirect + github.com/nats-io/nkeys v0.4.9 // indirect github.com/nats-io/nuid v1.0.1 // indirect + github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.1.13 // indirect + github.com/opencontainers/runc v1.1.14 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/pion/dtls/v3 v3.0.2 // indirect - github.com/pion/logging v0.2.2 // indirect - github.com/pion/transport/v3 v3.0.7 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/common v0.61.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/rabbitmq/amqp091-go v1.10.0 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/samber/lo v1.47.0 // indirect - github.com/segmentio/asm v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smarty/assertions v1.15.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -162,18 +118,24 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 // indirect go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.33.0 // indirect go.opentelemetry.io/proto/otlp v1.4.0 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/net v0.32.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.6.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + moul.io/http2curl v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index de4c20b2e..4229f772f 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= @@ -19,12 +21,14 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/absmach/callhome v0.14.0 h1:zB4tIZJ1YUmZ1VGHFPfMA/Lo6/Mv19y2dvoOiXj2BWs= github.com/absmach/callhome v0.14.0/go.mod h1:l12UJOfibK4Muvg/AbupHuquNV9qSz/ROdTEPg7f2Vk= -github.com/absmach/certs v0.0.0-20241014135535-3f118b801054 h1:NsIwp+ueKxDx8XftruA4hz8WUgyWq7eBE344nJt0LJg= -github.com/absmach/certs v0.0.0-20241014135535-3f118b801054/go.mod h1:bEAb/HjPztlrMmz8dLeJTke4Tzu9yW3+hY5eldEUtSY= +github.com/absmach/certs v0.0.0-20241209153600-91270de67b5a h1:gYbkOHsyOALSoR9D+A7fhgqGChMfakhMxy+6y3AGPPE= +github.com/absmach/certs v0.0.0-20241209153600-91270de67b5a/go.mod h1:g6Kqge7RVxwt+LRxqt+09cqa2SgPAwXvIPoyPsEqZlQ= github.com/absmach/mgate v0.4.5 h1:l6RmrEsR9jxkdb9WHUSecmT0HA41TkZZQVffFfUAIfI= github.com/absmach/mgate v0.4.5/go.mod h1:IvRIHZexZPEIAPmmaJF0L5DY2ERjj+GxRGitOW4s6qo= github.com/absmach/senml v1.0.6 h1:WPeIl6vQ00k7ghWSZYT/QP0KUxq2+4zQoaC7240pLFk= github.com/absmach/senml v1.0.6/go.mod h1:QnJNPy1DJPy0+qUW21PTcH/xoh0LgfYZxTfwriMIvmQ= +github.com/absmach/supermq v0.16.1-0.20241227183413-f12aacd1da31 h1:o3fWtPh4VjOf/0Y5pJcUltKmKKjhvSaI3ShNN98M1C0= +github.com/absmach/supermq v0.16.1-0.20241227183413-f12aacd1da31/go.mod h1:pbDQgySAcpaxn2lXR1kuxKy1vvDDt1Fzo9RY38gRQDw= github.com/authzed/authzed-go v1.2.0 h1:Ep1sRJMxcArB++kYqHbYKQCb/GgdGZI0cW4gZrJ1K40= github.com/authzed/authzed-go v1.2.0/go.mod h1:4lkFxvaCISG1roRdnUt35/Sk1StVuMD1QCwTd/BcWcM= github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b h1:wbh8IK+aMLTCey9sZasO7b6BWLAJnHHvb79fvWCXwxw= @@ -36,8 +40,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg= -github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc= +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -73,8 +77,6 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dsnet/golib/memfile v1.0.0 h1:J9pUspY2bDCbF9o+YGwcf3uG6MdyITfh/Fk3/CaEiFs= -github.com/dsnet/golib/memfile v1.0.0/go.mod h1:tXGNW9q3RwvWt1VV2qrRKlSSz0npnh12yftCSCy2T64= github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o= github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -90,18 +92,16 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= +github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -118,8 +118,6 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -152,35 +150,10 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= -github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= -github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= -github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= -github.com/hashicorp/vault/api/auth/approle v0.8.0 h1:FuVtWZ0xD6+wz1x0l5s0b4852RmVXQNEiKhVXt6lfQY= -github.com/hashicorp/vault/api/auth/approle v0.8.0/go.mod h1:NV7O9r5JUtNdVnqVZeMHva81AIdpG0WoIQohNt1VCPM= -github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= -github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -236,8 +209,8 @@ github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgS github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -252,8 +225,8 @@ github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfU github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -284,8 +257,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -300,8 +273,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -310,10 +281,10 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= -github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= +github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw= +github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= +github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= @@ -322,8 +293,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.13 h1:98S2srgG9vw0zWcDpFMn5TRrh8kLxa/5OFUstuUhmRs= -github.com/opencontainers/runc v1.1.13/go.mod h1:R016aXacfp/gwQBYw2FDGa9m+n6atbLWrYY8hNMT/sA= +github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= +github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= @@ -332,19 +303,11 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pion/dtls/v3 v3.0.2 h1:425DEeJ/jfuTTghhUDW0GtYZYIwwMtnKKJNMcWccTX0= -github.com/pion/dtls/v3 v3.0.2/go.mod h1:dfIXcFkKoujDQ+jtd8M6RgqKK3DuaUilm3YatAbGp5k= -github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= -github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= -github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/plgd-dev/go-coap/v3 v3.3.6 h1:8F7Y+ZYcFsvz2nBaphdYYd0cLdRNpjqCzjQjxGdGKFY= -github.com/plgd-dev/go-coap/v3 v3.3.6/go.mod h1:Cs6sfxmF/b8ktTVfPMf6FzihFx+0mEZ/ClbFNUnnsZw= +github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY= +github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -355,8 +318,8 @@ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/j github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= +github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= @@ -369,11 +332,9 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlXpTI= -github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= +github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4= +github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -397,8 +358,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= @@ -406,6 +367,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= @@ -417,7 +380,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= @@ -445,10 +407,10 @@ github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 h1:qtFISDHKolvIxzSs0gIaiPUPR0Cucb0F2coHC7ZLdps= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0/go.mod h1:Y+Pop1Q6hCOnETWTW4NROK/q1hv50hM7yDaUTjG8lp8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= @@ -470,8 +432,6 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -501,8 +461,8 @@ golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZP golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -527,8 +487,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -585,8 +545,6 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -617,10 +575,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def h1:0Km0hi+g2KXbXL0+riZzSCKz23f4MmwicuEb00JeonI= +google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def/go.mod h1:u2DoMSpCXjrzqLdobRccQMc9wrnMAJ1DLng0a2yqM2Q= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def h1:4P81qv5JXI/sDNae2ClVx88cgDDA6DPilADkG9tYKz8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def/go.mod h1:bdAgzvd4kFrpykc5/AC2eLUiegK9T/qxZHD4hXYf/ho= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -628,8 +586,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/health.go b/health.go deleted file mode 100644 index 833a3c0b4..000000000 --- a/health.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package magistrala - -import ( - "encoding/json" - "net/http" -) - -const ( - contentType = "Content-Type" - contentTypeJSON = "application/health+json" - svcStatus = "pass" - description = " service" -) - -var ( - // Version represents the last service git tag in git history. - // It's meant to be set using go build ldflags: - // -ldflags "-X 'github.com/absmach/magistrala.Version=0.0.0'". - Version = "0.0.0" - // Commit represents the service git commit hash. - // It's meant to be set using go build ldflags: - // -ldflags "-X 'github.com/absmach/magistrala.Commit=ffffffff'". - Commit = "ffffffff" - // BuildTime represetns the service build time. - // It's meant to be set using go build ldflags: - // -ldflags "-X 'github.com/absmach/magistrala.BuildTime=1970-01-01_00:00:00'". - BuildTime = "1970-01-01_00:00:00" -) - -// HealthInfo contains version endpoint response. -type HealthInfo struct { - // Status contains service status. - Status string `json:"status"` - - // Version contains current service version. - Version string `json:"version"` - - // Commit represents the git hash commit. - Commit string `json:"commit"` - - // Description contains service description. - Description string `json:"description"` - - // BuildTime contains service build time. - BuildTime string `json:"build_time"` - - // InstanceID contains the ID of the current service instance - InstanceID string `json:"instance_id"` -} - -// Health exposes an HTTP handler for retrieving service health. -func Health(service, instanceID string) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add(contentType, contentTypeJSON) - if r.Method != http.MethodGet && r.Method != http.MethodHead { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - res := HealthInfo{ - Status: svcStatus, - Version: Version, - Commit: Commit, - Description: service + description, - BuildTime: BuildTime, - InstanceID: instanceID, - } - - w.WriteHeader(http.StatusOK) - - if err := json.NewEncoder(w).Encode(res); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - }) -} diff --git a/http/README.md b/http/README.md deleted file mode 100644 index 5aeaa7518..000000000 --- a/http/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# HTTP adapter - -HTTP adapter provides an HTTP API for sending messages through the platform. - -## 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_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 | "" | - -## 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. -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 http -make http - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_HTTP_ADAPTER_LOG_LEVEL=info \ -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_MESSAGE_BROKER_URL=nats://localhost:4222 \ -MG_JAEGER_URL=http://localhost:14268/api/traces \ -MG_JAEGER_TRACE_RATIO=1.0 \ -MG_SEND_TELEMETRY=true \ -MG_HTTP_ADAPTER_INSTANCE_ID="" \ -$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. - -## 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). diff --git a/http/api/doc.go b/http/api/doc.go deleted file mode 100644 index 2424852cc..000000000 --- a/http/api/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains API-related concerns: endpoint definitions, middlewares -// and all resource representations. -package api diff --git a/http/api/endpoint.go b/http/api/endpoint.go deleted file mode 100644 index 1808f03e5..000000000 --- a/http/api/endpoint.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/go-kit/kit/endpoint" -) - -func sendMessageEndpoint() endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(publishReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - return publishMessageRes{}, nil - } -} diff --git a/http/api/endpoint_test.go b/http/api/endpoint_test.go deleted file mode 100644 index b41f223f3..000000000 --- a/http/api/endpoint_test.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/absmach/magistrala" - server "github.com/absmach/magistrala/http" - "github.com/absmach/magistrala/http/api" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/apiutil" - 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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const ( - instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" - invalidValue = "invalid" -) - -func newService(things magistrala.ThingsServiceClient) (session.Handler, *pubsub.PubSub) { - pub := new(pubsub.PubSub) - return server.NewHandler(pub, mglog.NewMock(), things), pub -} - -func newTargetHTTPServer() *httptest.Server { - mux := api.MakeHandler(mglog.NewMock(), instanceID) - return httptest.NewServer(mux) -} - -func newProxyHTPPServer(svc session.Handler, targetServer *httptest.Server) (*httptest.Server, error) { - config := mgate.Config{ - Address: "", - Target: targetServer.URL, - } - mp, err := proxy.NewProxy(config, svc, mglog.NewMock()) - if err != nil { - return nil, err - } - return httptest.NewServer(http.HandlerFunc(mp.ServeHTTP)), nil -} - -type testRequest struct { - client *http.Client - method string - url string - contentType string - token string - body io.Reader - basicAuth bool -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, tr.body) - if err != nil { - return nil, err - } - - if tr.token != "" { - req.Header.Set("Authorization", apiutil.ThingPrefix+tr.token) - } - if tr.basicAuth && tr.token != "" { - req.SetBasicAuth("", tr.token) - } - if tr.contentType != "" { - req.Header.Set("Content-Type", tr.contentType) - } - return tr.client.Do(req) -} - -func TestPublish(t *testing.T) { - things := new(thmocks.ThingsServiceClient) - chanID := "1" - ctSenmlJSON := "application/senml+json" - ctSenmlCBOR := "application/senml+cbor" - ctJSON := "application/json" - thingKey := "thing_key" - invalidKey := invalidValue - msg := `[{"n":"current","t":-1,"v":1.6}]` - msgJSON := `{"field1":"val1","field2":"val2"}` - msgCBOR := `81A3616E6763757272656E746174206176FB3FF999999999999A` - svc, pub := newService(things) - target := newTargetHTTPServer() - defer target.Close() - ts, err := newProxyHTPPServer(svc, target) - assert.Nil(t, err, fmt.Sprintf("failed to create proxy server with err: %v", err)) - - 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 - contentType string - key string - status int - basicAuth bool - }{ - "publish message": { - chanID: chanID, - msg: msg, - contentType: ctSenmlJSON, - key: thingKey, - status: http.StatusAccepted, - }, - "publish message with application/senml+cbor content-type": { - chanID: chanID, - msg: msgCBOR, - contentType: ctSenmlCBOR, - key: thingKey, - status: http.StatusAccepted, - }, - "publish message with application/json content-type": { - chanID: chanID, - msg: msgJSON, - contentType: ctJSON, - key: thingKey, - status: http.StatusAccepted, - }, - "publish message with empty key": { - chanID: chanID, - msg: msg, - contentType: ctSenmlJSON, - key: "", - status: http.StatusBadGateway, - }, - "publish message with basic auth": { - chanID: chanID, - msg: msg, - contentType: ctSenmlJSON, - key: thingKey, - basicAuth: true, - status: http.StatusAccepted, - }, - "publish message with invalid key": { - chanID: chanID, - msg: msg, - contentType: ctSenmlJSON, - key: invalidKey, - status: http.StatusUnauthorized, - }, - "publish message with invalid basic auth": { - chanID: chanID, - msg: msg, - contentType: ctSenmlJSON, - key: invalidKey, - basicAuth: true, - status: http.StatusUnauthorized, - }, - "publish message without content type": { - chanID: chanID, - msg: msg, - contentType: "", - key: thingKey, - status: http.StatusUnsupportedMediaType, - }, - "publish message to invalid channel": { - chanID: "", - msg: msg, - contentType: ctSenmlJSON, - key: thingKey, - status: http.StatusBadRequest, - }, - } - - for desc, tc := range cases { - t.Run(desc, func(t *testing.T) { - svcCall := pub.On("Publish", mock.Anything, tc.chanID, mock.Anything).Return(nil) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/channels/%s/messages", ts.URL, tc.chanID), - contentType: tc.contentType, - token: tc.key, - body: strings.NewReader(tc.msg), - basicAuth: tc.basicAuth, - } - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", desc, tc.status, res.StatusCode)) - svcCall.Unset() - }) - } -} diff --git a/http/api/request.go b/http/api/request.go deleted file mode 100644 index b4e3df88f..000000000 --- a/http/api/request.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/messaging" -) - -type publishReq struct { - msg *messaging.Message - token string -} - -func (req publishReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerKey - } - if len(req.msg.Payload) == 0 { - return apiutil.ErrEmptyMessage - } - - return nil -} diff --git a/http/api/response.go b/http/api/response.go deleted file mode 100644 index 5b43c92d1..000000000 --- a/http/api/response.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "net/http" - - "github.com/absmach/magistrala" -) - -var _ magistrala.Response = (*publishMessageRes)(nil) - -type publishMessageRes struct{} - -func (res publishMessageRes) Code() int { - return http.StatusAccepted -} - -func (res publishMessageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res publishMessageRes) Empty() bool { - return true -} diff --git a/http/api/transport.go b/http/api/transport.go deleted file mode 100644 index 52ed24208..000000000 --- a/http/api/transport.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "io" - "log/slog" - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/messaging" - "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" -) - -const ( - ctSenmlJSON = "application/senml+json" - ctSenmlCBOR = "application/senml+cbor" - contentType = "application/json" -) - -// MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(logger *slog.Logger, instanceID string) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - r := chi.NewRouter() - r.Post("/channels/{chanID}/messages", otelhttp.NewHandler(kithttp.NewServer( - sendMessageEndpoint(), - decodeRequest, - api.EncodeResponse, - opts..., - ), "publish").ServeHTTP) - - r.Post("/channels/{chanID}/messages/*", otelhttp.NewHandler(kithttp.NewServer( - sendMessageEndpoint(), - decodeRequest, - api.EncodeResponse, - opts..., - ), "publish").ServeHTTP) - r.Get("/health", magistrala.Health("http", instanceID)) - r.Handle("/metrics", promhttp.Handler()) - - return r -} - -func decodeRequest(_ context.Context, r *http.Request) (interface{}, error) { - ct := r.Header.Get("Content-Type") - if ct != ctSenmlJSON && ct != contentType && ct != ctSenmlCBOR { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - var req publishReq - _, pass, ok := r.BasicAuth() - switch { - case ok: - req.token = pass - case !ok: - req.token = apiutil.ExtractThingKey(r) - } - - payload, err := io.ReadAll(r.Body) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity) - } - defer r.Body.Close() - - req.msg = &messaging.Message{Payload: payload} - - return req, nil -} diff --git a/http/doc.go b/http/doc.go deleted file mode 100644 index a7348a004..000000000 --- a/http/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package http contains the domain concept definitions needed to support -// Magistrala HTTP Adapter functionality. -package http diff --git a/http/handler.go b/http/handler.go deleted file mode 100644 index f81059c52..000000000 --- a/http/handler.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "context" - "fmt" - "log/slog" - "net/http" - "net/url" - "regexp" - "strings" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/apiutil" - "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" - mgate "github.com/absmach/mgate/pkg/http" - "github.com/absmach/mgate/pkg/session" -) - -var _ session.Handler = (*handler)(nil) - -const protocol = "http" - -// Log message formats. -const ( - logInfoConnected = "connected with thing_key %s" - logInfoPublished = "published with client_id %s to the topic %s" -) - -// Error wrappers for MQTT errors. -var ( - errClientNotInitialized = errors.New("client is not initialized") - errFailedPublish = errors.New("failed to publish") - errFailedPublishToMsgBroker = errors.New("failed to publish to magistrala message broker") - errMalformedSubtopic = mgate.NewHTTPProxyError(http.StatusBadRequest, errors.New("malformed subtopic")) - errMalformedTopic = mgate.NewHTTPProxyError(http.StatusBadRequest, errors.New("malformed topic")) - errMissingTopicPub = mgate.NewHTTPProxyError(http.StatusBadRequest, errors.New("failed to publish due to missing topic")) - errFailedParseSubtopic = mgate.NewHTTPProxyError(http.StatusBadRequest, errors.New("failed to parse subtopic")) -) - -var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?]*)?(\?.*)?$`) - -// Event implements events.Event interface. -type handler struct { - publisher messaging.Publisher - things magistrala.ThingsServiceClient - logger *slog.Logger -} - -// NewHandler creates new Handler entity. -func NewHandler(publisher messaging.Publisher, logger *slog.Logger, thingsClient magistrala.ThingsServiceClient) session.Handler { - return &handler{ - logger: logger, - publisher: publisher, - things: thingsClient, - } -} - -// AuthConnect is called on device connection, -// prior forwarding to the HTTP server. -func (h *handler) AuthConnect(ctx context.Context) error { - s, ok := session.FromContext(ctx) - if !ok { - return errClientNotInitialized - } - - var tok string - 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) - default: - tok = string(s.Password) - } - - h.logger.Info(fmt.Sprintf(logInfoConnected, tok)) - return nil -} - -// AuthPublish is not used in HTTP service. -func (h *handler) AuthPublish(ctx context.Context, topic *string, payload *[]byte) error { - return nil -} - -// AuthSubscribe is not used in HTTP service. -func (h *handler) AuthSubscribe(ctx context.Context, topics *[]string) error { - return nil -} - -// Connect - after client successfully connected. -func (h *handler) Connect(ctx context.Context) error { - return nil -} - -// Publish - after client successfully published. -func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) error { - if topic == nil { - return errMissingTopicPub - } - topic = &strings.Split(*topic, "?")[0] - s, ok := session.FromContext(ctx) - 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)) - } - - chanID := channelParts[1] - subtopic := channelParts[2] - - subtopic, err := parseSubtopic(subtopic) - if err != nil { - return mgate.NewHTTPProxyError(http.StatusBadRequest, errors.Wrap(errFailedParseSubtopic, err)) - } - - msg := messaging.Message{ - Protocol: protocol, - Channel: chanID, - Subtopic: subtopic, - 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, - ChannelId: msg.Channel, - Permission: policies.PublishPermission, - } - res, err := h.things.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 err := h.publisher.Publish(ctx, msg.Channel, &msg); err != nil { - return errors.Wrap(errFailedPublishToMsgBroker, err) - } - - return nil -} - -// Subscribe - not used for HTTP. -func (h *handler) Subscribe(ctx context.Context, topics *[]string) error { - return nil -} - -// Unsubscribe - not used for HTTP. -func (h *handler) Unsubscribe(ctx context.Context, topics *[]string) error { - return nil -} - -// Disconnect - not used for HTTP. -func (h *handler) Disconnect(ctx context.Context) error { - return nil -} - -func parseSubtopic(subtopic string) (string, error) { - if subtopic == "" { - return subtopic, nil - } - - subtopic, err := url.QueryUnescape(subtopic) - if err != nil { - return "", mgate.NewHTTPProxyError(http.StatusBadRequest, errMalformedSubtopic) - } - subtopic = strings.ReplaceAll(subtopic, "/", ".") - - elems := strings.Split(subtopic, ".") - filteredElems := []string{} - for _, elem := range elems { - if elem == "" { - continue - } - - if len(elem) > 1 && (strings.Contains(elem, "*") || strings.Contains(elem, ">")) { - return "", mgate.NewHTTPProxyError(http.StatusBadRequest, errMalformedSubtopic) - } - - filteredElems = append(filteredElems, elem) - } - - subtopic = strings.Join(filteredElems, ".") - return subtopic, nil -} diff --git a/internal/api/auth.go b/internal/api/auth.go deleted file mode 100644 index 7831c4287..000000000 --- a/internal/api/auth.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "net/http" - - "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/go-chi/chi/v5" -) - -type sessionKeyType string - -const SessionKey = sessionKeyType("session") - -func AuthenticateMiddleware(authn mgauthn.Authentication, domainCheck bool) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - token := apiutil.ExtractBearerToken(r) - if token == "" { - EncodeError(r.Context(), apiutil.ErrBearerToken, w) - return - } - - resp, err := authn.Authenticate(r.Context(), token) - if err != nil { - EncodeError(r.Context(), err, w) - return - } - - if domainCheck { - domain := chi.URLParam(r, "domainID") - if domain == "" { - EncodeError(r.Context(), apiutil.ErrMissingDomainID, w) - return - } - resp.DomainID = domain - resp.DomainUserID = domain + "_" + resp.UserID - } - - ctx := context.WithValue(r.Context(), SessionKey, resp) - - next.ServeHTTP(w, r.WithContext(ctx)) - }) - } -} diff --git a/internal/api/common.go b/internal/api/common.go deleted file mode 100644 index 7c61ed26e..000000000 --- a/internal/api/common.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/certs" - "github.com/absmach/magistrala/internal/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" -) - -const ( - MemberKindKey = "member_kind" - PermissionKey = "permission" - RelationKey = "relation" - StatusKey = "status" - OffsetKey = "offset" - OrderKey = "order" - LimitKey = "limit" - MetadataKey = "metadata" - ParentKey = "parent_id" - OwnerKey = "owner_id" - ClientKey = "client" - UsernameKey = "username" - NameKey = "name" - GroupKey = "group" - ActionKey = "action" - TagKey = "tag" - FirstNameKey = "first_name" - LastNameKey = "last_name" - TotalKey = "total" - SubjectKey = "subject" - ObjectKey = "object" - LevelKey = "level" - TreeKey = "tree" - DirKey = "dir" - ListPerms = "list_perms" - VisibilityKey = "visibility" - EmailKey = "email" - SharedByKey = "shared_by" - TokenKey = "token" - DefPermission = "view" - DefTotal = uint64(100) - DefOffset = 0 - DefOrder = "updated_at" - DefDir = "asc" - DefLimit = 10 - DefLevel = 0 - DefStatus = "enabled" - DefClientStatus = things.Enabled - DefUserStatus = users.Enabled - DefGroupStatus = groups.Enabled - DefListPerms = false - SharedVisibility = "shared" - MyVisibility = "mine" - AllVisibility = "all" - // ContentType represents JSON content type. - ContentType = "application/json" - - // MaxNameSize limits name size to prevent making them too complex. - MaxLimitSize = 100 - MaxNameSize = 1024 - NameOrder = "name" - IDOrder = "id" - AscDir = "asc" - DescDir = "desc" -) - -// ValidateUUID validates UUID format. -func ValidateUUID(extID string) (err error) { - id, err := uuid.FromString(extID) - if id.String() != extID || err != nil { - return apiutil.ErrInvalidIDFormat - } - - return nil -} - -// EncodeResponse encodes successful response. -func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { - if ar, ok := response.(magistrala.Response); ok { - for k, v := range ar.Headers() { - w.Header().Set(k, v) - } - w.Header().Set("Content-Type", ContentType) - w.WriteHeader(ar.Code()) - - if ar.Empty() { - return nil - } - } - - return json.NewEncoder(w).Encode(response) -} - -// EncodeError encodes an error response. -func EncodeError(_ context.Context, err error, w http.ResponseWriter) { - var wrapper error - if errors.Contains(err, apiutil.ErrValidation) { - wrapper, err = errors.Unwrap(err) - } - - w.Header().Set("Content-Type", ContentType) - switch { - case errors.Contains(err, svcerr.ErrAuthorization), - errors.Contains(err, svcerr.ErrDomainAuthorization), - errors.Contains(err, bootstrap.ErrExternalKey), - errors.Contains(err, bootstrap.ErrExternalKeySecure): - err = unwrap(err) - w.WriteHeader(http.StatusForbidden) - - case errors.Contains(err, svcerr.ErrAuthentication), - errors.Contains(err, apiutil.ErrBearerToken), - errors.Contains(err, svcerr.ErrLogin): - err = unwrap(err) - w.WriteHeader(http.StatusUnauthorized) - case errors.Contains(err, svcerr.ErrMalformedEntity), - errors.Contains(err, apiutil.ErrMalformedPolicy), - errors.Contains(err, apiutil.ErrMissingSecret), - errors.Contains(err, errors.ErrMalformedEntity), - errors.Contains(err, apiutil.ErrMissingID), - errors.Contains(err, apiutil.ErrMissingName), - errors.Contains(err, apiutil.ErrMissingAlias), - errors.Contains(err, apiutil.ErrMissingEmail), - errors.Contains(err, apiutil.ErrInvalidEmail), - errors.Contains(err, apiutil.ErrMissingHost), - errors.Contains(err, apiutil.ErrInvalidResetPass), - errors.Contains(err, apiutil.ErrEmptyList), - errors.Contains(err, apiutil.ErrMissingMemberKind), - errors.Contains(err, apiutil.ErrMissingMemberType), - errors.Contains(err, apiutil.ErrLimitSize), - errors.Contains(err, apiutil.ErrBearerKey), - errors.Contains(err, svcerr.ErrInvalidStatus), - errors.Contains(err, apiutil.ErrNameSize), - errors.Contains(err, apiutil.ErrInvalidIDFormat), - errors.Contains(err, apiutil.ErrInvalidQueryParams), - errors.Contains(err, apiutil.ErrMissingRelation), - errors.Contains(err, apiutil.ErrValidation), - errors.Contains(err, apiutil.ErrMissingPass), - errors.Contains(err, apiutil.ErrMissingConfPass), - errors.Contains(err, apiutil.ErrPasswordFormat), - errors.Contains(err, svcerr.ErrInvalidRole), - errors.Contains(err, svcerr.ErrInvalidPolicy), - errors.Contains(err, apiutil.ErrInvitationState), - errors.Contains(err, apiutil.ErrInvalidAPIKey), - errors.Contains(err, svcerr.ErrViewEntity), - errors.Contains(err, apiutil.ErrBootstrapState), - errors.Contains(err, apiutil.ErrMissingCertData), - errors.Contains(err, apiutil.ErrInvalidContact), - errors.Contains(err, apiutil.ErrInvalidTopic), - errors.Contains(err, bootstrap.ErrAddBootstrap), - errors.Contains(err, apiutil.ErrInvalidCertData), - errors.Contains(err, apiutil.ErrEmptyMessage), - errors.Contains(err, apiutil.ErrInvalidLevel), - errors.Contains(err, apiutil.ErrInvalidDirection), - errors.Contains(err, apiutil.ErrInvalidEntityType), - errors.Contains(err, apiutil.ErrMissingEntityType), - errors.Contains(err, apiutil.ErrInvalidTimeFormat), - errors.Contains(err, svcerr.ErrSearch), - errors.Contains(err, apiutil.ErrEmptySearchQuery), - errors.Contains(err, apiutil.ErrLenSearchQuery), - errors.Contains(err, apiutil.ErrMissingDomainID), - errors.Contains(err, certs.ErrFailedReadFromPKI), - errors.Contains(err, apiutil.ErrMissingUsername), - errors.Contains(err, apiutil.ErrMissingFirstName), - errors.Contains(err, apiutil.ErrMissingLastName), - errors.Contains(err, apiutil.ErrInvalidUsername), - errors.Contains(err, apiutil.ErrMissingIdentity), - errors.Contains(err, apiutil.ErrInvalidProfilePictureURL): - err = unwrap(err) - w.WriteHeader(http.StatusBadRequest) - - case errors.Contains(err, svcerr.ErrCreateEntity), - errors.Contains(err, svcerr.ErrUpdateEntity), - errors.Contains(err, svcerr.ErrRemoveEntity), - errors.Contains(err, svcerr.ErrEnableClient): - err = unwrap(err) - w.WriteHeader(http.StatusUnprocessableEntity) - - case errors.Contains(err, svcerr.ErrNotFound), - errors.Contains(err, bootstrap.ErrBootstrap): - err = unwrap(err) - w.WriteHeader(http.StatusNotFound) - - case errors.Contains(err, errors.ErrStatusAlreadyAssigned), - errors.Contains(err, svcerr.ErrInvitationAlreadyRejected), - errors.Contains(err, svcerr.ErrInvitationAlreadyAccepted), - errors.Contains(err, svcerr.ErrConflict): - err = unwrap(err) - w.WriteHeader(http.StatusConflict) - - case errors.Contains(err, apiutil.ErrUnsupportedContentType): - err = unwrap(err) - w.WriteHeader(http.StatusUnsupportedMediaType) - - default: - w.WriteHeader(http.StatusInternalServerError) - } - - if wrapper != nil { - err = errors.Wrap(wrapper, err) - } - - if errorVal, ok := err.(errors.Error); ok { - if err := json.NewEncoder(w).Encode(errorVal); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - } -} - -func unwrap(err error) error { - wrapper, err := errors.Unwrap(err) - if wrapper != nil { - return wrapper - } - return err -} diff --git a/internal/api/common_test.go b/internal/api/common_test.go deleted file mode 100644 index 15bd938dc..000000000 --- a/internal/api/common_test.go +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "context" - "encoding/json" - "net/http" - "testing" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/api" - "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" - "github.com/stretchr/testify/assert" -) - -var _ magistrala.Response = (*response)(nil) - -var validUUID = testsutil.GenerateUUID(&testing.T{}) - -type responseWriter struct { - body []byte - statusCode int - header http.Header -} - -func newResponseWriter() *responseWriter { - return &responseWriter{ - header: http.Header{}, - } -} - -func (w *responseWriter) Header() http.Header { - return w.header -} - -func (w *responseWriter) Write(b []byte) (int, error) { - w.body = b - return 0, nil -} - -func (w *responseWriter) WriteHeader(statusCode int) { - w.statusCode = statusCode -} - -func (w *responseWriter) StatusCode() int { - return w.statusCode -} - -func (w *responseWriter) Body() []byte { - return w.body -} - -type response struct { - code int - headers map[string]string - empty bool - - ID string `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` -} - -func (res response) Code() int { - return res.code -} - -func (res response) Headers() map[string]string { - return res.headers -} - -func (res response) Empty() bool { - return res.empty -} - -type body struct { - Error string `json:"error,omitempty"` - Message string `json:"message"` -} - -func TestValidateUUID(t *testing.T) { - cases := []struct { - desc string - uuid string - err error - }{ - { - desc: "valid uuid", - uuid: validUUID, - err: nil, - }, - { - desc: "invalid uuid", - uuid: "invalid", - err: apiutil.ErrInvalidIDFormat, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - err := api.ValidateUUID(c.uuid) - assert.Equal(t, c.err, err) - }) - } -} - -func TestEncodeResponse(t *testing.T) { - now := time.Now() - validBody := []byte(`{"id":"` + validUUID + `","name":"test","created_at":"` + now.Format(time.RFC3339Nano) + `"}` + "\n" + ``) - - cases := []struct { - desc string - resp interface{} - header http.Header - code int - body []byte - err error - }{ - { - desc: "valid response", - resp: response{ - code: http.StatusOK, - headers: map[string]string{ - "Location": "/groups/" + validUUID, - }, - ID: validUUID, - Name: "test", - CreatedAt: now, - }, - header: http.Header{ - "Content-Type": []string{"application/json"}, - "Location": []string{"/groups/" + validUUID}, - }, - code: http.StatusOK, - body: validBody, - err: nil, - }, - { - desc: "valid response with no headers", - resp: response{ - code: http.StatusOK, - ID: validUUID, - Name: "test", - CreatedAt: now, - }, - header: http.Header{ - "Content-Type": []string{"application/json"}, - }, - code: http.StatusOK, - body: validBody, - err: nil, - }, - { - desc: "valid response with many headers", - resp: response{ - code: http.StatusOK, - headers: map[string]string{ - "X-Test": "test", - "X-Test2": "test2", - }, - ID: validUUID, - Name: "test", - CreatedAt: now, - }, - header: http.Header{ - "Content-Type": []string{"application/json"}, - "X-Test": []string{"test"}, - "X-Test2": []string{"test2"}, - }, - code: http.StatusOK, - body: validBody, - err: nil, - }, - { - desc: "valid response with empty body", - resp: response{ - code: http.StatusOK, - empty: true, - ID: validUUID, - }, - header: http.Header{ - "Content-Type": []string{"application/json"}, - }, - code: http.StatusOK, - body: []byte(``), - err: nil, - }, - { - desc: "invalid response", - resp: struct { - ID string `json:"id"` - }{ - ID: validUUID, - }, - header: http.Header{}, - code: 0, - body: []byte(`{"id":"` + validUUID + `"}` + "\n" + ``), - err: nil, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - responseWriter := newResponseWriter() - err := api.EncodeResponse(context.Background(), responseWriter, c.resp) - assert.Equal(t, c.err, err) - assert.Equal(t, c.header, responseWriter.Header()) - assert.Equal(t, c.code, responseWriter.StatusCode()) - assert.Equal(t, string(c.body), string(responseWriter.Body())) - }) - } -} - -func TestEncodeError(t *testing.T) { - cases := []struct { - desc string - errs []error - code int - }{ - { - desc: "BadRequest", - errs: []error{ - apiutil.ErrMissingSecret, - svcerr.ErrMalformedEntity, - errors.ErrMalformedEntity, - apiutil.ErrMissingID, - apiutil.ErrEmptyList, - apiutil.ErrMissingMemberType, - apiutil.ErrMissingMemberKind, - apiutil.ErrLimitSize, - apiutil.ErrNameSize, - svcerr.ErrViewEntity, - }, - code: http.StatusBadRequest, - }, - { - desc: "BadRequest with validation error", - errs: []error{ - errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingSecret), - errors.Wrap(apiutil.ErrValidation, svcerr.ErrMalformedEntity), - errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity), - errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), - errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyList), - errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingMemberType), - errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingMemberKind), - errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), - errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), - }, - code: http.StatusBadRequest, - }, - { - desc: "Unauthorized", - errs: []error{ - svcerr.ErrAuthentication, - svcerr.ErrAuthentication, - apiutil.ErrBearerToken, - }, - code: http.StatusUnauthorized, - }, - - { - desc: "NotFound", - errs: []error{ - svcerr.ErrNotFound, - }, - code: http.StatusNotFound, - }, - { - desc: "Conflict", - errs: []error{ - svcerr.ErrConflict, - svcerr.ErrConflict, - }, - code: http.StatusConflict, - }, - { - desc: "Forbidden", - errs: []error{ - svcerr.ErrAuthorization, - svcerr.ErrAuthorization, - svcerr.ErrDomainAuthorization, - }, - code: http.StatusForbidden, - }, - { - desc: "UnsupportedMediaType", - errs: []error{ - apiutil.ErrUnsupportedContentType, - }, - code: http.StatusUnsupportedMediaType, - }, - { - desc: "StatusUnprocessableEntity", - errs: []error{ - svcerr.ErrCreateEntity, - svcerr.ErrUpdateEntity, - svcerr.ErrRemoveEntity, - }, - code: http.StatusUnprocessableEntity, - }, - { - desc: "InternalServerError", - errs: []error{ - errors.New("test"), - }, - code: http.StatusInternalServerError, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - responseWriter := newResponseWriter() - for _, err := range c.errs { - api.EncodeError(context.Background(), err, responseWriter) - assert.Equal(t, c.code, responseWriter.StatusCode()) - - message := body{} - jerr := json.Unmarshal(responseWriter.Body(), &message) - assert.NoError(t, jerr) - - var wrapper error - switch errors.Contains(err, apiutil.ErrValidation) { - case true: - wrapper, err = errors.Unwrap(err) - assert.Equal(t, err.Error(), message.Error) - assert.Equal(t, wrapper.Error(), message.Message) - case false: - assert.Equal(t, err.Error(), message.Message) - } - } - }) - } -} diff --git a/internal/api/doc.go b/internal/api/doc.go deleted file mode 100644 index 6bffadcf7..000000000 --- a/internal/api/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains commonly used constants and functions -// for the HTTP endpoints. -package api diff --git a/internal/groups/api/decode.go b/internal/groups/api/decode.go deleted file mode 100644 index c560f5083..000000000 --- a/internal/groups/api/decode.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -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" - 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) - } - - memberKind, err := apiutil.ReadStringQuery(r, api.MemberKindKey, "") - 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) - } - - 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) { - 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) - } - - 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) - } - - 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, - }, - } - return req, nil -} - -func DecodeListChildrenRequest(_ 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) - } - - 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) - } - - 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, - }, - } - return req, nil -} - -func DecodeGroupCreate(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.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{ - 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 DecodeGroupRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := groupReq{ - id: chi.URLParam(r, "groupID"), - } - return req, nil -} - -func DecodeGroupPermsRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := groupPermsReq{ - id: chi.URLParam(r, "groupID"), - } - return req, nil -} - -func DecodeChangeGroupStatus(_ context.Context, r *http.Request) (interface{}, error) { - req := changeGroupStatusReq{ - id: chi.URLParam(r, "groupID"), - } - return req, nil -} - -func DecodeAssignMembersRequest(_ 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"), - } - 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 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) - } - req := unassignReq{ - 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 DecodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { - memberKind, err := apiutil.ReadStringQuery(r, api.MemberKindKey, "") - 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) - } - req := listMembersReq{ - groupID: chi.URLParam(r, "groupID"), - permission: permission, - memberKind: memberKind, - } - return req, nil -} - -func decodePageMeta(r *http.Request) (mggroups.PageMeta, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefGroupStatus) - if err != nil { - return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) - } - st, err := mggroups.ToStatus(s) - if err != nil { - return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) - } - offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) - } - limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) - } - name, err := apiutil.ReadStringQuery(r, api.NameKey, "") - if err != nil { - return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) - } - id, err := apiutil.ReadStringQuery(r, api.IDOrder, "") - if err != nil { - return mggroups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err) - } - meta, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) - 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, - } - return ret, nil -} diff --git a/internal/groups/api/decode_test.go b/internal/groups/api/decode_test.go deleted file mode 100644 index 2e45e3480..000000000 --- a/internal/groups/api/decode_test.go +++ /dev/null @@ -1,769 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "fmt" - "net/http" - "net/url" - "strings" - "testing" - - "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" -) - -func TestDecodeListGroupsRequest(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: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - Permission: api.DefPermission, - Direction: -1, - }, - }, - err: nil, - }, - { - desc: "valid request with all parameters", - url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", - 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, - ParentID: "random", - Permission: "random", - Direction: -1, - ListPerms: true, - }, - tree: true, - memberKind: "random", - }, - err: nil, - }, - { - desc: "valid request with invalid page metadata", - url: "http://localhost:8080?metadata=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid level", - url: "http://localhost:8080?level=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid parent", - url: "http://localhost:8080?parent_id=random&parent_id=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid tree", - url: "http://localhost:8080?tree=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid dir", - url: "http://localhost:8080?dir=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, - }, - { - desc: "valid request with invalid permission", - url: "http://localhost:8080?permission=random&permission=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid list permission", - url: "http://localhost:8080?&list_perms=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 := DecodeListGroupsRequest(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 TestDecodeListParentsRequest(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: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - Permission: api.DefPermission, - Direction: +1, - }, - }, - err: nil, - }, - { - desc: "valid request with all parameters", - url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", - 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, - }, - err: nil, - }, - { - desc: "valid request with invalid page metadata", - url: "http://localhost:8080?metadata=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid level", - url: "http://localhost:8080?level=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid tree", - url: "http://localhost:8080?tree=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid permission", - url: "http://localhost:8080?permission=random&permission=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid list permission", - url: "http://localhost:8080?&list_perms=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 := DecodeListParentsRequest(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 TestDecodeListChildrenRequest(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: listGroupsReq{ - Page: groups.Page{ - PageMeta: groups.PageMeta{ - Limit: 10, - }, - Permission: api.DefPermission, - Direction: -1, - }, - }, - err: nil, - }, - { - desc: "valid request with all parameters", - url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true", - 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, - }, - err: nil, - }, - { - desc: "valid request with invalid page metadata", - url: "http://localhost:8080?metadata=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid level", - url: "http://localhost:8080?level=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid tree", - url: "http://localhost:8080?tree=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid permission", - url: "http://localhost:8080?permission=random&permission=random", - resp: nil, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid list permission", - url: "http://localhost:8080?&list_perms=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 := 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) - 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 TestDecodePageMeta(t *testing.T) { - cases := []struct { - desc string - url string - resp groups.PageMeta - err error - }{ - { - desc: "valid request with no parameters", - url: "http://localhost:8080", - resp: groups.PageMeta{ - Limit: 10, - }, - err: nil, - }, - { - desc: "valid request with all parameters", - url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}", - resp: groups.PageMeta{ - Status: groups.EnabledStatus, - Offset: 10, - Limit: 10, - Name: "random", - Metadata: groups.Metadata{ - "test": "test", - }, - }, - err: nil, - }, - { - desc: "valid request with invalid status", - url: "http://localhost:8080?status=random", - resp: groups.PageMeta{}, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid status duplicated", - url: "http://localhost:8080?status=random&status=random", - resp: groups.PageMeta{}, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid offset", - url: "http://localhost:8080?offset=random", - resp: groups.PageMeta{}, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid limit", - url: "http://localhost:8080?limit=random", - resp: groups.PageMeta{}, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid name", - url: "http://localhost:8080?name=random&name=random", - resp: groups.PageMeta{}, - err: apiutil.ErrValidation, - }, - { - desc: "valid request with invalid page metadata", - url: "http://localhost:8080?metadata=random", - resp: groups.PageMeta{}, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - parsedURL, err := url.Parse(tc.url) - assert.NoError(t, err) - - req := &http.Request{URL: parsedURL} - resp, err := decodePageMeta(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)) - } -} - -func TestDecodeGroupCreate(t *testing.T) { - cases := []struct { - desc string - body string - header map[string][]string - resp interface{} - err error - }{ - { - desc: "valid request", - body: `{"name": "random", "description": "random"}`, - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - "Content-Type": {api.ContentType}, - }, - resp: createGroupReq{ - Group: groups.Group{ - Name: "random", - Description: "random", - }, - }, - err: nil, - }, - { - desc: "invalid content type", - body: `{"name": "random", "description": "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, - }, - } - - 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 := DecodeGroupCreate(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 TestDecodeGroupUpdate(t *testing.T) { - cases := []struct { - desc string - body string - header map[string][]string - resp interface{} - err error - }{ - { - desc: "valid request", - body: `{"name": "random", "description": "random"}`, - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - "Content-Type": {api.ContentType}, - }, - resp: updateGroupReq{ - Name: "random", - Description: "random", - }, - err: nil, - }, - { - desc: "invalid content type", - body: `{"name": "random", "description": "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, - }, - } - - for _, tc := range cases { - req, err := http.NewRequest(http.MethodPut, "http://localhost:8080", strings.NewReader(tc.body)) - assert.NoError(t, err) - req.Header = tc.header - resp, err := DecodeGroupUpdate(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 TestDecodeGroupRequest(t *testing.T) { - cases := []struct { - desc string - header map[string][]string - resp interface{} - err error - }{ - { - desc: "valid request", - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - }, - resp: groupReq{}, - err: nil, - }, - { - desc: "empty token", - resp: groupReq{}, - err: nil, - }, - } - - 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 := DecodeGroupRequest(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 TestDecodeGroupPermsRequest(t *testing.T) { - cases := []struct { - desc string - header map[string][]string - resp interface{} - err error - }{ - { - desc: "valid request", - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - }, - resp: groupPermsReq{}, - err: nil, - }, - { - desc: "empty token", - resp: groupPermsReq{}, - err: nil, - }, - } - - 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 := DecodeGroupPermsRequest(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 - }{ - { - desc: "valid request", - header: map[string][]string{ - "Authorization": {"Bearer 123"}, - }, - resp: changeGroupStatusReq{}, - err: nil, - }, - { - desc: "empty token", - resp: changeGroupStatusReq{}, - err: nil, - }, - } - - 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 TestDecodeAssignMembersRequest(t *testing.T) { - cases := []struct { - desc string - body string - header map[string][]string - resp interface{} - err error - }{ - { - 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, - }, - } - - 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 TestDecodeUnassignMembersRequest(t *testing.T) { - cases := []struct { - desc string - body string - header map[string][]string - resp interface{} - err error - }{ - { - 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, - }, - } - - 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)) - } -} diff --git a/internal/groups/api/doc.go b/internal/groups/api/doc.go deleted file mode 100644 index 2424852cc..000000000 --- a/internal/groups/api/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains API-related concerns: endpoint definitions, middlewares -// and all resource representations. -package api diff --git a/internal/groups/api/endpoint_test.go b/internal/groups/api/endpoint_test.go deleted file mode 100644 index 4a69f2fcd..000000000 --- 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/endpoints.go b/internal/groups/api/endpoints.go deleted file mode 100644 index 7082c3e58..000000000 --- a/internal/groups/api/endpoints.go +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -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/go-kit/kit/endpoint" -) - -const groupTypeChannels = "channels" - -func CreateGroupEndpoint(svc groups.Service, kind string) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createGroupReq) - if err := req.validate(); err != nil { - return createGroupRes{created: false}, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return createGroupRes{created: false}, svcerr.ErrAuthorization - } - - group, err := svc.CreateGroup(ctx, session, kind, req.Group) - if err != nil { - return createGroupRes{created: false}, err - } - - return createGroupRes{created: true, Group: group}, nil - } -} - -func ViewGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(groupReq) - if err := req.validate(); err != nil { - return viewGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return viewGroupRes{}, svcerr.ErrAuthorization - } - - group, err := svc.ViewGroup(ctx, session, req.id) - if err != nil { - return viewGroupRes{}, err - } - - return viewGroupRes{Group: group}, nil - } -} - -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) - if err := req.validate(); err != nil { - return updateGroupRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return updateGroupRes{}, svcerr.ErrAuthorization - } - - group := groups.Group{ - ID: req.id, - Name: req.Name, - Description: req.Description, - Metadata: req.Metadata, - } - - group, err := svc.UpdateGroup(ctx, session, group) - if err != nil { - return updateGroupRes{}, err - } - - return updateGroupRes{Group: group}, nil - } -} - -func EnableGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeGroupStatusReq) - if err := req.validate(); err != nil { - return changeStatusRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return changeStatusRes{}, svcerr.ErrAuthorization - } - - group, err := svc.EnableGroup(ctx, session, req.id) - if err != nil { - return changeStatusRes{}, err - } - return changeStatusRes{Group: group}, nil - } -} - -func DisableGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeGroupStatusReq) - if err := req.validate(); err != nil { - return changeStatusRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return changeStatusRes{}, svcerr.ErrAuthorization - } - - group, err := svc.DisableGroup(ctx, session, req.id) - if err != nil { - return changeStatusRes{}, err - } - return changeStatusRes{Group: group}, nil - } -} - -func ListGroupsEndpoint(svc groups.Service, groupType, memberKind string) 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 - } - - page, err := svc.ListGroups(ctx, session, req.memberKind, req.memberID, req.Page) - if err != nil { - if groupType == groupTypeChannels { - return channelPageRes{}, err - } - return groupPageRes{}, err - } - - if req.tree { - return buildGroupsResponseTree(page), nil - } - filterByID := req.Page.ParentID != "" - - if groupType == groupTypeChannels { - return buildChannelsResponse(page, filterByID), nil - } - return buildGroupsResponse(page, filterByID), nil - } -} - -func ListMembersEndpoint(svc groups.Service, memberKind string) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersReq) - if memberKind != "" { - req.memberKind = memberKind - } - if err := req.validate(); err != nil { - return listMembersRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return listMembersRes{}, svcerr.ErrAuthorization - } - - page, err := svc.ListMembers(ctx, session, req.groupID, req.permission, req.memberKind) - if err != nil { - return listMembersRes{}, err - } - - return listMembersRes{ - pageRes: pageRes{ - Limit: page.Limit, - Offset: page.Offset, - Total: page.Total, - }, - Members: page.Members, - }, nil - } -} - -func AssignMembersEndpoint(svc groups.Service, relation, memberKind string) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(assignReq) - if relation != "" { - req.Relation = relation - } - if memberKind != "" { - req.MemberKind = memberKind - } - if err := req.validate(); err != nil { - return assignRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return assignRes{}, svcerr.ErrAuthorization - } - - if err := svc.Assign(ctx, session, req.groupID, req.Relation, req.MemberKind, req.Members...); err != nil { - return assignRes{}, err - } - return assignRes{assigned: true}, nil - } -} - -func UnassignMembersEndpoint(svc groups.Service, relation, memberKind string) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(unassignReq) - if relation != "" { - req.Relation = relation - } - if memberKind != "" { - req.MemberKind = memberKind - } - if err := req.validate(); err != nil { - return unassignRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return unassignRes{}, svcerr.ErrAuthorization - } - - if err := svc.Unassign(ctx, session, req.groupID, req.Relation, req.MemberKind, req.Members...); err != nil { - return unassignRes{}, err - } - return unassignRes{unassigned: true}, nil - } -} - -func DeleteGroupEndpoint(svc groups.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - 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.ErrAuthorization - } - - if err := svc.DeleteGroup(ctx, session, req.id); err != nil { - return deleteGroupRes{}, err - } - return deleteGroupRes{deleted: true}, nil - } -} - -func buildGroupsResponseTree(page groups.Page) groupPageRes { - groupsMap := map[string]*groups.Group{} - // Parents' map keeps its array of children. - parentsMap := map[string][]*groups.Group{} - for i := range page.Groups { - if _, ok := groupsMap[page.Groups[i].ID]; !ok { - groupsMap[page.Groups[i].ID] = &page.Groups[i] - parentsMap[page.Groups[i].ID] = make([]*groups.Group, 0) - } - } - - for _, group := range groupsMap { - if children, ok := parentsMap[group.Parent]; ok { - children = append(children, group) - parentsMap[group.Parent] = children - } - } - - res := groupPageRes{ - pageRes: pageRes{ - Limit: page.Limit, - Offset: page.Offset, - Total: page.Total, - Level: page.Level, - }, - Groups: []viewGroupRes{}, - } - - for _, group := range groupsMap { - if children, ok := parentsMap[group.ID]; ok { - group.Children = children - } - } - - for _, group := range groupsMap { - view := toViewGroupRes(*group) - if children, ok := parentsMap[group.Parent]; len(children) == 0 || !ok { - res.Groups = append(res.Groups, view) - } - } - - return res -} - -func toViewGroupRes(group groups.Group) viewGroupRes { - view := viewGroupRes{ - Group: group, - } - return view -} - -func buildGroupsResponse(gp groups.Page, filterByID bool) groupPageRes { - res := groupPageRes{ - pageRes: pageRes{ - Total: gp.Total, - Level: gp.Level, - }, - Groups: []viewGroupRes{}, - } - - for _, group := range gp.Groups { - view := viewGroupRes{ - Group: group, - } - if filterByID && group.Level == 0 { - continue - } - res.Groups = append(res.Groups, view) - } - - 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/internal/groups/api/requests.go b/internal/groups/api/requests.go deleted file mode 100644 index 7144ef236..000000000 --- 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 ed9fa15ac..000000000 --- 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/api/responses.go b/internal/groups/api/responses.go deleted file mode 100644 index a2c30795b..000000000 --- a/internal/groups/api/responses.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/groups" -) - -var ( - _ magistrala.Response = (*createGroupRes)(nil) - _ magistrala.Response = (*groupPageRes)(nil) - _ magistrala.Response = (*changeStatusRes)(nil) - _ magistrala.Response = (*viewGroupRes)(nil) - _ magistrala.Response = (*updateGroupRes)(nil) - _ magistrala.Response = (*assignRes)(nil) - _ magistrala.Response = (*unassignRes)(nil) -) - -type viewGroupRes struct { - groups.Group `json:",inline"` -} - -func (res viewGroupRes) Code() int { - return http.StatusOK -} - -func (res viewGroupRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewGroupRes) Empty() bool { - return false -} - -type 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 -} - -func (res createGroupRes) Code() int { - if res.created { - return http.StatusCreated - } - - return http.StatusOK -} - -func (res createGroupRes) Headers() map[string]string { - if res.created { - return map[string]string{ - "Location": fmt.Sprintf("/groups/%s", res.ID), - } - } - - return map[string]string{} -} - -func (res createGroupRes) Empty() bool { - return false -} - -type groupPageRes struct { - pageRes - Groups []viewGroupRes `json:"groups"` -} - -type pageRes struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset"` - Total uint64 `json:"total"` - Level uint64 `json:"level,omitempty"` -} - -func (res groupPageRes) Code() int { - return http.StatusOK -} - -func (res groupPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res groupPageRes) Empty() bool { - return false -} - -type 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"` -} - -func (res updateGroupRes) Code() int { - return http.StatusOK -} - -func (res updateGroupRes) Headers() map[string]string { - return map[string]string{} -} - -func (res updateGroupRes) Empty() bool { - return false -} - -type changeStatusRes struct { - groups.Group `json:",inline"` -} - -func (res changeStatusRes) Code() int { - return http.StatusOK -} - -func (res changeStatusRes) Headers() map[string]string { - return map[string]string{} -} - -func (res changeStatusRes) Empty() bool { - return false -} - -type assignRes struct { - assigned bool -} - -func (res assignRes) Code() int { - if res.assigned { - return http.StatusCreated - } - - return http.StatusBadRequest -} - -func (res assignRes) Headers() map[string]string { - return map[string]string{} -} - -func (res assignRes) Empty() bool { - return true -} - -type unassignRes struct { - unassigned bool -} - -func (res unassignRes) Code() int { - if res.unassigned { - return http.StatusCreated - } - - return http.StatusBadRequest -} - -func (res unassignRes) Headers() map[string]string { - return map[string]string{} -} - -func (res unassignRes) Empty() bool { - return true -} - -type listMembersRes struct { - pageRes - Members []groups.Member `json:"members"` -} - -func (res listMembersRes) Code() int { - return http.StatusOK -} - -func (res listMembersRes) Headers() map[string]string { - return map[string]string{} -} - -func (res listMembersRes) Empty() bool { - return false -} - -type deleteGroupRes struct { - deleted bool -} - -func (res deleteGroupRes) Code() int { - if res.deleted { - return http.StatusNoContent - } - - return http.StatusBadRequest -} - -func (res deleteGroupRes) Headers() map[string]string { - return map[string]string{} -} - -func (res deleteGroupRes) Empty() bool { - return true -} diff --git a/internal/groups/events/doc.go b/internal/groups/events/doc.go deleted file mode 100644 index f1cd64cb7..000000000 --- a/internal/groups/events/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package events contains event source Redis client implementation. -package events diff --git a/internal/groups/events/events.go b/internal/groups/events/events.go deleted file mode 100644 index eb65fd411..000000000 --- a/internal/groups/events/events.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "time" - - "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" -) - -var ( - _ events.Event = (*assignEvent)(nil) - _ events.Event = (*unassignEvent)(nil) - _ events.Event = (*createGroupEvent)(nil) - _ events.Event = (*updateGroupEvent)(nil) - _ events.Event = (*changeStatusGroupEvent)(nil) - _ events.Event = (*viewGroupEvent)(nil) - _ events.Event = (*deleteGroupEvent)(nil) - _ events.Event = (*viewGroupEvent)(nil) - _ events.Event = (*listGroupEvent)(nil) - _ events.Event = (*listGroupMembershipEvent)(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 -} - -func (cge createGroupEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": groupCreate, - "id": cge.ID, - "status": cge.Status.String(), - "created_at": cge.CreatedAt, - } - - if cge.Domain != "" { - val["domain"] = cge.Domain - } - if cge.Parent != "" { - val["parent"] = cge.Parent - } - if cge.Name != "" { - val["name"] = cge.Name - } - if cge.Description != "" { - val["description"] = cge.Description - } - if cge.Metadata != nil { - val["metadata"] = cge.Metadata - } - if cge.Status.String() != "" { - val["status"] = cge.Status.String() - } - - return val, nil -} - -type updateGroupEvent struct { - groups.Group -} - -func (uge updateGroupEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": groupUpdate, - "updated_at": uge.UpdatedAt, - "updated_by": uge.UpdatedBy, - } - - if uge.ID != "" { - val["id"] = uge.ID - } - if uge.Domain != "" { - val["domain"] = uge.Domain - } - if uge.Parent != "" { - val["parent"] = uge.Parent - } - if uge.Name != "" { - val["name"] = uge.Name - } - if uge.Description != "" { - val["description"] = uge.Description - } - if uge.Metadata != nil { - val["metadata"] = uge.Metadata - } - if !uge.CreatedAt.IsZero() { - val["created_at"] = uge.CreatedAt - } - if uge.Status.String() != "" { - val["status"] = uge.Status.String() - } - - return val, nil -} - -type changeStatusGroupEvent struct { - id string - status string - updatedAt time.Time - updatedBy string -} - -func (rge changeStatusGroupEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": groupChangeStatus, - "id": rge.id, - "status": rge.status, - "updated_at": rge.updatedAt, - "updated_by": rge.updatedBy, - }, nil -} - -type viewGroupEvent struct { - groups.Group -} - -func (vge viewGroupEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": groupView, - "id": vge.ID, - } - - if vge.Domain != "" { - val["domain"] = vge.Domain - } - if vge.Parent != "" { - val["parent"] = vge.Parent - } - if vge.Name != "" { - val["name"] = vge.Name - } - if vge.Description != "" { - val["description"] = vge.Description - } - if vge.Metadata != nil { - val["metadata"] = vge.Metadata - } - if !vge.CreatedAt.IsZero() { - val["created_at"] = vge.CreatedAt - } - if !vge.UpdatedAt.IsZero() { - val["updated_at"] = vge.UpdatedAt - } - if vge.UpdatedBy != "" { - val["updated_by"] = vge.UpdatedBy - } - if vge.Status.String() != "" { - val["status"] = vge.Status.String() - } - - 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 -} - -func (lge listGroupEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": groupList, - "total": lge.Total, - "offset": lge.Offset, - "limit": lge.Limit, - } - - if lge.Name != "" { - val["name"] = lge.Name - } - if lge.DomainID != "" { - val["domain_id"] = lge.DomainID - } - if lge.Tag != "" { - val["tag"] = lge.Tag - } - if lge.Metadata != nil { - val["metadata"] = lge.Metadata - } - if lge.Status.String() != "" { - val["status"] = lge.Status.String() - } - - return val, nil -} - -type listGroupMembershipEvent struct { - groupID string - permission string - memberKind string -} - -func (lgme listGroupMembershipEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": groupListMemberships, - "id": lgme.groupID, - "permission": lgme.permission, - "member_kind": lgme.memberKind, - }, nil -} - -type deleteGroupEvent struct { - id string -} - -func (rge deleteGroupEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": groupRemove, - "id": rge.id, - }, nil -} diff --git a/internal/groups/events/streams.go b/internal/groups/events/streams.go deleted file mode 100644 index b473c5e1f..000000000 --- a/internal/groups/events/streams.go +++ /dev/null @@ -1,212 +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/pkg/groups" -) - -var _ groups.Service = (*eventStore)(nil) - -type eventStore struct { - events.Publisher - svc groups.Service -} - -// NewEventStoreMiddleware returns wrapper around things service that sends -// events to event store. -func NewEventStoreMiddleware(ctx context.Context, svc groups.Service, url, streamID string) (groups.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) CreateGroup(ctx context.Context, session authn.Session, kind string, group groups.Group) (groups.Group, error) { - group, err := es.svc.CreateGroup(ctx, session, kind, group) - if err != nil { - return group, err - } - - event := createGroupEvent{ - group, - } - - if err := es.Publish(ctx, event); err != nil { - return group, err - } - - return group, nil -} - -func (es eventStore) UpdateGroup(ctx context.Context, session authn.Session, group groups.Group) (groups.Group, error) { - group, err := es.svc.UpdateGroup(ctx, session, group) - if err != nil { - return group, err - } - - event := updateGroupEvent{ - group, - } - - if err := es.Publish(ctx, event); err != nil { - return group, err - } - - return group, nil -} - -func (es eventStore) ViewGroup(ctx context.Context, session authn.Session, id string) (groups.Group, error) { - group, err := es.svc.ViewGroup(ctx, session, id) - if err != nil { - return group, err - } - event := viewGroupEvent{ - group, - } - - if err := es.Publish(ctx, event); err != nil { - return group, err - } - - 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) - if err != nil { - return gp, err - } - event := listGroupEvent{ - pm, - } - - if err := es.Publish(ctx, event); err != nil { - return gp, err - } - - 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) - if err != nil { - return mp, err - } - event := listGroupMembershipEvent{ - groupID, permission, memberKind, - } - - if err := es.Publish(ctx, event); err != nil { - return mp, err - } - - return mp, nil -} - -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 group, err - } - - 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, - } - - if err := es.Publish(ctx, event); err != nil { - return err - } - - return 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 { - return err - } - - event := unassignEvent{ - groupID: groupID, - relation: relation, - memberKind: memberKind, - memberIDs: memberIDs, - } - - if err := es.Publish(ctx, event); err != nil { - return err - } - return es.svc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -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 - } - - return es.changeStatus(ctx, group) -} - -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 group, err - } - - return group, 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 -} diff --git a/internal/groups/middleware/authorization.go b/internal/groups/middleware/authorization.go deleted file mode 100644 index d6a2e0acf..000000000 --- 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/middleware/doc.go b/internal/groups/middleware/doc.go deleted file mode 100644 index 2ffa0936a..000000000 --- a/internal/groups/middleware/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package middleware provides middleware for Magistrala Groups service. -package middleware diff --git a/internal/groups/middleware/logging.go b/internal/groups/middleware/logging.go deleted file mode 100644 index f1840efd1..000000000 --- a/internal/groups/middleware/logging.go +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/groups" -) - -var _ groups.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc groups.Service -} - -// LoggingMiddleware adds logging facilities to the groups service. -func LoggingMiddleware(svc groups.Service, logger *slog.Logger) groups.Service { - return &loggingMiddleware{logger, svc} -} - -// CreateGroup logs the create_group request. It logs the group name, id and session 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("group", - slog.String("id", g.ID), - slog.String("name", g.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - 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) -} - -// UpdateGroup logs the update_group request. It logs the group name, id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateGroup(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()), - slog.Group("group", - slog.String("id", group.ID), - slog.String("name", group.Name), - slog.Any("metadata", group.Metadata), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update group failed", args...) - return - } - lm.logger.Info("Update group completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateGroup(ctx, session, group) -} - -// ViewGroup logs the view_group request. It logs the group name, id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ViewGroup(ctx context.Context, session authn.Session, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("group", - slog.String("id", g.ID), - slog.String("name", g.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View group failed", args...) - return - } - lm.logger.Info("View group completed successfully", args...) - }(time.Now()) - 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) { - 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("total", cg.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - 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) -} - -// EnableGroup logs the enable_group request. It logs the group name, id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) EnableGroup(ctx context.Context, session authn.Session, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("group", - slog.String("id", id), - slog.String("name", g.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Enable group failed", args...) - return - } - lm.logger.Info("Enable group completed successfully", args...) - }(time.Now()) - return lm.svc.EnableGroup(ctx, session, id) -} - -// DisableGroup logs the disable_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) DisableGroup(ctx context.Context, session authn.Session, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("group", - slog.String("id", id), - slog.String("name", g.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Disable group failed", args...) - return - } - lm.logger.Info("Disable group completed successfully", args...) - }(time.Now()) - 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) { - 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), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List members failed", args...) - return - } - lm.logger.Info("List members completed successfully", args...) - }(time.Now()) - return lm.svc.ListMembers(ctx, session, groupID, permission, memberKind) -} - -func (lm *loggingMiddleware) Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...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), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Assign member to group failed", args...) - return - } - lm.logger.Info("Assign member to group completed successfully", args...) - }(time.Now()) - - return lm.svc.Assign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -func (lm *loggingMiddleware) Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...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), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Unassign member from group failed", args...) - return - } - lm.logger.Info("Unassign member from group completed successfully", args...) - }(time.Now()) - - return lm.svc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -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", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Delete group failed", args...) - return - } - lm.logger.Info("Delete group completed successfully", args...) - }(time.Now()) - return lm.svc.DeleteGroup(ctx, session, id) -} diff --git a/internal/groups/middleware/metrics.go b/internal/groups/middleware/metrics.go deleted file mode 100644 index 7d6fa13f7..000000000 --- a/internal/groups/middleware/metrics.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "time" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/groups" - "github.com/go-kit/kit/metrics" -) - -var _ groups.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc groups.Service -} - -// MetricsMiddleware instruments policies service by tracking request count and latency. -func MetricsMiddleware(svc groups.Service, counter metrics.Counter, latency metrics.Histogram) groups.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -// CreateGroup instruments CreateGroup method with metrics. -func (ms *metricsMiddleware) CreateGroup(ctx context.Context, session authn.Session, kind string, g groups.Group) (groups.Group, error) { - defer func(begin time.Time) { - ms.counter.With("method", "create_group").Add(1) - ms.latency.With("method", "create_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.CreateGroup(ctx, session, kind, g) -} - -// UpdateGroup instruments UpdateGroup method with metrics. -func (ms *metricsMiddleware) UpdateGroup(ctx context.Context, session authn.Session, group groups.Group) (rGroup groups.Group, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_group").Add(1) - ms.latency.With("method", "update_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateGroup(ctx, session, group) -} - -// ViewGroup instruments ViewGroup method with metrics. -func (ms *metricsMiddleware) ViewGroup(ctx context.Context, session authn.Session, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_group").Add(1) - ms.latency.With("method", "view_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ViewGroup(ctx, 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) { - 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) -} - -// EnableGroup instruments EnableGroup method with metrics. -func (ms *metricsMiddleware) EnableGroup(ctx context.Context, session authn.Session, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "enable_group").Add(1) - ms.latency.With("method", "enable_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.EnableGroup(ctx, session, id) -} - -// DisableGroup instruments DisableGroup method with metrics. -func (ms *metricsMiddleware) DisableGroup(ctx context.Context, session authn.Session, id string) (g groups.Group, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "disable_group").Add(1) - ms.latency.With("method", "disable_group").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.DisableGroup(ctx, 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) { - defer func(begin time.Time) { - ms.counter.With("method", "list_memberships").Add(1) - ms.latency.With("method", "list_memberships").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListMembers(ctx, session, groupID, permission, memberKind) -} - -// Assign instruments Assign method with metrics. -func (ms *metricsMiddleware) Assign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) { - defer func(begin time.Time) { - ms.counter.With("method", "assign").Add(1) - ms.latency.With("method", "assign").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.Assign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -// Unassign instruments Unassign method with metrics. -func (ms *metricsMiddleware) Unassign(ctx context.Context, session authn.Session, groupID, relation, memberKind string, memberIDs ...string) (err error) { - defer func(begin time.Time) { - ms.counter.With("method", "unassign").Add(1) - ms.latency.With("method", "unassign").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.Unassign(ctx, session, groupID, relation, memberKind, memberIDs...) -} - -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) -} diff --git a/internal/groups/postgres/doc.go b/internal/groups/postgres/doc.go deleted file mode 100644 index 96fe21175..000000000 --- a/internal/groups/postgres/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres contains the database implementation of groups repository layer. -package postgres diff --git a/internal/groups/postgres/groups.go b/internal/groups/postgres/groups.go deleted file mode 100644 index 15d9b3973..000000000 --- a/internal/groups/postgres/groups.go +++ /dev/null @@ -1,502 +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/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" - "github.com/jmoiron/sqlx" -) - -var _ mggroups.Repository = (*groupRepository)(nil) - -type groupRepository struct { - db postgres.Database -} - -// New instantiates a PostgreSQL implementation of group -// repository. -func New(db postgres.Database) mggroups.Repository { - return &groupRepository{ - db: db, - } -} - -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;` - dbg, err := toDBGroup(g) - if err != nil { - return mggroups.Group{}, err - } - row, err := repo.db.NamedQueryContext(ctx, q, dbg) - if err != nil { - return mggroups.Group{}, postgres.HandleError(repoerr.ErrCreateEntity, err) - } - - defer row.Close() - row.Next() - dbg = dbGroup{} - if err := row.StructScan(&dbg); err != nil { - return mggroups.Group{}, err - } - - return toGroup(dbg) -} - -func (repo groupRepository) Update(ctx context.Context, g mggroups.Group) (mggroups.Group, error) { - var query []string - var upq string - if g.Name != "" { - query = append(query, "name = :name,") - } - if g.Description != "" { - query = append(query, "description = :description,") - } - if g.Metadata != nil { - query = append(query, "metadata = :metadata,") - } - if len(query) > 0 { - upq = strings.Join(query, " ") - } - g.Status = mggroups.EnabledStatus - q := fmt.Sprintf(`UPDATE groups SET %s updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, name, description, domain_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status`, upq) - - dbu, err := toDBGroup(g) - if err != nil { - return mggroups.Group{}, errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - row, err := repo.db.NamedQueryContext(ctx, q, dbu) - if err != nil { - return mggroups.Group{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - - defer row.Close() - if ok := row.Next(); !ok { - return mggroups.Group{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) - } - dbu = dbGroup{} - if err := row.StructScan(&dbu); err != nil { - return mggroups.Group{}, errors.Wrap(err, repoerr.ErrUpdateEntity) - } - return toGroup(dbu) -} - -func (repo groupRepository) ChangeStatus(ctx context.Context, group mggroups.Group) (mggroups.Group, error) { - qc := `UPDATE groups SET status = :status, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id - RETURNING id, name, description, domain_id, COALESCE(parent_id, '') AS parent_id, metadata, created_at, updated_at, updated_by, status` - - dbg, err := toDBGroup(group) - if err != nil { - return mggroups.Group{}, errors.Wrap(repoerr.ErrUpdateEntity, err) - } - row, err := repo.db.NamedQueryContext(ctx, qc, dbg) - if err != nil { - return mggroups.Group{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - defer row.Close() - if ok := row.Next(); !ok { - return mggroups.Group{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) - } - dbg = dbGroup{} - if err := row.StructScan(&dbg); err != nil { - return mggroups.Group{}, errors.Wrap(err, repoerr.ErrUpdateEntity) - } - - return toGroup(dbg) -} - -func (repo groupRepository) RetrieveByID(ctx context.Context, id string) (mggroups.Group, error) { - q := `SELECT id, name, domain_id, COALESCE(parent_id, '') AS parent_id, description, metadata, created_at, updated_at, updated_by, status FROM groups - WHERE id = :id` - - dbg := dbGroup{ - ID: id, - } - - row, err := repo.db.NamedQueryContext(ctx, q, dbg) - if err != nil { - return mggroups.Group{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - defer row.Close() - - dbg = dbGroup{} - if row.Next() { - if err := row.StructScan(&dbg); err != nil { - return mggroups.Group{}, errors.Wrap(repoerr.ErrNotFound, err) - } - } - - return toGroup(dbg) -} - -func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) (mggroups.Page, error) { - var q string - query := buildQuery(gm) - - 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) - - dbPage, err := toDBGroupPage(gm) - if err != nil { - return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - items, err := repo.processRows(rows) - if err != nil { - return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - cq := "SELECT COUNT(*) FROM groups g" - if query != "" { - cq = fmt.Sprintf(" %s %s", cq, query) - } - - total, err := postgres.Total(ctx, repo.db, cq, dbPage) - if err != nil { - return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - page := gm - page.Groups = items - page.Total = total - - return page, nil -} - -func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, 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 - } - query := buildQuery(gm, 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) - - dbPage, err := toDBGroupPage(gm) - if err != nil { - return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - items, err := repo.processRows(rows) - if err != nil { - return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - cq := "SELECT COUNT(*) FROM groups g" - if query != "" { - cq = fmt.Sprintf(" %s %s", cq, query) - } - - total, err := postgres.Total(ctx, repo.db, cq, dbPage) - if err != nil { - return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - page := gm - page.Groups = items - page.Total = total - - return page, nil -} - -func (repo groupRepository) AssignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { - if len(groupIDs) == 0 { - return nil - } - var updateColumns []string - for _, groupID := range groupIDs { - updateColumns = append(updateColumns, fmt.Sprintf("('%s', '%s') ", groupID, parentGroupID)) - } - 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) - if err != nil { - return postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - defer row.Close() - - return nil -} - -func (repo groupRepository) UnassignParentGroup(ctx context.Context, parentGroupID string, groupIDs ...string) error { - if len(groupIDs) == 0 { - return nil - } - var updateColumns []string - for _, groupID := range groupIDs { - updateColumns = append(updateColumns, fmt.Sprintf("('%s', '%s') ", groupID, parentGroupID)) - } - uc := strings.Join(updateColumns, ",") - query := fmt.Sprintf(` - UPDATE groups AS g SET - parent_id = NULL - FROM (VALUES - %s - ) AS u(id, parent_group_id) - WHERE g.id = u.id ; - `, uc) - - row, err := repo.db.QueryContext(ctx, query) - if err != nil { - return postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - defer row.Close() - - return nil -} - -func (repo groupRepository) Delete(ctx context.Context, groupID string) error { - q := "DELETE FROM groups AS g WHERE g.id = $1;" - - result, err := repo.db.ExecContext(ctx, q, groupID) - if err != nil { - return postgres.HandleError(repoerr.ErrRemoveEntity, err) - } - if rows, _ := result.RowsAffected(); rows == 0 { - return repoerr.ErrNotFound - } - 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 { - queries := []string{} - - if len(ids) > 0 { - queries = append(queries, fmt.Sprintf(" id in ('%s') ", strings.Join(ids, "', '"))) - } - if gm.Name != "" { - queries = append(queries, "g.name ILIKE '%' || :name || '%'") - } - if gm.PageMeta.ID != "" { - queries = append(queries, "g.id ILIKE '%' || :id || '%'") - } - if gm.Status != mggroups.AllStatus { - queries = append(queries, "g.status = :status") - } - if gm.DomainID != "" { - queries = append(queries, "g.domain_id = :domain_id") - } - if len(gm.Metadata) > 0 { - queries = append(queries, "g.metadata @> :metadata") - } - if len(queries) > 0 { - return fmt.Sprintf("WHERE %s", strings.Join(queries, " AND ")) - } - - return "" -} - -type dbGroup struct { - ID string `db:"id"` - ParentID *string `db:"parent_id,omitempty"` - DomainID string `db:"domain_id,omitempty"` - Name string `db:"name"` - Description string `db:"description,omitempty"` - Level int `db:"level"` - Path string `db:"path,omitempty"` - Metadata []byte `db:"metadata,omitempty"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt sql.NullTime `db:"updated_at,omitempty"` - UpdatedBy *string `db:"updated_by,omitempty"` - Status mggroups.Status `db:"status"` -} - -func toDBGroup(g mggroups.Group) (dbGroup, error) { - data := []byte("{}") - if len(g.Metadata) > 0 { - b, err := json.Marshal(g.Metadata) - if err != nil { - return dbGroup{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - data = b - } - var parentID *string - if g.Parent != "" { - parentID = &g.Parent - } - var updatedAt sql.NullTime - if !g.UpdatedAt.IsZero() { - updatedAt = sql.NullTime{Time: g.UpdatedAt, Valid: true} - } - var updatedBy *string - if g.UpdatedBy != "" { - updatedBy = &g.UpdatedBy - } - return dbGroup{ - ID: g.ID, - Name: g.Name, - ParentID: parentID, - DomainID: g.Domain, - Description: g.Description, - Metadata: data, - Path: g.Path, - CreatedAt: g.CreatedAt, - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - Status: g.Status, - }, nil -} - -func toGroup(g dbGroup) (mggroups.Group, error) { - var metadata groups.Metadata - if g.Metadata != nil { - if err := json.Unmarshal(g.Metadata, &metadata); err != nil { - return mggroups.Group{}, errors.Wrap(repoerr.ErrMalformedEntity, err) - } - } - var parentID string - if g.ParentID != nil { - parentID = *g.ParentID - } - var updatedAt time.Time - if g.UpdatedAt.Valid { - updatedAt = g.UpdatedAt.Time - } - var updatedBy string - if g.UpdatedBy != nil { - updatedBy = *g.UpdatedBy - } - - return mggroups.Group{ - ID: g.ID, - Name: g.Name, - Parent: parentID, - Domain: g.DomainID, - Description: g.Description, - Metadata: metadata, - Level: g.Level, - Path: g.Path, - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - CreatedAt: g.CreatedAt, - Status: g.Status, - }, nil -} - -func toDBGroupPage(pm mggroups.Page) (dbGroupPage, error) { - level := mggroups.MaxLevel - if pm.Level < mggroups.MaxLevel { - level = pm.Level - } - data := []byte("{}") - if len(pm.Metadata) > 0 { - b, err := json.Marshal(pm.Metadata) - if err != nil { - return dbGroupPage{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - data = b - } - return dbGroupPage{ - ID: pm.ID, - Name: pm.Name, - Metadata: data, - Path: pm.Path, - Level: level, - Total: pm.Total, - Offset: pm.Offset, - Limit: pm.Limit, - ParentID: pm.ParentID, - DomainID: pm.DomainID, - Status: pm.Status, - }, nil -} - -type dbGroupPage struct { - ClientID string `db:"client_id"` - ID string `db:"id"` - Name string `db:"name"` - ParentID string `db:"parent_id"` - DomainID string `db:"domain_id"` - Metadata []byte `db:"metadata"` - Path string `db:"path"` - Level uint64 `db:"level"` - Total uint64 `db:"total"` - Limit uint64 `db:"limit"` - Offset uint64 `db:"offset"` - Subject string `db:"subject"` - Action string `db:"action"` - Status mggroups.Status `db:"status"` -} - -func (repo groupRepository) processRows(rows *sqlx.Rows) ([]mggroups.Group, error) { - var items []mggroups.Group - for rows.Next() { - dbg := dbGroup{} - if err := rows.StructScan(&dbg); err != nil { - return items, err - } - group, err := toGroup(dbg) - if err != nil { - return items, err - } - items = append(items, group) - } - return items, nil -} diff --git a/internal/groups/postgres/groups_test.go b/internal/groups/postgres/groups_test.go deleted file mode 100644 index 7bbbee20d..000000000 --- a/internal/groups/postgres/groups_test.go +++ /dev/null @@ -1,1212 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/0x6flab/namegenerator" - "github.com/absmach/magistrala/internal/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" -) - -var ( - namegen = namegenerator.NewGenerator() - invalidID = strings.Repeat("a", 37) - validGroup = mggroups.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, - } -) - -func TestSave(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - - cases := []struct { - desc string - group mggroups.Group - err error - }{ - { - desc: "add new group successfully", - group: validGroup, - err: nil, - }, - { - desc: "add duplicate group", - group: validGroup, - err: repoerr.ErrConflict, - }, - { - desc: "add group with invalid ID", - group: mggroups.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, - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add group with invalid domain", - group: mggroups.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, - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add group with invalid parent", - group: mggroups.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, - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add group with invalid name", - group: mggroups.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, - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add group with invalid description", - group: mggroups.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, - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add group with invalid metadata", - group: mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Description: strings.Repeat("a", 64), - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add group with empty domain", - group: mggroups.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, - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add group with empty name", - group: mggroups.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, - }, - err: repoerr.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - switch group, err := repo.Save(context.Background(), tc.group); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.group, group, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group, group)) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestUpdate(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - - group, err := repo.Save(context.Background(), validGroup) - require.Nil(t, err, fmt.Sprintf("save group unexpected error: %s", err)) - - cases := []struct { - desc string - group mggroups.Group - err error - }{ - { - desc: "update group successfully", - group: mggroups.Group{ - ID: group.ID, - Name: namegen.Generate(), - Description: strings.Repeat("a", 64), - Metadata: map[string]interface{}{"key": "value"}, - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "update group name", - group: mggroups.Group{ - ID: group.ID, - Name: namegen.Generate(), - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "update group description", - group: mggroups.Group{ - ID: group.ID, - Description: strings.Repeat("a", 64), - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "update group metadata", - group: mggroups.Group{ - ID: group.ID, - Metadata: map[string]interface{}{"key": "value"}, - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "update group with invalid ID", - group: mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Description: strings.Repeat("a", 64), - Metadata: map[string]interface{}{"key": "value"}, - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update group with empty ID", - group: mggroups.Group{ - Name: namegen.Generate(), - Description: strings.Repeat("a", 64), - Metadata: map[string]interface{}{"key": "value"}, - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - switch group, err := repo.Update(context.Background(), tc.group); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.group.ID, group.ID, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group.ID, group.ID)) - assert.Equal(t, tc.group.UpdatedAt, group.UpdatedAt, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group.UpdatedAt, group.UpdatedAt)) - assert.Equal(t, tc.group.UpdatedBy, group.UpdatedBy, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group.UpdatedBy, group.UpdatedBy)) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestChangeStatus(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - - group, err := repo.Save(context.Background(), validGroup) - require.Nil(t, err, fmt.Sprintf("save group unexpected error: %s", err)) - - cases := []struct { - desc string - group mggroups.Group - err error - }{ - { - desc: "change status group successfully", - group: mggroups.Group{ - ID: group.ID, - Status: mggroups.DisabledStatus, - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "change status group with invalid ID", - group: mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Status: mggroups.DisabledStatus, - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "change status group with empty ID", - group: mggroups.Group{ - Status: mggroups.DisabledStatus, - UpdatedAt: time.Now().UTC().Truncate(time.Microsecond), - UpdatedBy: testsutil.GenerateUUID(t), - }, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - switch group, err := repo.ChangeStatus(context.Background(), tc.group); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.group.ID, group.ID, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group.ID, group.ID)) - assert.Equal(t, tc.group.UpdatedAt, group.UpdatedAt, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group.UpdatedAt, group.UpdatedAt)) - assert.Equal(t, tc.group.UpdatedBy, group.UpdatedBy, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group.UpdatedBy, group.UpdatedBy)) - default: - 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 groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - - group, err := repo.Save(context.Background(), validGroup) - require.Nil(t, err, fmt.Sprintf("save group unexpected error: %s", err)) - - cases := []struct { - desc string - id string - group mggroups.Group - err error - }{ - { - desc: "retrieve group by id successfully", - id: group.ID, - group: validGroup, - err: nil, - }, - { - desc: "retrieve group by id with invalid ID", - id: invalidID, - group: mggroups.Group{}, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve group by id with empty ID", - id: "", - group: mggroups.Group{}, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - switch group, err := repo.RetrieveByID(context.Background(), tc.id); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.group, group, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.group, group)) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestRetrieveAll(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - num := 200 - - var items []mggroups.Group - parentID := "" - for i := 0; i < num; i++ { - name := namegen.Generate() - group := mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Parent: parentID, - Name: name, - Description: strings.Repeat("a", 64), - Metadata: map[string]interface{}{"name": name}, - CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, - } - _, err := repo.Save(context.Background(), group) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - items = append(items, group) - parentID = group.ID - } - - cases := []struct { - desc string - page mggroups.Page - response mggroups.Page - err error - }{ - { - desc: "retrieve groups successfully", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 0, - Limit: 10, - }, - Groups: items[:10], - }, - err: nil, - }, - { - desc: "retrieve groups with offset", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 10, - Limit: 10, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 10, - Limit: 10, - }, - Groups: items[10:20], - }, - err: nil, - }, - { - desc: "retrieve groups with limit", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 50, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 0, - Limit: 50, - }, - Groups: items[:50], - }, - err: nil, - }, - { - desc: "retrieve groups with offset and limit", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 50, - Limit: 50, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 50, - Limit: 50, - }, - Groups: items[50:100], - }, - err: nil, - }, - { - desc: "retrieve groups with offset out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 1000, - Limit: 50, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 1000, - Limit: 50, - }, - Groups: []mggroups.Group(nil), - }, - err: nil, - }, - { - desc: "retrieve groups with offset and limit out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 170, - Limit: 50, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 170, - Limit: 50, - }, - Groups: items[170:200], - }, - err: nil, - }, - { - desc: "retrieve groups with limit out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 1000, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 0, - Limit: 1000, - }, - Groups: items, - }, - err: nil, - }, - { - desc: "retrieve groups with empty page", - page: mggroups.Page{}, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 0, - Limit: 0, - }, - Groups: []mggroups.Group(nil), - }, - err: nil, - }, - { - desc: "retrieve groups with name", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - Name: items[0].Name, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve groups with domain", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - DomainID: items[0].Domain, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve groups with metadata", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - Metadata: items[0].Metadata, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve groups with invalid metadata", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 0, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group(nil), - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "retrieve parent groups", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: uint64(num), - }, - ParentID: items[5].ID, - Direction: 1, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 0, - Limit: uint64(num), - }, - Groups: items[:6], - }, - err: nil, - }, - { - desc: "retrieve children groups", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: uint64(num), - }, - ParentID: items[150].ID, - Direction: -1, - }, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: uint64(num), - Offset: 0, - Limit: uint64(num), - }, - Groups: items[150:], - }, - err: nil, - }, - } - - for _, tc := range cases { - switch groups, err := repo.RetrieveAll(context.Background(), tc.page); { - 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)) - assert.Equal(t, tc.response.Limit, groups.Limit, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Limit, groups.Limit)) - assert.Equal(t, tc.response.Offset, groups.Offset, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Offset, groups.Offset)) - for i := range tc.response.Groups { - tc.response.Groups[i].Level = groups.Groups[i].Level - tc.response.Groups[i].Path = groups.Groups[i].Path - } - assert.ElementsMatch(t, groups.Groups, tc.response.Groups, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, tc.response.Groups, groups.Groups)) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestRetrieveByIDs(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - num := 200 - - var items []mggroups.Group - parentID := "" - for i := 0; i < num; i++ { - name := namegen.Generate() - group := mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Parent: parentID, - Name: name, - Description: strings.Repeat("a", 64), - Metadata: map[string]interface{}{"name": name}, - CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, - } - _, err := repo.Save(context.Background(), group) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - items = append(items, group) - parentID = group.ID - } - - cases := []struct { - desc string - page mggroups.Page - ids []string - response mggroups.Page - err error - }{ - { - desc: "retrieve groups successfully", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - }, - }, - ids: getIDs(items[0:3]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 3, - Offset: 0, - Limit: 10, - }, - Groups: items[0:3], - }, - err: nil, - }, - { - desc: "retrieve groups with empty ids", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - }, - }, - ids: []string{}, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group(nil), - }, - err: nil, - }, - { - desc: "retrieve groups with empty ids but with domain", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - DomainID: items[0].Domain, - }, - }, - ids: []string{}, - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve groups with offset", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 10, - Limit: 10, - }, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 20, - Offset: 10, - Limit: 10, - }, - Groups: items[10:20], - }, - err: nil, - }, - { - desc: "retrieve groups with offset out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 1000, - Limit: 50, - }, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 20, - Offset: 1000, - Limit: 50, - }, - Groups: []mggroups.Group(nil), - }, - err: nil, - }, - { - desc: "retrieve groups with offset and limit out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 15, - Limit: 10, - }, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 20, - Offset: 15, - Limit: 10, - }, - Groups: items[15:20], - }, - err: nil, - }, - { - desc: "retrieve groups with limit out of range", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 1000, - }, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 20, - Offset: 0, - Limit: 1000, - }, - Groups: items[:20], - }, - err: nil, - }, - { - desc: "retrieve groups with name", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - Name: items[0].Name, - }, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve groups with domain", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - DomainID: items[0].Domain, - }, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve groups with metadata", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - Metadata: items[0].Metadata, - }, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve groups with invalid metadata", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: 10, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 0, - Offset: 0, - Limit: 10, - }, - Groups: []mggroups.Group(nil), - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "retrieve parent groups", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: uint64(num), - }, - ParentID: items[5].ID, - Direction: 1, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 20, - Offset: 0, - Limit: uint64(num), - }, - Groups: items[:6], - }, - err: nil, - }, - { - desc: "retrieve children groups", - page: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Offset: 0, - Limit: uint64(num), - }, - ParentID: items[15].ID, - Direction: -1, - }, - ids: getIDs(items[0:20]), - response: mggroups.Page{ - PageMeta: mggroups.PageMeta{ - Total: 20, - Offset: 0, - Limit: uint64(num), - }, - Groups: items[15:20], - }, - err: nil, - }, - } - - for _, tc := range cases { - switch groups, err := repo.RetrieveByIDs(context.Background(), tc.page, 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)) - assert.Equal(t, tc.response.Limit, groups.Limit, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Limit, groups.Limit)) - assert.Equal(t, tc.response.Offset, groups.Offset, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Offset, groups.Offset)) - for i := range tc.response.Groups { - tc.response.Groups[i].Level = groups.Groups[i].Level - tc.response.Groups[i].Path = groups.Groups[i].Path - } - assert.ElementsMatch(t, groups.Groups, tc.response.Groups, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, tc.response.Groups, groups.Groups)) - default: - 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 groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - - group, err := repo.Save(context.Background(), validGroup) - require.Nil(t, err, fmt.Sprintf("save group unexpected error: %s", err)) - - cases := []struct { - desc string - id string - err error - }{ - { - desc: "delete group successfully", - id: group.ID, - err: nil, - }, - { - desc: "delete group with invalid ID", - id: invalidID, - err: repoerr.ErrNotFound, - }, - { - desc: "delete group with empty ID", - id: "", - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - switch err := repo.Delete(context.Background(), tc.id); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestAssignParentGroup(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - - num := 10 - - var items []mggroups.Group - parentID := "" - for i := 0; i < num; i++ { - name := namegen.Generate() - group := mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Parent: parentID, - Name: name, - Description: strings.Repeat("a", 64), - Metadata: map[string]interface{}{"name": name}, - CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, - } - _, err := repo.Save(context.Background(), group) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - items = append(items, group) - parentID = group.ID - } - - cases := []struct { - desc string - id string - ids []string - err error - }{ - { - desc: "assign parent group successfully", - id: items[0].ID, - ids: []string{items[1].ID, items[2].ID, items[3].ID, items[4].ID, items[5].ID}, - err: nil, - }, - { - desc: "assign parent group with invalid ID", - id: testsutil.GenerateUUID(t), - ids: []string{items[1].ID, items[2].ID, items[3].ID, items[4].ID, items[5].ID}, - err: repoerr.ErrCreateEntity, - }, - { - desc: "assign parent group with empty ID", - id: "", - ids: []string{items[1].ID, items[2].ID, items[3].ID, items[4].ID, items[5].ID}, - err: repoerr.ErrCreateEntity, - }, - { - desc: "assign parent group with invalid group IDs", - id: items[0].ID, - ids: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t), testsutil.GenerateUUID(t), testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - err: nil, - }, - { - desc: "assign parent group with empty group IDs", - id: items[0].ID, - ids: []string{}, - err: nil, - }, - } - - for _, tc := range cases { - switch err := repo.AssignParentGroup(context.Background(), tc.id, tc.ids...); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestUnassignParentGroup(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM groups") - require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err)) - }) - - repo := postgres.New(database) - - num := 10 - - var items []mggroups.Group - parentID := "" - for i := 0; i < num; i++ { - name := namegen.Generate() - group := mggroups.Group{ - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Parent: parentID, - Name: name, - Description: strings.Repeat("a", 64), - Metadata: map[string]interface{}{"name": name}, - CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - Status: mggroups.EnabledStatus, - } - _, err := repo.Save(context.Background(), group) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - items = append(items, group) - parentID = group.ID - } - - cases := []struct { - desc string - id string - ids []string - err error - }{ - { - desc: "un-assign parent group successfully", - id: items[0].ID, - ids: []string{items[1].ID, items[2].ID, items[3].ID, items[4].ID, items[5].ID}, - err: nil, - }, - { - desc: "un-assign parent group with invalid ID", - id: testsutil.GenerateUUID(t), - ids: []string{items[1].ID, items[2].ID, items[3].ID, items[4].ID, items[5].ID}, - err: repoerr.ErrCreateEntity, - }, - { - desc: "un-assign parent group with empty ID", - id: "", - ids: []string{items[1].ID, items[2].ID, items[3].ID, items[4].ID, items[5].ID}, - err: repoerr.ErrCreateEntity, - }, - { - desc: "un-assign parent group with invalid group IDs", - id: items[0].ID, - ids: []string{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t), testsutil.GenerateUUID(t), testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - err: nil, - }, - { - desc: "un-assign parent group with empty group IDs", - id: items[0].ID, - ids: []string{}, - err: nil, - }, - } - - for _, tc := range cases { - switch err := repo.UnassignParentGroup(context.Background(), tc.id, tc.ids...); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func getIDs(groups []mggroups.Group) []string { - var ids []string - for _, group := range groups { - ids = append(ids, group.ID) - } - - return ids -} diff --git a/internal/groups/postgres/init.go b/internal/groups/postgres/init.go deleted file mode 100644 index 0b799c46c..000000000 --- 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/postgres/setup_test.go b/internal/groups/postgres/setup_test.go deleted file mode 100644 index a809a2b48..000000000 --- a/internal/groups/postgres/setup_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "database/sql" - "fmt" - "log" - "os" - "testing" - "time" - - gpostgres "github.com/absmach/magistrala/internal/groups/postgres" - "github.com/absmach/magistrala/pkg/postgres" - pgclient "github.com/absmach/magistrala/pkg/postgres" - "github.com/jmoiron/sqlx" - "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") - - // exponential backoff-retry, because the application in the container might not be ready to accept connections yet - pool.MaxWait = 120 * time.Second - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err := sql.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := pgclient.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = pgclient.Setup(dbConfig, *gpostgres.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/internal/groups/service.go b/internal/groups/service.go deleted file mode 100644 index 807a91772..000000000 --- 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 799a03f91..000000000 --- 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/status.go b/internal/groups/status.go deleted file mode 100644 index d967dbc0b..000000000 --- a/internal/groups/status.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package groups - -import svcerr "github.com/absmach/magistrala/pkg/errors/service" - -// Status represents Group status. -type Status uint8 - -// Possible Group status values. -const ( - // EnabledStatus represents enabled Group. - EnabledStatus Status = iota - // DisabledStatus represents disabled Group. - DisabledStatus - - // AllStatus is used for querying purposes to list groups irrespective - // of their status - both active and inactive. It is never stored in the - // database as the actual Group status and should always be the largest - // value in this enumeration. - AllStatus -) - -// String representation of the possible status values. -const ( - Disabled = "disabled" - Enabled = "enabled" - All = "all" - Unknown = "unknown" -) - -// String converts group status to string literal. -func (s Status) String() string { - switch s { - case DisabledStatus: - return Disabled - case EnabledStatus: - return Enabled - case AllStatus: - return All - default: - return Unknown - } -} - -// ToStatus converts string value to a valid Group status. -func ToStatus(status string) (Status, error) { - switch status { - case Disabled: - return DisabledStatus, nil - case Enabled: - return EnabledStatus, nil - case All: - return AllStatus, nil - } - return Status(0), svcerr.ErrInvalidStatus -} diff --git a/internal/groups/status_test.go b/internal/groups/status_test.go deleted file mode 100644 index a715ee392..000000000 --- a/internal/groups/status_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package groups_test - -import ( - "testing" - - "github.com/absmach/magistrala/internal/groups" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/stretchr/testify/assert" -) - -func TestStatus_String(t *testing.T) { - cases := []struct { - name string - status groups.Status - expected string - }{ - {"Enabled", groups.EnabledStatus, "enabled"}, - {"Disabled", groups.DisabledStatus, "disabled"}, - {"All", groups.AllStatus, "all"}, - {"Unknown", groups.Status(100), "unknown"}, - } - - for _, tc := range cases { - got := tc.status.String() - assert.Equal(t, tc.expected, got, "Status.String() = %v, expected %v", got, tc.expected) - } -} - -func TestToStatus(t *testing.T) { - cases := []struct { - name string - status string - gstatus groups.Status - err error - }{ - {"Enabled", "enabled", groups.EnabledStatus, nil}, - {"Disabled", "disabled", groups.DisabledStatus, nil}, - {"All", "all", groups.AllStatus, nil}, - {"Unknown", "unknown", groups.Status(0), svcerr.ErrInvalidStatus}, - } - - for _, tc := range cases { - got, err := groups.ToStatus(tc.status) - assert.Equal(t, tc.err, err, "ToStatus() error = %v, expected %v", err, tc.err) - assert.Equal(t, tc.gstatus, got, "ToStatus() = %v, expected %v", got, tc.gstatus) - } -} diff --git a/internal/groups/tracing/doc.go b/internal/groups/tracing/doc.go deleted file mode 100644 index 6a419f3b5..000000000 --- a/internal/groups/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala Users Groups service. -// -// This package provides tracing middleware for Magistrala Users Groups service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala Users Groups 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/internal/groups/tracing/tracing.go b/internal/groups/tracing/tracing.go deleted file mode 100644 index 190188668..000000000 --- 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/testsutil/common.go b/internal/testsutil/common.go index f6048a856..83f869621 100644 --- a/internal/testsutil/common.go +++ b/internal/testsutil/common.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/absmach/magistrala/pkg/uuid" + "github.com/absmach/supermq/pkg/uuid" "github.com/stretchr/testify/require" ) diff --git a/invitations/README.md b/invitations/README.md deleted file mode 100644 index de5c65fba..000000000 --- a/invitations/README.md +++ /dev/null @@ -1,80 +0,0 @@ -# Invitation Service - -Invitation service is responsible for sending invitations to users to join a domain. - -## 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_INVITATION_LOG_LEVEL | Log level for the Invitation service | debug | -| MG_USERS_URL | Users service URL | | -| MG_DOMAINS_URL | Domains service URL | | -| MG_INVITATIONS_HTTP_HOST | Invitation service HTTP listening host | localhost | -| MG_INVITATIONS_HTTP_PORT | Invitation service HTTP listening port | 9020 | -| MG_INVITATIONS_HTTP_SERVER_CERT | Invitation service server certificate | "" | -| MG_INVITATIONS_HTTP_SERVER_KEY | Invitation service server key | "" | -| MG_AUTH_GRPC_URL | Auth service gRPC URL | localhost:8181 | -| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | -| MG_AUTH_GRPC_CLIENT_CERT | Path to client certificate in PEM format | "" | -| MG_AUTH_GRPC_CLIENT_KEY | Path to client key in PEM format | "" | -| MG_AUTH_GRPC_CLIENT_CA_CERTS | Path to trusted CAs in PEM format | "" | -| MG_INVITATIONS_DB_HOST | Invitation service database host | localhost | -| MG_INVITATIONS_DB_USER | Invitation service database user | magistrala | -| MG_INVITATIONS_DB_PASS | Invitation service database password | magistrala | -| MG_INVITATIONS_DB_PORT | Invitation service database port | 5432 | -| MG_INVITATIONS_DB_NAME | Invitation service database name | invitations | -| MG_INVITATIONS_DB_SSL_MODE | Invitation service database SSL mode | disable | -| MG_INVITATIONS_DB_SSL_CERT | Invitation service database SSL certificate | "" | -| MG_INVITATIONS_DB_SSL_KEY | Invitation service database SSL key | "" | -| MG_INVITATIONS_DB_SSL_ROOT_CERT | Invitation service database SSL root certificate | "" | -| MG_INVITATIONS_INSTANCE_ID | Invitation service instance ID | | - -## Deployment - -The service itself is distributed as Docker container. Check the [`invitation`](https://github.com/absmach/amdm/blob/main/docker/docker-compose.yml) 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 http -make invitation - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_INVITATION_LOG_LEVEL=info \ -MG_INVITATIONS_ENDPOINT=/invitations \ -MG_USERS_URL="http://localhost:9002" \ -MG_DOMAINS_URL="http://localhost:8189" \ -MG_INVITATIONS_HTTP_HOST=localhost \ -MG_INVITATIONS_HTTP_PORT=9020 \ -MG_INVITATIONS_HTTP_SERVER_CERT="" \ -MG_INVITATIONS_HTTP_SERVER_KEY="" \ -MG_AUTH_GRPC_URL=localhost:8181 \ -MG_AUTH_GRPC_TIMEOUT=1s \ -MG_AUTH_GRPC_CLIENT_CERT="" \ -MG_AUTH_GRPC_CLIENT_KEY="" \ -MG_AUTH_GRPC_CLIENT_CA_CERTS="" \ -MG_INVITATIONS_DB_HOST=localhost \ -MG_INVITATIONS_DB_USER=magistrala \ -MG_INVITATIONS_DB_PASS=magistrala \ -MG_INVITATIONS_DB_PORT=5432 \ -MG_INVITATIONS_DB_NAME=invitations \ -MG_INVITATIONS_DB_SSL_MODE=disable \ -MG_INVITATIONS_DB_SSL_CERT="" \ -MG_INVITATIONS_DB_SSL_KEY="" \ -MG_INVITATIONS_DB_SSL_ROOT_CERT="" \ -$GOBIN/magistrala-invitation -``` - -## Usage - -For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=invitations.yml). diff --git a/invitations/api/doc.go b/invitations/api/doc.go deleted file mode 100644 index 7cd03c095..000000000 --- a/invitations/api/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api diff --git a/invitations/api/endpoint.go b/invitations/api/endpoint.go deleted file mode 100644 index 08adfc43d..000000000 --- a/invitations/api/endpoint.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/invitations" - "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" -) - -// InvitationSent is the message returned when an invitation is sent. -const InvitationSent = "invitation sent" - -func sendInvitationEndpoint(svc invitations.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(sendInvitationReq) - 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 - } - session.DomainID = req.DomainID - invitation := invitations.Invitation{ - UserID: req.UserID, - DomainID: req.DomainID, - Relation: req.Relation, - Resend: req.Resend, - } - - if err := svc.SendInvitation(ctx, session, invitation); err != nil { - return nil, err - } - - return sendInvitationRes{ - Message: InvitationSent, - }, nil - } -} - -func viewInvitationEndpoint(svc invitations.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(invitationReq) - 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 - } - session.DomainID = req.domainID - invitation, err := svc.ViewInvitation(ctx, session, req.userID, req.domainID) - if err != nil { - return nil, err - } - - return viewInvitationRes{ - Invitation: invitation, - }, nil - } -} - -func listInvitationsEndpoint(svc invitations.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listInvitationsReq) - 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 - } - session.DomainID = req.DomainID - - page, err := svc.ListInvitations(ctx, session, req.Page) - if err != nil { - return nil, err - } - - return listInvitationsRes{ - page, - }, nil - } -} - -func acceptInvitationEndpoint(svc invitations.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(acceptInvitationReq) - 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.AcceptInvitation(ctx, session, req.DomainID); err != nil { - return nil, err - } - - return acceptInvitationRes{}, nil - } -} - -func rejectInvitationEndpoint(svc invitations.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(acceptInvitationReq) - 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.RejectInvitation(ctx, session, req.DomainID); err != nil { - return nil, err - } - - return rejectInvitationRes{}, nil - } -} - -func deleteInvitationEndpoint(svc invitations.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(invitationReq) - 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 - } - session.DomainID = req.domainID - - if err := svc.DeleteInvitation(ctx, session, req.userID, req.domainID); err != nil { - return nil, err - } - - return deleteInvitationRes{}, nil - } -} diff --git a/invitations/api/endpoint_test.go b/invitations/api/endpoint_test.go deleted file mode 100644 index c81e5ee0c..000000000 --- a/invitations/api/endpoint_test.go +++ /dev/null @@ -1,672 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/invitations/api" - "github.com/absmach/magistrala/invitations/mocks" - 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" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - validToken = "valid" - validContenType = "application/json" - validID = testsutil.GenerateUUID(&testing.T{}) - domainID = testsutil.GenerateUUID(&testing.T{}) -) - -type testRequest struct { - client *http.Client - method string - url string - token string - contentType string - body io.Reader -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, tr.body) - if err != nil { - return nil, err - } - - if tr.token != "" { - req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) - } - - if tr.contentType != "" { - req.Header.Set("Content-Type", tr.contentType) - } - - return tr.client.Do(req) -} - -func newIvitationsServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - svc := new(mocks.Service) - logger := mglog.NewMock() - authn := new(authnmocks.Authentication) - mux := api.MakeHandler(svc, logger, authn, "test") - return httptest.NewServer(mux), svc, authn -} - -func TestSendInvitation(t *testing.T) { - is, svc, authn := newIvitationsServer() - - cases := []struct { - desc string - token string - data string - contentType string - status int - authnRes mgauthn.Session - authnErr error - svcErr error - }{ - { - desc: "valid request", - token: validToken, - data: fmt.Sprintf(`{"user_id": "%s","domain_id": "%s", "relation": "%s"}`, validID, domainID, "domain"), - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - status: http.StatusCreated, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid token", - token: "", - data: fmt.Sprintf(`{"user_id": "%s","domain_id": "%s", "relation": "%s"}`, validID, validID, "domain"), - status: http.StatusUnauthorized, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "empty domain_id", - token: validToken, - data: fmt.Sprintf(`{"user_id": "%s","domain_id": "%s", "relation": "%s"}`, validID, "", "domain"), - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid content type", - token: validToken, - data: fmt.Sprintf(`{"user_id": "%s","domain_id": "%s", "relation": "%s"}`, validID, validID, "domain"), - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - status: http.StatusUnsupportedMediaType, - contentType: "text/plain", - svcErr: nil, - }, - { - desc: "invalid data", - token: validToken, - data: `data`, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with service error", - token: validToken, - data: fmt.Sprintf(`{"user_id": "%s", "domain_id": "%s", "relation": "%s"}`, validID, domainID, "domain"), - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - status: http.StatusForbidden, - contentType: validContenType, - svcErr: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - repoCall := svc.On("SendInvitation", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcErr) - req := testRequest{ - client: is.Client(), - method: http.MethodPost, - url: is.URL + "/invitations", - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(tc.data), - } - - res, err := req.make() - assert.Nil(t, err, tc.desc) - assert.Equal(t, tc.status, res.StatusCode, tc.desc) - repoCall.Unset() - authnCall.Unset() - }) - } -} - -func TestListInvitation(t *testing.T) { - is, svc, authn := newIvitationsServer() - - cases := []struct { - desc string - token string - query string - contentType string - status int - svcErr error - authnRes mgauthn.Session - authnErr error - }{ - { - desc: "valid request", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid token", - token: "", - status: http.StatusUnauthorized, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with offset", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: "offset=1", - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with invalid offset", - token: validToken, - query: "offset=invalid", - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with limit", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: "limit=1", - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with invalid limit", - token: validToken, - query: "limit=invalid", - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with user_id", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: fmt.Sprintf("user_id=%s", validID), - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with duplicate user_id", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: "user_id=1&user_id=2", - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with invited_by", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: fmt.Sprintf("invited_by=%s", validID), - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with duplicate invited_by", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: "invited_by=1&invited_by=2", - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with relation", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: fmt.Sprintf("relation=%s", "relation"), - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with duplicate relation", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: "relation=1&relation=2", - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with state", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - query: "state=pending", - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with invalid state", - token: validToken, - query: "state=invalid", - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with duplicate state", - token: validToken, - query: "state=all&state=all", - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with service error", - authnRes: mgauthn.Session{UserID: validID, DomainUserID: domainID + "_" + validID}, - token: validToken, - status: http.StatusForbidden, - contentType: validContenType, - svcErr: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - repoCall := svc.On("ListInvitations", mock.Anything, tc.authnRes, mock.Anything).Return(invitations.InvitationPage{}, tc.svcErr) - req := testRequest{ - client: is.Client(), - method: http.MethodGet, - url: is.URL + "/invitations?" + tc.query, - token: tc.token, - contentType: tc.contentType, - } - res, err := req.make() - assert.Nil(t, err, tc.desc) - assert.Equal(t, tc.status, res.StatusCode, tc.desc) - repoCall.Unset() - authnCall.Unset() - }) - } -} - -func TestViewInvitation(t *testing.T) { - is, svc, authn := newIvitationsServer() - - cases := []struct { - desc string - token string - domainID string - userID string - contentType string - status int - svcErr error - authnRes mgauthn.Session - authnErr error - }{ - { - desc: "valid request", - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - token: validToken, - userID: validID, - domainID: domainID, - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid token", - token: "", - userID: validID, - domainID: domainID, - status: http.StatusUnauthorized, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with service error", - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - token: validToken, - userID: validID, - domainID: domainID, - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: svcerr.ErrViewEntity, - }, - { - desc: "with empty user_id", - token: validToken, - userID: "", - domainID: domainID, - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with empty domain", - token: validToken, - userID: validID, - domainID: "", - status: http.StatusNotFound, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with empty user_id and domain_id", - token: validToken, - userID: "", - domainID: "", - status: http.StatusNotFound, - contentType: validContenType, - svcErr: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - repoCall := svc.On("ViewInvitation", mock.Anything, tc.authnRes, tc.userID, tc.domainID).Return(invitations.Invitation{}, tc.svcErr) - req := testRequest{ - client: is.Client(), - method: http.MethodGet, - url: is.URL + "/invitations/" + tc.userID + "/" + tc.domainID, - token: tc.token, - contentType: tc.contentType, - } - - res, err := req.make() - assert.Nil(t, err, tc.desc) - assert.Equal(t, tc.status, res.StatusCode, tc.desc) - repoCall.Unset() - authnCall.Unset() - }) - } -} - -func TestDeleteInvitation(t *testing.T) { - is, svc, authn := newIvitationsServer() - _ = authn - - cases := []struct { - desc string - token string - domainID string - userID string - contentType string - status int - svcErr error - authnRes mgauthn.Session - authnErr error - }{ - { - desc: "valid request", - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - token: validToken, - userID: validID, - domainID: domainID, - status: http.StatusNoContent, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid token", - token: "", - userID: validID, - domainID: domainID, - status: http.StatusUnauthorized, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with service error", - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - token: validToken, - userID: validID, - domainID: domainID, - status: http.StatusForbidden, - contentType: validContenType, - svcErr: svcerr.ErrAuthorization, - }, - { - desc: "with empty user_id", - token: validToken, - userID: "", - domainID: domainID, - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with empty domain_id", - token: validToken, - userID: validID, - domainID: "", - status: http.StatusNotFound, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with empty user_id and domain_id", - token: validToken, - userID: "", - domainID: "", - status: http.StatusNotFound, - contentType: validContenType, - svcErr: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - repoCall := svc.On("DeleteInvitation", mock.Anything, tc.authnRes, tc.userID, tc.domainID).Return(tc.svcErr) - req := testRequest{ - client: is.Client(), - method: http.MethodDelete, - url: is.URL + "/invitations/" + tc.userID + "/" + tc.domainID, - token: tc.token, - contentType: tc.contentType, - } - - res, err := req.make() - assert.Nil(t, err, tc.desc) - assert.Equal(t, tc.status, res.StatusCode, tc.desc) - repoCall.Unset() - authnCall.Unset() - }) - } -} - -func TestAcceptInvitation(t *testing.T) { - is, svc, authn := newIvitationsServer() - _ = authn - cases := []struct { - desc string - token string - data string - contentType string - status int - svcErr error - authnRes mgauthn.Session - authnErr error - }{ - { - desc: "valid request", - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - data: fmt.Sprintf(`{"domain_id": "%s"}`, validID), - token: validToken, - status: http.StatusNoContent, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid token", - token: "", - data: fmt.Sprintf(`{"domain_id": "%s"}`, validID), - status: http.StatusUnauthorized, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "with service error", - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - token: validToken, - data: fmt.Sprintf(`{"domain_id": "%s"}`, validID), - status: http.StatusForbidden, - contentType: validContenType, - svcErr: svcerr.ErrAuthorization, - }, - { - desc: "invalid content type", - token: validToken, - data: fmt.Sprintf(`{"domain_id": "%s"}`, validID), - status: http.StatusUnsupportedMediaType, - contentType: "text/plain", - svcErr: nil, - }, - { - desc: "invalid data", - token: validToken, - data: `data`, - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - repoCall := svc.On("AcceptInvitation", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcErr) - req := testRequest{ - client: is.Client(), - method: http.MethodPost, - url: is.URL + "/invitations/accept", - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(tc.data), - } - - res, err := req.make() - assert.Nil(t, err, tc.desc) - assert.Equal(t, tc.status, res.StatusCode, tc.desc) - repoCall.Unset() - authnCall.Unset() - }) - } -} - -func TestRejectInvitation(t *testing.T) { - is, svc, authn := newIvitationsServer() - _ = authn - - cases := []struct { - desc string - token string - data string - contentType string - status int - svcErr error - authnRes mgauthn.Session - authnErr error - }{ - { - desc: "valid request", - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - token: validToken, - data: fmt.Sprintf(`{"domain_id": "%s"}`, validID), - status: http.StatusNoContent, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid token", - token: "", - data: fmt.Sprintf(`{"domain_id": "%s"}`, validID), - status: http.StatusUnauthorized, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "unauthorized error", - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID}, - token: validToken, - data: fmt.Sprintf(`{"domain_id": "%s"}`, "invalid"), - status: http.StatusForbidden, - contentType: validContenType, - svcErr: svcerr.ErrAuthorization, - }, - { - desc: "invalid content type", - token: validToken, - data: fmt.Sprintf(`{"domain_id": "%s"}`, validID), - status: http.StatusUnsupportedMediaType, - contentType: "text/plain", - svcErr: nil, - }, - { - desc: "invalid data", - token: validToken, - data: `data`, - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - repoCall := svc.On("RejectInvitation", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcErr) - req := testRequest{ - client: is.Client(), - method: http.MethodPost, - url: is.URL + "/invitations/reject", - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(tc.data), - } - - res, err := req.make() - assert.Nil(t, err, tc.desc) - assert.Equal(t, tc.status, res.StatusCode, tc.desc) - repoCall.Unset() - authnCall.Unset() - }) - } -} diff --git a/invitations/api/requests.go b/invitations/api/requests.go deleted file mode 100644 index 74c42acaa..000000000 --- a/invitations/api/requests.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/apiutil" -) - -const maxLimitSize = 100 - -type sendInvitationReq struct { - UserID string `json:"user_id,omitempty"` - DomainID string `json:"domain_id,omitempty"` - Relation string `json:"relation,omitempty"` - Resend bool `json:"resend,omitempty"` -} - -func (req *sendInvitationReq) validate() error { - if req.UserID == "" { - return apiutil.ErrMissingID - } - if req.DomainID == "" { - return apiutil.ErrMissingDomainID - } - if err := invitations.CheckRelation(req.Relation); err != nil { - return err - } - - return nil -} - -type listInvitationsReq struct { - invitations.Page -} - -func (req *listInvitationsReq) validate() error { - if req.Page.Limit > maxLimitSize || req.Page.Limit < 1 { - return apiutil.ErrLimitSize - } - - return nil -} - -type acceptInvitationReq struct { - DomainID string `json:"domain_id,omitempty"` -} - -func (req *acceptInvitationReq) validate() error { - if req.DomainID == "" { - return apiutil.ErrMissingDomainID - } - - return nil -} - -type invitationReq struct { - userID string - domainID string -} - -func (req *invitationReq) validate() error { - if req.userID == "" { - return apiutil.ErrMissingID - } - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - - return nil -} diff --git a/invitations/api/requests_test.go b/invitations/api/requests_test.go deleted file mode 100644 index 17d731d79..000000000 --- a/invitations/api/requests_test.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "testing" - - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/policies" - "github.com/stretchr/testify/assert" -) - -var valid = "valid" - -func TestSendInvitationReqValidation(t *testing.T) { - cases := []struct { - desc string - req sendInvitationReq - err error - }{ - { - desc: "valid request", - req: sendInvitationReq{ - UserID: valid, - DomainID: valid, - Relation: policies.DomainRelation, - Resend: true, - }, - err: nil, - }, - { - desc: "empty user ID", - req: sendInvitationReq{ - UserID: "", - DomainID: valid, - Relation: policies.DomainRelation, - Resend: true, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty domain_id", - req: sendInvitationReq{ - UserID: valid, - DomainID: "", - Relation: policies.DomainRelation, - Resend: true, - }, - err: apiutil.ErrMissingDomainID, - }, - { - desc: "missing relation", - req: sendInvitationReq{ - UserID: valid, - DomainID: valid, - Relation: "", - Resend: true, - }, - err: apiutil.ErrMissingRelation, - }, - { - desc: "invalid relation", - req: sendInvitationReq{ - UserID: valid, - DomainID: valid, - Relation: "invalid", - Resend: true, - }, - err: apiutil.ErrInvalidRelation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - }) - } -} - -func TestListInvitationsReq(t *testing.T) { - cases := []struct { - desc string - req listInvitationsReq - err error - }{ - { - desc: "valid request", - req: listInvitationsReq{ - Page: invitations.Page{Limit: 1}, - }, - err: nil, - }, - { - desc: "invalid limit", - req: listInvitationsReq{ - Page: invitations.Page{Limit: 1000}, - }, - err: apiutil.ErrLimitSize, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - }) - } -} - -func TestAcceptInvitationReq(t *testing.T) { - cases := []struct { - desc string - req acceptInvitationReq - err error - }{ - { - desc: "valid request", - req: acceptInvitationReq{ - DomainID: valid, - }, - err: nil, - }, - { - desc: "empty domain_id", - req: acceptInvitationReq{ - DomainID: "", - }, - err: apiutil.ErrMissingDomainID, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - err := tc.req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - }) - } -} - -func TestInvitationReqValidation(t *testing.T) { - cases := []struct { - desc string - req invitationReq - err error - }{ - { - desc: "valid request", - req: invitationReq{ - userID: valid, - domainID: valid, - }, - err: nil, - }, - { - desc: "empty user ID", - req: invitationReq{ - userID: "", - domainID: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty domain", - req: invitationReq{ - userID: valid, - domainID: "", - }, - err: apiutil.ErrMissingDomainID, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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/invitations/api/responses.go b/invitations/api/responses.go deleted file mode 100644 index 300ce90d6..000000000 --- a/invitations/api/responses.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/invitations" -) - -var ( - _ magistrala.Response = (*sendInvitationRes)(nil) - _ magistrala.Response = (*viewInvitationRes)(nil) - _ magistrala.Response = (*listInvitationsRes)(nil) - _ magistrala.Response = (*acceptInvitationRes)(nil) - _ magistrala.Response = (*rejectInvitationRes)(nil) - _ magistrala.Response = (*deleteInvitationRes)(nil) -) - -type sendInvitationRes struct { - Message string `json:"message"` -} - -func (res sendInvitationRes) Code() int { - return http.StatusCreated -} - -func (res sendInvitationRes) Headers() map[string]string { - return map[string]string{} -} - -func (res sendInvitationRes) Empty() bool { - return true -} - -type viewInvitationRes struct { - invitations.Invitation `json:",inline"` -} - -func (res viewInvitationRes) Code() int { - return http.StatusOK -} - -func (res viewInvitationRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewInvitationRes) Empty() bool { - return false -} - -type listInvitationsRes struct { - invitations.InvitationPage `json:",inline"` -} - -func (res listInvitationsRes) Code() int { - return http.StatusOK -} - -func (res listInvitationsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res listInvitationsRes) Empty() bool { - return false -} - -type acceptInvitationRes struct{} - -func (res acceptInvitationRes) Code() int { - return http.StatusNoContent -} - -func (res acceptInvitationRes) Headers() map[string]string { - return map[string]string{} -} - -func (res acceptInvitationRes) Empty() bool { - return true -} - -type deleteInvitationRes struct{} - -func (res deleteInvitationRes) Code() int { - return http.StatusNoContent -} - -func (res deleteInvitationRes) Headers() map[string]string { - return map[string]string{} -} - -func (res deleteInvitationRes) Empty() bool { - return true -} - -type rejectInvitationRes struct{} - -func (res rejectInvitationRes) Code() int { - return http.StatusNoContent -} - -func (res rejectInvitationRes) Headers() map[string]string { - return map[string]string{} -} - -func (res rejectInvitationRes) Empty() bool { - return true -} diff --git a/invitations/api/transport.go b/invitations/api/transport.go deleted file mode 100644 index b8d6b6921..000000000 --- a/invitations/api/transport.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "encoding/json" - "log/slog" - "net/http" - "strings" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - "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" -) - -const ( - userIDKey = "user_id" - domainIDKey = "domain_id" - invitedByKey = "invited_by" - relationKey = "relation" - stateKey = "state" -) - -func MakeHandler(svc invitations.Service, logger *slog.Logger, authn mgauthn.Authentication, instanceID string) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - mux := chi.NewRouter() - - mux.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, false)) - - r.Route("/invitations", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - sendInvitationEndpoint(svc), - decodeSendInvitationReq, - api.EncodeResponse, - opts..., - ), "send_invitation").ServeHTTP) - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - listInvitationsEndpoint(svc), - decodeListInvitationsReq, - api.EncodeResponse, - opts..., - ), "list_invitations").ServeHTTP) - r.Route("/{user_id}/{domain_id}", func(r chi.Router) { - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - viewInvitationEndpoint(svc), - decodeInvitationReq, - api.EncodeResponse, - opts..., - ), "view_invitations").ServeHTTP) - r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( - deleteInvitationEndpoint(svc), - decodeInvitationReq, - api.EncodeResponse, - opts..., - ), "delete_invitation").ServeHTTP) - }) - r.Post("/accept", otelhttp.NewHandler(kithttp.NewServer( - acceptInvitationEndpoint(svc), - decodeAcceptInvitationReq, - api.EncodeResponse, - opts..., - ), "accept_invitation").ServeHTTP) - r.Post("/reject", otelhttp.NewHandler(kithttp.NewServer( - rejectInvitationEndpoint(svc), - decodeAcceptInvitationReq, - api.EncodeResponse, - opts..., - ), "reject_invitation").ServeHTTP) - }) - }) - - mux.Get("/health", magistrala.Health("invitations", instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} - -func decodeSendInvitationReq(_ 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 req sendInvitationReq - 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 decodeListInvitationsReq(_ context.Context, r *http.Request) (interface{}, error) { - offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - userID, err := apiutil.ReadStringQuery(r, userIDKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - invitedBy, err := apiutil.ReadStringQuery(r, invitedByKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - relation, err := apiutil.ReadStringQuery(r, relationKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - domainID, err := apiutil.ReadStringQuery(r, domainIDKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - st, err := apiutil.ReadStringQuery(r, stateKey, invitations.All.String()) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - state, err := invitations.ToState(st) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listInvitationsReq{ - Page: invitations.Page{ - Offset: offset, - Limit: limit, - InvitedBy: invitedBy, - UserID: userID, - Relation: relation, - DomainID: domainID, - State: state, - }, - } - - return req, nil -} - -func decodeAcceptInvitationReq(_ 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 req acceptInvitationReq - 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 decodeInvitationReq(_ context.Context, r *http.Request) (interface{}, error) { - req := invitationReq{ - userID: chi.URLParam(r, "user_id"), - domainID: chi.URLParam(r, "domain_id"), - } - - return req, nil -} diff --git a/invitations/doc.go b/invitations/doc.go deleted file mode 100644 index 124fb7577..000000000 --- a/invitations/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package invitations provides the API to manage invitations. -// -// An invitation is a request to join a domain. -package invitations diff --git a/invitations/invitations.go b/invitations/invitations.go deleted file mode 100644 index 86973f3f7..000000000 --- a/invitations/invitations.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package invitations - -import ( - "context" - "encoding/json" - "time" - - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/policies" -) - -// Invitation is an invitation to join a domain. -type Invitation struct { - InvitedBy string `json:"invited_by"` - UserID string `json:"user_id"` - DomainID string `json:"domain_id"` - Token string `json:"token,omitempty"` - Relation string `json:"relation,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - ConfirmedAt time.Time `json:"confirmed_at,omitempty"` - RejectedAt time.Time `json:"rejected_at,omitempty"` - Resend bool `json:"resend,omitempty"` -} - -// Page is a page of invitations. -type Page struct { - Offset uint64 `json:"offset" db:"offset"` - Limit uint64 `json:"limit" db:"limit"` - InvitedBy string `json:"invited_by,omitempty" db:"invited_by,omitempty"` - UserID string `json:"user_id,omitempty" db:"user_id,omitempty"` - DomainID string `json:"domain_id,omitempty" db:"domain_id,omitempty"` - Relation string `json:"relation,omitempty" db:"relation,omitempty"` - InvitedByOrUserID string `db:"invited_by_or_user_id,omitempty"` - State State `json:"state,omitempty"` -} - -// InvitationPage is a page of invitations. -type InvitationPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Invitations []Invitation `json:"invitations"` -} - -func (page InvitationPage) MarshalJSON() ([]byte, error) { - type Alias InvitationPage - a := struct { - Alias - }{ - Alias: Alias(page), - } - - if a.Invitations == nil { - a.Invitations = make([]Invitation, 0) - } - - return json.Marshal(a) -} - -// Service is an interface that defines methods for managing invitations. -// -//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" -type Service interface { - // SendInvitation sends an invitation to the given user. - // Only domain administrators and platform administrators can send invitations. - SendInvitation(ctx context.Context, session authn.Session, invitation Invitation) (err error) - - // ViewInvitation returns an invitation. - // People who can view invitations are: - // - the invited user: they can view their own invitations - // - the user who sent the invitation - // - domain administrators - // - platform administrators - ViewInvitation(ctx context.Context, session authn.Session, userID, domainID string) (invitation Invitation, err error) - - // ListInvitations returns a list of invitations. - // People who can list invitations are: - // - platform administrators can list all invitations - // - domain administrators can list invitations for their domain - // By default, it will list invitations the current user has sent or received. - ListInvitations(ctx context.Context, session authn.Session, page Page) (invitations InvitationPage, err error) - - // AcceptInvitation accepts an invitation by adding the user to the domain. - AcceptInvitation(ctx context.Context, session authn.Session, domainID string) (err error) - - // DeleteInvitation deletes an invitation. - // People who can delete invitations are: - // - the invited user: they can delete their own invitations - // - the user who sent the invitation - // - domain administrators - // - platform administrators - DeleteInvitation(ctx context.Context, session authn.Session, userID, domainID string) (err error) - - // RejectInvitation rejects an invitation. - // People who can reject invitations are: - // - the invited user: they can reject their own invitations - RejectInvitation(ctx context.Context, session authn.Session, domainID string) (err error) -} - -//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" -type Repository interface { - // Create creates an invitation. - Create(ctx context.Context, invitation Invitation) (err error) - - // Retrieve returns an invitation. - Retrieve(ctx context.Context, userID, domainID string) (Invitation, error) - - // RetrieveAll returns a list of invitations based on the given page. - RetrieveAll(ctx context.Context, page Page) (invitations InvitationPage, err error) - - // UpdateToken updates an invitation by setting the token. - UpdateToken(ctx context.Context, invitation Invitation) (err error) - - // UpdateConfirmation updates an invitation by setting the confirmation time. - UpdateConfirmation(ctx context.Context, invitation Invitation) (err error) - - // UpdateRejection updates an invitation by setting the rejection time. - UpdateRejection(ctx context.Context, invitation Invitation) (err error) - - // Delete deletes an invitation. - Delete(ctx context.Context, userID, domainID string) (err error) -} - -// CheckRelation checks if the given relation is valid. -// It returns an error if the relation is empty or invalid. -func CheckRelation(relation string) error { - if relation == "" { - return apiutil.ErrMissingRelation - } - if relation != policies.AdministratorRelation && - relation != policies.EditorRelation && - relation != policies.ContributorRelation && - relation != policies.MemberRelation && - relation != policies.GuestRelation && - relation != policies.DomainRelation && - relation != policies.ParentGroupRelation && - relation != policies.RoleGroupRelation && - relation != policies.GroupRelation && - relation != policies.PlatformRelation { - return apiutil.ErrInvalidRelation - } - - return nil -} diff --git a/invitations/invitations_test.go b/invitations/invitations_test.go deleted file mode 100644 index 2dce31648..000000000 --- a/invitations/invitations_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package invitations_test - -import ( - "fmt" - "testing" - - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/stretchr/testify/assert" -) - -func TestInvitation_MarshalJSON(t *testing.T) { - cases := []struct { - desc string - page invitations.InvitationPage - res string - }{ - { - desc: "empty page", - page: invitations.InvitationPage{ - Invitations: []invitations.Invitation(nil), - }, - res: `{"total":0,"offset":0,"limit":0,"invitations":[]}`, - }, - { - desc: "page with invitations", - page: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 0, - Invitations: []invitations.Invitation{ - { - InvitedBy: "John", - UserID: "123", - DomainID: "123", - }, - }, - }, - res: `{"total":1,"offset":0,"limit":0,"invitations":[{"invited_by":"John","user_id":"123","domain_id":"123","created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","confirmed_at":"0001-01-01T00:00:00Z","rejected_at":"0001-01-01T00:00:00Z"}]}`, - }, - } - - for _, tc := range cases { - data, err := tc.page.MarshalJSON() - assert.NoError(t, err, "Unexpected error: %v", err) - assert.Equal(t, tc.res, string(data), fmt.Sprintf("%s: expected %s, got %s", tc.desc, tc.res, string(data))) - } -} - -func TestCheckRelation(t *testing.T) { - cases := []struct { - relation string - err error - }{ - {"", apiutil.ErrMissingRelation}, - {"admin", apiutil.ErrInvalidRelation}, - {"editor", nil}, - {"contributor", nil}, - {"member", nil}, - {"guest", nil}, - {"domain", nil}, - {"parent_group", nil}, - {"role_group", nil}, - {"group", nil}, - {"platform", nil}, - } - - for _, tc := range cases { - err := invitations.CheckRelation(tc.relation) - assert.Equal(t, tc.err, err, "CheckRelation(%q) expected %v, got %v", tc.relation, tc.err, err) - } -} diff --git a/invitations/middleware/authorization.go b/invitations/middleware/authorization.go deleted file mode 100644 index 84d0a3293..000000000 --- a/invitations/middleware/authorization.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - - "github.com/absmach/magistrala/auth" - "github.com/absmach/magistrala/invitations" - "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" -) - -// ErrMemberExist indicates that the user is already a member of the domain. -var ErrMemberExist = errors.New("user is already a member of the domain") - -var _ invitations.Service = (*tracing)(nil) - -type authorizationMiddleware struct { - authz authz.Authorization - svc invitations.Service -} - -func AuthorizationMiddleware(authz authz.Authorization, svc invitations.Service) invitations.Service { - return &authorizationMiddleware{authz, svc} -} - -func (am *authorizationMiddleware) SendInvitation(ctx context.Context, session authn.Session, invitation invitations.Invitation) (err error) { - session.DomainUserID = auth.EncodeDomainUserID(session.DomainID, session.UserID) - domainUserId := auth.EncodeDomainUserID(invitation.DomainID, invitation.UserID) - if err := am.authorize(ctx, domainUserId, policies.MembershipPermission, policies.DomainType, invitation.DomainID); err == nil { - // return error if the user is already a member of the domain - return errors.Wrap(svcerr.ErrConflict, ErrMemberExist) - } - - if err := am.checkAdmin(ctx, session); err != nil { - return err - } - - return am.svc.SendInvitation(ctx, session, invitation) -} - -func (am *authorizationMiddleware) ViewInvitation(ctx context.Context, session authn.Session, userID, domain string) (invitation invitations.Invitation, err error) { - session.DomainUserID = auth.EncodeDomainUserID(session.DomainID, session.UserID) - if session.UserID != userID { - if err := am.checkAdmin(ctx, session); err != nil { - return invitations.Invitation{}, err - } - } - - return am.svc.ViewInvitation(ctx, session, userID, domain) -} - -func (am *authorizationMiddleware) ListInvitations(ctx context.Context, session authn.Session, page invitations.Page) (invs invitations.InvitationPage, err error) { - session.DomainUserID = auth.EncodeDomainUserID(session.DomainID, session.UserID) - if err := am.authorize(ctx, session.UserID, policies.AdminPermission, policies.PlatformType, policies.MagistralaObject); err == nil { - session.SuperAdmin = true - } - - if !session.SuperAdmin { - switch { - case page.DomainID != "": - if err := am.authorize(ctx, session.DomainUserID, policies.AdminPermission, policies.DomainType, page.DomainID); err != nil { - return invitations.InvitationPage{}, err - } - default: - page.InvitedByOrUserID = session.UserID - } - } - - return am.svc.ListInvitations(ctx, session, page) -} - -func (am *authorizationMiddleware) AcceptInvitation(ctx context.Context, session authn.Session, domainID string) (err error) { - return am.svc.AcceptInvitation(ctx, session, domainID) -} - -func (am *authorizationMiddleware) RejectInvitation(ctx context.Context, session authn.Session, domainID string) (err error) { - return am.svc.RejectInvitation(ctx, session, domainID) -} - -func (am *authorizationMiddleware) DeleteInvitation(ctx context.Context, session authn.Session, userID, domainID string) (err error) { - session.DomainUserID = auth.EncodeDomainUserID(session.DomainID, session.UserID) - if err := am.checkAdmin(ctx, session); err != nil { - return err - } - - return am.svc.DeleteInvitation(ctx, session, userID, domainID) -} - -// checkAdmin checks if the given user is a domain or platform administrator. -func (am *authorizationMiddleware) checkAdmin(ctx context.Context, session authn.Session) error { - if err := am.authorize(ctx, session.DomainUserID, policies.AdminPermission, policies.DomainType, session.DomainID); err == nil { - return nil - } - - if err := am.authorize(ctx, session.UserID, policies.AdminPermission, policies.PlatformType, policies.MagistralaObject); err == nil { - return nil - } - - return svcerr.ErrAuthorization -} - -func (am *authorizationMiddleware) authorize(ctx context.Context, subj, perm, objType, obj string) error { - req := authz.PolicyReq{ - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Subject: subj, - Permission: perm, - ObjectType: objType, - Object: obj, - } - if err := am.authz.Authorize(ctx, req); err != nil { - return err - } - - return nil -} diff --git a/invitations/middleware/doc.go b/invitations/middleware/doc.go deleted file mode 100644 index 1fdf252ff..000000000 --- a/invitations/middleware/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package middleware contains the middleware for the invitations service. -// It is responsible for the following: -// - Logging -// - Metrics -// - Tracing -package middleware diff --git a/invitations/middleware/logging.go b/invitations/middleware/logging.go deleted file mode 100644 index 1a64e5a9a..000000000 --- a/invitations/middleware/logging.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/authn" -) - -var _ invitations.Service = (*logging)(nil) - -type logging struct { - logger *slog.Logger - svc invitations.Service -} - -func Logging(logger *slog.Logger, svc invitations.Service) invitations.Service { - return &logging{logger, svc} -} - -func (lm *logging) SendInvitation(ctx context.Context, session authn.Session, invitation invitations.Invitation) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("user_id", invitation.UserID), - slog.String("domain_id", invitation.DomainID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Send invitation failed", args...) - return - } - lm.logger.Info("Send invitation completed successfully", args...) - }(time.Now()) - return lm.svc.SendInvitation(ctx, session, invitation) -} - -func (lm *logging) ViewInvitation(ctx context.Context, session authn.Session, userID, domainID string) (invitation invitations.Invitation, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("user_id", userID), - slog.String("domain_id", domainID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View invitation failed", args...) - return - } - lm.logger.Info("View invitation completed successfully", args...) - }(time.Now()) - return lm.svc.ViewInvitation(ctx, session, userID, domainID) -} - -func (lm *logging) ListInvitations(ctx context.Context, session authn.Session, page invitations.Page) (invs invitations.InvitationPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("page", - slog.Uint64("offset", page.Offset), - slog.Uint64("limit", page.Limit), - slog.Uint64("total", invs.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List invitations failed", args...) - return - } - lm.logger.Info("List invitations completed successfully", args...) - }(time.Now()) - return lm.svc.ListInvitations(ctx, session, page) -} - -func (lm *logging) AcceptInvitation(ctx context.Context, session authn.Session, domainID string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("domain_id", domainID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Accept invitation failed", args...) - return - } - lm.logger.Info("Accept invitation completed successfully", args...) - }(time.Now()) - return lm.svc.AcceptInvitation(ctx, session, domainID) -} - -func (lm *logging) RejectInvitation(ctx context.Context, session authn.Session, domainID string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("domain_id", domainID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Reject invitation failed", args...) - return - } - lm.logger.Info("Reject invitation completed successfully", args...) - }(time.Now()) - return lm.svc.RejectInvitation(ctx, session, domainID) -} - -func (lm *logging) DeleteInvitation(ctx context.Context, session authn.Session, userID, domainID string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("user_id", userID), - slog.String("domain_id", domainID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Delete invitation failed", args...) - return - } - lm.logger.Info("Delete invitation completed successfully", args...) - }(time.Now()) - return lm.svc.DeleteInvitation(ctx, session, userID, domainID) -} diff --git a/invitations/middleware/metrics.go b/invitations/middleware/metrics.go deleted file mode 100644 index 82acac84f..000000000 --- a/invitations/middleware/metrics.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "time" - - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/authn" - "github.com/go-kit/kit/metrics" -) - -var _ invitations.Service = (*metricsmw)(nil) - -type metricsmw struct { - counter metrics.Counter - latency metrics.Histogram - svc invitations.Service -} - -func Metrics(counter metrics.Counter, latency metrics.Histogram, svc invitations.Service) invitations.Service { - return &metricsmw{ - counter: counter, - latency: latency, - svc: svc, - } -} - -func (mm *metricsmw) SendInvitation(ctx context.Context, session authn.Session, invitation invitations.Invitation) (err error) { - defer func(begin time.Time) { - mm.counter.With("method", "send_invitation").Add(1) - mm.latency.With("method", "send_invitation").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return mm.svc.SendInvitation(ctx, session, invitation) -} - -func (mm *metricsmw) ViewInvitation(ctx context.Context, session authn.Session, userID, domainID string) (invitation invitations.Invitation, err error) { - defer func(begin time.Time) { - mm.counter.With("method", "view_invitation").Add(1) - mm.latency.With("method", "view_invitation").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return mm.svc.ViewInvitation(ctx, session, userID, domainID) -} - -func (mm *metricsmw) ListInvitations(ctx context.Context, session authn.Session, page invitations.Page) (invs invitations.InvitationPage, err error) { - defer func(begin time.Time) { - mm.counter.With("method", "list_invitations").Add(1) - mm.latency.With("method", "list_invitations").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return mm.svc.ListInvitations(ctx, session, page) -} - -func (mm *metricsmw) AcceptInvitation(ctx context.Context, session authn.Session, domainID string) (err error) { - defer func(begin time.Time) { - mm.counter.With("method", "accept_invitation").Add(1) - mm.latency.With("method", "accept_invitation").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return mm.svc.AcceptInvitation(ctx, session, domainID) -} - -func (mm *metricsmw) RejectInvitation(ctx context.Context, session authn.Session, domainID string) (err error) { - defer func(begin time.Time) { - mm.counter.With("method", "reject_invitation").Add(1) - mm.latency.With("method", "reject_invitation").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return mm.svc.RejectInvitation(ctx, session, domainID) -} - -func (mm *metricsmw) DeleteInvitation(ctx context.Context, session authn.Session, userID, domainID string) (err error) { - defer func(begin time.Time) { - mm.counter.With("method", "delete_invitation").Add(1) - mm.latency.With("method", "delete_invitation").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return mm.svc.DeleteInvitation(ctx, session, userID, domainID) -} diff --git a/invitations/middleware/tracing.go b/invitations/middleware/tracing.go deleted file mode 100644 index 16d39d647..000000000 --- a/invitations/middleware/tracing.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/authn" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ invitations.Service = (*tracing)(nil) - -type tracing struct { - tracer trace.Tracer - svc invitations.Service -} - -func Tracing(svc invitations.Service, tracer trace.Tracer) invitations.Service { - return &tracing{tracer, svc} -} - -func (tm *tracing) SendInvitation(ctx context.Context, session authn.Session, invitation invitations.Invitation) (err error) { - ctx, span := tm.tracer.Start(ctx, "send_invitation", trace.WithAttributes( - attribute.String("domain_id", invitation.DomainID), - attribute.String("user_id", invitation.UserID), - )) - defer span.End() - - return tm.svc.SendInvitation(ctx, session, invitation) -} - -func (tm *tracing) ViewInvitation(ctx context.Context, session authn.Session, userID, domain string) (invitation invitations.Invitation, err error) { - ctx, span := tm.tracer.Start(ctx, "view_invitation", trace.WithAttributes( - attribute.String("user_id", userID), - attribute.String("domain_id", domain), - )) - defer span.End() - - return tm.svc.ViewInvitation(ctx, session, userID, domain) -} - -func (tm *tracing) ListInvitations(ctx context.Context, session authn.Session, page invitations.Page) (invs invitations.InvitationPage, err error) { - ctx, span := tm.tracer.Start(ctx, "list_invitations", trace.WithAttributes( - attribute.Int("limit", int(page.Limit)), - attribute.Int("offset", int(page.Offset)), - attribute.String("user_id", page.UserID), - attribute.String("domain_id", page.DomainID), - attribute.String("invited_by", page.InvitedBy), - )) - defer span.End() - - return tm.svc.ListInvitations(ctx, session, page) -} - -func (tm *tracing) AcceptInvitation(ctx context.Context, session authn.Session, domainID string) (err error) { - ctx, span := tm.tracer.Start(ctx, "accept_invitation", trace.WithAttributes( - attribute.String("domain_id", domainID), - )) - defer span.End() - - return tm.svc.AcceptInvitation(ctx, session, domainID) -} - -func (tm *tracing) RejectInvitation(ctx context.Context, session authn.Session, domainID string) (err error) { - ctx, span := tm.tracer.Start(ctx, "reject_invitation", trace.WithAttributes( - attribute.String("domain_id", domainID), - )) - defer span.End() - - return tm.svc.RejectInvitation(ctx, session, domainID) -} - -func (tm *tracing) DeleteInvitation(ctx context.Context, session authn.Session, userID, domainID string) (err error) { - ctx, span := tm.tracer.Start(ctx, "delete_invitation", trace.WithAttributes( - attribute.String("user_id", userID), - attribute.String("domain_id", domainID), - )) - defer span.End() - - return tm.svc.DeleteInvitation(ctx, session, userID, domainID) -} diff --git a/invitations/mocks/doc.go b/invitations/mocks/doc.go deleted file mode 100644 index 4d95a3c13..000000000 --- a/invitations/mocks/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mocks provides a mock implementation of the invitations repository. -package mocks diff --git a/invitations/mocks/repository.go b/invitations/mocks/repository.go deleted file mode 100644 index e7d6832ff..000000000 --- a/invitations/mocks/repository.go +++ /dev/null @@ -1,177 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - invitations "github.com/absmach/magistrala/invitations" - mock "github.com/stretchr/testify/mock" -) - -// Repository is an autogenerated mock type for the Repository type -type Repository struct { - mock.Mock -} - -// Create provides a mock function with given fields: ctx, invitation -func (_m *Repository) Create(ctx context.Context, invitation invitations.Invitation) error { - ret := _m.Called(ctx, invitation) - - if len(ret) == 0 { - panic("no return value specified for Create") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, invitations.Invitation) error); ok { - r0 = rf(ctx, invitation) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Delete provides a mock function with given fields: ctx, userID, domainID -func (_m *Repository) Delete(ctx context.Context, userID string, domainID string) error { - ret := _m.Called(ctx, userID, domainID) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, userID, domainID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Retrieve provides a mock function with given fields: ctx, userID, domainID -func (_m *Repository) Retrieve(ctx context.Context, userID string, domainID string) (invitations.Invitation, error) { - ret := _m.Called(ctx, userID, domainID) - - if len(ret) == 0 { - panic("no return value specified for Retrieve") - } - - var r0 invitations.Invitation - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (invitations.Invitation, error)); ok { - return rf(ctx, userID, domainID) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) invitations.Invitation); ok { - r0 = rf(ctx, userID, domainID) - } else { - r0 = ret.Get(0).(invitations.Invitation) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, userID, domainID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveAll provides a mock function with given fields: ctx, page -func (_m *Repository) RetrieveAll(ctx context.Context, page invitations.Page) (invitations.InvitationPage, error) { - ret := _m.Called(ctx, page) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAll") - } - - var r0 invitations.InvitationPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, invitations.Page) (invitations.InvitationPage, error)); ok { - return rf(ctx, page) - } - if rf, ok := ret.Get(0).(func(context.Context, invitations.Page) invitations.InvitationPage); ok { - r0 = rf(ctx, page) - } else { - r0 = ret.Get(0).(invitations.InvitationPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, invitations.Page) error); ok { - r1 = rf(ctx, page) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateConfirmation provides a mock function with given fields: ctx, invitation -func (_m *Repository) UpdateConfirmation(ctx context.Context, invitation invitations.Invitation) error { - ret := _m.Called(ctx, invitation) - - if len(ret) == 0 { - panic("no return value specified for UpdateConfirmation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, invitations.Invitation) error); ok { - r0 = rf(ctx, invitation) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UpdateRejection provides a mock function with given fields: ctx, invitation -func (_m *Repository) UpdateRejection(ctx context.Context, invitation invitations.Invitation) error { - ret := _m.Called(ctx, invitation) - - if len(ret) == 0 { - panic("no return value specified for UpdateRejection") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, invitations.Invitation) error); ok { - r0 = rf(ctx, invitation) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UpdateToken provides a mock function with given fields: ctx, invitation -func (_m *Repository) UpdateToken(ctx context.Context, invitation invitations.Invitation) error { - ret := _m.Called(ctx, invitation) - - if len(ret) == 0 { - panic("no return value specified for UpdateToken") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, invitations.Invitation) error); ok { - r0 = rf(ctx, invitation) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// 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/invitations/mocks/service.go b/invitations/mocks/service.go deleted file mode 100644 index 3992c7cb5..000000000 --- a/invitations/mocks/service.go +++ /dev/null @@ -1,162 +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" - - invitations "github.com/absmach/magistrala/invitations" - - mock "github.com/stretchr/testify/mock" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// AcceptInvitation provides a mock function with given fields: ctx, session, domainID -func (_m *Service) AcceptInvitation(ctx context.Context, session authn.Session, domainID string) error { - ret := _m.Called(ctx, session, domainID) - - if len(ret) == 0 { - panic("no return value specified for AcceptInvitation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { - r0 = rf(ctx, session, domainID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// DeleteInvitation provides a mock function with given fields: ctx, session, userID, domainID -func (_m *Service) DeleteInvitation(ctx context.Context, session authn.Session, userID string, domainID string) error { - ret := _m.Called(ctx, session, userID, domainID) - - if len(ret) == 0 { - panic("no return value specified for DeleteInvitation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) error); ok { - r0 = rf(ctx, session, userID, domainID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ListInvitations provides a mock function with given fields: ctx, session, page -func (_m *Service) ListInvitations(ctx context.Context, session authn.Session, page invitations.Page) (invitations.InvitationPage, error) { - ret := _m.Called(ctx, session, page) - - if len(ret) == 0 { - panic("no return value specified for ListInvitations") - } - - var r0 invitations.InvitationPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, invitations.Page) (invitations.InvitationPage, error)); ok { - return rf(ctx, session, page) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, invitations.Page) invitations.InvitationPage); ok { - r0 = rf(ctx, session, page) - } else { - r0 = ret.Get(0).(invitations.InvitationPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, invitations.Page) error); ok { - r1 = rf(ctx, session, page) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RejectInvitation provides a mock function with given fields: ctx, session, domainID -func (_m *Service) RejectInvitation(ctx context.Context, session authn.Session, domainID string) error { - ret := _m.Called(ctx, session, domainID) - - if len(ret) == 0 { - panic("no return value specified for RejectInvitation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { - r0 = rf(ctx, session, domainID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SendInvitation provides a mock function with given fields: ctx, session, invitation -func (_m *Service) SendInvitation(ctx context.Context, session authn.Session, invitation invitations.Invitation) error { - ret := _m.Called(ctx, session, invitation) - - if len(ret) == 0 { - panic("no return value specified for SendInvitation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, invitations.Invitation) error); ok { - r0 = rf(ctx, session, invitation) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ViewInvitation provides a mock function with given fields: ctx, session, userID, domainID -func (_m *Service) ViewInvitation(ctx context.Context, session authn.Session, userID string, domainID string) (invitations.Invitation, error) { - ret := _m.Called(ctx, session, userID, domainID) - - if len(ret) == 0 { - panic("no return value specified for ViewInvitation") - } - - var r0 invitations.Invitation - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (invitations.Invitation, error)); ok { - return rf(ctx, session, userID, domainID) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) invitations.Invitation); ok { - r0 = rf(ctx, session, userID, domainID) - } else { - r0 = ret.Get(0).(invitations.Invitation) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { - r1 = rf(ctx, session, userID, domainID) - } 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/invitations/postgres/doc.go b/invitations/postgres/doc.go deleted file mode 100644 index 086a7bb4c..000000000 --- a/invitations/postgres/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres provides a postgres implementation of the invitations repository. -package postgres diff --git a/invitations/postgres/init.go b/invitations/postgres/init.go deleted file mode 100644 index 442d8e615..000000000 --- a/invitations/postgres/init.go +++ /dev/null @@ -1,48 +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: "invitations_01", - // VARCHAR(36) for colums with IDs as UUIDS have a maximum of 36 characters - Up: []string{ - `CREATE TABLE IF NOT EXISTS invitations ( - invited_by VARCHAR(36) NOT NULL, - user_id VARCHAR(36) NOT NULL, - domain_id VARCHAR(36) NOT NULL, - token TEXT NOT NULL, - relation VARCHAR(254) NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP, - confirmed_at TIMESTAMP, - UNIQUE (user_id, domain_id), - PRIMARY KEY (user_id, domain_id) - )`, - }, - Down: []string{ - `DROP TABLE IF EXISTS invitations`, - }, - }, - { - Id: "invitations_02_add_rejection", - Up: []string{ - `ALTER TABLE invitations - ADD COLUMN rejected_at TIMESTAMP`, - }, - Down: []string{ - `ALTER TABLE invitations - DROP COLUMN rejected_at`, - }, - }, - }, - } -} diff --git a/invitations/postgres/invitations.go b/invitations/postgres/invitations.go deleted file mode 100644 index f1de8c412..000000000 --- a/invitations/postgres/invitations.go +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "database/sql" - "fmt" - "strings" - "time" - - "github.com/absmach/magistrala/invitations" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/postgres" -) - -type repository struct { - db postgres.Database -} - -func NewRepository(db postgres.Database) invitations.Repository { - return &repository{db: db} -} - -func (repo *repository) Create(ctx context.Context, invitation invitations.Invitation) (err error) { - q := `INSERT INTO invitations (invited_by, user_id, domain_id, token, relation, created_at) - VALUES (:invited_by, :user_id, :domain_id, :token, :relation, :created_at)` - - dbInv := toDBInvitation(invitation) - if _, err = repo.db.NamedExecContext(ctx, q, dbInv); err != nil { - return postgres.HandleError(repoerr.ErrCreateEntity, err) - } - - return nil -} - -func (repo *repository) Retrieve(ctx context.Context, userID, domainID string) (invitations.Invitation, error) { - q := `SELECT invited_by, user_id, domain_id, token, relation, created_at, updated_at, confirmed_at, rejected_at FROM invitations WHERE user_id = :user_id AND domain_id = :domain_id;` - - dbinv := dbInvitation{ - UserID: userID, - DomainID: domainID, - } - rows, err := repo.db.NamedQueryContext(ctx, q, dbinv) - if err != nil { - return invitations.Invitation{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - dbinv = dbInvitation{} - if rows.Next() { - if err = rows.StructScan(&dbinv); err != nil { - return invitations.Invitation{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - - return toInvitation(dbinv), nil - } - - return invitations.Invitation{}, repoerr.ErrNotFound -} - -func (repo *repository) RetrieveAll(ctx context.Context, page invitations.Page) (invitations.InvitationPage, error) { - query := pageQuery(page) - - q := fmt.Sprintf("SELECT invited_by, user_id, domain_id, relation, created_at, updated_at, confirmed_at, rejected_at FROM invitations %s LIMIT :limit OFFSET :offset;", query) - - rows, err := repo.db.NamedQueryContext(ctx, q, page) - if err != nil { - return invitations.InvitationPage{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - var items []invitations.Invitation - for rows.Next() { - var dbinv dbInvitation - if err = rows.StructScan(&dbinv); err != nil { - return invitations.InvitationPage{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - items = append(items, toInvitation(dbinv)) - } - - tq := fmt.Sprintf(`SELECT COUNT(*) FROM invitations %s`, query) - - total, err := postgres.Total(ctx, repo.db, tq, page) - if err != nil { - return invitations.InvitationPage{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - - invPage := invitations.InvitationPage{ - Total: total, - Offset: page.Offset, - Limit: page.Limit, - Invitations: items, - } - - return invPage, nil -} - -func (repo *repository) UpdateToken(ctx context.Context, invitation invitations.Invitation) (err error) { - q := `UPDATE invitations SET token = :token, updated_at = :updated_at WHERE user_id = :user_id AND domain_id = :domain_id` - - dbinv := toDBInvitation(invitation) - result, err := repo.db.NamedExecContext(ctx, q, dbinv) - if err != nil { - return postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - if rows, _ := result.RowsAffected(); rows == 0 { - return repoerr.ErrNotFound - } - - return nil -} - -func (repo *repository) UpdateConfirmation(ctx context.Context, invitation invitations.Invitation) (err error) { - q := `UPDATE invitations SET confirmed_at = :confirmed_at, updated_at = :updated_at WHERE user_id = :user_id AND domain_id = :domain_id` - - dbinv := toDBInvitation(invitation) - result, err := repo.db.NamedExecContext(ctx, q, dbinv) - if err != nil { - return postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - if rows, _ := result.RowsAffected(); rows == 0 { - return repoerr.ErrNotFound - } - - return nil -} - -func (repo *repository) UpdateRejection(ctx context.Context, invitation invitations.Invitation) (err error) { - q := `UPDATE invitations SET rejected_at = :rejected_at, updated_at = :updated_at WHERE user_id = :user_id AND domain_id = :domain_id` - - dbInv := toDBInvitation(invitation) - result, err := repo.db.NamedExecContext(ctx, q, dbInv) - if err != nil { - return postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - if rows, _ := result.RowsAffected(); rows == 0 { - return repoerr.ErrNotFound - } - - return nil -} - -func (repo *repository) Delete(ctx context.Context, userID, domain string) (err error) { - q := `DELETE FROM invitations WHERE user_id = $1 AND domain_id = $2` - - result, err := repo.db.ExecContext(ctx, q, userID, domain) - if err != nil { - return postgres.HandleError(repoerr.ErrRemoveEntity, err) - } - if rows, _ := result.RowsAffected(); rows == 0 { - return repoerr.ErrNotFound - } - - return nil -} - -func pageQuery(pm invitations.Page) string { - var query []string - var emq string - if pm.DomainID != "" { - query = append(query, "domain_id = :domain_id") - } - if pm.UserID != "" { - query = append(query, "user_id = :user_id") - } - if pm.InvitedBy != "" { - query = append(query, "invited_by = :invited_by") - } - if pm.Relation != "" { - query = append(query, "relation = :relation") - } - if pm.InvitedByOrUserID != "" { - query = append(query, "(invited_by = :invited_by_or_user_id OR user_id = :invited_by_or_user_id)") - } - if pm.State == invitations.Accepted { - query = append(query, "confirmed_at IS NOT NULL") - } - if pm.State == invitations.Pending { - query = append(query, "confirmed_at IS NULL AND rejected_at IS NULL") - } - if pm.State == invitations.Rejected { - query = append(query, "rejected_at IS NOT NULL") - } - - if len(query) > 0 { - emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")) - } - - return emq -} - -type dbInvitation struct { - InvitedBy string `db:"invited_by"` - UserID string `db:"user_id"` - DomainID string `db:"domain_id"` - Token string `db:"token,omitempty"` - Relation string `db:"relation"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt sql.NullTime `db:"updated_at,omitempty"` - ConfirmedAt sql.NullTime `db:"confirmed_at,omitempty"` - RejectedAt sql.NullTime `db:"rejected_at,omitempty"` -} - -func toDBInvitation(inv invitations.Invitation) dbInvitation { - var updatedAt, confirmedAt, rejectedAt sql.NullTime - if inv.UpdatedAt != (time.Time{}) { - updatedAt = sql.NullTime{Time: inv.UpdatedAt, Valid: true} - } - if inv.ConfirmedAt != (time.Time{}) { - confirmedAt = sql.NullTime{Time: inv.ConfirmedAt, Valid: true} - } - if inv.RejectedAt != (time.Time{}) { - rejectedAt = sql.NullTime{Time: inv.RejectedAt, Valid: true} - } - - return dbInvitation{ - InvitedBy: inv.InvitedBy, - UserID: inv.UserID, - DomainID: inv.DomainID, - Token: inv.Token, - Relation: inv.Relation, - CreatedAt: inv.CreatedAt, - UpdatedAt: updatedAt, - ConfirmedAt: confirmedAt, - RejectedAt: rejectedAt, - } -} - -func toInvitation(dbinv dbInvitation) invitations.Invitation { - var updatedAt, confirmedAt, rejectedAt time.Time - if dbinv.UpdatedAt.Valid { - updatedAt = dbinv.UpdatedAt.Time - } - if dbinv.ConfirmedAt.Valid { - confirmedAt = dbinv.ConfirmedAt.Time - } - if dbinv.RejectedAt.Valid { - rejectedAt = dbinv.RejectedAt.Time - } - - return invitations.Invitation{ - InvitedBy: dbinv.InvitedBy, - UserID: dbinv.UserID, - DomainID: dbinv.DomainID, - Token: dbinv.Token, - Relation: dbinv.Relation, - CreatedAt: dbinv.CreatedAt, - UpdatedAt: updatedAt, - ConfirmedAt: confirmedAt, - RejectedAt: rejectedAt, - } -} diff --git a/invitations/postgres/invitations_test.go b/invitations/postgres/invitations_test.go deleted file mode 100644 index 147539e07..000000000 --- a/invitations/postgres/invitations_test.go +++ /dev/null @@ -1,811 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/invitations/postgres" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - invalidUUID = strings.Repeat("a", 37) - validToken = strings.Repeat("a", 1024) - relation = "relation" -) - -func TestInvitationCreate(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM invitations") - require.Nil(t, err, fmt.Sprintf("clean invitations unexpected error: %s", err)) - }) - repo := postgres.NewRepository(database) - - domainID := testsutil.GenerateUUID(t) - userID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - invitation invitations.Invitation - err error - }{ - { - desc: "add new invitation successfully", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: userID, - DomainID: domainID, - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - }, - err: nil, - }, - { - desc: "add new invitation with an confirmed_at date", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - ConfirmedAt: time.Now(), - }, - err: nil, - }, - { - desc: "add invitation with duplicate invitation", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: userID, - DomainID: domainID, - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - }, - err: repoerr.ErrConflict, - }, - { - desc: "add invitation with invalid invitation invited_by", - invitation: invitations.Invitation{ - InvitedBy: invalidUUID, - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add invitation with invalid invitation relation", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: strings.Repeat("a", 255), - CreatedAt: time.Now(), - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add invitation with invalid invitation domain", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: invalidUUID, - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add invitation with invalid invitation user id", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: invalidUUID, - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "add invitation with empty invitation domain", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - }, - err: nil, - }, - { - desc: "add invitation with empty invitation user id", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - }, - err: nil, - }, - { - desc: "add invitation with empty invitation invited_by", - invitation: invitations.Invitation{ - DomainID: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: relation, - CreatedAt: time.Now(), - }, - err: nil, - }, - { - desc: "add invitation with empty invitation token", - invitation: invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - Relation: relation, - CreatedAt: time.Now(), - }, - err: nil, - }, - } - for _, tc := range cases { - switch err := repo.Create(context.Background(), tc.invitation); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - } -} - -func TestInvitationRetrieve(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM invitations") - require.Nil(t, err, fmt.Sprintf("clean invitations unexpected error: %s", err)) - }) - repo := postgres.NewRepository(database) - - invitation := invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: relation, - CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - } - err := repo.Create(context.Background(), invitation) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - - cases := []struct { - desc string - userID string - domainID string - response invitations.Invitation - err error - }{ - { - desc: "retrieve invitations successfully", - userID: invitation.UserID, - domainID: invitation.DomainID, - response: invitation, - err: nil, - }, - { - desc: "retrieve invitations with invalid invitation user id", - userID: testsutil.GenerateUUID(t), - domainID: invitation.DomainID, - response: invitations.Invitation{}, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve invitations with invalid invitation domain_id", - userID: invitation.UserID, - domainID: testsutil.GenerateUUID(t), - response: invitations.Invitation{}, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve invitations with invalid invitation user id and domain_id", - userID: testsutil.GenerateUUID(t), - domainID: testsutil.GenerateUUID(t), - response: invitations.Invitation{}, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve invitations with empty invitation user id", - userID: "", - domainID: invitation.DomainID, - response: invitations.Invitation{}, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve invitations with empty invitation domain_id", - userID: invitation.UserID, - domainID: "", - response: invitations.Invitation{}, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve invitations with empty invitation user id and domain_id", - userID: "", - domainID: "", - response: invitations.Invitation{}, - err: repoerr.ErrNotFound, - }, - } - for _, tc := range cases { - page, err := repo.Retrieve(context.Background(), tc.userID, tc.domainID) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, page, fmt.Sprintf("desc: %s\n", tc.desc)) - } -} - -func TestInvitationRetrieveAll(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM invitations") - require.Nil(t, err, fmt.Sprintf("clean invitations unexpected error: %s", err)) - }) - repo := postgres.NewRepository(database) - - num := 200 - - var items []invitations.Invitation - for i := 0; i < num; i++ { - invitation := invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: fmt.Sprintf("%s-%d", relation, i), - CreatedAt: time.Now().UTC().Truncate(time.Microsecond), - } - err := repo.Create(context.Background(), invitation) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - invitation.Token = "" - items = append(items, invitation) - } - items[100].ConfirmedAt = time.Now().UTC().Truncate(time.Microsecond) - err := repo.UpdateConfirmation(context.Background(), items[100]) - require.Nil(t, err, fmt.Sprintf("update invitation unexpected error: %s", err)) - - swap := items[100] - items = append(items[:100], items[101:]...) - items = append(items, swap) - - cases := []struct { - desc string - page invitations.Page - response invitations.InvitationPage - err error - }{ - { - desc: "retrieve invitations successfully", - page: invitations.Page{ - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Invitations: items[:10], - }, - err: nil, - }, - { - desc: "retrieve invitations with offset", - page: invitations.Page{ - Offset: 10, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: uint64(num), - Offset: 10, - Limit: 10, - Invitations: items[10:20], - }, - }, - { - desc: "retrieve invitations with limit", - page: invitations.Page{ - Offset: 0, - Limit: 50, - }, - response: invitations.InvitationPage{ - Total: uint64(num), - Offset: 0, - Limit: 50, - Invitations: items[:50], - }, - }, - { - desc: "retrieve invitations with offset and limit", - page: invitations.Page{ - Offset: 10, - Limit: 50, - }, - response: invitations.InvitationPage{ - Total: uint64(num), - Offset: 10, - Limit: 50, - Invitations: items[10:60], - }, - }, - { - desc: "retrieve invitations with offset out of range", - page: invitations.Page{ - Offset: 1000, - Limit: 50, - }, - response: invitations.InvitationPage{ - Total: uint64(num), - Offset: 1000, - Limit: 50, - Invitations: []invitations.Invitation(nil), - }, - }, - { - desc: "retrieve invitations with offset and limit out of range", - page: invitations.Page{ - Offset: 170, - Limit: 50, - }, - response: invitations.InvitationPage{ - Total: uint64(num), - Offset: 170, - Limit: 50, - Invitations: items[170:200], - }, - }, - { - desc: "retrieve invitations with limit out of range", - page: invitations.Page{ - Offset: 0, - Limit: 1000, - }, - response: invitations.InvitationPage{ - Total: uint64(num), - Offset: 0, - Limit: 1000, - Invitations: items, - }, - }, - { - desc: "retrieve invitations with empty page", - page: invitations.Page{}, - response: invitations.InvitationPage{ - Total: uint64(num), - Offset: 0, - Limit: 0, - Invitations: []invitations.Invitation(nil), - }, - }, - { - desc: "retrieve invitations with domain", - page: invitations.Page{ - DomainID: items[0].DomainID, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with user id", - page: invitations.Page{ - UserID: items[0].UserID, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with invited_by", - page: invitations.Page{ - InvitedBy: items[0].InvitedBy, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with invited_by_or_user_id", - page: invitations.Page{ - InvitedByOrUserID: items[0].UserID, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with relation", - page: invitations.Page{ - Relation: relation + "-0", - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with domain_id and user id", - page: invitations.Page{ - DomainID: items[0].DomainID, - UserID: items[0].UserID, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with domain_id and invited_by", - page: invitations.Page{ - DomainID: items[0].DomainID, - InvitedBy: items[0].InvitedBy, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with user id and invited_by", - page: invitations.Page{ - UserID: items[0].UserID, - InvitedBy: items[0].InvitedBy, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with domain_id, user id and invited_by", - page: invitations.Page{ - DomainID: items[0].DomainID, - UserID: items[0].UserID, - InvitedBy: items[0].InvitedBy, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with domain_id, user id, invited_by and relation", - page: invitations.Page{ - DomainID: items[0].DomainID, - UserID: items[0].UserID, - InvitedBy: items[0].InvitedBy, - Relation: relation + "-0", - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[0]}, - }, - }, - { - desc: "retrieve invitations with invalid domain", - page: invitations.Page{ - DomainID: invalidUUID, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 0, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation(nil), - }, - }, - { - desc: "retrieve invitations with invalid user id", - page: invitations.Page{ - UserID: testsutil.GenerateUUID(t), - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 0, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation(nil), - }, - }, - { - desc: "retrieve invitations with invalid invited_by", - page: invitations.Page{ - InvitedBy: invalidUUID, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 0, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation(nil), - }, - }, - { - desc: "retrieve invitations with invalid relation", - page: invitations.Page{ - Relation: invalidUUID, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 0, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation(nil), - }, - }, - { - desc: "retrieve invitations with accepted state", - page: invitations.Page{ - State: invitations.Accepted, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{items[num-1]}, - }, - }, - { - desc: "retrieve invitations with pending state", - page: invitations.Page{ - State: invitations.Pending, - Offset: 0, - Limit: 10, - }, - response: invitations.InvitationPage{ - Total: uint64(num - 1), - Offset: 0, - Limit: 10, - Invitations: items[0:10], - }, - }, - } - for _, tc := range cases { - page, err := repo.RetrieveAll(context.Background(), tc.page) - assert.Equal(t, tc.response.Total, page.Total, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Total, page.Total)) - assert.Equal(t, tc.response.Offset, page.Offset, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Offset, page.Offset)) - assert.Equal(t, tc.response.Limit, page.Limit, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.response.Limit, page.Limit)) - assert.ElementsMatch(t, page.Invitations, tc.response.Invitations, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response.Invitations, page.Invitations)) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestInvitationUpdateToken(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM invitations") - require.Nil(t, err, fmt.Sprintf("clean invitations unexpected error: %s", err)) - }) - repo := postgres.NewRepository(database) - - invitation := invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - CreatedAt: time.Now(), - } - err := repo.Create(context.Background(), invitation) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - - cases := []struct { - desc string - invitation invitations.Invitation - err error - }{ - { - desc: "update invitation successfully", - invitation: invitations.Invitation{ - DomainID: invitation.DomainID, - UserID: invitation.UserID, - Token: validToken, - UpdatedAt: time.Now(), - }, - err: nil, - }, - { - desc: "update invitation with invalid user id", - invitation: invitations.Invitation{ - UserID: testsutil.GenerateUUID(t), - DomainID: invitation.DomainID, - Token: validToken, - UpdatedAt: time.Now(), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update invitation with invalid domain_id", - invitation: invitations.Invitation{ - UserID: invitation.UserID, - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - UpdatedAt: time.Now(), - }, - err: repoerr.ErrNotFound, - }, - } - for _, tc := range cases { - err := repo.UpdateToken(context.Background(), tc.invitation) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestInvitationUpdateConfirmation(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM invitations") - require.Nil(t, err, fmt.Sprintf("clean invitations unexpected error: %s", err)) - }) - repo := postgres.NewRepository(database) - - invitation := invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - CreatedAt: time.Now(), - } - err := repo.Create(context.Background(), invitation) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - - cases := []struct { - desc string - invitation invitations.Invitation - err error - }{ - { - desc: "update invitation successfully", - invitation: invitations.Invitation{ - DomainID: invitation.DomainID, - UserID: invitation.UserID, - ConfirmedAt: time.Now(), - }, - err: nil, - }, - { - desc: "update invitation with invalid user id", - invitation: invitations.Invitation{ - UserID: testsutil.GenerateUUID(t), - DomainID: invitation.UserID, - ConfirmedAt: time.Now(), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update invitation with invalid domain", - invitation: invitations.Invitation{ - UserID: invitation.UserID, - DomainID: testsutil.GenerateUUID(t), - ConfirmedAt: time.Now(), - }, - err: repoerr.ErrNotFound, - }, - } - for _, tc := range cases { - err := repo.UpdateConfirmation(context.Background(), tc.invitation) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestInvitationDelete(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM invitations") - require.Nil(t, err, fmt.Sprintf("clean invitations unexpected error: %s", err)) - }) - repo := postgres.NewRepository(database) - - invitation := invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - CreatedAt: time.Now(), - } - err := repo.Create(context.Background(), invitation) - require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err)) - - cases := []struct { - desc string - invitation invitations.Invitation - err error - }{ - { - desc: "delete invitation successfully", - invitation: invitations.Invitation{ - UserID: invitation.UserID, - DomainID: invitation.DomainID, - }, - err: nil, - }, - { - desc: "delete invitation with invalid invitation id", - invitation: invitations.Invitation{ - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "delete invitation with empty invitation id", - invitation: invitations.Invitation{}, - err: repoerr.ErrNotFound, - }, - } - for _, tc := range cases { - err := repo.Delete(context.Background(), tc.invitation.UserID, tc.invitation.DomainID) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} diff --git a/invitations/postgres/setup_test.go b/invitations/postgres/setup_test.go deleted file mode 100644 index 5d220b3ea..000000000 --- a/invitations/postgres/setup_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "database/sql" - "fmt" - "log" - "os" - "testing" - "time" - - ipostgres "github.com/absmach/magistrala/invitations/postgres" - "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") - - // exponential backoff-retry, because the application in the container might not be ready to accept connections yet - pool.MaxWait = 120 * time.Second - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err := sql.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := postgres.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = postgres.Setup(dbConfig, *ipostgres.Migration()); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - if db, err = postgres.Connect(dbConfig); 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/invitations/service.go b/invitations/service.go deleted file mode 100644 index 6bc636d57..000000000 --- a/invitations/service.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package invitations - -import ( - "context" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth" - "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 - repo Repository - sdk mgsdk.SDK -} - -func NewService(token magistrala.TokenServiceClient, repo Repository, sdk mgsdk.SDK) Service { - return &service{ - token: token, - repo: repo, - sdk: sdk, - } -} - -func (svc *service) SendInvitation(ctx context.Context, session authn.Session, invitation Invitation) error { - if err := CheckRelation(invitation.Relation); err != nil { - return err - } - - invitation.InvitedBy = session.UserID - - joinToken, err := svc.token.Issue(ctx, &magistrala.IssueReq{UserId: session.UserID, Type: uint32(auth.InvitationKey)}) - if err != nil { - return err - } - invitation.Token = joinToken.GetAccessToken() - - if invitation.Resend { - invitation.UpdatedAt = time.Now() - - return svc.repo.UpdateToken(ctx, invitation) - } - - invitation.CreatedAt = time.Now() - - if err := svc.repo.Create(ctx, invitation); err != nil { - return err - } - return nil -} - -func (svc *service) ViewInvitation(ctx context.Context, session authn.Session, userID, domainID string) (invitation Invitation, err error) { - inv, err := svc.repo.Retrieve(ctx, userID, domainID) - if err != nil { - return Invitation{}, err - } - inv.Token = "" - - return inv, nil -} - -func (svc *service) ListInvitations(ctx context.Context, session authn.Session, page Page) (invitations InvitationPage, err error) { - ip, err := svc.repo.RetrieveAll(ctx, page) - if err != nil { - return InvitationPage{}, err - } - return ip, nil -} - -func (svc *service) AcceptInvitation(ctx context.Context, session authn.Session, domainID string) error { - inv, err := svc.repo.Retrieve(ctx, session.UserID, domainID) - if err != nil { - return err - } - - if inv.UserID != session.UserID { - return svcerr.ErrAuthorization - } - - if !inv.ConfirmedAt.IsZero() { - return svcerr.ErrInvitationAlreadyAccepted - } - - if !inv.RejectedAt.IsZero() { - return svcerr.ErrInvitationAlreadyRejected - } - - req := mgsdk.UsersRelationRequest{ - Relation: inv.Relation, - UserIDs: []string{session.UserID}, - } - if sdkerr := svc.sdk.AddUserToDomain(inv.DomainID, req, inv.Token); sdkerr != nil { - return sdkerr - } - - inv.ConfirmedAt = time.Now() - inv.UpdatedAt = inv.ConfirmedAt - return svc.repo.UpdateConfirmation(ctx, inv) -} - -func (svc *service) RejectInvitation(ctx context.Context, session authn.Session, domainID string) error { - inv, err := svc.repo.Retrieve(ctx, session.UserID, domainID) - if err != nil { - return err - } - - if inv.UserID != session.UserID { - return svcerr.ErrAuthorization - } - - if !inv.ConfirmedAt.IsZero() { - return svcerr.ErrInvitationAlreadyAccepted - } - - if !inv.RejectedAt.IsZero() { - return svcerr.ErrInvitationAlreadyRejected - } - - inv.RejectedAt = time.Now() - inv.UpdatedAt = inv.RejectedAt - return svc.repo.UpdateRejection(ctx, inv) -} - -func (svc *service) DeleteInvitation(ctx context.Context, session authn.Session, userID, domainID string) error { - if session.UserID == userID { - return svc.repo.Delete(ctx, userID, domainID) - } - - inv, err := svc.repo.Retrieve(ctx, userID, domainID) - if err != nil { - return err - } - - if inv.InvitedBy == session.UserID { - return svc.repo.Delete(ctx, userID, domainID) - } - - return svc.repo.Delete(ctx, userID, domainID) -} diff --git a/invitations/service_test.go b/invitations/service_test.go deleted file mode 100644 index 92538652c..000000000 --- a/invitations/service_test.go +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package invitations_test - -import ( - "context" - "testing" - "time" - - "github.com/absmach/magistrala" - authmocks "github.com/absmach/magistrala/auth/mocks" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/invitations/mocks" - "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" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/policies" - sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - validInvitation = invitations.Invitation{ - UserID: testsutil.GenerateUUID(&testing.T{}), - DomainID: testsutil.GenerateUUID(&testing.T{}), - Relation: policies.ContributorRelation, - } - validDomainUserID = "domain_user_id" - validUserID = "user_id" - validDomainID = "domain_id" - validToken = "valid_token" - invalidToken = "invalid" -) - -func TestSendInvitation(t *testing.T) { - repo := new(mocks.Repository) - token := new(authmocks.TokenServiceClient) - svc := invitations.NewService(token, repo, nil) - - cases := []struct { - desc string - token string - session authn.Session - tokenUserID string - req invitations.Invitation - err error - issueErr error - repoErr error - }{ - { - desc: "send invitation successful", - token: validToken, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - tokenUserID: testsutil.GenerateUUID(t), - req: validInvitation, - err: nil, - issueErr: nil, - repoErr: nil, - }, - { - desc: "failed to issue token", - token: invalidToken, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - tokenUserID: testsutil.GenerateUUID(t), - req: validInvitation, - err: svcerr.ErrCreateEntity, - issueErr: svcerr.ErrCreateEntity, - repoErr: nil, - }, - { - desc: "invalid relation", - token: validToken, - tokenUserID: testsutil.GenerateUUID(t), - req: invitations.Invitation{Relation: "invalid"}, - err: apiutil.ErrInvalidRelation, - issueErr: nil, - repoErr: nil, - }, - { - desc: "resend invitation", - token: invalidToken, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - tokenUserID: testsutil.GenerateUUID(t), - req: invitations.Invitation{ - UserID: validInvitation.UserID, - DomainID: validInvitation.DomainID, - Relation: validInvitation.Relation, - Resend: true, - }, - err: nil, - issueErr: nil, - repoErr: nil, - }, - { - desc: "error during token issuance", - token: validToken, - tokenUserID: testsutil.GenerateUUID(t), - req: validInvitation, - err: svcerr.ErrAuthentication, - issueErr: svcerr.ErrAuthentication, - repoErr: nil, - }, - } - - 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() - }) - } -} - -func TestViewInvitation(t *testing.T) { - repo := new(mocks.Repository) - token := new(authmocks.TokenServiceClient) - svc := invitations.NewService(token, repo, nil) - - validInvitation := invitations.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Relation: policies.ContributorRelation, - CreatedAt: time.Now().Add(-time.Hour), - UpdatedAt: time.Now().Add(-time.Hour), - ConfirmedAt: time.Now().Add(-time.Hour), - } - cases := []struct { - desc string - token string - userID string - domainID string - session authn.Session - tokenUserID string - req invitations.Invitation - resp invitations.Invitation - err error - issueErr error - repoErr error - }{ - { - desc: "view invitation successful", - token: validToken, - tokenUserID: testsutil.GenerateUUID(t), - userID: validInvitation.UserID, - domainID: validInvitation.DomainID, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - resp: validInvitation, - err: nil, - repoErr: nil, - }, - - { - desc: "error retrieving invitation", - token: validToken, - userID: validInvitation.UserID, - domainID: validInvitation.DomainID, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - tokenUserID: testsutil.GenerateUUID(t), - err: svcerr.ErrNotFound, - repoErr: svcerr.ErrNotFound, - }, - { - desc: "valid invitation for the same user", - token: validToken, - userID: validInvitation.UserID, - domainID: validInvitation.DomainID, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - resp: validInvitation, - tokenUserID: validInvitation.UserID, - err: nil, - repoErr: nil, - }, - { - desc: "valid invitation for the invited user", - token: validToken, - userID: validInvitation.UserID, - domainID: validInvitation.DomainID, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - tokenUserID: validInvitation.InvitedBy, - resp: validInvitation, - err: nil, - repoErr: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall1 := repo.On("Retrieve", context.Background(), mock.Anything, mock.Anything).Return(tc.resp, tc.repoErr) - inv, err := svc.ViewInvitation(context.Background(), tc.session, tc.userID, tc.domainID) - assert.Equal(t, tc.err, err, tc.desc) - assert.Equal(t, tc.resp, inv, tc.desc) - repocall1.Unset() - }) - } -} - -func TestListInvitations(t *testing.T) { - repo := new(mocks.Repository) - token := new(authmocks.TokenServiceClient) - svc := invitations.NewService(token, repo, nil) - - validPage := invitations.Page{ - Offset: 0, - Limit: 10, - } - validResp := invitations.InvitationPage{ - Total: 1, - Offset: 0, - Limit: 10, - Invitations: []invitations.Invitation{ - { - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Relation: policies.ContributorRelation, - CreatedAt: time.Now().Add(-time.Hour), - UpdatedAt: time.Now().Add(-time.Hour), - ConfirmedAt: time.Now().Add(-time.Hour), - }, - }, - } - - cases := []struct { - desc string - session authn.Session - page invitations.Page - resp invitations.InvitationPage - err error - repoErr error - }{ - { - desc: "list invitations successful", - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - page: validPage, - resp: validResp, - err: nil, - repoErr: nil, - }, - - { - desc: "list invitations unsuccessful", - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: validUserID}, - page: validPage, - err: repoerr.ErrViewEntity, - resp: invitations.InvitationPage{}, - repoErr: repoerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall1 := repo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.resp, tc.repoErr) - resp, err := svc.ListInvitations(context.Background(), tc.session, tc.page) - assert.Equal(t, tc.err, err, tc.desc) - assert.Equal(t, tc.resp, resp, tc.desc) - repocall1.Unset() - }) - } -} - -func TestAcceptInvitation(t *testing.T) { - repo := new(mocks.Repository) - token := new(authmocks.TokenServiceClient) - sdksvc := new(sdkmocks.SDK) - svc := invitations.NewService(token, repo, sdksvc) - - userID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - token string - domainID string - session authn.Session - resp invitations.Invitation - err error - repoErr error - sdkErr errors.SDKError - repoErr1 error - }{ - { - desc: "accept invitation successful", - token: validToken, - domainID: "", - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - resp: invitations.Invitation{ - UserID: userID, - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: policies.ContributorRelation, - }, - err: nil, - repoErr: nil, - }, - { - desc: "accept invitation with failed to retrieve all", - token: validToken, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - err: svcerr.ErrNotFound, - repoErr: svcerr.ErrNotFound, - }, - { - desc: "accept invitation with sdk err", - token: validToken, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - domainID: "", - resp: invitations.Invitation{ - UserID: userID, - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: policies.ContributorRelation, - }, - err: errors.NewSDKError(svcerr.ErrConflict), - repoErr: nil, - sdkErr: errors.NewSDKError(svcerr.ErrConflict), - }, - { - desc: "accept invitation with failed update confirmation", - token: validToken, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - domainID: "", - resp: invitations.Invitation{ - UserID: userID, - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: policies.ContributorRelation, - }, - err: svcerr.ErrUpdateEntity, - repoErr: nil, - repoErr1: svcerr.ErrUpdateEntity, - }, - { - desc: "accept invitation that is already confirmed", - token: validToken, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - domainID: "", - resp: invitations.Invitation{ - UserID: userID, - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: policies.ContributorRelation, - ConfirmedAt: time.Now(), - }, - err: svcerr.ErrInvitationAlreadyAccepted, - repoErr: nil, - }, - { - desc: "accept rejected invitation", - token: validToken, - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - domainID: "", - resp: invitations.Invitation{ - UserID: userID, - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: policies.ContributorRelation, - RejectedAt: time.Now(), - }, - err: svcerr.ErrInvitationAlreadyRejected, - repoErr: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall1 := repo.On("Retrieve", context.Background(), mock.Anything, tc.domainID).Return(tc.resp, tc.repoErr) - sdkcall := sdksvc.On("AddUserToDomain", mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr) - repocall2 := repo.On("UpdateConfirmation", context.Background(), mock.Anything).Return(tc.repoErr1) - err := svc.AcceptInvitation(context.Background(), tc.session, tc.domainID) - assert.Equal(t, tc.err, err, tc.desc) - repocall1.Unset() - sdkcall.Unset() - repocall2.Unset() - }) - } -} - -func TestDeleteInvitation(t *testing.T) { - repo := new(mocks.Repository) - token := new(authmocks.TokenServiceClient) - svc := invitations.NewService(token, repo, nil) - - cases := []struct { - desc string - token string - userID string - domainID string - resp invitations.Invitation - err error - repoErr error - }{ - { - desc: "delete invitations successful", - userID: testsutil.GenerateUUID(t), - domainID: testsutil.GenerateUUID(t), - resp: validInvitation, - err: nil, - repoErr: nil, - }, - { - desc: "delete invitations for the same user", - token: validToken, - userID: validInvitation.UserID, - domainID: validInvitation.DomainID, - resp: validInvitation, - err: nil, - repoErr: nil, - }, - { - desc: "delete invitations for the invited user", - token: validToken, - userID: validInvitation.UserID, - domainID: validInvitation.DomainID, - resp: validInvitation, - err: nil, - repoErr: nil, - }, - { - desc: "error retrieving invitation", - token: validToken, - userID: validInvitation.UserID, - domainID: validInvitation.DomainID, - resp: invitations.Invitation{}, - err: svcerr.ErrNotFound, - repoErr: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall1 := repo.On("Retrieve", context.Background(), mock.Anything, mock.Anything).Return(tc.resp, tc.repoErr) - repocall2 := repo.On("Delete", context.Background(), mock.Anything, mock.Anything).Return(tc.repoErr) - err := svc.DeleteInvitation(context.Background(), authn.Session{}, tc.userID, tc.domainID) - assert.Equal(t, tc.err, err, tc.desc) - repocall1.Unset() - repocall2.Unset() - }) - } -} - -func TestRejectInvitation(t *testing.T) { - repo := new(mocks.Repository) - token := new(authmocks.TokenServiceClient) - svc := invitations.NewService(token, repo, nil) - userID := validInvitation.UserID - - cases := []struct { - desc string - session authn.Session - domainID string - resp invitations.Invitation - err error - repoErr error - repoErr1 error - }{ - { - desc: "reject invitations for the same user", - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - domainID: validInvitation.DomainID, - resp: validInvitation, - err: nil, - repoErr: nil, - repoErr1: nil, - }, - { - desc: "reject invitations for the invited user", - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - domainID: validInvitation.DomainID, - resp: invitations.Invitation{}, - err: svcerr.ErrAuthorization, - repoErr: nil, - repoErr1: nil, - }, - { - desc: "error retrieving invitation", - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - domainID: validInvitation.DomainID, - resp: invitations.Invitation{}, - err: repoerr.ErrNotFound, - repoErr: repoerr.ErrNotFound, - repoErr1: nil, - }, - { - desc: "error updating rejection", - session: authn.Session{DomainUserID: validDomainUserID, DomainID: validDomainID, UserID: userID}, - domainID: validInvitation.DomainID, - resp: validInvitation, - err: repoerr.ErrUpdateEntity, - repoErr: nil, - repoErr1: repoerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall1 := repo.On("Retrieve", context.Background(), mock.Anything, mock.Anything).Return(tc.resp, tc.repoErr) - repocall3 := repo.On("UpdateRejection", context.Background(), mock.Anything).Return(tc.repoErr1) - err := svc.RejectInvitation(context.Background(), tc.session, tc.domainID) - assert.Equal(t, tc.err, err, tc.desc) - repocall1.Unset() - repocall3.Unset() - }) - } -} diff --git a/invitations/state.go b/invitations/state.go deleted file mode 100644 index afd392da3..000000000 --- a/invitations/state.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package invitations - -import ( - "encoding/json" - "strings" - - "github.com/absmach/magistrala/pkg/apiutil" -) - -// State represents invitation state. -type State uint8 - -const ( - All State = iota // All is used for querying purposes to list invitations irrespective of their state - both pending and accepted. - Pending // Pending is the state of an invitation that has not been accepted yet. - Accepted // Accepted is the state of an invitation that has been accepted. - Rejected // Rejected is the state of an invitation that has been rejected. -) - -// String representation of the possible state values. -const ( - all = "all" - pending = "pending" - accepted = "accepted" - rejected = "rejected" - unknown = "unknown" -) - -// String converts invitation state to string literal. -func (s State) String() string { - switch s { - case All: - return all - case Pending: - return pending - case Accepted: - return accepted - case Rejected: - return rejected - default: - return unknown - } -} - -// ToState converts string value to a valid invitation state. -func ToState(status string) (State, error) { - switch status { - case all: - return All, nil - case pending: - return Pending, nil - case accepted: - return Accepted, nil - case rejected: - return Rejected, nil - } - - return State(0), apiutil.ErrInvitationState -} - -func (s State) MarshalJSON() ([]byte, error) { - return json.Marshal(s.String()) -} - -// Custom Unmarshaler for Client/Groups. -func (s *State) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToState(str) - *s = val - return err -} diff --git a/invitations/state_test.go b/invitations/state_test.go deleted file mode 100644 index 006072ef2..000000000 --- a/invitations/state_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package invitations_test - -import ( - "testing" - - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/stretchr/testify/assert" -) - -func TestState_String(t *testing.T) { - tests := []struct { - name string - state invitations.State - expected string - }{ - {"Pending", invitations.Pending, "pending"}, - {"Accepted", invitations.Accepted, "accepted"}, - {"Rejected", invitations.Rejected, "rejected"}, - {"All", invitations.All, "all"}, - {"Unknown", invitations.State(100), "unknown"}, - } - - for _, tt := range tests { - got := tt.state.String() - assert.Equal(t, tt.expected, got, "State.String() = %v, expected %v", got, tt.expected) - } -} - -func TestToState(t *testing.T) { - tests := []struct { - name string - status string - state invitations.State - err error - }{ - {"Pending", "pending", invitations.Pending, nil}, - {"Accepted", "accepted", invitations.Accepted, nil}, - {"Rejected", "rejected", invitations.Rejected, nil}, - {"All", "all", invitations.All, nil}, - {"Unknown", "unknown", invitations.State(0), apiutil.ErrInvitationState}, - } - - for _, tt := range tests { - got, err := invitations.ToState(tt.status) - assert.Equal(t, tt.err, err, "ToState() error = %v, expected %v", err, tt.err) - assert.Equal(t, tt.state, got, "ToState() = %v, expected %v", got, tt.state) - } -} - -func TestState_MarshalJSON(t *testing.T) { - tests := []struct { - name string - state invitations.State - expected []byte - err error - }{ - {"Pending", invitations.Pending, []byte(`"pending"`), nil}, - {"Accepted", invitations.Accepted, []byte(`"accepted"`), nil}, - {"Rejected", invitations.Rejected, []byte(`"rejected"`), nil}, - {"All", invitations.All, []byte(`"all"`), nil}, - {"Unknown", invitations.State(100), []byte(`"unknown"`), nil}, - } - - for _, tt := range tests { - got, err := tt.state.MarshalJSON() - assert.Equal(t, tt.expected, got, "State.MarshalJSON() = %v, expected %v", got, tt.expected) - assert.Equal(t, tt.err, err, "State.MarshalJSON() error = %v, expected %v", err, tt.err) - } -} - -func TestState_UnmarshalJSON(t *testing.T) { - tests := []struct { - name string - data []byte - state invitations.State - err error - }{ - {"Pending", []byte(`"pending"`), invitations.Pending, nil}, - {"Accepted", []byte(`"accepted"`), invitations.Accepted, nil}, - {"Rejected", []byte(`"rejected"`), invitations.Rejected, nil}, - {"All", []byte(`"all"`), invitations.All, nil}, - {"Unknown", []byte(`"unknown"`), invitations.State(0), apiutil.ErrInvitationState}, - } - - for _, tt := range tests { - var state invitations.State - err := state.UnmarshalJSON(tt.data) - assert.Equal(t, tt.err, err, "State.UnmarshalJSON() error = %v, expected %v", err, tt.err) - assert.Equal(t, tt.state, state, "State.UnmarshalJSON() = %v, expected %v", state, tt.state) - } -} diff --git a/journal/api/doc.go b/journal/api/doc.go deleted file mode 100644 index 2424852cc..000000000 --- a/journal/api/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains API-related concerns: endpoint definitions, middlewares -// and all resource representations. -package api diff --git a/journal/api/endpoint.go b/journal/api/endpoint.go deleted file mode 100644 index b19567917..000000000 --- a/journal/api/endpoint.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/journal" - "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 retrieveJournalsEndpoint(svc journal.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(retrieveJournalsReq) - 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.RetrieveAll(ctx, session, req.page) - if err != nil { - return nil, err - } - - return pageRes{ - JournalsPage: page, - }, nil - } -} diff --git a/journal/api/endpoint_test.go b/journal/api/endpoint_test.go deleted file mode 100644 index 89178b13b..000000000 --- a/journal/api/endpoint_test.go +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "fmt" - "io" - "net/http" - "net/http/httptest" - "strconv" - "testing" - "time" - - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/journal/api" - "github.com/absmach/magistrala/journal/mocks" - 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" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var validToken = "valid" - -type testRequest struct { - client *http.Client - method string - url string - token string - body io.Reader -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, tr.body) - if err != nil { - return nil, err - } - - if tr.token != "" { - req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) - } - - return tr.client.Do(req) -} - -func newjournalServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - svc := new(mocks.Service) - - logger := mglog.NewMock() - authn := new(authnmocks.Authentication) - mux := api.MakeHandler(svc, authn, logger, "journal-log", "test") - return httptest.NewServer(mux), svc, authn -} - -func TestListUserJournalsEndpoint(t *testing.T) { - es, svc, authn := newjournalServer() - - cases := []struct { - desc string - token string - session mgauthn.Session - url string - contentType string - status int - svcErr error - }{ - { - desc: "successful", - token: validToken, - url: "/user/123", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "empty token", - token: "", - url: "/user/123", - status: http.StatusUnauthorized, - svcErr: nil, - }, - { - desc: "with service error", - token: validToken, - url: "/user/123", - status: http.StatusForbidden, - svcErr: svcerr.ErrAuthorization, - }, - { - desc: "with offset", - token: validToken, - url: "/user/123?offset=10", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with invalid offset", - token: validToken, - url: "/user/123?offset=ten", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with limit", - token: validToken, - url: "/user/123?limit=10", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with invalid limit", - token: validToken, - url: "/user/123?limit=ten", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with operation", - token: validToken, - url: "/user/123?operation=user.create", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with malformed operation", - token: validToken, - url: "/user/123?operation=user.create&operation=user.update", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with from", - token: validToken, - url: fmt.Sprintf("/user/123?from=%d", time.Now().Unix()), - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with invalid from", - token: validToken, - url: "/user/123?from=ten", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with invalid from as UnixNano", - token: validToken, - url: fmt.Sprintf("/user/123?from=%d", time.Now().UnixNano()), - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with to", - token: validToken, - url: fmt.Sprintf("/user/123?to=%d", time.Now().Unix()), - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with invalid to", - token: validToken, - url: "/user/123?to=ten", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with invalid to as UnixNano", - token: validToken, - url: fmt.Sprintf("/user/123?to=%d", time.Now().UnixNano()), - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with attributes", - token: validToken, - url: fmt.Sprintf("/user/123?with_attributes=%s", strconv.FormatBool(true)), - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with invalid attributes", - token: validToken, - url: "/user/123?with_attributes=ten", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with metadata", - token: validToken, - url: fmt.Sprintf("/user/123?with_metadata=%s", strconv.FormatBool(true)), - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with invalid metadata", - token: validToken, - url: "/user/123?with_metadata=ten", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with asc direction", - token: validToken, - url: "/user/123?dir=asc", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with desc direction", - token: validToken, - url: "/user/123?dir=desc", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with invalid direction", - token: validToken, - url: "/user/123?dir=ten", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with malformed direction", - token: validToken, - url: "/user/123?dir=invalid&dir=invalid2", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with empty url", - token: validToken, - url: "", - status: http.StatusNotFound, - svcErr: nil, - }, - { - desc: "with empty entity type", - token: validToken, - url: "//123", - status: http.StatusNotFound, - svcErr: nil, - }, - { - desc: "with empty entity ID", - token: validToken, - url: "/user/", - status: http.StatusNotFound, - svcErr: nil, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - if c.token == validToken { - c.session = mgauthn.Session{ - UserID: testsutil.GenerateUUID(t), - } - } - authCall := authn.On("Authenticate", mock.Anything, c.token).Return(c.session, nil) - svcCall := svc.On("RetrieveAll", mock.Anything, c.session, mock.Anything).Return(journal.JournalsPage{}, c.svcErr) - req := testRequest{ - client: es.Client(), - method: http.MethodGet, - url: es.URL + "/journal" + c.url, - token: c.token, - } - - resp, err := req.make() - assert.Nil(t, err, c.desc) - defer resp.Body.Close() - assert.Equal(t, c.status, resp.StatusCode, c.desc) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListEntityJournalsEndpoint(t *testing.T) { - es, svc, authn := newjournalServer() - - domainID := testsutil.GenerateUUID(t) - userID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - token string - session mgauthn.Session - domainID string - url string - contentType string - status int - authnErr error - svcErr error - }{ - { - desc: "with group type successful", - token: validToken, - domainID: domainID, - url: "/group/123", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with channel type successful", - token: validToken, - domainID: domainID, - url: "/channel/123", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with thing type successful", - token: validToken, - domainID: domainID, - url: "/thing/123", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with service error", - token: validToken, - domainID: domainID, - url: "/thing/123", - status: http.StatusForbidden, - svcErr: svcerr.ErrAuthorization, - }, - { - desc: "with operation", - token: validToken, - domainID: domainID, - url: "/channel/123?operation=channel.create", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: "with malformed operation", - token: validToken, - domainID: domainID, - url: "/user/123?operation=user.create&operation=user.update", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with invalid entity type", - token: validToken, - domainID: domainID, - url: "/invalid/123", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with all query params", - token: validToken, - domainID: domainID, - url: "/group/123?offset=10&limit=10&operation=group.create&from=0&to=10&with_attributes=true&with_metadata=true&dir=asc", - status: http.StatusOK, - svcErr: nil, - }, - { - desc: " with empty token", - url: "/group/123", - domainID: domainID, - status: http.StatusUnauthorized, - svcErr: nil, - }, - { - desc: "with empty domain ID", - token: validToken, - url: "/group/", - status: http.StatusNotFound, - svcErr: nil, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - if c.token == validToken { - c.session = mgauthn.Session{ - UserID: userID, - DomainID: domainID, - DomainUserID: domainID + "_" + userID, - } - } - authCall := authn.On("Authenticate", mock.Anything, c.token).Return(c.session, c.authnErr) - svcCall := svc.On("RetrieveAll", mock.Anything, c.session, mock.Anything).Return(journal.JournalsPage{}, c.svcErr) - req := testRequest{ - client: es.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/journal%s", es.URL, c.domainID, c.url), - token: c.token, - } - resp, err := req.make() - assert.Nil(t, err, c.desc) - defer resp.Body.Close() - assert.Equal(t, c.status, resp.StatusCode, c.desc) - svcCall.Unset() - authCall.Unset() - }) - } -} diff --git a/journal/api/requests.go b/journal/api/requests.go deleted file mode 100644 index ba633e55f..000000000 --- a/journal/api/requests.go +++ /dev/null @@ -1,32 +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/journal" - "github.com/absmach/magistrala/pkg/apiutil" -) - -type retrieveJournalsReq struct { - token string - page journal.Page -} - -func (req retrieveJournalsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.page.Limit > api.DefLimit { - return apiutil.ErrLimitSize - } - if req.page.Direction != "" && req.page.Direction != api.AscDir && req.page.Direction != api.DescDir { - return apiutil.ErrInvalidDirection - } - if req.page.EntityID == "" { - return apiutil.ErrMissingID - } - - return nil -} diff --git a/journal/api/requests_test.go b/journal/api/requests_test.go deleted file mode 100644 index 31b9b4199..000000000 --- a/journal/api/requests_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "testing" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/stretchr/testify/assert" -) - -var ( - token = "token" - limit uint64 = 10 -) - -func TestRetrieveJournalsReqValidate(t *testing.T) { - cases := []struct { - desc string - req retrieveJournalsReq - err error - }{ - { - desc: "valid", - req: retrieveJournalsReq{ - token: token, - page: journal.Page{ - Limit: limit, - EntityID: "id", - EntityType: journal.UserEntity, - }, - }, - err: nil, - }, - { - desc: "missing token", - req: retrieveJournalsReq{ - page: journal.Page{ - Limit: limit, - EntityID: "id", - EntityType: journal.UserEntity, - }, - }, - err: apiutil.ErrBearerToken, - }, - { - desc: "invalid limit size", - req: retrieveJournalsReq{ - token: token, - page: journal.Page{ - Limit: api.DefLimit + 1, - EntityID: "id", - EntityType: journal.UserEntity, - }, - }, - err: apiutil.ErrLimitSize, - }, - { - desc: "invalid sorting direction", - req: retrieveJournalsReq{ - token: token, - page: journal.Page{ - Limit: limit, - Direction: "invalid", - EntityID: "id", - EntityType: journal.UserEntity, - }, - }, - err: apiutil.ErrInvalidDirection, - }, - { - desc: "valid id and entity type", - req: retrieveJournalsReq{ - token: token, - page: journal.Page{ - Limit: limit, - EntityID: "id", - EntityType: journal.UserEntity, - }, - }, - err: nil, - }, - { - desc: "valid id and empty entity type", - req: retrieveJournalsReq{ - token: token, - page: journal.Page{ - Limit: limit, - EntityID: "id", - }, - }, - err: nil, - }, - { - desc: "empty id and empty entity type", - req: retrieveJournalsReq{ - token: token, - page: journal.Page{ - Limit: limit, - }, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty id and valid entity type", - req: retrieveJournalsReq{ - token: token, - page: journal.Page{ - Limit: limit, - EntityType: journal.UserEntity, - }, - }, - err: apiutil.ErrMissingID, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - err := c.req.validate() - assert.Equal(t, c.err, err) - }) - } -} diff --git a/journal/api/responses.go b/journal/api/responses.go deleted file mode 100644 index 81b3702cf..000000000 --- a/journal/api/responses.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/journal" -) - -var _ magistrala.Response = (*pageRes)(nil) - -type pageRes struct { - journal.JournalsPage `json:",inline"` -} - -func (res pageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res pageRes) Code() int { - return http.StatusOK -} - -func (res pageRes) Empty() bool { - return false -} diff --git a/journal/api/transport.go b/journal/api/transport.go deleted file mode 100644 index 07b15c15c..000000000 --- a/journal/api/transport.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "log/slog" - "math" - "net/http" - "strings" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - "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" -) - -const ( - operationKey = "operation" - fromKey = "from" - toKey = "to" - attributesKey = "with_attributes" - metadataKey = "with_metadata" - entityIDKey = "id" - entityTypeKey = "entity_type" -) - -// MakeHandler returns a HTTP API handler with health check and metrics. -func MakeHandler(svc journal.Service, authn mgauthn.Authentication, logger *slog.Logger, svcName, instanceID string) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - mux := chi.NewRouter() - - mux.With(api.AuthenticateMiddleware(authn, false)).Get("/journal/user/{userID}", otelhttp.NewHandler(kithttp.NewServer( - retrieveJournalsEndpoint(svc), - decodeRetrieveUserJournalReq, - api.EncodeResponse, - opts..., - ), "list_user_journals").ServeHTTP) - - mux.With(api.AuthenticateMiddleware(authn, true)).Get("/{domainID}/journal/{entityType}/{entityID}", otelhttp.NewHandler(kithttp.NewServer( - retrieveJournalsEndpoint(svc), - decodeRetrieveEntityJournalReq, - api.EncodeResponse, - opts..., - ), "list__entity_journals").ServeHTTP) - - mux.Get("/health", magistrala.Health(svcName, instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} - -func decodeRetrieveEntityJournalReq(_ context.Context, r *http.Request) (interface{}, error) { - page, err := decodePageQuery(r) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - entityType, err := journal.ToEntityType(chi.URLParam(r, "entityType")) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - page.EntityID = chi.URLParam(r, "entityID") - page.EntityType = entityType - - if entityType == journal.ChannelEntity { - page.Operation = strings.ReplaceAll(page.Operation, "channel", "group") - } - - req := retrieveJournalsReq{ - token: apiutil.ExtractBearerToken(r), - page: page, - } - - return req, nil -} - -func decodeRetrieveUserJournalReq(_ context.Context, r *http.Request) (interface{}, error) { - page, err := decodePageQuery(r) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - page.EntityID = chi.URLParam(r, "userID") - page.EntityType = journal.UserEntity - - req := retrieveJournalsReq{ - token: apiutil.ExtractBearerToken(r), - page: page, - } - - return req, nil -} - -func decodePageQuery(r *http.Request) (journal.Page, error) { - offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - operation, err := apiutil.ReadStringQuery(r, operationKey, "") - if err != nil { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - from, err := apiutil.ReadNumQuery[int64](r, fromKey, 0) - if err != nil { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - if from > math.MaxInt32 { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTimeFormat) - } - var fromTime time.Time - if from != 0 { - fromTime = time.Unix(from, 0) - } - to, err := apiutil.ReadNumQuery[int64](r, toKey, 0) - if err != nil { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - if to > math.MaxInt32 { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTimeFormat) - } - var toTime time.Time - if to != 0 { - toTime = time.Unix(to, 0) - } - attributes, err := apiutil.ReadBoolQuery(r, attributesKey, false) - if err != nil { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - metadata, err := apiutil.ReadBoolQuery(r, metadataKey, false) - if err != nil { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DescDir) - if err != nil { - return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - - return journal.Page{ - Offset: offset, - Limit: limit, - Operation: operation, - From: fromTime, - To: toTime, - WithAttributes: attributes, - WithMetadata: metadata, - Direction: dir, - }, nil -} diff --git a/journal/doc.go b/journal/doc.go deleted file mode 100644 index 3b6860678..000000000 --- a/journal/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package journal contains the journal service. -// This service is responsible for storing events from the event store to a -// journal log repository. It is also responsible for providing a REST API to query events. -package journal diff --git a/journal/events/consumer.go b/journal/events/consumer.go deleted file mode 100644 index e2636ed7f..000000000 --- a/journal/events/consumer.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "context" - "errors" - "time" - - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/store" -) - -var ErrMissingOccurredAt = errors.New("missing occurred_at") - -// Start method starts consuming messages received from Event store. -func Start(ctx context.Context, consumer string, sub events.Subscriber, service journal.Service) error { - subCfg := events.SubscriberConfig{ - Consumer: consumer, - Stream: store.StreamAllEvents, - Handler: Handle(service), - } - - return sub.Subscribe(ctx, subCfg) -} - -func Handle(service journal.Service) handleFunc { - return func(ctx context.Context, event events.Event) error { - data, err := event.Encode() - if err != nil { - return err - } - - operation, ok := data["operation"].(string) - if !ok { - return errors.New("missing operation") - } - delete(data, "operation") - - if operation == "" { - return errors.New("missing operation") - } - - occurredAt, ok := data["occurred_at"].(float64) - if !ok { - return ErrMissingOccurredAt - } - delete(data, "occurred_at") - - if occurredAt == 0 { - return ErrMissingOccurredAt - } - - metadata, ok := data["metadata"].(map[string]interface{}) - if !ok { - metadata = make(map[string]interface{}) - } - delete(data, "metadata") - - if len(data) == 0 { - return errors.New("missing attributes") - } - - j := journal.Journal{ - Operation: operation, - OccurredAt: time.Unix(0, int64(occurredAt)), - Attributes: data, - Metadata: metadata, - } - - return service.Save(ctx, j) - } -} - -type handleFunc func(ctx context.Context, event events.Event) error - -func (h handleFunc) Handle(ctx context.Context, event events.Event) error { - return h(ctx, event) -} - -func (h handleFunc) Cancel() error { - return nil -} diff --git a/journal/events/consumer_test.go b/journal/events/consumer_test.go deleted file mode 100644 index 3f2e37e02..000000000 --- a/journal/events/consumer_test.go +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events_test - -import ( - "context" - "encoding/json" - "errors" - "math/rand" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/journal" - aevents "github.com/absmach/magistrala/journal/events" - "github.com/absmach/magistrala/journal/mocks" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - operation = "users.create" - payload = map[string]interface{}{ - "temperature": rand.Float64(), - "humidity": float64(rand.Intn(1000)), - "locations": []interface{}{ - strings.Repeat("a", 100), - strings.Repeat("a", 100), - }, - "status": "active", - } - idProvider = uuid.New() -) - -type testEvent struct { - data map[string]interface{} - err error -} - -func (e testEvent) Encode() (map[string]interface{}, error) { - return e.data, e.err -} - -func NewTestEvent(data map[string]interface{}, err error) testEvent { - return testEvent{data: data, err: err} -} - -func TestHandle(t *testing.T) { - repo := new(mocks.Repository) - svc := journal.NewService(idProvider, repo) - - cases := []struct { - desc string - event map[string]interface{} - encodeErr error - repoErr error - err error - }{ - { - desc: "success", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - err: nil, - }, - { - desc: "with encode error", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - encodeErr: errors.New("encode error"), - err: errors.New("encode error"), - }, - { - desc: "with missing operation", - event: map[string]interface{}{ - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - err: errors.New("missing operation"), - }, - { - desc: "with empty operation", - event: map[string]interface{}{ - "operation": "", - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - err: errors.New("missing operation"), - }, - { - desc: "with invalid operation", - event: map[string]interface{}{ - "operation": 1, - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - err: errors.New("missing operation"), - }, - { - desc: "with missing occurred_at", - event: map[string]interface{}{ - "operation": operation, - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - err: aevents.ErrMissingOccurredAt, - }, - { - desc: "with empty occurred_at", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(0), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - err: aevents.ErrMissingOccurredAt, - }, - { - desc: "with invalid occurred_at", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": "invalid", - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - err: aevents.ErrMissingOccurredAt, - }, - { - desc: "with missing metadata", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - }, - err: nil, - }, - { - desc: "with empty metadata", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": map[string]interface{}{}, - }, - err: nil, - }, - { - desc: "with invalid metadata", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": 1, - }, - err: nil, - }, - { - desc: "with missing attributes", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "metadata": payload, - }, - err: errors.New("missing attributes"), - }, - { - desc: "with empty attributes", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "id": "", - "tags": []interface{}{}, - "number": float64(0), - "metadata": payload, - }, - err: nil, - }, - { - desc: "with invalid attributes", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "nested": map[string]interface{}{ - "key": float64(rand.Intn(1000)), - "nested": map[string]interface{}{ - "key": float64(rand.Intn(1000)), - "nested": map[string]interface{}{ - "key": float64(rand.Intn(1000)), - "nested": map[string]interface{}{ - "key": float64(rand.Intn(1000)), - "nested": map[string]interface{}{ - "key": float64(rand.Intn(1000)), - "nested": map[string]interface{}{ - "key": float64(rand.Intn(1000)), - }, - }, - }, - }, - }, - }, - "metadata": payload, - }, - err: nil, - }, - { - desc: "success", - event: map[string]interface{}{ - "operation": operation, - "occurred_at": float64(time.Now().UnixNano()), - "id": testsutil.GenerateUUID(t), - "tags": []interface{}{testsutil.GenerateUUID(t), testsutil.GenerateUUID(t)}, - "number": float64(rand.Intn(1000)), - "metadata": payload, - }, - repoErr: repoerr.ErrCreateEntity, - err: repoerr.ErrCreateEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data, err := json.Marshal(tc.event) - assert.NoError(t, err) - - event := map[string]interface{}{} - err = json.Unmarshal(data, &event) - assert.NoError(t, err) - - repoCall := repo.On("Save", context.Background(), mock.Anything).Return(tc.repoErr) - err = aevents.Handle(svc)(context.Background(), NewTestEvent(event, tc.encodeErr)) - switch { - case tc.err == nil: - assert.NoError(t, err) - default: - assert.ErrorContains(t, err, tc.err.Error()) - } - repoCall.Unset() - }) - } -} diff --git a/journal/events/doc.go b/journal/events/doc.go deleted file mode 100644 index 5023696f8..000000000 --- a/journal/events/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package events provides the event consumer for the journal service. -// This package is responsible for consuming events from the event store and -// processing them. -package events diff --git a/journal/journal.go b/journal/journal.go deleted file mode 100644 index cc8d94660..000000000 --- a/journal/journal.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package journal - -import ( - "context" - "encoding/json" - "time" - - "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/policies" -) - -type EntityType uint8 - -const ( - UserEntity EntityType = iota - GroupEntity - ThingEntity - ChannelEntity -) - -// String representation of the possible entity type values. -const ( - userEntityType = "user" - groupEntityType = "group" - thingEntityType = "thing" - channelEntityType = "channel" -) - -// String converts entity type to string literal. -func (e EntityType) String() string { - switch e { - case UserEntity: - return userEntityType - case GroupEntity: - return groupEntityType - case ThingEntity: - return thingEntityType - case ChannelEntity: - return channelEntityType - default: - return "" - } -} - -// AuthString returns the entity type as a string for authorization. -func (e EntityType) AuthString() string { - switch e { - case UserEntity: - return policies.UserType - case GroupEntity, ChannelEntity: - return policies.GroupType - case ThingEntity: - return policies.ThingType - default: - return "" - } -} - -// ToEntityType converts string value to a valid entity type. -func ToEntityType(entityType string) (EntityType, error) { - switch entityType { - case userEntityType: - return UserEntity, nil - case groupEntityType: - return GroupEntity, nil - case thingEntityType: - return ThingEntity, nil - case channelEntityType: - return ChannelEntity, nil - default: - return EntityType(0), apiutil.ErrInvalidEntityType - } -} - -// Query returns the SQL condition for the entity type. -func (e EntityType) Query() string { - switch e { - case UserEntity: - 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))" - default: - return "" - } -} - -// Journal represents an event journal that occurred in the system. -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. - Metadata map[string]interface{} `json:"metadata,omitempty" db:"metadata,omitempty"` // This is decoded metadata from the journal. -} - -// JournalsPage represents a page of journals. -type JournalsPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Journals []Journal `json:"journals"` -} - -// Page is used to filter journals. -type Page struct { - Offset uint64 `json:"offset" db:"offset"` - Limit uint64 `json:"limit" db:"limit"` - Operation string `json:"operation,omitempty" db:"operation,omitempty"` - From time.Time `json:"from,omitempty" db:"from,omitempty"` - To time.Time `json:"to,omitempty" db:"to,omitempty"` - WithAttributes bool `json:"with_attributes,omitempty"` - WithMetadata bool `json:"with_metadata,omitempty"` - EntityID string `json:"entity_id,omitempty" db:"entity_id,omitempty"` - EntityType EntityType `json:"entity_type,omitempty" db:"entity_type,omitempty"` - Direction string `json:"direction,omitempty"` -} - -func (page JournalsPage) MarshalJSON() ([]byte, error) { - type Alias JournalsPage - a := struct { - Alias - }{ - Alias: Alias(page), - } - - if a.Journals == nil { - a.Journals = make([]Journal, 0) - } - - return json.Marshal(a) -} - -// Service provides access to the journal log service. -// -//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" -type Service interface { - // Save saves the journal to the database. - Save(ctx context.Context, journal Journal) error - - // RetrieveAll retrieves all journals from the database with the given page. - RetrieveAll(ctx context.Context, session mgauthn.Session, page Page) (JournalsPage, error) -} - -// Repository provides access to the journal log database. -// -//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" -type Repository interface { - // Save persists the journal to a database. - Save(ctx context.Context, journal Journal) error - - // RetrieveAll retrieves all journals from the database with the given page. - RetrieveAll(ctx context.Context, page Page) (JournalsPage, error) -} diff --git a/journal/journal_test.go b/journal/journal_test.go deleted file mode 100644 index 0772ed009..000000000 --- a/journal/journal_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package journal_test - -import ( - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/stretchr/testify/assert" -) - -func TestJournalsPage_MarshalJSON(t *testing.T) { - occurredAt := time.Now() - - cases := []struct { - desc string - page journal.JournalsPage - res string - }{ - { - desc: "empty page", - page: journal.JournalsPage{ - Journals: []journal.Journal(nil), - }, - res: `{"total":0,"offset":0,"limit":0,"journals":[]}`, - }, - { - desc: "page with journals", - page: journal.JournalsPage{ - Total: 1, - Offset: 0, - Limit: 0, - Journals: []journal.Journal{ - { - Operation: "123", - OccurredAt: occurredAt, - Attributes: map[string]interface{}{"123": "123"}, - Metadata: map[string]interface{}{"123": "123"}, - }, - }, - }, - res: fmt.Sprintf(`{"total":1,"offset":0,"limit":0,"journals":[{"operation":"123","occurred_at":"%s","attributes":{"123":"123"},"metadata":{"123":"123"}}]}`, occurredAt.Format(time.RFC3339Nano)), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data, err := tc.page.MarshalJSON() - assert.NoError(t, err, "Unexpected error: %v", err) - assert.Equal(t, tc.res, string(data)) - }) - } -} - -func TestEntityType(t *testing.T) { - cases := []struct { - desc string - e journal.EntityType - str string - authString string - queryString string - }{ - { - desc: "UserEntity", - e: journal.UserEntity, - str: "user", - authString: "user", - }, - { - desc: "ThingEntity", - e: journal.ThingEntity, - str: "thing", - authString: "thing", - }, - { - desc: "GroupEntity", - e: journal.GroupEntity, - str: "group", - authString: "group", - }, - { - desc: "ChannelEntity", - e: journal.ChannelEntity, - str: "channel", - authString: "group", - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - assert.Equal(t, tc.str, tc.e.String()) - assert.Equal(t, tc.authString, tc.e.AuthString()) - assert.NotEmpty(t, tc.e.Query()) - }) - } -} - -func TestToEntityType(t *testing.T) { - cases := []struct { - desc string - entityType string - expected journal.EntityType - expectedErr error - }{ - { - desc: "UserEntity", - entityType: "user", - expected: journal.UserEntity, - }, - { - desc: "ThingEntity", - entityType: "thing", - expected: journal.ThingEntity, - }, - { - desc: "GroupEntity", - entityType: "group", - expected: journal.GroupEntity, - }, - { - desc: "ChannelEntity", - entityType: "channel", - expected: journal.ChannelEntity, - }, - { - desc: "Invalid entity type", - entityType: "invalid", - expectedErr: apiutil.ErrInvalidEntityType, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - entityType, err := journal.ToEntityType(tc.entityType) - assert.Equal(t, tc.expected, entityType) - assert.Equal(t, tc.expectedErr, err) - }) - } -} diff --git a/journal/middleware/authorization.go b/journal/middleware/authorization.go deleted file mode 100644 index fc68f8d4f..000000000 --- a/journal/middleware/authorization.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - - "github.com/absmach/magistrala/journal" - mgauthn "github.com/absmach/magistrala/pkg/authn" - mgauthz "github.com/absmach/magistrala/pkg/authz" - "github.com/absmach/magistrala/pkg/policies" -) - -var _ journal.Service = (*authorizationMiddleware)(nil) - -type authorizationMiddleware struct { - svc journal.Service - authz mgauthz.Authorization -} - -// AuthorizationMiddleware adds authorization to the journal service. -func AuthorizationMiddleware(svc journal.Service, authz mgauthz.Authorization) journal.Service { - return &authorizationMiddleware{ - svc: svc, - authz: authz, - } -} - -func (am *authorizationMiddleware) Save(ctx context.Context, journal journal.Journal) error { - return am.svc.Save(ctx, journal) -} - -func (am *authorizationMiddleware) RetrieveAll(ctx context.Context, session mgauthn.Session, page journal.Page) (journal.JournalsPage, error) { - permission := policies.ViewPermission - objectType := page.EntityType.AuthString() - object := page.EntityID - subject := session.DomainUserID - - // If the entity is a user, we need to check if the user is an admin - if page.EntityType.AuthString() == policies.UserType { - permission = policies.AdminPermission - objectType = policies.PlatformType - object = policies.MagistralaObject - subject = session.UserID - } - - req := mgauthz.PolicyReq{ - Domain: session.DomainID, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Subject: subject, - Permission: permission, - ObjectType: objectType, - Object: object, - } - if err := am.authz.Authorize(ctx, req); err != nil { - return journal.JournalsPage{}, err - } - - return am.svc.RetrieveAll(ctx, session, page) -} diff --git a/journal/middleware/doc.go b/journal/middleware/doc.go deleted file mode 100644 index 71d257133..000000000 --- a/journal/middleware/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package middleware provides middleware for the journal service. -// This is logging, metrics, and tracing middleware. -package middleware diff --git a/journal/middleware/logging.go b/journal/middleware/logging.go deleted file mode 100644 index 560010db7..000000000 --- a/journal/middleware/logging.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/journal" - mgauthn "github.com/absmach/magistrala/pkg/authn" -) - -var _ journal.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - service journal.Service -} - -// LoggingMiddleware adds logging facilities to the adapter. -func LoggingMiddleware(service journal.Service, logger *slog.Logger) journal.Service { - return &loggingMiddleware{ - logger: logger, - service: service, - } -} - -func (lm *loggingMiddleware) Save(ctx context.Context, j journal.Journal) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("journal", - slog.String("occurred_at", j.OccurredAt.Format(time.RFC3339Nano)), - slog.String("operation", j.Operation), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Save journal failed", args...) - return - } - lm.logger.Info("Save journal completed successfully", args...) - }(time.Now()) - - return lm.service.Save(ctx, j) -} - -func (lm *loggingMiddleware) RetrieveAll(ctx context.Context, session mgauthn.Session, page journal.Page) (journalsPage journal.JournalsPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("page", - slog.String("operation", page.Operation), - slog.String("entity_type", page.EntityType.String()), - slog.Uint64("offset", page.Offset), - slog.Uint64("limit", page.Limit), - slog.Uint64("total", journalsPage.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Retrieve all journals failed", args...) - return - } - lm.logger.Info("Retrieve all journals completed successfully", args...) - }(time.Now()) - - return lm.service.RetrieveAll(ctx, session, page) -} diff --git a/journal/middleware/metrics.go b/journal/middleware/metrics.go deleted file mode 100644 index f572aee73..000000000 --- a/journal/middleware/metrics.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "time" - - "github.com/absmach/magistrala/journal" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/go-kit/kit/metrics" -) - -var _ journal.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - service journal.Service -} - -// MetricsMiddleware returns new message repository -// with Save method wrapped to expose metrics. -func MetricsMiddleware(service journal.Service, counter metrics.Counter, latency metrics.Histogram) journal.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - service: service, - } -} - -func (mm *metricsMiddleware) Save(ctx context.Context, j journal.Journal) error { - defer func(begin time.Time) { - mm.counter.With("method", "save").Add(1) - mm.latency.With("method", "save").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.service.Save(ctx, j) -} - -func (mm *metricsMiddleware) RetrieveAll(ctx context.Context, session mgauthn.Session, page journal.Page) (journal.JournalsPage, error) { - defer func(begin time.Time) { - mm.counter.With("method", "retrieve_all").Add(1) - mm.latency.With("method", "retrieve_all").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.service.RetrieveAll(ctx, session, page) -} diff --git a/journal/middleware/tracing.go b/journal/middleware/tracing.go deleted file mode 100644 index 50a0794be..000000000 --- a/journal/middleware/tracing.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - - "github.com/absmach/magistrala/journal" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ journal.Service = (*tracing)(nil) - -type tracing struct { - tracer trace.Tracer - svc journal.Service -} - -func Tracing(svc journal.Service, tracer trace.Tracer) journal.Service { - return &tracing{tracer, svc} -} - -func (tm *tracing) Save(ctx context.Context, j journal.Journal) error { - ctx, span := tm.tracer.Start(ctx, "save", trace.WithAttributes( - attribute.String("occurred_at", j.OccurredAt.String()), - attribute.String("operation", j.Operation), - )) - defer span.End() - - return tm.svc.Save(ctx, j) -} - -func (tm *tracing) RetrieveAll(ctx context.Context, session mgauthn.Session, page journal.Page) (resp journal.JournalsPage, err error) { - ctx, span := tm.tracer.Start(ctx, "retrieve_all", trace.WithAttributes( - attribute.Int64("offset", int64(page.Offset)), - attribute.Int64("limit", int64(page.Limit)), - attribute.Int64("total", int64(resp.Total)), - attribute.String("entity_type", page.EntityType.String()), - attribute.String("operation", page.Operation), - )) - defer span.End() - - return tm.svc.RetrieveAll(ctx, session, page) -} diff --git a/journal/mocks/doc.go b/journal/mocks/doc.go deleted file mode 100644 index 16ed198af..000000000 --- a/journal/mocks/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mocks contains mocks for testing purposes. -package mocks diff --git a/journal/mocks/repository.go b/journal/mocks/repository.go deleted file mode 100644 index 8b3fb5129..000000000 --- a/journal/mocks/repository.go +++ /dev/null @@ -1,77 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - journal "github.com/absmach/magistrala/journal" - mock "github.com/stretchr/testify/mock" -) - -// Repository is an autogenerated mock type for the Repository type -type Repository struct { - mock.Mock -} - -// RetrieveAll provides a mock function with given fields: ctx, page -func (_m *Repository) RetrieveAll(ctx context.Context, page journal.Page) (journal.JournalsPage, error) { - ret := _m.Called(ctx, page) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAll") - } - - var r0 journal.JournalsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, journal.Page) (journal.JournalsPage, error)); ok { - return rf(ctx, page) - } - if rf, ok := ret.Get(0).(func(context.Context, journal.Page) journal.JournalsPage); ok { - r0 = rf(ctx, page) - } else { - r0 = ret.Get(0).(journal.JournalsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, journal.Page) error); ok { - r1 = rf(ctx, page) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, _a1 -func (_m *Repository) Save(ctx context.Context, _a1 journal.Journal) error { - ret := _m.Called(ctx, _a1) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, journal.Journal) error); ok { - r0 = rf(ctx, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// 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/journal/mocks/service.go b/journal/mocks/service.go deleted file mode 100644 index 59853e25a..000000000 --- a/journal/mocks/service.go +++ /dev/null @@ -1,80 +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" - - journal "github.com/absmach/magistrala/journal" - - mock "github.com/stretchr/testify/mock" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// RetrieveAll provides a mock function with given fields: ctx, session, page -func (_m *Service) RetrieveAll(ctx context.Context, session authn.Session, page journal.Page) (journal.JournalsPage, error) { - ret := _m.Called(ctx, session, page) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAll") - } - - var r0 journal.JournalsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, journal.Page) (journal.JournalsPage, error)); ok { - return rf(ctx, session, page) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, journal.Page) journal.JournalsPage); ok { - r0 = rf(ctx, session, page) - } else { - r0 = ret.Get(0).(journal.JournalsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, journal.Page) error); ok { - r1 = rf(ctx, session, page) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, _a1 -func (_m *Service) Save(ctx context.Context, _a1 journal.Journal) error { - ret := _m.Called(ctx, _a1) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, journal.Journal) error); ok { - r0 = rf(ctx, _a1) - } 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/journal/postgres/doc.go b/journal/postgres/doc.go deleted file mode 100644 index 1007b3120..000000000 --- a/journal/postgres/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres provides a postgres implementation of the journal log repository. -package postgres diff --git a/journal/postgres/init.go b/journal/postgres/init.go deleted file mode 100644 index adad7979c..000000000 --- a/journal/postgres/init.go +++ /dev/null @@ -1,36 +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: "journal_01", - Up: []string{ - `CREATE TABLE IF NOT EXISTS journal ( - id VARCHAR(36) PRIMARY KEY, - operation VARCHAR NOT NULL, - occurred_at TIMESTAMP NOT NULL, - attributes JSONB NOT NULL, - metadata JSONB, - UNIQUE(operation, occurred_at, attributes) - )`, - `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_channel_filter ON journal(operation, (attributes->>'id'), (attributes->>'channel_id'), occurred_at DESC);`, - }, - Down: []string{ - `DROP TABLE IF EXISTS journal`, - }, - }, - }, - } -} diff --git a/journal/postgres/journal.go b/journal/postgres/journal.go deleted file mode 100644 index ff6606efd..000000000 --- a/journal/postgres/journal.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/postgres" -) - -type repository struct { - db postgres.Database -} - -func NewRepository(db postgres.Database) journal.Repository { - return &repository{db: db} -} - -func (repo *repository) Save(ctx context.Context, j journal.Journal) (err error) { - q := `INSERT INTO journal (id, operation, occurred_at, attributes, metadata) - VALUES (:id, :operation, :occurred_at, :attributes, :metadata);` - - dbJournal, err := toDBJournal(j) - if err != nil { - return errors.Wrap(repoerr.ErrCreateEntity, err) - } - - if _, err = repo.db.NamedExecContext(ctx, q, dbJournal); err != nil { - return postgres.HandleError(repoerr.ErrCreateEntity, err) - } - - return nil -} - -func (repo *repository) RetrieveAll(ctx context.Context, page journal.Page) (journal.JournalsPage, error) { - query := pageQuery(page) - - sq := "operation, occurred_at" - if page.WithAttributes { - sq += ", attributes" - } - if page.WithMetadata { - sq += ", metadata" - } - if page.Direction == "" { - page.Direction = "ASC" - } - q := fmt.Sprintf("SELECT %s FROM journal %s ORDER BY occurred_at %s LIMIT :limit OFFSET :offset;", sq, query, page.Direction) - - rows, err := repo.db.NamedQueryContext(ctx, q, page) - if err != nil { - return journal.JournalsPage{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - var items []journal.Journal - for rows.Next() { - var item dbJournal - if err = rows.StructScan(&item); err != nil { - return journal.JournalsPage{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - j, err := toJournal(item) - if err != nil { - return journal.JournalsPage{}, err - } - items = append(items, j) - } - - tq := fmt.Sprintf(`SELECT COUNT(*) FROM journal %s;`, query) - - total, err := postgres.Total(ctx, repo.db, tq, page) - if err != nil { - return journal.JournalsPage{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - - journalsPage := journal.JournalsPage{ - Total: total, - Offset: page.Offset, - Limit: page.Limit, - Journals: items, - } - - return journalsPage, nil -} - -func pageQuery(pm journal.Page) string { - var query []string - var emq string - if pm.Operation != "" { - query = append(query, "operation = :operation") - } - if !pm.From.IsZero() { - query = append(query, "occurred_at >= :from") - } - if !pm.To.IsZero() { - query = append(query, "occurred_at <= :to") - } - if pm.EntityID != "" { - query = append(query, pm.EntityType.Query()) - } - - if len(query) > 0 { - emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")) - } - - return emq -} - -type dbJournal struct { - ID string `db:"id"` - Operation string `db:"operation"` - OccurredAt time.Time `db:"occurred_at"` - Attributes []byte `db:"attributes"` - Metadata []byte `db:"metadata"` -} - -func toDBJournal(j journal.Journal) (dbJournal, error) { - if j.OccurredAt.IsZero() { - j.OccurredAt = time.Now() - } - - attributes := []byte("{}") - if len(j.Attributes) > 0 { - b, err := json.Marshal(j.Attributes) - if err != nil { - return dbJournal{}, errors.Wrap(repoerr.ErrMalformedEntity, err) - } - attributes = b - } - - metadata := []byte("{}") - if len(j.Metadata) > 0 { - b, err := json.Marshal(j.Metadata) - if err != nil { - return dbJournal{}, errors.Wrap(repoerr.ErrMalformedEntity, err) - } - metadata = b - } - - return dbJournal{ - ID: j.ID, - Operation: j.Operation, - OccurredAt: j.OccurredAt, - Attributes: attributes, - Metadata: metadata, - }, nil -} - -func toJournal(dbj dbJournal) (journal.Journal, error) { - var attributes map[string]interface{} - if dbj.Attributes != nil { - if err := json.Unmarshal(dbj.Attributes, &attributes); err != nil { - return journal.Journal{}, errors.Wrap(repoerr.ErrMalformedEntity, err) - } - } - - var metadata map[string]interface{} - if dbj.Metadata != nil { - if err := json.Unmarshal(dbj.Metadata, &metadata); err != nil { - return journal.Journal{}, errors.Wrap(repoerr.ErrMalformedEntity, err) - } - } - - return journal.Journal{ - Operation: dbj.Operation, - OccurredAt: dbj.OccurredAt, - Attributes: attributes, - Metadata: metadata, - }, nil -} diff --git a/journal/postgres/journal_test.go b/journal/postgres/journal_test.go deleted file mode 100644 index 3b58b26b9..000000000 --- a/journal/postgres/journal_test.go +++ /dev/null @@ -1,727 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "math/rand" - "sort" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/journal/postgres" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - operation = "user.create" - payload = map[string]interface{}{ - "temperature": rand.Float64(), - "humidity": float64(rand.Intn(1000)), - "locations": []interface{}{ - strings.Repeat("a", 100), - strings.Repeat("a", 100), - }, - "status": "active", - "nested": map[string]interface{}{ - "nested": map[string]interface{}{ - "nested": map[string]interface{}{ - "nested": map[string]interface{}{ - "key": "value", - }, - }, - }, - }, - } - - entityID = testsutil.GenerateUUID(&testing.T{}) - thingOperation = "thing.create" - thingAttributesV1 = map[string]interface{}{ - "id": entityID, - "status": "enabled", - "created_at": time.Now().Add(-time.Hour), - "name": "thing", - "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, - } - userAttributesV1 = map[string]interface{}{ - "id": entityID, - "status": "enabled", - "created_at": time.Now().Add(-time.Hour), - "name": "user", - "tags": []interface{}{"tag1", "tag2"}, - "domain": testsutil.GenerateUUID(&testing.T{}), - "metadata": payload, - "identity": testsutil.GenerateUUID(&testing.T{}), - } - userAttributesV2 = map[string]interface{}{ - "user_id": entityID, - "metadata": payload, - } - validTimeStamp = time.Now().UTC().Truncate(time.Millisecond) -) - -func TestJournalSave(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM journal") - require.Nil(t, err, fmt.Sprintf("clean journal unexpected error: %s", err)) - }) - repo := postgres.NewRepository(database) - - occurredAt := time.Now() - id := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - journal journal.Journal - err error - }{ - { - desc: "new journal successfully", - journal: journal.Journal{ - ID: id, - Operation: operation, - OccurredAt: occurredAt, - Attributes: payload, - Metadata: payload, - }, - err: nil, - }, - { - desc: "with duplicate journal", - journal: journal.Journal{ - ID: id, - Operation: operation, - OccurredAt: occurredAt, - Attributes: payload, - Metadata: payload, - }, - err: repoerr.ErrConflict, - }, - { - desc: "with massive journal metadata and attributes", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation, - OccurredAt: time.Now(), - Attributes: map[string]interface{}{ - "attributes": map[string]interface{}{ - "attributes": map[string]interface{}{ - "attributes": map[string]interface{}{ - "attributes": map[string]interface{}{ - "attributes": map[string]interface{}{ - "data": payload, - }, - "data": payload, - }, - "data": payload, - }, - "data": payload, - }, - "data": payload, - }, - "data": payload, - }, - Metadata: map[string]interface{}{ - "metadata": map[string]interface{}{ - "metadata": map[string]interface{}{ - "metadata": map[string]interface{}{ - "metadata": map[string]interface{}{ - "metadata": map[string]interface{}{ - "data": payload, - }, - "data": payload, - }, - "data": payload, - }, - "data": payload, - }, - "data": payload, - }, - "data": payload, - }, - }, - err: nil, - }, - { - desc: "with nil journal operation", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - OccurredAt: time.Now(), - Attributes: payload, - Metadata: payload, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "with empty journal operation", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: "", - OccurredAt: time.Now().Add(-time.Hour), - Attributes: payload, - Metadata: payload, - }, - err: nil, - }, - { - desc: "with nil journal occurred_at", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation, - Attributes: payload, - Metadata: payload, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "with empty journal occurred_at", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation, - OccurredAt: time.Time{}, - Attributes: payload, - Metadata: payload, - }, - err: nil, - }, - { - desc: "with nil journal attributes", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation + ".with.nil.attributes", - OccurredAt: time.Now(), - Metadata: payload, - }, - err: nil, - }, - { - desc: "with invalid journal attributes", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation, - OccurredAt: time.Now(), - Attributes: map[string]interface{}{"invalid": make(chan struct{})}, - Metadata: payload, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "with empty journal attributes", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation + ".with.empty.attributes", - OccurredAt: time.Now(), - Attributes: map[string]interface{}{}, - Metadata: payload, - }, - err: nil, - }, - { - desc: "with nil journal metadata", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation + ".with.nil.metadata", - OccurredAt: time.Now(), - Attributes: payload, - }, - err: nil, - }, - { - desc: "with invalid journal metadata", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation, - OccurredAt: time.Now(), - Metadata: map[string]interface{}{"invalid": make(chan struct{})}, - Attributes: payload, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "with empty journal metadata", - journal: journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: operation + ".with.empty.metadata", - OccurredAt: time.Now(), - Metadata: map[string]interface{}{}, - Attributes: payload, - }, - err: nil, - }, - { - desc: "with empty journal", - journal: journal.Journal{}, - err: repoerr.ErrCreateEntity, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - switch err := repo.Save(context.Background(), tc.journal); { - case err == nil: - assert.Nil(t, err) - default: - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } - }) - } -} - -func TestJournalRetrieveAll(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM journal") - require.Nil(t, err, fmt.Sprintf("clean journal unexpected error: %s", err)) - }) - repo := postgres.NewRepository(database) - - num := 200 - - var items []journal.Journal - for i := 0; i < num; i++ { - j := journal.Journal{ - ID: testsutil.GenerateUUID(t), - Operation: fmt.Sprintf("%s-%d", operation, i), - OccurredAt: time.Now().UTC().Truncate(time.Millisecond), - Attributes: userAttributesV1, - Metadata: payload, - } - if i%2 == 0 { - j.Operation = fmt.Sprintf("%s-%d", thingOperation, i) - j.Attributes = thingAttributesV1 - } - if i%3 == 0 { - j.Attributes = userAttributesV2 - } - if i%5 == 0 { - j.Attributes = thingAttributesV2 - } - err := repo.Save(context.Background(), j) - require.Nil(t, err, fmt.Sprintf("create journal unexpected error: %s", err)) - j.ID = "" - items = append(items, j) - } - - reversedItems := make([]journal.Journal, len(items)) - copy(reversedItems, items) - sort.Slice(reversedItems, func(i, j int) bool { - return reversedItems[i].OccurredAt.After(reversedItems[j].OccurredAt) - }) - - cases := []struct { - desc string - page journal.Page - response journal.JournalsPage - err error - }{ - { - desc: "successfully", - page: journal.Page{ - Offset: 0, - Limit: 1, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 1, - Journals: items[:1], - }, - err: nil, - }, - { - desc: "with offset and empty limit", - page: journal.Page{ - Offset: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 10, - Limit: 0, - Journals: []journal.Journal(nil), - }, - }, - { - desc: "with limit and empty offset", - page: journal.Page{ - Limit: 50, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 50, - Journals: items[:50], - }, - }, - { - desc: "with offset and limit", - page: journal.Page{ - Offset: 10, - Limit: 50, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 10, - Limit: 50, - Journals: items[10:60], - }, - }, - { - desc: "with offset out of range", - page: journal.Page{ - Offset: 1000, - Limit: 50, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 1000, - Limit: 50, - Journals: []journal.Journal(nil), - }, - }, - { - desc: "with offset and limit out of range", - page: journal.Page{ - Offset: 170, - Limit: 50, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 170, - Limit: 50, - Journals: items[170:200], - }, - }, - { - desc: "with limit out of range", - page: journal.Page{ - Offset: 0, - Limit: 1000, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 1000, - Journals: items, - }, - }, - { - desc: "with empty page", - page: journal.Page{}, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 0, - Journals: []journal.Journal(nil), - }, - }, - { - desc: "with operation", - page: journal.Page{ - Operation: items[0].Operation, - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Journals: []journal.Journal{items[0]}, - }, - }, - { - desc: "with invalid operation", - page: journal.Page{ - Operation: strings.Repeat("a", 37), - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: 0, - Offset: 0, - Limit: 10, - Journals: []journal.Journal(nil), - }, - }, - { - desc: "with attributes", - page: journal.Page{ - WithAttributes: true, - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Journals: items[:10], - }, - }, - { - desc: "with metadata", - page: journal.Page{ - WithMetadata: true, - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Journals: items[:10], - }, - }, - { - desc: "with attributes and Metadata", - page: journal.Page{ - WithAttributes: true, - WithMetadata: true, - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Journals: items[:10], - }, - }, - { - desc: "with from", - page: journal.Page{ - From: items[0].OccurredAt, - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Journals: items[:10], - }, - }, - { - desc: "with invalid from", - page: journal.Page{ - From: time.Now().UTC().Truncate(time.Millisecond).Add(time.Hour), - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: 0, - Offset: 0, - Limit: 10, - Journals: []journal.Journal(nil), - }, - }, - { - desc: "with to", - page: journal.Page{ - To: items[num-1].OccurredAt, - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Journals: items[:10], - }, - }, - { - desc: "with invalid to", - page: journal.Page{ - To: time.Now().UTC().Truncate(time.Millisecond).Add(-time.Hour), - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: 0, - Offset: 0, - Limit: 10, - Journals: []journal.Journal(nil), - }, - }, - { - desc: "with from and to", - page: journal.Page{ - From: items[0].OccurredAt, - To: items[num-1].OccurredAt, - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Journals: items[:10], - }, - }, - { - desc: "with asc direction", - page: journal.Page{ - Direction: "ASC", - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Journals: items[:10], - }, - }, - { - desc: "with desc direction", - page: journal.Page{ - Direction: "DESC", - Offset: 0, - Limit: 10, - }, - response: journal.JournalsPage{ - Total: uint64(num), - Offset: 0, - Limit: 10, - Journals: reversedItems[:10], - }, - }, - { - desc: "with user entity type", - page: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: entityID, - EntityType: journal.UserEntity, - }, - response: journal.JournalsPage{ - Total: uint64(len(extractEntities(items, journal.UserEntity, entityID))), - Offset: 0, - Limit: 10, - Journals: extractEntities(items, journal.UserEntity, entityID)[:10], - }, - }, - { - desc: "with user entity type, attributes and metadata", - page: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: entityID, - EntityType: journal.UserEntity, - WithAttributes: true, - WithMetadata: true, - }, - response: journal.JournalsPage{ - Total: uint64(len(extractEntities(items, journal.UserEntity, entityID))), - Offset: 0, - Limit: 10, - Journals: extractEntities(items, journal.UserEntity, entityID)[:10], - }, - }, - { - desc: "with thing entity type", - page: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: entityID, - EntityType: journal.ThingEntity, - }, - response: journal.JournalsPage{ - Total: uint64(len(extractEntities(items, journal.ThingEntity, entityID))), - Offset: 0, - Limit: 10, - Journals: extractEntities(items, journal.ThingEntity, entityID)[:10], - }, - }, - { - desc: "with invalid entity id", - page: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: testsutil.GenerateUUID(&testing.T{}), - EntityType: journal.ChannelEntity, - }, - response: journal.JournalsPage{ - Total: 0, - Offset: 0, - Limit: 10, - Journals: []journal.Journal(nil), - }, - }, - { - desc: "with all filters", - page: journal.Page{ - Offset: 0, - Limit: 10, - Operation: items[0].Operation, - From: items[0].OccurredAt, - To: items[num-1].OccurredAt, - WithAttributes: true, - WithMetadata: true, - Direction: "asc", - }, - response: journal.JournalsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Journals: []journal.Journal{items[0]}, - }, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - page, err := repo.RetrieveAll(context.Background(), tc.page) - assert.Equal(t, tc.response.Total, page.Total) - assert.Equal(t, tc.response.Offset, page.Offset) - assert.Equal(t, tc.response.Limit, page.Limit) - for i := range tc.response.Journals { - tc.response.Journals[i].Attributes = map[string]interface{}{} - page.Journals[i].Attributes = map[string]interface{}{} - tc.response.Journals[i].Metadata = map[string]interface{}{} - page.Journals[i].Metadata = map[string]interface{}{} - tc.response.Journals[i].OccurredAt = validTimeStamp - page.Journals[i].OccurredAt = validTimeStamp - } - assert.ElementsMatch(t, tc.response.Journals, page.Journals) - - assert.Equal(t, tc.err, err) - }) - } -} - -func extractEntities(journals []journal.Journal, entityType journal.EntityType, entityID string) []journal.Journal { - var entities []journal.Journal - for _, j := range journals { - switch entityType { - case journal.UserEntity: - if strings.HasPrefix(j.Operation, "user.") && j.Attributes["id"] == entityID || j.Attributes["user_id"] == entityID { - entities = append(entities, j) - } - case journal.GroupEntity: - 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 { - entities = append(entities, j) - } - case journal.ChannelEntity: - if strings.HasPrefix(j.Operation, "channel.") && j.Attributes["id"] == entityID || j.Attributes["group_id"] == entityID { - entities = append(entities, j) - } - } - } - - return entities -} diff --git a/journal/postgres/setup_test.go b/journal/postgres/setup_test.go deleted file mode 100644 index bb9a1307a..000000000 --- a/journal/postgres/setup_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "database/sql" - "fmt" - "log" - "os" - "testing" - "time" - - jpostgres "github.com/absmach/magistrala/journal/postgres" - "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") - - // exponential backoff-retry, because the application in the container might not be ready to accept connections yet - pool.MaxWait = 120 * time.Second - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err := sql.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := postgres.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = postgres.Setup(dbConfig, *jpostgres.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/journal/service.go b/journal/service.go deleted file mode 100644 index 05de91cde..000000000 --- a/journal/service.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package journal - -import ( - "context" - - "github.com/absmach/magistrala" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" -) - -type service struct { - idProvider magistrala.IDProvider - repository Repository -} - -func NewService(idp magistrala.IDProvider, repository Repository) Service { - return &service{ - idProvider: idp, - repository: repository, - } -} - -func (svc *service) Save(ctx context.Context, journal Journal) error { - id, err := svc.idProvider.ID() - if err != nil { - return err - } - journal.ID = id - - return svc.repository.Save(ctx, journal) -} - -func (svc *service) RetrieveAll(ctx context.Context, session mgauthn.Session, page Page) (JournalsPage, error) { - journalPage, err := svc.repository.RetrieveAll(ctx, page) - if err != nil { - return JournalsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - return journalPage, nil -} diff --git a/journal/service_test.go b/journal/service_test.go deleted file mode 100644 index 594a4b548..000000000 --- a/journal/service_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package journal_test - -import ( - "context" - "fmt" - "math/rand" - "testing" - "time" - - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/journal/mocks" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - validJournal = journal.Journal{ - Operation: "user.create", - OccurredAt: time.Now().Add(-time.Hour), - Attributes: map[string]interface{}{ - "temperature": rand.Float64(), - "humidity": rand.Float64(), - }, - Metadata: map[string]interface{}{ - "sensor_id": rand.Intn(1000), - }, - } - idProvider = uuid.New() -) - -func TestSave(t *testing.T) { - repo := new(mocks.Repository) - svc := journal.NewService(idProvider, repo) - - cases := []struct { - desc string - journal journal.Journal - repoErr error - err error - }{ - { - desc: "successful with ID and EntityType", - journal: validJournal, - repoErr: nil, - err: nil, - }, - { - desc: "with repo error", - repoErr: repoerr.ErrCreateEntity, - err: repoerr.ErrCreateEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := repo.On("Save", context.Background(), mock.Anything).Return(tc.repoErr) - err := svc.Save(context.Background(), tc.journal) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - }) - } -} - -func TestReadAll(t *testing.T) { - repo := new(mocks.Repository) - svc := journal.NewService(idProvider, repo) - - validSession := mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t), DomainID: testsutil.GenerateUUID(t)} - validPage := journal.Page{ - Offset: 0, - Limit: 10, - EntityID: testsutil.GenerateUUID(t), - EntityType: journal.ThingEntity, - } - - cases := []struct { - desc string - session mgauthn.Session - page journal.Page - resp journal.JournalsPage - authErr error - repoErr error - err error - }{ - { - desc: "successful", - session: validSession, - page: validPage, - resp: journal.JournalsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Journals: []journal.Journal{validJournal}, - }, - authErr: nil, - repoErr: nil, - err: nil, - }, - { - desc: "successful for user", - session: validSession, - page: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: testsutil.GenerateUUID(t), - EntityType: journal.UserEntity, - }, - resp: journal.JournalsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Journals: []journal.Journal{validJournal}, - }, - authErr: nil, - repoErr: nil, - err: nil, - }, - { - desc: "with repo error", - session: validSession, - page: validPage, - resp: journal.JournalsPage{}, - repoErr: repoerr.ErrViewEntity, - err: repoerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := repo.On("RetrieveAll", context.Background(), tc.page).Return(tc.resp, tc.repoErr) - resp, err := svc.RetrieveAll(context.Background(), tc.session, tc.page) - if tc.err == nil { - assert.Equal(t, tc.resp, resp, tc.desc) - } - 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/logger/doc.go b/logger/doc.go deleted file mode 100644 index e2f32e36a..000000000 --- a/logger/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package logger contains logger API definition, wrapper that -// can be used around any other logger. -package logger diff --git a/logger/exit.go b/logger/exit.go deleted file mode 100644 index e8dde0493..000000000 --- a/logger/exit.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package logger - -import "os" - -// ExitWithError closes the current process with error code. -func ExitWithError(code *int) { - os.Exit(*code) -} diff --git a/logger/logger.go b/logger/logger.go deleted file mode 100644 index edaf84e31..000000000 --- a/logger/logger.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package logger - -import ( - "fmt" - "io" - "log/slog" - "time" -) - -// New returns wrapped slog logger. -func New(w io.Writer, levelText string) (*slog.Logger, error) { - var level slog.Level - if err := level.UnmarshalText([]byte(levelText)); err != nil { - return &slog.Logger{}, fmt.Errorf(`{"level":"error","message":"%s: %s","ts":"%s"}`, err, levelText, time.RFC3339Nano) - } - - logHandler := slog.NewJSONHandler(w, &slog.HandlerOptions{ - Level: level, - }) - - return slog.New(logHandler), nil -} diff --git a/logger/logger_test.go b/logger/logger_test.go deleted file mode 100644 index 9612f889a..000000000 --- a/logger/logger_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package logger_test - -import ( - "log/slog" - "testing" - - mglog "github.com/absmach/magistrala/logger" - "github.com/stretchr/testify/assert" -) - -type mockWriter struct { - value []byte -} - -func (writer *mockWriter) Write(p []byte) (int, error) { - writer.value = p - return len(p), nil -} - -func TestLoggerInitialization(t *testing.T) { - cases := []struct { - desc string - level string - }{ - { - desc: "debug level", - level: slog.LevelDebug.String(), - }, - { - desc: "info level", - level: slog.LevelInfo.String(), - }, - { - desc: "warn level", - level: slog.LevelWarn.String(), - }, - { - desc: "error level", - level: slog.LevelError.String(), - }, - { - desc: "invalid level", - level: "invalid", - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - writer := &mockWriter{} - logger, err := mglog.New(writer, tc.level) - if tc.level == "invalid" { - assert.NotNil(t, err, "expected error during logger initialization") - assert.NotNil(t, logger, "logger should not be nil when an error occurs") - } else { - assert.Nil(t, err, "unexpected error during logger initialization") - assert.NotNil(t, logger, "logger should not be nil") - } - }) - } -} diff --git a/logger/mock.go b/logger/mock.go deleted file mode 100644 index 190fc229d..000000000 --- a/logger/mock.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package logger - -import ( - "bytes" - "log/slog" -) - -// NewMock returns wrapped slog logger mock. -func NewMock() *slog.Logger { - buf := &bytes.Buffer{} - - return slog.New(slog.NewJSONHandler(buf, nil)) -} diff --git a/mqtt/README.md b/mqtt/README.md deleted file mode 100644 index 49a66d837..000000000 --- a/mqtt/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# MQTT adapter - -MQTT adapter provides an MQTT API for sending messages through the platform. MQTT adapter uses [mProxy](https://github.com/absmach/mproxy) for proxying traffic between client and MQTT broker. - -## 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_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 | "" | - -## 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. -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 mqtt -make mqtt - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_MQTT_ADAPTER_LOG_LEVEL=info \ -MG_MQTT_ADAPTER_MQTT_PORT=1883 \ -MG_MQTT_ADAPTER_MQTT_TARGET_HOST=localhost \ -MG_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 \ -MG_MQTT_ADAPTER_MQTT_QOS=1 \ -MG_MQTT_ADAPTER_FORWARDER_TIMEOUT=30s \ -MG_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK="" \ -MG_MQTT_ADAPTER_WS_PORT=8080 \ -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_ES_URL=nats://localhost:4222 \ -MG_MESSAGE_BROKER_URL=nats://localhost:4222 \ -MG_JAEGER_URL=http://localhost:14268/api/traces \ -MG_JAEGER_TRACE_RATIO=1.0 \ -MG_SEND_TELEMETRY=true \ -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. - -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/doc.go b/mqtt/doc.go deleted file mode 100644 index 112d3df1a..000000000 --- a/mqtt/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mqtt contains the domain concept definitions needed to support -// Magistrala MQTT service functionality. -package mqtt diff --git a/mqtt/events/doc.go b/mqtt/events/doc.go deleted file mode 100644 index 83ccf23cb..000000000 --- a/mqtt/events/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package events provides the domain concept definitions needed to support -// mqtt events functionality. -package events diff --git a/mqtt/events/events.go b/mqtt/events/events.go deleted file mode 100644 index 9ae960bed..000000000 --- a/mqtt/events/events.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import "github.com/absmach/magistrala/pkg/events" - -var _ events.Event = (*mqttEvent)(nil) - -type mqttEvent struct { - clientID string - operation string - instance string -} - -func (me mqttEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "thing_id": me.clientID, - "operation": me.operation, - "instance": me.instance, - }, nil -} diff --git a/mqtt/events/streams.go b/mqtt/events/streams.go deleted file mode 100644 index 780d1a6ea..000000000 --- a/mqtt/events/streams.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "context" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/store" -) - -const streamID = "magistrala.mqtt" - -//go:generate mockery --name EventStore --output=../mocks --filename events.go --quiet --note "Copyright (c) Abstract Machines" -type EventStore interface { - Connect(ctx context.Context, clientID string) error - Disconnect(ctx context.Context, clientID string) error -} - -// EventStore is a struct used to store event streams in Redis. -type eventStore struct { - events.Publisher - instance string -} - -// NewEventStore returns wrapper around mProxy service that sends -// events to event store. -func NewEventStore(ctx context.Context, url, instance string) (EventStore, error) { - publisher, err := store.NewPublisher(ctx, url, streamID) - if err != nil { - return nil, err - } - - return &eventStore{ - instance: instance, - Publisher: publisher, - }, nil -} - -// Connect issues event on MQTT CONNECT. -func (es *eventStore) Connect(ctx context.Context, clientID string) error { - ev := mqttEvent{ - clientID: clientID, - operation: "connect", - instance: es.instance, - } - - return es.Publish(ctx, ev) -} - -// Disconnect issues event on MQTT CONNECT. -func (es *eventStore) Disconnect(ctx context.Context, clientID string) error { - ev := mqttEvent{ - clientID: clientID, - operation: "disconnect", - instance: es.instance, - } - - return es.Publish(ctx, ev) -} diff --git a/mqtt/forwarder.go b/mqtt/forwarder.go deleted file mode 100644 index 735b29c28..000000000 --- a/mqtt/forwarder.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mqtt - -import ( - "context" - "fmt" - "log/slog" - "strings" - - "github.com/absmach/magistrala/pkg/messaging" -) - -// Forwarder specifies MQTT forwarder interface API. -type Forwarder interface { - // Forward subscribes to the Subscriber and - // publishes messages using provided Publisher. - Forward(ctx context.Context, id string, sub messaging.Subscriber, pub messaging.Publisher) error -} - -type forwarder struct { - topic string - logger *slog.Logger -} - -// NewForwarder returns new Forwarder implementation. -func NewForwarder(topic string, logger *slog.Logger) Forwarder { - return forwarder{ - topic: topic, - logger: logger, - } -} - -func (f forwarder) Forward(ctx context.Context, id string, sub messaging.Subscriber, pub messaging.Publisher) error { - subCfg := messaging.SubscriberConfig{ - ID: id, - Topic: f.topic, - Handler: handle(ctx, pub, f.logger), - } - - return sub.Subscribe(ctx, subCfg) -} - -func handle(ctx context.Context, pub messaging.Publisher, logger *slog.Logger) handleFunc { - return func(msg *messaging.Message) error { - if msg.GetProtocol() == protocol { - return nil - } - // Use concatenation instead of fmt.Sprintf for the - // sake of simplicity and performance. - topic := "channels/" + msg.GetChannel() + "/messages" - if msg.GetSubtopic() != "" { - topic = topic + "/" + strings.ReplaceAll(msg.GetSubtopic(), ".", "/") - } - - go func() { - if err := pub.Publish(ctx, topic, msg); err != nil { - logger.Warn(fmt.Sprintf("Failed to forward message: %s", err)) - } - }() - - return nil - } -} - -type handleFunc func(msg *messaging.Message) error - -func (h handleFunc) Handle(msg *messaging.Message) error { - return h(msg) -} - -func (h handleFunc) Cancel() error { - return nil -} diff --git a/mqtt/handler.go b/mqtt/handler.go deleted file mode 100644 index e3999fbbd..000000000 --- a/mqtt/handler.go +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mqtt - -import ( - "context" - "fmt" - "log/slog" - "net/url" - "regexp" - "strings" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/mqtt/events" - "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" - "github.com/absmach/mgate/pkg/session" -) - -var _ session.Handler = (*handler)(nil) - -const protocol = "mqtt" - -// Log message formats. -const ( - LogInfoSubscribed = "subscribed with client_id %s to topics %s" - LogInfoUnsubscribed = "unsubscribed client_id %s from topics %s" - LogInfoConnected = "connected with client_id %s" - LogInfoDisconnected = "disconnected client_id %s and username %s" - LogInfoPublished = "published with client_id %s to the topic %s" -) - -// Error wrappers for MQTT errors. -var ( - ErrMalformedSubtopic = errors.New("malformed subtopic") - ErrClientNotInitialized = errors.New("client is not initialized") - ErrMalformedTopic = errors.New("malformed topic") - ErrMissingClientID = errors.New("client_id not found") - ErrMissingTopicPub = errors.New("failed to publish due to missing topic") - ErrMissingTopicSub = errors.New("failed to subscribe due to missing topic") - ErrFailedConnect = errors.New("failed to connect") - ErrFailedSubscribe = errors.New("failed to subscribe") - ErrFailedUnsubscribe = errors.New("failed to unsubscribe") - ErrFailedPublish = errors.New("failed to publish") - ErrFailedDisconnect = errors.New("failed to disconnect") - ErrFailedPublishDisconnectEvent = errors.New("failed to publish disconnect event") - ErrFailedParseSubtopic = errors.New("failed to parse subtopic") - ErrFailedPublishConnectEvent = errors.New("failed to publish connect event") - ErrFailedPublishToMsgBroker = errors.New("failed to publish to magistrala message broker") -) - -var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?]*)?(\?.*)?$`) - -// Event implements events.Event interface. -type handler struct { - publisher messaging.Publisher - things magistrala.ThingsServiceClient - 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 { - return &handler{ - es: es, - logger: logger, - publisher: publisher, - things: thingsClient, - } -} - -// AuthConnect is called on device connection, -// prior forwarding to the MQTT broker. -func (h *handler) AuthConnect(ctx context.Context) error { - s, ok := session.FromContext(ctx) - if !ok { - return ErrClientNotInitialized - } - - if s.ID == "" { - return ErrMissingClientID - } - - pwd := string(s.Password) - - if err := h.es.Connect(ctx, pwd); err != nil { - h.logger.Error(errors.Wrap(ErrFailedPublishConnectEvent, err).Error()) - } - - return nil -} - -// AuthPublish is called on device publish, -// prior forwarding to the MQTT broker. -func (h *handler) AuthPublish(ctx context.Context, topic *string, payload *[]byte) error { - if topic == nil { - return ErrMissingTopicPub - } - s, ok := session.FromContext(ctx) - if !ok { - return ErrClientNotInitialized - } - - return h.authAccess(ctx, string(s.Password), *topic, policies.PublishPermission) -} - -// AuthSubscribe is called on device subscribe, -// prior forwarding to the MQTT broker. -func (h *handler) AuthSubscribe(ctx context.Context, topics *[]string) error { - s, ok := session.FromContext(ctx) - if !ok { - return ErrClientNotInitialized - } - if topics == nil || *topics == nil { - return ErrMissingTopicSub - } - - for _, v := range *topics { - if err := h.authAccess(ctx, string(s.Password), v, policies.SubscribePermission); err != nil { - return err - } - } - - return nil -} - -// Connect - after client successfully connected. -func (h *handler) Connect(ctx context.Context) error { - s, ok := session.FromContext(ctx) - if !ok { - return errors.Wrap(ErrFailedConnect, ErrClientNotInitialized) - } - h.logger.Info(fmt.Sprintf(LogInfoConnected, s.ID)) - return nil -} - -// Publish - after client successfully published. -func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) error { - s, ok := session.FromContext(ctx) - 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 errors.Wrap(ErrFailedPublish, ErrMalformedTopic) - } - - chanID := channelParts[1] - subtopic := channelParts[2] - - subtopic, err := parseSubtopic(subtopic) - if err != nil { - return errors.Wrap(ErrFailedParseSubtopic, err) - } - - msg := messaging.Message{ - Protocol: protocol, - Channel: chanID, - Subtopic: subtopic, - Publisher: s.Username, - Payload: *payload, - Created: time.Now().UnixNano(), - } - - if err := h.publisher.Publish(ctx, msg.GetChannel(), &msg); err != nil { - return errors.Wrap(ErrFailedPublishToMsgBroker, err) - } - - return nil -} - -// Subscribe - after client successfully subscribed. -func (h *handler) Subscribe(ctx context.Context, topics *[]string) error { - s, ok := session.FromContext(ctx) - if !ok { - return errors.Wrap(ErrFailedSubscribe, ErrClientNotInitialized) - } - h.logger.Info(fmt.Sprintf(LogInfoSubscribed, s.ID, strings.Join(*topics, ","))) - return nil -} - -// Unsubscribe - after client unsubscribed. -func (h *handler) Unsubscribe(ctx context.Context, topics *[]string) error { - s, ok := session.FromContext(ctx) - if !ok { - return errors.Wrap(ErrFailedUnsubscribe, ErrClientNotInitialized) - } - h.logger.Info(fmt.Sprintf(LogInfoUnsubscribed, s.ID, strings.Join(*topics, ","))) - return nil -} - -// Disconnect - connection with broker or client lost. -func (h *handler) Disconnect(ctx context.Context) error { - s, ok := session.FromContext(ctx) - if !ok { - return errors.Wrap(ErrFailedDisconnect, ErrClientNotInitialized) - } - h.logger.Error(fmt.Sprintf(LogInfoDisconnected, s.ID, s.Password)) - if err := h.es.Disconnect(ctx, string(s.Password)); err != nil { - return errors.Wrap(ErrFailedPublishDisconnectEvent, err) - } - return nil -} - -func (h *handler) authAccess(ctx context.Context, password, topic, action string) error { - // Topics are in the format: - // channels//messages//.../ct/ - if !channelRegExp.MatchString(topic) { - return ErrMalformedTopic - } - - channelParts := channelRegExp.FindStringSubmatch(topic) - if len(channelParts) < 1 { - return ErrMalformedTopic - } - - chanID := channelParts[1] - - ar := &magistrala.ThingsAuthzReq{ - Permission: action, - ThingKey: password, - ChannelId: chanID, - } - res, err := h.things.Authorize(ctx, ar) - if err != nil { - return err - } - if !res.GetAuthorized() { - return svcerr.ErrAuthorization - } - - return nil -} - -func parseSubtopic(subtopic string) (string, error) { - if subtopic == "" { - return subtopic, nil - } - - subtopic, err := url.QueryUnescape(subtopic) - if err != nil { - return "", ErrMalformedSubtopic - } - subtopic = strings.ReplaceAll(subtopic, "/", ".") - - elems := strings.Split(subtopic, ".") - filteredElems := []string{} - for _, elem := range elems { - if elem == "" { - continue - } - - if len(elem) > 1 && (strings.Contains(elem, "*") || strings.Contains(elem, ">")) { - return "", ErrMalformedSubtopic - } - - filteredElems = append(filteredElems, elem) - } - - subtopic = strings.Join(filteredElems, ".") - return subtopic, nil -} diff --git a/mqtt/handler_test.go b/mqtt/handler_test.go deleted file mode 100644 index 8f0ff9543..000000000 --- a/mqtt/handler_test.go +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mqtt_test - -import ( - "bytes" - "context" - "fmt" - "log" - "testing" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/testsutil" - 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" - invalidID = "invalidID" - invalidValue = "invalidValue" - clientID = "clientID" - clientID1 = "clientID1" - subtopic = "testSubtopic" - invalidChannelIDTopic = "channels/**/messages" -) - -var ( - topicMsg = "channels/%s/messages" - topic = fmt.Sprintf(topicMsg, chanID) - invalidTopic = invalidValue - payload = []byte("[{'n':'test-name', 'v': 1.2}]") - topics = []string{topic} - invalidTopics = []string{invalidValue} - invalidChanIDTopics = []string{fmt.Sprintf(topicMsg, invalidValue)} - // Test log messages for cases the handler does not provide a return value. - logBuffer = bytes.Buffer{} - sessionClient = session.Session{ - ID: clientID, - Username: thingID, - Password: []byte(password), - } - sessionClientSub = session.Session{ - ID: clientID1, - Username: thingID1, - Password: []byte(password1), - } - invalidThingSessionClient = session.Session{ - ID: clientID, - Username: invalidID, - Password: []byte(password), - } -) - -func TestAuthConnect(t *testing.T) { - handler, _, eventStore := newHandler() - - cases := []struct { - desc string - err error - session *session.Session - }{ - { - desc: "connect without active session", - err: mqtt.ErrClientNotInitialized, - session: nil, - }, - { - desc: "connect without clientID", - err: mqtt.ErrMissingClientID, - session: &session.Session{ - ID: "", - Username: thingID, - Password: []byte(password), - }, - }, - { - desc: "connect with invalid password", - err: nil, - session: &session.Session{ - ID: clientID, - Username: thingID, - Password: []byte(""), - }, - }, - { - desc: "connect with valid password and invalid username", - err: nil, - session: &invalidThingSessionClient, - }, - { - desc: "connect with valid username and password", - err: nil, - session: &sessionClient, - }, - } - for _, tc := range cases { - ctx := context.TODO() - password := "" - if tc.session != nil { - ctx = session.NewContext(ctx, tc.session) - password = string(tc.session.Password) - } - svcCall := eventStore.On("Connect", mock.Anything, password).Return(tc.err) - err := handler.AuthConnect(ctx) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - svcCall.Unset() - } -} - -func TestAuthPublish(t *testing.T) { - handler, things, _ := newHandler() - - cases := []struct { - desc string - session *session.Session - err error - topic *string - payload []byte - }{ - { - desc: "publish with an inactive client", - session: nil, - err: mqtt.ErrClientNotInitialized, - topic: &topic, - payload: payload, - }, - { - desc: "publish without topic", - session: &sessionClient, - err: mqtt.ErrMissingTopicPub, - topic: nil, - payload: payload, - }, - { - desc: "publish with malformed topic", - session: &sessionClient, - err: mqtt.ErrMalformedTopic, - topic: &invalidTopic, - payload: payload, - }, - { - desc: "publish successfully", - session: &sessionClient, - err: nil, - topic: &topic, - payload: payload, - }, - } - - 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() - - cases := []struct { - desc string - session *session.Session - err error - topic *[]string - }{ - { - desc: "subscribe without active session", - session: nil, - err: mqtt.ErrClientNotInitialized, - topic: &topics, - }, - { - desc: "subscribe without topics", - session: &sessionClient, - err: mqtt.ErrMissingTopicSub, - topic: nil, - }, - { - desc: "subscribe with invalid topics", - session: &sessionClient, - err: mqtt.ErrMalformedTopic, - topic: &invalidTopics, - }, - { - desc: "subscribe with invalid channel ID", - session: &sessionClient, - err: svcerr.ErrAuthorization, - topic: &invalidChanIDTopics, - }, - { - desc: "subscribe successfully", - session: &sessionClientSub, - err: nil, - topic: &topics, - }, - } - - 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() - logBuffer.Reset() - - cases := []struct { - desc string - session *session.Session - err error - logMsg string - }{ - { - desc: "connect without active session", - session: nil, - err: errors.Wrap(mqtt.ErrFailedConnect, mqtt.ErrClientNotInitialized), - }, - { - desc: "connect with active session", - session: &sessionClient, - logMsg: fmt.Sprintf(mqtt.LogInfoConnected, clientID), - err: nil, - }, - } - - for _, tc := range cases { - ctx := context.TODO() - if tc.session != nil { - ctx = session.NewContext(ctx, tc.session) - } - err := handler.Connect(ctx) - assert.Contains(t, logBuffer.String(), tc.logMsg) - assert.Equal(t, tc.err, err) - } -} - -func TestPublish(t *testing.T) { - handler, _, _ := newHandler() - logBuffer.Reset() - - malformedSubtopics := topic + "/" + subtopic + "%" - wrongCharSubtopics := topic + "/" + subtopic + ">" - validSubtopic := topic + "/" + subtopic - - cases := []struct { - desc string - session *session.Session - topic string - payload []byte - logMsg string - err error - }{ - { - desc: "publish without active session", - session: nil, - topic: topic, - payload: payload, - err: errors.Wrap(mqtt.ErrFailedPublish, mqtt.ErrClientNotInitialized), - }, - { - desc: "publish with invalid topic", - session: &sessionClient, - topic: invalidTopic, - payload: payload, - logMsg: fmt.Sprintf(mqtt.LogInfoPublished, clientID, invalidTopic), - err: errors.Wrap(mqtt.ErrFailedPublish, mqtt.ErrMalformedTopic), - }, - { - desc: "publish with invalid channel ID", - session: &sessionClient, - topic: invalidChannelIDTopic, - payload: payload, - err: errors.Wrap(mqtt.ErrFailedPublish, mqtt.ErrMalformedTopic), - }, - { - desc: "publish with malformed subtopic", - session: &sessionClient, - topic: malformedSubtopics, - payload: payload, - err: errors.Wrap(mqtt.ErrFailedParseSubtopic, mqtt.ErrMalformedSubtopic), - }, - { - desc: "publish with subtopic containing wrong character", - session: &sessionClient, - topic: wrongCharSubtopics, - payload: payload, - err: errors.Wrap(mqtt.ErrFailedParseSubtopic, mqtt.ErrMalformedSubtopic), - }, - { - desc: "publish with subtopic", - session: &sessionClient, - topic: validSubtopic, - payload: payload, - logMsg: subtopic, - }, - { - desc: "publish without subtopic", - session: &sessionClient, - topic: topic, - payload: payload, - logMsg: "", - }, - } - - for _, tc := range cases { - ctx := context.TODO() - if tc.session != nil { - ctx = session.NewContext(ctx, tc.session) - } - err := handler.Publish(ctx, &tc.topic, &tc.payload) - assert.Contains(t, logBuffer.String(), tc.logMsg) - assert.Equal(t, tc.err, err) - } -} - -func TestSubscribe(t *testing.T) { - handler, _, _ := newHandler() - logBuffer.Reset() - - cases := []struct { - desc string - session *session.Session - topic []string - logMsg string - err error - }{ - { - desc: "subscribe without active session", - session: nil, - topic: topics, - err: errors.Wrap(mqtt.ErrFailedSubscribe, mqtt.ErrClientNotInitialized), - }, - { - desc: "subscribe with valid session and topics", - session: &sessionClient, - topic: topics, - logMsg: fmt.Sprintf(mqtt.LogInfoSubscribed, clientID, topics[0]), - }, - } - - for _, tc := range cases { - ctx := context.TODO() - if tc.session != nil { - ctx = session.NewContext(ctx, tc.session) - } - err := handler.Subscribe(ctx, &tc.topic) - assert.Contains(t, logBuffer.String(), tc.logMsg) - assert.Equal(t, tc.err, err) - } -} - -func TestUnsubscribe(t *testing.T) { - handler, _, _ := newHandler() - logBuffer.Reset() - - cases := []struct { - desc string - session *session.Session - topic []string - logMsg string - err error - }{ - { - desc: "unsubscribe without active session", - session: nil, - topic: topics, - err: errors.Wrap(mqtt.ErrFailedUnsubscribe, mqtt.ErrClientNotInitialized), - }, - { - desc: "unsubscribe with valid session and topics", - session: &sessionClient, - topic: topics, - logMsg: fmt.Sprintf(mqtt.LogInfoUnsubscribed, clientID, topics[0]), - }, - } - - for _, tc := range cases { - ctx := context.TODO() - if tc.session != nil { - ctx = session.NewContext(ctx, tc.session) - } - err := handler.Unsubscribe(ctx, &tc.topic) - assert.Contains(t, logBuffer.String(), tc.logMsg) - assert.Equal(t, tc.err, err) - } -} - -func TestDisconnect(t *testing.T) { - handler, _, eventStore := newHandler() - logBuffer.Reset() - - cases := []struct { - desc string - session *session.Session - topic []string - logMsg string - err error - }{ - { - desc: "disconnect without active session", - session: nil, - topic: topics, - err: errors.Wrap(mqtt.ErrFailedDisconnect, mqtt.ErrClientNotInitialized), - }, - { - desc: "disconnect with valid session", - session: &sessionClient, - topic: topics, - err: nil, - }, - } - - for _, tc := range cases { - ctx := context.TODO() - password := "" - if tc.session != nil { - ctx = session.NewContext(ctx, tc.session) - password = string(tc.session.Password) - } - svcCall := eventStore.On("Disconnect", mock.Anything, password).Return(tc.err) - err := handler.Disconnect(ctx) - assert.Contains(t, logBuffer.String(), tc.logMsg) - assert.Equal(t, tc.err, err) - svcCall.Unset() - } -} - -func newHandler() (session.Handler, *thmocks.ThingsServiceClient, *mocks.EventStore) { - logger, err := mglog.New(&logBuffer, "debug") - if err != nil { - log.Fatalf("failed to create logger: %s", err) - } - things := new(thmocks.ThingsServiceClient) - eventStore := new(mocks.EventStore) - return mqtt.NewHandler(mocks.NewPublisher(), eventStore, logger, things), things, eventStore -} diff --git a/mqtt/mocks/doc.go b/mqtt/mocks/doc.go deleted file mode 100644 index 16ed198af..000000000 --- a/mqtt/mocks/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mocks contains mocks for testing purposes. -package mocks diff --git a/mqtt/mocks/events.go b/mqtt/mocks/events.go deleted file mode 100644 index 7dcebfd76..000000000 --- a/mqtt/mocks/events.go +++ /dev/null @@ -1,66 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// EventStore is an autogenerated mock type for the EventStore type -type EventStore struct { - mock.Mock -} - -// Connect provides a mock function with given fields: ctx, clientID -func (_m *EventStore) Connect(ctx context.Context, clientID string) error { - ret := _m.Called(ctx, clientID) - - if len(ret) == 0 { - panic("no return value specified for Connect") - } - - 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 -} - -// Disconnect provides a mock function with given fields: ctx, clientID -func (_m *EventStore) Disconnect(ctx context.Context, clientID string) error { - ret := _m.Called(ctx, clientID) - - if len(ret) == 0 { - panic("no return value specified for Disconnect") - } - - 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 -} - -// NewEventStore creates a new instance of EventStore. 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 NewEventStore(t interface { - mock.TestingT - Cleanup(func()) -}) *EventStore { - mock := &EventStore{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mqtt/mocks/publisher.go b/mqtt/mocks/publisher.go deleted file mode 100644 index b86a56212..000000000 --- a/mqtt/mocks/publisher.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mocks - -import ( - "context" - - "github.com/absmach/magistrala/pkg/messaging" -) - -type MockPublisher struct{} - -// NewPublisher returns mock message publisher. -func NewPublisher() messaging.Publisher { - return MockPublisher{} -} - -func (pub MockPublisher) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - return nil -} - -func (pub MockPublisher) Close() error { - return nil -} diff --git a/mqtt/tracing/doc.go b/mqtt/tracing/doc.go deleted file mode 100644 index 88ed02e7f..000000000 --- a/mqtt/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala MQTT adapter service. -// -// This package provides tracing middleware for Magistrala MQTT adapter service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala MQTT adapter 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/mqtt/tracing/forwarder.go b/mqtt/tracing/forwarder.go deleted file mode 100644 index 2300d2dcd..000000000 --- a/mqtt/tracing/forwarder.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - "fmt" - - "github.com/absmach/magistrala/mqtt" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -const forwardOP = "process" - -var _ mqtt.Forwarder = (*forwarderMiddleware)(nil) - -type forwarderMiddleware struct { - topic string - forwarder mqtt.Forwarder - tracer trace.Tracer - host server.Config -} - -// New creates new mqtt forwarder tracing middleware. -func New(config server.Config, tracer trace.Tracer, forwarder mqtt.Forwarder, topic string) mqtt.Forwarder { - return &forwarderMiddleware{ - forwarder: forwarder, - tracer: tracer, - topic: topic, - host: config, - } -} - -// Forward traces mqtt forward operations. -func (fm *forwarderMiddleware) Forward(ctx context.Context, id string, sub messaging.Subscriber, pub messaging.Publisher) error { - subject := fmt.Sprintf("channels.%s.messages", fm.topic) - spanName := fmt.Sprintf("%s %s", subject, forwardOP) - - ctx, span := fm.tracer.Start(ctx, - spanName, - trace.WithAttributes( - attribute.String("messaging.system", "mqtt"), - attribute.Bool("messaging.destination.anonymous", false), - attribute.String("messaging.destination.template", "channels/{channelID}/messages/*"), - attribute.Bool("messaging.destination.temporary", true), - attribute.String("network.protocol.name", "mqtt"), - attribute.String("network.protocol.version", "3.1.1"), - attribute.String("network.transport", "tcp"), - attribute.String("network.type", "ipv4"), - attribute.String("messaging.operation", forwardOP), - attribute.String("messaging.client_id", id), - attribute.String("server.address", fm.host.Host), - attribute.String("server.socket.port", fm.host.Port), - ), - ) - defer span.End() - - return fm.forwarder.Forward(ctx, id, sub, pub) -} diff --git a/pkg/apiutil/errors.go b/pkg/apiutil/errors.go deleted file mode 100644 index 2b5337512..000000000 --- a/pkg/apiutil/errors.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package apiutil - -import "github.com/absmach/magistrala/pkg/errors" - -// Errors defined in this file are used by the LoggingErrorEncoder decorator -// to distinguish and log API request validation errors and avoid that service -// errors are logged twice. -var ( - // ErrValidation indicates that an error was returned by the API. - ErrValidation = errors.New("something went wrong with the request") - - // ErrBearerToken indicates missing or invalid bearer user token. - ErrBearerToken = errors.New("missing or invalid bearer user token") - - // ErrBearerKey indicates missing or invalid bearer entity key. - ErrBearerKey = errors.New("missing or invalid bearer entity key") - - // ErrMissingID indicates missing entity ID. - ErrMissingID = errors.New("missing entity id") - - // ErrInvalidAuthKey indicates invalid auth key. - ErrInvalidAuthKey = errors.New("invalid auth key") - - // ErrInvalidIDFormat indicates an invalid ID format. - ErrInvalidIDFormat = errors.New("invalid id format provided") - - // ErrNameSize indicates that name size exceeds the max. - ErrNameSize = errors.New("invalid name size") - - // ErrEmailSize indicates that email size exceeds the max. - ErrEmailSize = errors.New("invalid email size") - - // ErrInvalidRole indicates that an invalid role. - ErrInvalidRole = errors.New("invalid client role") - - // ErrLimitSize indicates that an invalid limit. - ErrLimitSize = errors.New("invalid limit size") - - // ErrOffsetSize indicates an invalid offset. - ErrOffsetSize = errors.New("invalid offset size") - - // ErrInvalidOrder indicates an invalid list order. - ErrInvalidOrder = errors.New("invalid list order provided") - - // ErrInvalidDirection indicates an invalid list direction. - ErrInvalidDirection = errors.New("invalid list direction provided") - - // ErrInvalidMemberKind indicates an invalid member kind. - ErrInvalidMemberKind = errors.New("invalid member kind") - - // ErrEmptyList indicates that entity data is empty. - ErrEmptyList = errors.New("empty list provided") - - // ErrMalformedPolicy indicates that policies are malformed. - ErrMalformedPolicy = errors.New("malformed policy") - - // ErrMissingPolicySub indicates that policies are subject. - ErrMissingPolicySub = errors.New("malformed policy subject") - - // ErrMissingPolicyObj indicates missing policies object. - ErrMissingPolicyObj = errors.New("malformed policy object") - - // ErrMalformedPolicyAct indicates missing policies action. - ErrMalformedPolicyAct = errors.New("malformed policy action") - - // ErrMissingPolicyEntityType indicates missing policies entity type. - ErrMissingPolicyEntityType = errors.New("missing policy entity type") - - // ErrMalformedPolicyPer indicates missing policies relation. - ErrMalformedPolicyPer = errors.New("malformed policy permission") - - // ErrMissingCertData indicates missing cert data (ttl). - ErrMissingCertData = errors.New("missing certificate data") - - // ErrInvalidCertData indicates invalid cert data (ttl). - ErrInvalidCertData = errors.New("invalid certificate data") - - // ErrInvalidTopic indicates an invalid subscription topic. - ErrInvalidTopic = errors.New("invalid Subscription topic") - - // ErrInvalidContact indicates an invalid subscription contract. - ErrInvalidContact = errors.New("invalid Subscription contact") - - // ErrMissingEmail indicates missing email. - ErrMissingEmail = errors.New("missing email") - - // ErrInvalidEmail indicates missing email. - ErrInvalidEmail = errors.New("invalid email") - - // ErrMissingHost indicates missing host. - ErrMissingHost = errors.New("missing host") - - // ErrMissingPass indicates missing password. - ErrMissingPass = errors.New("missing password") - - // ErrMissingConfPass indicates missing conf password. - ErrMissingConfPass = errors.New("missing conf password") - - // ErrInvalidResetPass indicates an invalid reset password. - ErrInvalidResetPass = errors.New("invalid reset password") - - // ErrInvalidComparator indicates an invalid comparator. - ErrInvalidComparator = errors.New("invalid comparator") - - // ErrMissingMemberType indicates missing group member type. - ErrMissingMemberType = errors.New("missing group member type") - - // ErrMissingMemberKind indicates missing group member kind. - ErrMissingMemberKind = errors.New("missing group member kind") - - // ErrMissingRelation indicates missing relation. - ErrMissingRelation = errors.New("missing relation") - - // ErrInvalidRelation indicates an invalid relation. - ErrInvalidRelation = errors.New("invalid relation") - - // ErrInvalidAPIKey indicates an invalid API key type. - ErrInvalidAPIKey = errors.New("invalid api key type") - - // ErrBootstrapState indicates an invalid bootstrap state. - ErrBootstrapState = errors.New("invalid bootstrap state") - - // ErrInvitationState indicates an invalid invitation state. - ErrInvitationState = errors.New("invalid invitation state") - - // ErrMissingIdentity indicates missing entity Identity. - ErrMissingIdentity = errors.New("missing entity identity") - - // ErrMissingSecret indicates missing secret. - ErrMissingSecret = errors.New("missing secret") - - // ErrPasswordFormat indicates weak password. - ErrPasswordFormat = errors.New("password does not meet the requirements") - - // ErrMissingName indicates missing identity name. - ErrMissingName = errors.New("missing identity name") - - // ErrMissingName indicates missing alias. - ErrMissingAlias = errors.New("missing alias") - - // ErrInvalidLevel indicates an invalid group level. - ErrInvalidLevel = errors.New("invalid group level (should be between 0 and 5)") - - // ErrNotFoundParam indicates that the parameter was not found in the query. - ErrNotFoundParam = errors.New("parameter not found in the query") - - // ErrInvalidQueryParams indicates invalid query parameters. - ErrInvalidQueryParams = errors.New("invalid query parameters") - - // ErrInvalidVisibilityType indicates invalid visibility type. - ErrInvalidVisibilityType = errors.New("invalid visibility type") - - // ErrUnsupportedContentType indicates unacceptable or lack of Content-Type. - ErrUnsupportedContentType = errors.New("unsupported content type") - - // ErrRollbackTx indicates failed to rollback transaction. - ErrRollbackTx = errors.New("failed to rollback transaction") - - // ErrInvalidAggregation indicates invalid aggregation value. - ErrInvalidAggregation = errors.New("invalid aggregation value") - - // ErrInvalidInterval indicates invalid interval value. - ErrInvalidInterval = errors.New("invalid interval value") - - // ErrMissingFrom indicates missing from value. - ErrMissingFrom = errors.New("missing from time value") - - // ErrMissingTo indicates missing to value. - ErrMissingTo = errors.New("missing to time value") - - // ErrEmptyMessage indicates empty message. - ErrEmptyMessage = errors.New("empty message") - - // ErrMissingEntityType indicates missing entity type. - ErrMissingEntityType = errors.New("missing entity type") - - // ErrInvalidEntityType indicates invalid entity type. - ErrInvalidEntityType = errors.New("invalid entity type") - - // ErrInvalidTimeFormat indicates invalid time format i.e not unix time. - ErrInvalidTimeFormat = errors.New("invalid time format use unix time") - - // ErrEmptySearchQuery indicates search query should not be empty. - ErrEmptySearchQuery = errors.New("search query must not be empty") - - // ErrLenSearchQuery indicates search query length. - ErrLenSearchQuery = errors.New("search query must be at least 3 characters") - - // ErrMissingDomainID indicates missing domainID. - ErrMissingDomainID = errors.New("missing domainID") - - // ErrMissingUsername indicates missing user name. - ErrMissingUsername = errors.New("missing username") - - // ErrInvalidUsername indicates missing user name. - ErrInvalidUsername = errors.New("invalid username") - - // ErrMissingFirstName indicates missing first name. - ErrMissingFirstName = errors.New("missing first name") - - // ErrMissingLastName indicates missing last name. - ErrMissingLastName = errors.New("missing last name") - - // ErrInvalidProfilePictureURL indicates that the profile picture url is invalid. - ErrInvalidProfilePictureURL = errors.New("invalid profile picture url") -) diff --git a/pkg/apiutil/responses.go b/pkg/apiutil/responses.go deleted file mode 100644 index 9b032d7cd..000000000 --- a/pkg/apiutil/responses.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package apiutil - -// ErrorRes represents the HTTP error response body. -type ErrorRes struct { - Err string `json:"error"` - Msg string `json:"message"` -} diff --git a/pkg/apiutil/token.go b/pkg/apiutil/token.go deleted file mode 100644 index 563b60a17..000000000 --- a/pkg/apiutil/token.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package apiutil - -import ( - "net/http" - "strings" -) - -// BearerPrefix represents the token prefix for Bearer authentication scheme. -const BearerPrefix = "Bearer " - -// ThingPrefix represents the key prefix for Thing authentication scheme. -const ThingPrefix = "Thing " - -// ExtractBearerToken returns value of the bearer token. If there is no bearer token - an empty value is returned. -func ExtractBearerToken(r *http.Request) string { - token := r.Header.Get("Authorization") - - if !strings.HasPrefix(token, BearerPrefix) { - return "" - } - - 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 { - token := r.Header.Get("Authorization") - - if !strings.HasPrefix(token, ThingPrefix) { - return "" - } - - return strings.TrimPrefix(token, ThingPrefix) -} diff --git a/pkg/apiutil/token_test.go b/pkg/apiutil/token_test.go deleted file mode 100644 index 6194b9bb7..000000000 --- a/pkg/apiutil/token_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package apiutil_test - -import ( - "net/http" - "testing" - - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/stretchr/testify/assert" -) - -func TestExtractBearerToken(t *testing.T) { - cases := []struct { - desc string - request *http.Request - token string - }{ - { - desc: "valid bearer token", - request: &http.Request{ - Header: map[string][]string{ - "Authorization": {"Bearer 123"}, - }, - }, - token: "123", - }, - { - desc: "invalid bearer token", - request: &http.Request{ - Header: map[string][]string{ - "Authorization": {"123"}, - }, - }, - token: "", - }, - { - desc: "empty bearer token", - request: &http.Request{ - Header: map[string][]string{ - "Authorization": {""}, - }, - }, - token: "", - }, - { - desc: "empty header", - request: &http.Request{ - Header: map[string][]string{}, - }, - token: "", - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - token := apiutil.ExtractBearerToken(c.request) - assert.Equal(t, c.token, token) - }) - } -} - -func TestExtractThingKey(t *testing.T) { - cases := []struct { - desc string - request *http.Request - token string - }{ - { - desc: "valid bearer token", - request: &http.Request{ - Header: map[string][]string{ - "Authorization": {"Thing 123"}, - }, - }, - token: "123", - }, - { - desc: "invalid bearer token", - request: &http.Request{ - Header: map[string][]string{ - "Authorization": {"123"}, - }, - }, - token: "", - }, - { - desc: "empty bearer token", - request: &http.Request{ - Header: map[string][]string{ - "Authorization": {""}, - }, - }, - token: "", - }, - { - desc: "empty header", - request: &http.Request{ - Header: map[string][]string{}, - }, - token: "", - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - token := apiutil.ExtractThingKey(c.request) - assert.Equal(t, c.token, token) - }) - } -} diff --git a/pkg/apiutil/transport.go b/pkg/apiutil/transport.go deleted file mode 100644 index 35e22a3bc..000000000 --- a/pkg/apiutil/transport.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package apiutil - -import ( - "context" - "encoding/json" - "log/slog" - "net/http" - "strconv" - - "github.com/absmach/magistrala/pkg/errors" - kithttp "github.com/go-kit/kit/transport/http" -) - -// LoggingErrorEncoder is a go-kit error encoder logging decorator. -func LoggingErrorEncoder(logger *slog.Logger, enc kithttp.ErrorEncoder) kithttp.ErrorEncoder { - return func(ctx context.Context, err error, w http.ResponseWriter) { - if errors.Contains(err, ErrValidation) { - logger.Error(err.Error()) - } - enc(ctx, err, w) - } -} - -// ReadStringQuery reads the value of string http query parameters for a given key. -func ReadStringQuery(r *http.Request, key, def string) (string, error) { - vals := r.URL.Query()[key] - if len(vals) > 1 { - return "", ErrInvalidQueryParams - } - - if len(vals) == 0 { - return def, nil - } - - return vals[0], nil -} - -// ReadMetadataQuery reads the value of json http query parameters for a given key. -func ReadMetadataQuery(r *http.Request, key string, def map[string]interface{}) (map[string]interface{}, error) { - vals := r.URL.Query()[key] - if len(vals) > 1 { - return nil, ErrInvalidQueryParams - } - - if len(vals) == 0 { - return def, nil - } - - m := make(map[string]interface{}) - err := json.Unmarshal([]byte(vals[0]), &m) - if err != nil { - return nil, errors.Wrap(ErrInvalidQueryParams, err) - } - - return m, nil -} - -// ReadBoolQuery reads boolean query parameters in a given http request. -func ReadBoolQuery(r *http.Request, key string, def bool) (bool, error) { - vals := r.URL.Query()[key] - if len(vals) > 1 { - return false, ErrInvalidQueryParams - } - - if len(vals) == 0 { - return def, nil - } - - b, err := strconv.ParseBool(vals[0]) - if err != nil { - return false, errors.Wrap(ErrInvalidQueryParams, err) - } - - return b, nil -} - -type number interface { - int64 | float64 | uint16 | uint64 -} - -// ReadNumQuery returns a numeric value. -func ReadNumQuery[N number](r *http.Request, key string, def N) (N, error) { - vals := r.URL.Query()[key] - if len(vals) > 1 { - return 0, ErrInvalidQueryParams - } - if len(vals) == 0 { - return def, nil - } - val := vals[0] - - switch any(def).(type) { - case int64: - v, err := strconv.ParseInt(val, 10, 64) - if err != nil { - return 0, errors.Wrap(ErrInvalidQueryParams, err) - } - return N(v), nil - case uint64: - v, err := strconv.ParseUint(val, 10, 64) - if err != nil { - return 0, errors.Wrap(ErrInvalidQueryParams, err) - } - return N(v), nil - case uint16: - v, err := strconv.ParseUint(val, 10, 16) - if err != nil { - return 0, errors.Wrap(ErrInvalidQueryParams, err) - } - return N(v), nil - case float64: - v, err := strconv.ParseFloat(val, 64) - if err != nil { - return 0, errors.Wrap(ErrInvalidQueryParams, err) - } - return N(v), nil - default: - return def, nil - } -} diff --git a/pkg/apiutil/transport_test.go b/pkg/apiutil/transport_test.go deleted file mode 100644 index fec20d977..000000000 --- a/pkg/apiutil/transport_test.go +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package apiutil_test - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/stretchr/testify/assert" -) - -func TestReadStringQuery(t *testing.T) { - cases := []struct { - desc string - url string - key string - ret string - err error - }{ - { - desc: "valid string query", - url: "http://localhost:8080/?key=test", - key: "key", - ret: "test", - err: nil, - }, - { - desc: "empty string query", - url: "http://localhost:8080/", - key: "key", - ret: "", - err: nil, - }, - { - desc: "multiple string query", - url: "http://localhost:8080/?key=test&key=random", - key: "key", - ret: "", - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - parsedURL, err := url.Parse(c.url) - assert.NoError(t, err) - - r := &http.Request{URL: parsedURL} - ret, err := apiutil.ReadStringQuery(r, c.key, "") - assert.Equal(t, c.err, err) - assert.Equal(t, c.ret, ret) - }) - } -} - -func TestReadMetadataQuery(t *testing.T) { - cases := []struct { - desc string - url string - key string - ret map[string]interface{} - err error - }{ - { - desc: "valid metadata query", - url: "http://localhost:8080/?key={\"test\":\"test\"}", - key: "key", - ret: map[string]interface{}{"test": "test"}, - err: nil, - }, - { - desc: "empty metadata query", - url: "http://localhost:8080/", - key: "key", - ret: nil, - err: nil, - }, - { - desc: "multiple metadata query", - url: "http://localhost:8080/?key={\"test\":\"test\"}&key={\"random\":\"random\"}", - key: "key", - ret: nil, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "invalid metadata query", - url: "http://localhost:8080/?key=abc", - key: "key", - ret: nil, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - parsedURL, err := url.Parse(c.url) - assert.NoError(t, err) - - r := &http.Request{URL: parsedURL} - ret, err := apiutil.ReadMetadataQuery(r, c.key, nil) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected: %v, got: %v", c.err, err)) - assert.Equal(t, c.ret, ret) - }) - } -} - -func TestReadBoolQuery(t *testing.T) { - cases := []struct { - desc string - url string - key string - ret bool - err error - }{ - { - desc: "valid bool query", - url: "http://localhost:8080/?key=true", - key: "key", - ret: true, - err: nil, - }, - { - desc: "valid bool query", - url: "http://localhost:8080/?key=false", - key: "key", - ret: false, - err: nil, - }, - { - desc: "invalid bool query", - url: "http://localhost:8080/?key=abc", - key: "key", - ret: false, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "empty bool query", - url: "http://localhost:8080/", - key: "key", - ret: false, - err: nil, - }, - { - desc: "multiple bool query", - url: "http://localhost:8080/?key=true&key=false", - key: "key", - ret: false, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - parsedURL, err := url.Parse(c.url) - assert.NoError(t, err) - - r := &http.Request{URL: parsedURL} - ret, err := apiutil.ReadBoolQuery(r, c.key, false) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected: %v, got: %v", c.err, err)) - assert.Equal(t, c.ret, ret) - }) - } -} - -func TestReadNumQuery(t *testing.T) { - cases := []struct { - desc string - url string - key string - numType string - ret interface{} - err error - }{ - { - desc: "valid int64 query", - url: "http://localhost:8080/?key=123", - key: "key", - numType: "int64", - ret: int64(123), - err: nil, - }, - { - desc: "valid float64 query", - url: "http://localhost:8080/?key=1.23", - key: "key", - numType: "float64", - ret: float64(1.23), - err: nil, - }, - { - desc: "valid uint64 query", - url: "http://localhost:8080/?key=123", - key: "key", - numType: "uint64", - ret: uint64(123), - err: nil, - }, - { - desc: "valid uint16 query", - url: "http://localhost:8080/?key=123", - key: "key", - numType: "uint16", - ret: uint16(123), - err: nil, - }, - { - desc: "invalid int64 query", - url: "http://localhost:8080/?key=abc", - key: "key", - numType: "int64", - ret: int64(0), - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "invalid float64 query", - url: "http://localhost:8080/?key=abc", - key: "key", - numType: "float64", - ret: float64(0), - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "invalid uint64 query", - url: "http://localhost:8080/?key=abc", - key: "key", - numType: "uint64", - ret: uint64(0), - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "invalid uint16 query", - url: "http://localhost:8080/?key=abc", - key: "key", - numType: "uint16", - ret: uint16(0), - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "empty int64 query", - url: "http://localhost:8080/", - key: "key", - numType: "int64", - ret: int64(0), - err: nil, - }, - { - desc: "empty float64 query", - url: "http://localhost:8080/", - key: "key", - numType: "float64", - ret: float64(0), - err: nil, - }, - { - desc: "empty uint16 query", - url: "http://localhost:8080/", - key: "key", - numType: "uint16", - ret: uint16(0), - err: nil, - }, - { - desc: "empty uint64 query", - url: "http://localhost:8080/", - key: "key", - numType: "uint64", - ret: uint64(0), - err: nil, - }, - { - desc: "multiple int64 query", - url: "http://localhost:8080/?key=123&key=456", - key: "key", - numType: "int64", - ret: int64(0), - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "multiple float64 query", - url: "http://localhost:8080/?key=1.23&key=4.56", - key: "key", - numType: "float64", - ret: float64(0), - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "multiple uint16 query", - url: "http://localhost:8080/?key=123&key=456", - key: "key", - numType: "uint16", - ret: uint16(0), - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "multiple uint64 query", - url: "http://localhost:8080/?key=123&key=456", - key: "key", - numType: "uint64", - ret: uint64(0), - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - parsedURL, err := url.Parse(c.url) - assert.NoError(t, err) - - r := &http.Request{URL: parsedURL} - var ret interface{} - switch c.numType { - case "int64": - ret, err = apiutil.ReadNumQuery[int64](r, c.key, 0) - case "float64": - ret, err = apiutil.ReadNumQuery[float64](r, c.key, 0) - case "uint64": - ret, err = apiutil.ReadNumQuery[uint64](r, c.key, 0) - case "uint16": - ret, err = apiutil.ReadNumQuery[uint16](r, c.key, 0) - } - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected: %v, got: %v", c.err, err)) - assert.Equal(t, c.ret, ret) - }) - } -} - -func TestLoggingErrorEncoder(t *testing.T) { - cases := []struct { - desc string - err error - }{ - { - desc: "error contains ErrValidation", - err: errors.Wrap(apiutil.ErrValidation, svcerr.ErrAuthentication), - }, - { - desc: "error does not contain ErrValidation", - err: svcerr.ErrAuthentication, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - encCalled := false - encFunc := func(ctx context.Context, err error, w http.ResponseWriter) { - encCalled = true - } - - errorEncoder := apiutil.LoggingErrorEncoder(mglog.NewMock(), encFunc) - errorEncoder(context.Background(), c.err, httptest.NewRecorder()) - - assert.True(t, encCalled) - }) - } -} diff --git a/pkg/authn/authn.go b/pkg/authn/authn.go deleted file mode 100644 index d5f91060e..000000000 --- a/pkg/authn/authn.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package authn - -import ( - "context" -) - -type Session struct { - DomainUserID string - UserID string - DomainID string - SuperAdmin bool -} - -// Authn is magistrala authentication library. -// -//go:generate mockery --name Authentication --output=./mocks --filename authn.go --quiet --note "Copyright (c) Abstract Machines" -type Authentication interface { - Authenticate(ctx context.Context, token string) (Session, error) -} diff --git a/pkg/authn/authsvc/authn.go b/pkg/authn/authsvc/authn.go deleted file mode 100644 index 88b44c518..000000000 --- a/pkg/authn/authsvc/authn.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package authsvc - -import ( - "context" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth/api/grpc/auth" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/grpcclient" - grpchealth "google.golang.org/grpc/health/grpc_health_v1" -) - -type authentication struct { - authSvcClient magistrala.AuthServiceClient -} - -var _ authn.Authentication = (*authentication)(nil) - -func NewAuthentication(ctx context.Context, cfg grpcclient.Config) (authn.Authentication, grpcclient.Handler, error) { - client, err := grpcclient.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, grpcclient.ErrSvcNotServing - } - authSvcClient := auth.NewAuthClient(client.Connection(), cfg.Timeout) - return authentication{authSvcClient}, client, nil -} - -func (a authentication) Authenticate(ctx context.Context, token string) (authn.Session, error) { - res, err := a.authSvcClient.Authenticate(ctx, &magistrala.AuthNReq{Token: token}) - if err != nil { - return authn.Session{}, errors.Wrap(errors.ErrAuthentication, err) - } - return authn.Session{DomainUserID: res.GetId(), UserID: res.GetUserId(), DomainID: res.GetDomainId()}, nil -} diff --git a/pkg/authn/doc.go b/pkg/authn/doc.go deleted file mode 100644 index e2d3aaa85..000000000 --- a/pkg/authn/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package authn diff --git a/pkg/authn/mocks/authn.go b/pkg/authn/mocks/authn.go deleted file mode 100644 index 9360870cd..000000000 --- a/pkg/authn/mocks/authn.go +++ /dev/null @@ -1,60 +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" -) - -// Authentication is an autogenerated mock type for the Authentication type -type Authentication struct { - mock.Mock -} - -// Authenticate provides a mock function with given fields: ctx, token -func (_m *Authentication) Authenticate(ctx context.Context, token string) (authn.Session, error) { - ret := _m.Called(ctx, token) - - if len(ret) == 0 { - panic("no return value specified for Authenticate") - } - - var r0 authn.Session - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (authn.Session, error)); ok { - return rf(ctx, token) - } - if rf, ok := ret.Get(0).(func(context.Context, string) authn.Session); ok { - r0 = rf(ctx, token) - } else { - r0 = ret.Get(0).(authn.Session) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, token) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewAuthentication creates a new instance of Authentication. 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 NewAuthentication(t interface { - mock.TestingT - Cleanup(func()) -}) *Authentication { - mock := &Authentication{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/authz/authsvc/authz.go b/pkg/authz/authsvc/authz.go deleted file mode 100644 index 47db088e2..000000000 --- a/pkg/authz/authsvc/authz.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package authsvc - -import ( - "context" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/auth/api/grpc/auth" - "github.com/absmach/magistrala/pkg/authz" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/grpcclient" - grpchealth "google.golang.org/grpc/health/grpc_health_v1" -) - -type authorization struct { - authSvcClient magistrala.AuthServiceClient -} - -var _ authz.Authorization = (*authorization)(nil) - -func NewAuthorization(ctx context.Context, cfg grpcclient.Config) (authz.Authorization, grpcclient.Handler, error) { - client, err := grpcclient.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, grpcclient.ErrSvcNotServing - } - authSvcClient := auth.NewAuthClient(client.Connection(), cfg.Timeout) - return authorization{authSvcClient}, client, nil -} - -func (a authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error { - req := magistrala.AuthZReq{ - Domain: pr.Domain, - SubjectType: pr.SubjectType, - SubjectKind: pr.SubjectKind, - SubjectRelation: pr.SubjectRelation, - Subject: pr.Subject, - Relation: pr.Relation, - Permission: pr.Permission, - Object: pr.Object, - ObjectType: pr.ObjectType, - } - res, err := a.authSvcClient.Authorize(ctx, &req) - if err != nil { - return errors.Wrap(errors.ErrAuthorization, err) - } - if !res.Authorized { - return errors.ErrAuthorization - } - return nil -} diff --git a/pkg/authz/authz.go b/pkg/authz/authz.go deleted file mode 100644 index a76993ef4..000000000 --- a/pkg/authz/authz.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package authz - -import "context" - -type PolicyReq struct { - // Domain contains the domain ID. - Domain string `json:"domain,omitempty"` - - // Subject contains the subject ID or Token. - Subject string `json:"subject"` - - // SubjectType contains the subject type. Supported subject types are - // platform, group, domain, thing, users. - SubjectType string `json:"subject_type"` - - // SubjectKind contains the subject kind. Supported subject kinds are - // token, users, platform, things, channels, groups, domain. - SubjectKind string `json:"subject_kind"` - - // SubjectRelation contains subject relations. - SubjectRelation string `json:"subject_relation,omitempty"` - - // Object contains the object ID. - Object string `json:"object"` - - // ObjectKind contains the object kind. Supported object kinds are - // users, platform, things, channels, groups, domain. - ObjectKind string `json:"object_kind"` - - // ObjectType contains the object type. Supported object types are - // platform, group, domain, thing, users. - ObjectType string `json:"object_type"` - - // Relation contains the relation. Supported relations are administrator, editor, contributor, member, guest, parent_group,group,domain. - Relation string `json:"relation,omitempty"` - - // Permission contains the permission. Supported permissions are admin, delete, edit, share, view, - // membership, create, admin_only, edit_only, view_only, membership_only, ext_admin, ext_edit, ext_view. - Permission string `json:"permission,omitempty"` -} - -// Authz is magistrala authorization library. -// -//go:generate mockery --name Authorization --output=./mocks --filename authz.go --quiet --note "Copyright (c) Abstract Machines" -type Authorization interface { - Authorize(ctx context.Context, pr PolicyReq) error -} diff --git a/pkg/authz/doc.go b/pkg/authz/doc.go deleted file mode 100644 index 83cb21a4e..000000000 --- a/pkg/authz/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package authz diff --git a/pkg/authz/mocks/authz.go b/pkg/authz/mocks/authz.go deleted file mode 100644 index fe190f2ce..000000000 --- a/pkg/authz/mocks/authz.go +++ /dev/null @@ -1,50 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - authz "github.com/absmach/magistrala/pkg/authz" - - mock "github.com/stretchr/testify/mock" -) - -// Authorization is an autogenerated mock type for the Authorization type -type Authorization struct { - mock.Mock -} - -// Authorize provides a mock function with given fields: ctx, pr -func (_m *Authorization) Authorize(ctx context.Context, pr authz.PolicyReq) error { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for Authorize") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authz.PolicyReq) error); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewAuthorization creates a new instance of Authorization. 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 NewAuthorization(t interface { - mock.TestingT - Cleanup(func()) -}) *Authorization { - mock := &Authorization{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/events/events.go b/pkg/events/events.go deleted file mode 100644 index 65845a785..000000000 --- a/pkg/events/events.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "context" - "time" -) - -const ( - UnpublishedEventsCheckInterval = 1 * time.Minute - ConnCheckInterval = 100 * time.Millisecond - MaxUnpublishedEvents uint64 = 1e4 - MaxEventStreamLen int64 = 1e6 -) - -// Event represents an event. -type Event interface { - // Encode encodes event to map. - Encode() (map[string]interface{}, error) -} - -// Publisher specifies events publishing API. -// -//go:generate mockery --name Publisher --output=./mocks --filename publisher.go --quiet --note "Copyright (c) Abstract Machines" -type Publisher interface { - // Publish publishes event to stream. - Publish(ctx context.Context, event Event) error - - // Close gracefully closes event publisher's connection. - Close() error -} - -// EventHandler represents event handler for Subscriber. -type EventHandler interface { - // Handle handles events passed by underlying implementation. - Handle(ctx context.Context, event Event) error -} - -// SubscriberConfig represents event subscriber configuration. -type SubscriberConfig struct { - Consumer string - Stream string - Handler EventHandler -} - -// Subscriber specifies event subscription API. -// -//go:generate mockery --name Subscriber --output=./mocks --filename subscriber.go --quiet --note "Copyright (c) Abstract Machines" -type Subscriber interface { - // Subscribe subscribes to the event stream and consumes events. - Subscribe(ctx context.Context, cfg SubscriberConfig) error - - // Close gracefully closes event subscriber's connection. - Close() error -} - -// Read reads value from event map. -// If value is not of type T, returns default value. -func Read[T any](event map[string]interface{}, key string, def T) T { - val, ok := event[key].(T) - if !ok { - return def - } - - return val -} - -// ReadStringSlice reads string slice from event map. -// If value is not a string slice, returns empty slice. -func ReadStringSlice(event map[string]interface{}, key string) []string { - var res []string - - vals, ok := event[key].([]interface{}) - if !ok { - return res - } - - for _, v := range vals { - if s, ok := v.(string); ok { - res = append(res, s) - } - } - - return res -} diff --git a/pkg/events/mocks/publisher.go b/pkg/events/mocks/publisher.go deleted file mode 100644 index 7159efd4a..000000000 --- a/pkg/events/mocks/publisher.go +++ /dev/null @@ -1,67 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - events "github.com/absmach/magistrala/pkg/events" - mock "github.com/stretchr/testify/mock" -) - -// Publisher is an autogenerated mock type for the Publisher type -type Publisher struct { - mock.Mock -} - -// Close provides a mock function with given fields: -func (_m *Publisher) Close() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Close") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Publish provides a mock function with given fields: ctx, event -func (_m *Publisher) Publish(ctx context.Context, event events.Event) error { - ret := _m.Called(ctx, event) - - if len(ret) == 0 { - panic("no return value specified for Publish") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, events.Event) error); ok { - r0 = rf(ctx, event) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewPublisher creates a new instance of Publisher. 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 NewPublisher(t interface { - mock.TestingT - Cleanup(func()) -}) *Publisher { - mock := &Publisher{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/events/mocks/subscriber.go b/pkg/events/mocks/subscriber.go deleted file mode 100644 index acad2e96e..000000000 --- a/pkg/events/mocks/subscriber.go +++ /dev/null @@ -1,67 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - events "github.com/absmach/magistrala/pkg/events" - mock "github.com/stretchr/testify/mock" -) - -// Subscriber is an autogenerated mock type for the Subscriber type -type Subscriber struct { - mock.Mock -} - -// Close provides a mock function with given fields: -func (_m *Subscriber) Close() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Close") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Subscribe provides a mock function with given fields: ctx, cfg -func (_m *Subscriber) Subscribe(ctx context.Context, cfg events.SubscriberConfig) error { - ret := _m.Called(ctx, cfg) - - if len(ret) == 0 { - panic("no return value specified for Subscribe") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, events.SubscriberConfig) error); ok { - r0 = rf(ctx, cfg) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewSubscriber creates a new instance of Subscriber. 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 NewSubscriber(t interface { - mock.TestingT - Cleanup(func()) -}) *Subscriber { - mock := &Subscriber{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/events/nats/doc.go b/pkg/events/nats/doc.go deleted file mode 100644 index 9b372ff5e..000000000 --- a/pkg/events/nats/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package redis contains the domain concept definitions needed to support -// Magistrala redis events source service functionality. -// -// It provides the abstraction of the redis stream and its operations. -package nats diff --git a/pkg/events/nats/publisher.go b/pkg/events/nats/publisher.go deleted file mode 100644 index e711f9701..000000000 --- a/pkg/events/nats/publisher.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats - -import ( - "context" - "encoding/json" - "time" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/messaging" - broker "github.com/absmach/magistrala/pkg/messaging/nats" - "github.com/nats-io/nats.go" - "github.com/nats-io/nats.go/jetstream" -) - -// Max message payload size is 1MB. -var reconnectBufSize = 1024 * 1024 * int(events.MaxUnpublishedEvents) - -type pubEventStore struct { - url string - conn *nats.Conn - publisher messaging.Publisher - stream string -} - -func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, error) { - conn, err := nats.Connect(url, nats.MaxReconnects(maxReconnects), nats.ReconnectBufSize(reconnectBufSize)) - if err != nil { - return nil, err - } - js, err := jetstream.New(conn) - if err != nil { - return nil, err - } - if _, err := js.CreateStream(ctx, jsStreamConfig); err != nil { - return nil, err - } - - publisher, err := broker.NewPublisher(ctx, url, broker.Prefix(eventsPrefix), broker.JSStream(js)) - if err != nil { - return nil, err - } - - es := &pubEventStore{ - url: url, - conn: conn, - publisher: publisher, - stream: stream, - } - - return es, nil -} - -func (es *pubEventStore) Publish(ctx context.Context, event events.Event) error { - values, err := event.Encode() - if err != nil { - return err - } - values["occurred_at"] = time.Now().UnixNano() - - data, err := json.Marshal(values) - if err != nil { - return err - } - - record := &messaging.Message{ - Payload: data, - } - - return es.publisher.Publish(ctx, es.stream, record) -} - -func (es *pubEventStore) Close() error { - es.conn.Close() - - return es.publisher.Close() -} diff --git a/pkg/events/nats/publisher_test.go b/pkg/events/nats/publisher_test.go deleted file mode 100644 index 20086ea55..000000000 --- a/pkg/events/nats/publisher_test.go +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats_test - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "math/rand" - "testing" - "time" - - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/nats" - "github.com/stretchr/testify/assert" -) - -var ( - eventsChan = make(chan map[string]interface{}) - logger = mglog.NewMock() - errFailed = errors.New("failed") - numEvents = 100 -) - -type testEvent struct { - Data map[string]interface{} -} - -func (te testEvent) Encode() (map[string]interface{}, error) { - data := make(map[string]interface{}) - for k, v := range te.Data { - switch v.(type) { - case string: - data[k] = v - case float64: - data[k] = v - default: - b, err := json.Marshal(v) - if err != nil { - return nil, err - } - data[k] = string(b) - } - } - - return data, nil -} - -func TestPublish(t *testing.T) { - _, err := nats.NewPublisher(context.Background(), "http://invaliurl.com", stream) - assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - - publisher, err := nats.NewPublisher(context.Background(), natsURL, stream) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - defer publisher.Close() - - _, err = nats.NewSubscriber(context.Background(), "http://invaliurl.com", logger) - assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - - subcriber, err := nats.NewSubscriber(context.Background(), natsURL, logger) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - defer subcriber.Close() - - cfg := events.SubscriberConfig{ - Stream: "events." + stream, - Consumer: consumer, - Handler: handler{}, - } - err = subcriber.Subscribe(context.Background(), cfg) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) - - cases := []struct { - desc string - event map[string]interface{} - err error - }{ - { - desc: "publish event successfully", - err: nil, - event: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": "abc123", - "location": "Earth", - "status": "normal", - "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), - "operation": "create", - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "publish with nil event", - err: nil, - event: nil, - }, - { - desc: "publish event with invalid event location", - err: fmt.Errorf("json: unsupported type: chan int"), - event: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": "abc123", - "location": make(chan int), - "status": "normal", - "timestamp": "invalid", - "operation": "create", - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "publish event with nested sting value", - err: nil, - event: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": "abc123", - "location": map[string]string{ - "lat": fmt.Sprintf("%f", rand.Float64()), - "lng": fmt.Sprintf("%f", rand.Float64()), - }, - "status": "normal", - "timestamp": "invalid", - "operation": "create", - "occurred_at": time.Now().UnixNano(), - }, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - event := testEvent{Data: tc.event} - - err := publisher.Publish(context.Background(), event) - switch tc.err { - case nil: - receivedEvent := <-eventsChan - - val := int64(receivedEvent["occurred_at"].(float64)) - if assert.WithinRange(t, time.Unix(0, val), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { - delete(receivedEvent, "occurred_at") - delete(tc.event, "occurred_at") - } - - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) - assert.Equal(t, tc.event["status"], receivedEvent["status"]) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) - default: - assert.ErrorContains(t, err, tc.err.Error()) - } - }) - } -} - -func TestPubsub(t *testing.T) { - cases := []struct { - desc string - stream string - consumer string - err error - handler events.EventHandler - }{ - { - desc: "Subscribe to a stream", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to the same stream", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to an empty stream with an empty consumer", - stream: "", - consumer: "", - err: nats.ErrEmptyStream, - handler: handler{false}, - }, - { - desc: "Subscribe to an empty stream with a valid consumer", - stream: "", - consumer: consumer, - err: nats.ErrEmptyStream, - handler: handler{false}, - }, - { - desc: "Subscribe to a valid stream with an empty consumer", - stream: fmt.Sprintf("events.%s", stream), - consumer: "", - err: nats.ErrEmptyConsumer, - handler: handler{false}, - }, - { - desc: "Subscribe to another stream", - stream: fmt.Sprintf("events.%s.%d", stream, 1), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to a stream with malformed handler", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{true}, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - subcriber, err := nats.NewSubscriber(context.Background(), natsURL, logger) - if err != nil { - assert.Equal(t, err, tc.err) - - return - } - - cfg := events.SubscriberConfig{ - Stream: tc.stream, - Consumer: tc.consumer, - Handler: tc.handler, - } - switch err := subcriber.Subscribe(context.Background(), cfg); { - case err == nil: - assert.Nil(t, err) - default: - assert.Equal(t, err, tc.err) - } - - err = subcriber.Close() - assert.Nil(t, err) - }) - } -} - -func TestUnavailablePublish(t *testing.T) { - publisher, err := nats.NewPublisher(context.Background(), natsURL, stream) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - - subcriber, err := nats.NewSubscriber(context.Background(), natsURL, logger) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - - cfg := events.SubscriberConfig{ - Stream: "events." + stream, - Consumer: consumer, - Handler: handler{}, - } - err = subcriber.Subscribe(context.Background(), cfg) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) - - err = pool.Client.PauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on pausing container: %s", err)) - - spawnGoroutines(publisher, t) - - time.Sleep(1 * time.Second) - - err = pool.Client.UnpauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on unpausing container: %s", err)) - - // Wait for the events to be published. - time.Sleep(1 * time.Second) - - err = publisher.Close() - assert.Nil(t, err, fmt.Sprintf("got unexpected error on closing publisher: %s", err)) - - // read all the events from the channel and assert that they are 10. - var receivedEvents []map[string]interface{} - for i := 0; i < numEvents; i++ { - event := <-eventsChan - receivedEvents = append(receivedEvents, event) - } - assert.Len(t, receivedEvents, numEvents, "got unexpected number of events") -} - -func generateRandomEvent() testEvent { - return testEvent{ - Data: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": fmt.Sprintf("%d", rand.Intn(1000)), - "location": fmt.Sprintf("%f", rand.Float64()), - "status": fmt.Sprintf("%d", rand.Intn(1000)), - "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), - "operation": "create", - }, - } -} - -func spawnGoroutines(publisher events.Publisher, t *testing.T) { - for i := 0; i < numEvents; i++ { - go func() { - err := publisher.Publish(context.Background(), generateRandomEvent()) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - }() - } -} - -type handler struct { - fail bool -} - -func (h handler) Handle(_ context.Context, event events.Event) error { - if h.fail { - return errFailed - } - data, err := event.Encode() - if err != nil { - return err - } - - eventsChan <- data - - return nil -} diff --git a/pkg/events/nats/setup_test.go b/pkg/events/nats/setup_test.go deleted file mode 100644 index e539aca53..000000000 --- a/pkg/events/nats/setup_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats_test - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - "syscall" - "testing" - - "github.com/absmach/magistrala/pkg/events/nats" - "github.com/ory/dockertest/v3" -) - -var ( - natsURL string - stream = "tests.events" - consumer = "tests-consumer" - pool *dockertest.Pool - container *dockertest.Resource -) - -func TestMain(m *testing.M) { - var err error - pool, err = dockertest.NewPool("") - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - container, err = pool.RunWithOptions(&dockertest.RunOptions{ - Repository: "nats", - Tag: "2.10.9-alpine", - Cmd: []string{"-DVV", "-js"}, - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - - handleInterrupt(pool, container) - - natsURL = fmt.Sprintf("nats://%s:%s", "localhost", container.GetPort("4222/tcp")) - - if err := pool.Retry(func() error { - _, err = nats.NewPublisher(context.Background(), natsURL, stream) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - if err := pool.Retry(func() error { - _, err = nats.NewSubscriber(context.Background(), natsURL, logger) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - code := m.Run() - - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} - -func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - - go func() { - <-c - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - os.Exit(0) - }() -} diff --git a/pkg/events/nats/subscriber.go b/pkg/events/nats/subscriber.go deleted file mode 100644 index ca99f8312..000000000 --- a/pkg/events/nats/subscriber.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "log/slog" - "time" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/messaging" - broker "github.com/absmach/magistrala/pkg/messaging/nats" - "github.com/nats-io/nats.go" - "github.com/nats-io/nats.go/jetstream" -) - -const maxReconnects = -1 - -var _ events.Subscriber = (*subEventStore)(nil) - -var ( - eventsPrefix = "events" - - jsStreamConfig = jetstream.StreamConfig{ - Name: "events", - Description: "Magistrala stream for sending and receiving messages in between Magistrala events", - Subjects: []string{"events.>"}, - Retention: jetstream.LimitsPolicy, - MaxMsgsPerSubject: 1e9, - MaxAge: time.Hour * 24, - MaxMsgSize: 1024 * 1024, - Discard: jetstream.DiscardOld, - Storage: jetstream.FileStorage, - } - - // ErrEmptyStream is returned when stream name is empty. - ErrEmptyStream = errors.New("stream name cannot be empty") - - // ErrEmptyConsumer is returned when consumer name is empty. - ErrEmptyConsumer = errors.New("consumer name cannot be empty") -) - -type subEventStore struct { - conn *nats.Conn - pubsub messaging.PubSub - logger *slog.Logger -} - -func NewSubscriber(ctx context.Context, url string, logger *slog.Logger) (events.Subscriber, error) { - conn, err := nats.Connect(url, nats.MaxReconnects(maxReconnects)) - if err != nil { - return nil, err - } - js, err := jetstream.New(conn) - if err != nil { - return nil, err - } - jsStream, err := js.CreateStream(ctx, jsStreamConfig) - if err != nil { - return nil, err - } - - pubsub, err := broker.NewPubSub(ctx, url, logger, broker.Stream(jsStream)) - if err != nil { - return nil, err - } - - return &subEventStore{ - conn: conn, - pubsub: pubsub, - logger: logger, - }, nil -} - -func (es *subEventStore) Subscribe(ctx context.Context, cfg events.SubscriberConfig) error { - if cfg.Stream == "" { - return ErrEmptyStream - } - if cfg.Consumer == "" { - return ErrEmptyConsumer - } - - subCfg := messaging.SubscriberConfig{ - ID: cfg.Consumer, - Topic: cfg.Stream, - Handler: &eventHandler{ - handler: cfg.Handler, - ctx: ctx, - logger: es.logger, - }, - DeliveryPolicy: messaging.DeliverNewPolicy, - } - - return es.pubsub.Subscribe(ctx, subCfg) -} - -func (es *subEventStore) Close() error { - es.conn.Close() - return es.pubsub.Close() -} - -type event struct { - Data map[string]interface{} -} - -func (re event) Encode() (map[string]interface{}, error) { - return re.Data, nil -} - -type eventHandler struct { - handler events.EventHandler - ctx context.Context - logger *slog.Logger -} - -func (eh *eventHandler) Handle(msg *messaging.Message) error { - event := event{ - Data: make(map[string]interface{}), - } - - if err := json.Unmarshal(msg.GetPayload(), &event.Data); err != nil { - return err - } - - if err := eh.handler.Handle(eh.ctx, event); err != nil { - eh.logger.Warn(fmt.Sprintf("failed to handle nats event: %s", err)) - } - - return nil -} - -func (eh *eventHandler) Cancel() error { - return nil -} diff --git a/pkg/events/rabbitmq/doc.go b/pkg/events/rabbitmq/doc.go deleted file mode 100644 index a39b21dc4..000000000 --- a/pkg/events/rabbitmq/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package redis contains the domain concept definitions needed to support -// Magistrala redis events source service functionality. -// -// It provides the abstraction of the redis stream and its operations. -package rabbitmq diff --git a/pkg/events/rabbitmq/publisher.go b/pkg/events/rabbitmq/publisher.go deleted file mode 100644 index ba7d735ae..000000000 --- a/pkg/events/rabbitmq/publisher.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq - -import ( - "context" - "encoding/json" - "time" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/messaging" - broker "github.com/absmach/magistrala/pkg/messaging/rabbitmq" - amqp "github.com/rabbitmq/amqp091-go" -) - -type pubEventStore struct { - conn *amqp.Connection - publisher messaging.Publisher - stream string -} - -func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, error) { - conn, err := amqp.Dial(url) - if err != nil { - return nil, err - } - ch, err := conn.Channel() - if err != nil { - return nil, err - } - if err := ch.ExchangeDeclare(exchangeName, amqp.ExchangeTopic, true, false, false, false, nil); err != nil { - return nil, err - } - - publisher, err := broker.NewPublisher(url, broker.Prefix(eventsPrefix), broker.Exchange(exchangeName), broker.Channel(ch)) - if err != nil { - return nil, err - } - - es := &pubEventStore{ - conn: conn, - publisher: publisher, - stream: stream, - } - - return es, nil -} - -func (es *pubEventStore) Publish(ctx context.Context, event events.Event) error { - values, err := event.Encode() - if err != nil { - return err - } - values["occurred_at"] = time.Now().UnixNano() - - data, err := json.Marshal(values) - if err != nil { - return err - } - - record := &messaging.Message{ - Payload: data, - } - - return es.publisher.Publish(ctx, es.stream, record) -} - -func (es *pubEventStore) Close() error { - es.conn.Close() - - return es.publisher.Close() -} diff --git a/pkg/events/rabbitmq/publisher_test.go b/pkg/events/rabbitmq/publisher_test.go deleted file mode 100644 index f14534654..000000000 --- a/pkg/events/rabbitmq/publisher_test.go +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq_test - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "math/rand" - "testing" - "time" - - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/rabbitmq" - "github.com/stretchr/testify/assert" -) - -var ( - eventsChan = make(chan map[string]interface{}) - logger = mglog.NewMock() - errFailed = errors.New("failed") - numEvents = 100 -) - -type testEvent struct { - Data map[string]interface{} -} - -func (te testEvent) Encode() (map[string]interface{}, error) { - data := make(map[string]interface{}) - for k, v := range te.Data { - switch v.(type) { - case string: - data[k] = v - case float64: - data[k] = v - default: - b, err := json.Marshal(v) - if err != nil { - return nil, err - } - data[k] = string(b) - } - } - - return data, nil -} - -func TestPublish(t *testing.T) { - _, err := rabbitmq.NewPublisher(context.Background(), "http://invaliurl.com", stream) - assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - - publisher, err := rabbitmq.NewPublisher(context.Background(), rabbitmqURL, stream) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - defer publisher.Close() - - _, err = rabbitmq.NewSubscriber("http://invaliurl.com", logger) - assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - - subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, logger) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - defer subcriber.Close() - - cfg := events.SubscriberConfig{ - Stream: "events." + stream, - Consumer: consumer, - Handler: handler{}, - } - err = subcriber.Subscribe(context.Background(), cfg) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) - - cases := []struct { - desc string - event map[string]interface{} - err error - }{ - { - desc: "publish event successfully", - err: nil, - event: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": "abc123", - "location": "Earth", - "status": "normal", - "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), - "operation": "create", - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "publish with nil event", - err: nil, - event: nil, - }, - { - desc: "publish event with invalid event location", - err: fmt.Errorf("json: unsupported type: chan int"), - event: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": "abc123", - "location": make(chan int), - "status": "normal", - "timestamp": "invalid", - "operation": "create", - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "publish event with nested sting value", - err: nil, - event: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": "abc123", - "location": map[string]string{ - "lat": fmt.Sprintf("%f", rand.Float64()), - "lng": fmt.Sprintf("%f", rand.Float64()), - }, - "status": "normal", - "timestamp": "invalid", - "operation": "create", - "occurred_at": time.Now().UnixNano(), - }, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - event := testEvent{Data: tc.event} - - err := publisher.Publish(context.Background(), event) - switch tc.err { - case nil: - receivedEvent := <-eventsChan - - val := int64(receivedEvent["occurred_at"].(float64)) - if assert.WithinRange(t, time.Unix(0, val), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { - delete(receivedEvent, "occurred_at") - delete(tc.event, "occurred_at") - } - - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) - assert.Equal(t, tc.event["status"], receivedEvent["status"]) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) - - default: - assert.ErrorContains(t, err, tc.err.Error()) - } - }) - } -} - -func TestPubsub(t *testing.T) { - cases := []struct { - desc string - stream string - consumer string - err error - handler events.EventHandler - }{ - { - desc: "Subscribe to a stream", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to the same stream", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to an empty stream with an empty consumer", - stream: "", - consumer: "", - err: rabbitmq.ErrEmptyStream, - handler: handler{false}, - }, - { - desc: "Subscribe to an empty stream with a valid consumer", - stream: "", - consumer: consumer, - err: rabbitmq.ErrEmptyStream, - handler: handler{false}, - }, - { - desc: "Subscribe to a valid stream with an empty consumer", - stream: fmt.Sprintf("events.%s", stream), - consumer: "", - err: rabbitmq.ErrEmptyConsumer, - handler: handler{false}, - }, - { - desc: "Subscribe to another stream", - stream: fmt.Sprintf("events.%s.%d", stream, 1), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to a stream with malformed handler", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{true}, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, logger) - if err != nil { - assert.Equal(t, err, tc.err) - - return - } - - cfg := events.SubscriberConfig{ - Stream: tc.stream, - Consumer: tc.consumer, - Handler: tc.handler, - } - switch err := subcriber.Subscribe(context.Background(), cfg); { - case err == nil: - assert.Nil(t, err) - default: - assert.Equal(t, err, tc.err) - } - - err = subcriber.Close() - assert.Nil(t, err) - }) - } -} - -func TestUnavailablePublish(t *testing.T) { - publisher, err := rabbitmq.NewPublisher(context.Background(), rabbitmqURL, stream) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - - subcriber, err := rabbitmq.NewSubscriber(rabbitmqURL, logger) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - - cfg := events.SubscriberConfig{ - Stream: "events." + stream, - Consumer: consumer, - Handler: handler{}, - } - err = subcriber.Subscribe(context.Background(), cfg) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) - - err = pool.Client.PauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on pausing container: %s", err)) - - spawnGoroutines(publisher, t) - - time.Sleep(1 * time.Second) - - err = pool.Client.UnpauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on unpausing container: %s", err)) - - // Wait for the events to be published. - time.Sleep(1 * time.Second) - - err = publisher.Close() - assert.Nil(t, err, fmt.Sprintf("got unexpected error on closing publisher: %s", err)) - - // read all the events from the channel and assert that they are 10. - var receivedEvents []map[string]interface{} - for i := 0; i < numEvents; i++ { - event := <-eventsChan - receivedEvents = append(receivedEvents, event) - } - assert.Len(t, receivedEvents, numEvents, "got unexpected number of events") -} - -func generateRandomEvent() testEvent { - return testEvent{ - Data: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": fmt.Sprintf("%d", rand.Intn(1000)), - "location": fmt.Sprintf("%f", rand.Float64()), - "status": fmt.Sprintf("%d", rand.Intn(1000)), - "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), - "operation": "create", - }, - } -} - -func spawnGoroutines(publisher events.Publisher, t *testing.T) { - for i := 0; i < numEvents; i++ { - go func() { - err := publisher.Publish(context.Background(), generateRandomEvent()) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - }() - } -} - -type handler struct { - fail bool -} - -func (h handler) Handle(_ context.Context, event events.Event) error { - if h.fail { - return errFailed - } - data, err := event.Encode() - if err != nil { - return err - } - - eventsChan <- data - - return nil -} diff --git a/pkg/events/rabbitmq/setup_test.go b/pkg/events/rabbitmq/setup_test.go deleted file mode 100644 index dcbf066af..000000000 --- a/pkg/events/rabbitmq/setup_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq_test - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - "syscall" - "testing" - - "github.com/absmach/magistrala/pkg/events/rabbitmq" - "github.com/ory/dockertest/v3" -) - -var ( - rabbitmqURL string - stream = "tests.events" - consumer = "tests-consumer" - pool *dockertest.Pool - container *dockertest.Resource -) - -func TestMain(m *testing.M) { - var err error - pool, err = dockertest.NewPool("") - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - container, err = pool.RunWithOptions(&dockertest.RunOptions{ - Repository: "rabbitmq", - Tag: "3.12.12", - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - - handleInterrupt(pool, container) - - rabbitmqURL = fmt.Sprintf("amqp://%s:%s", "localhost", container.GetPort("5672/tcp")) - - if err := pool.Retry(func() error { - _, err = rabbitmq.NewPublisher(context.Background(), rabbitmqURL, stream) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - if err := pool.Retry(func() error { - _, err = rabbitmq.NewSubscriber(rabbitmqURL, logger) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - code := m.Run() - - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} - -func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) { - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - os.Exit(0) - }() -} diff --git a/pkg/events/rabbitmq/subscriber.go b/pkg/events/rabbitmq/subscriber.go deleted file mode 100644 index bba6b1631..000000000 --- a/pkg/events/rabbitmq/subscriber.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "log/slog" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/messaging" - broker "github.com/absmach/magistrala/pkg/messaging/rabbitmq" - amqp "github.com/rabbitmq/amqp091-go" -) - -var _ events.Subscriber = (*subEventStore)(nil) - -var ( - exchangeName = "events" - eventsPrefix = "events" - - // ErrEmptyStream is returned when stream name is empty. - ErrEmptyStream = errors.New("stream name cannot be empty") - - // ErrEmptyConsumer is returned when consumer name is empty. - ErrEmptyConsumer = errors.New("consumer name cannot be empty") -) - -type subEventStore struct { - conn *amqp.Connection - pubsub messaging.PubSub - logger *slog.Logger -} - -func NewSubscriber(url string, logger *slog.Logger) (events.Subscriber, error) { - conn, err := amqp.Dial(url) - if err != nil { - return nil, err - } - ch, err := conn.Channel() - if err != nil { - return nil, err - } - if err := ch.ExchangeDeclare(exchangeName, amqp.ExchangeTopic, true, false, false, false, nil); err != nil { - return nil, err - } - - pubsub, err := broker.NewPubSub(url, logger, broker.Channel(ch), broker.Exchange(exchangeName)) - if err != nil { - return nil, err - } - - return &subEventStore{ - conn: conn, - pubsub: pubsub, - logger: logger, - }, nil -} - -func (es *subEventStore) Subscribe(ctx context.Context, cfg events.SubscriberConfig) error { - if cfg.Stream == "" { - return ErrEmptyStream - } - if cfg.Consumer == "" { - return ErrEmptyConsumer - } - - subCfg := messaging.SubscriberConfig{ - ID: cfg.Consumer, - Topic: cfg.Stream, - Handler: &eventHandler{ - handler: cfg.Handler, - ctx: ctx, - logger: es.logger, - }, - DeliveryPolicy: messaging.DeliverNewPolicy, - } - - return es.pubsub.Subscribe(ctx, subCfg) -} - -func (es *subEventStore) Close() error { - es.conn.Close() - return es.pubsub.Close() -} - -type event struct { - Data map[string]interface{} -} - -func (re event) Encode() (map[string]interface{}, error) { - return re.Data, nil -} - -type eventHandler struct { - handler events.EventHandler - ctx context.Context - logger *slog.Logger -} - -func (eh *eventHandler) Handle(msg *messaging.Message) error { - event := event{ - Data: make(map[string]interface{}), - } - - if err := json.Unmarshal(msg.GetPayload(), &event.Data); err != nil { - return err - } - - if err := eh.handler.Handle(eh.ctx, event); err != nil { - eh.logger.Warn(fmt.Sprintf("failed to handle rabbitmq event: %s", err)) - } - - return nil -} - -func (eh *eventHandler) Cancel() error { - return nil -} diff --git a/pkg/events/redis/doc.go b/pkg/events/redis/doc.go deleted file mode 100644 index 249256266..000000000 --- a/pkg/events/redis/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package redis contains the domain concept definitions needed to support -// Magistrala redis events source service functionality. -// -// It provides the abstraction of the redis stream and its operations. -package redis diff --git a/pkg/events/redis/publisher.go b/pkg/events/redis/publisher.go deleted file mode 100644 index 77bb537b2..000000000 --- a/pkg/events/redis/publisher.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package redis - -import ( - "context" - "encoding/json" - "sync" - "time" - - "github.com/absmach/magistrala/pkg/events" - "github.com/redis/go-redis/v9" -) - -type pubEventStore struct { - client *redis.Client - unpublishedEvents chan *redis.XAddArgs - stream string - mu sync.Mutex - flushPeriod time.Duration -} - -func NewPublisher(ctx context.Context, url, stream string, flushPeriod time.Duration) (events.Publisher, error) { - opts, err := redis.ParseURL(url) - if err != nil { - return nil, err - } - - es := &pubEventStore{ - client: redis.NewClient(opts), - unpublishedEvents: make(chan *redis.XAddArgs, events.MaxUnpublishedEvents), - stream: eventsPrefix + stream, - flushPeriod: flushPeriod, - } - - go es.flushUnpublished(ctx) - - return es, nil -} - -func (es *pubEventStore) Publish(ctx context.Context, event events.Event) error { - values, err := event.Encode() - if err != nil { - return err - } - values["occurred_at"] = time.Now().UnixNano() - - data, err := json.Marshal(values) - if err != nil { - return err - } - - record := &redis.XAddArgs{ - Stream: es.stream, - MaxLen: events.MaxEventStreamLen, - Approx: true, - Values: map[string]interface{}{"data": string(data)}, - } - - switch err := es.checkConnection(ctx); err { - case nil: - return es.client.XAdd(ctx, record).Err() - default: - es.mu.Lock() - defer es.mu.Unlock() - - // If the channel is full (rarely happens), drop the events. - if len(es.unpublishedEvents) == int(events.MaxUnpublishedEvents) { - return nil - } - - es.unpublishedEvents <- record - - return nil - } -} - -// flushUnpublished periodically checks the Redis connection and publishes -// the events that were not published due to a connection error. -func (es *pubEventStore) flushUnpublished(ctx context.Context) { - defer close(es.unpublishedEvents) - - ticker := time.NewTicker(es.flushPeriod) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if err := es.checkConnection(ctx); err == nil { - es.mu.Lock() - for i := len(es.unpublishedEvents) - 1; i >= 0; i-- { - record := <-es.unpublishedEvents - if err := es.client.XAdd(ctx, record).Err(); err != nil { - es.unpublishedEvents <- record - - break - } - } - es.mu.Unlock() - } - case <-ctx.Done(): - return - } - } -} - -func (es *pubEventStore) Close() error { - return es.client.Close() -} - -func (es *pubEventStore) checkConnection(ctx context.Context) error { - // A timeout is used to avoid blocking the main thread - ctx, cancel := context.WithTimeout(ctx, events.ConnCheckInterval) - defer cancel() - - return es.client.Ping(ctx).Err() -} diff --git a/pkg/events/redis/publisher_test.go b/pkg/events/redis/publisher_test.go deleted file mode 100644 index 5760d79dc..000000000 --- a/pkg/events/redis/publisher_test.go +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package redis_test - -import ( - "context" - "errors" - "fmt" - "math/rand" - "testing" - "time" - - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/redis" - "github.com/stretchr/testify/assert" -) - -var ( - stream = "tests.events" - consumer = "test-consumer" - eventsChan = make(chan map[string]interface{}) - logger = mglog.NewMock() - errFailed = errors.New("failed") - numEvents = 100 -) - -type testEvent struct { - Data map[string]interface{} -} - -func (te testEvent) Encode() (map[string]interface{}, error) { - if te.Data == nil { - return map[string]interface{}{}, nil - } - - return te.Data, nil -} - -func TestPublish(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error on flushing redis: %s", err)) - - _, err = redis.NewPublisher(context.Background(), "http://invaliurl.com", stream, events.UnpublishedEventsCheckInterval) - assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - - publisher, err := redis.NewPublisher(context.Background(), redisURL, stream, events.UnpublishedEventsCheckInterval) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - defer publisher.Close() - - _, err = redis.NewSubscriber("http://invaliurl.com", logger) - assert.NotNilf(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err), err) - - subcriber, err := redis.NewSubscriber(redisURL, logger) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - defer subcriber.Close() - - cfg := events.SubscriberConfig{ - Stream: "events." + stream, - Consumer: consumer, - Handler: handler{}, - } - err = subcriber.Subscribe(context.Background(), cfg) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) - - cases := []struct { - desc string - event map[string]interface{} - err error - }{ - { - desc: "publish event successfully", - err: nil, - event: map[string]interface{}{ - "temperature": float64(rand.Float64()), - "humidity": float64(rand.Float64()), - "sensor_id": "abc123", - "location": "Earth", - "status": "normal", - "timestamp": float64(time.Now().UnixNano()), - "operation": "create", - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "publish with nil event", - err: nil, - event: nil, - }, - { - desc: "publish event with invalid event location", - err: fmt.Errorf("json: unsupported type: chan int"), - event: map[string]interface{}{ - "temperature": float64(rand.Float64()), - "humidity": float64(rand.Float64()), - "sensor_id": "abc123", - "location": make(chan int), - "status": "normal", - "timestamp": "invalid", - "operation": "create", - "occurred_at": float64(time.Now().UnixNano()), - }, - }, - { - desc: "publish event with nested sting value", - err: nil, - event: map[string]interface{}{ - "temperature": float64(rand.Float64()), - "humidity": float64(rand.Float64()), - "sensor_id": "abc123", - "location": map[string]string{ - "lat": fmt.Sprintf("%f", rand.Float64()), - "lng": fmt.Sprintf("%f", rand.Float64()), - }, - "status": "normal", - "timestamp": "invalid", - "operation": "create", - "occurred_at": float64(time.Now().UnixNano()), - }, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - event := testEvent{Data: tc.event} - - err := publisher.Publish(context.Background(), event) - switch tc.err { - case nil: - receivedEvent := <-eventsChan - - roa := receivedEvent["occurred_at"].(float64) - assert.Nil(t, err) - if assert.WithinRange(t, time.Unix(0, int64(roa)), time.Now().Add(-time.Second), time.Now().Add(time.Second)) { - delete(receivedEvent, "occurred_at") - delete(tc.event, "occurred_at") - } - - assert.Equal(t, tc.event["temperature"], receivedEvent["temperature"]) - assert.Equal(t, tc.event["humidity"], receivedEvent["humidity"]) - assert.Equal(t, tc.event["sensor_id"], receivedEvent["sensor_id"]) - assert.Equal(t, tc.event["status"], receivedEvent["status"]) - assert.Equal(t, tc.event["timestamp"], receivedEvent["timestamp"]) - assert.Equal(t, tc.event["operation"], receivedEvent["operation"]) - - default: - assert.ErrorContains(t, err, tc.err.Error()) - } - }) - } -} - -func TestPubsub(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error on flushing redis: %s", err)) - - cases := []struct { - desc string - stream string - consumer string - err error - handler events.EventHandler - }{ - { - desc: "Subscribe to a stream", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to the same stream", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to an empty stream with an empty consumer", - stream: "", - consumer: "", - err: redis.ErrEmptyStream, - handler: handler{false}, - }, - { - desc: "Subscribe to an empty stream with a valid consumer", - stream: "", - consumer: consumer, - err: redis.ErrEmptyStream, - handler: handler{false}, - }, - { - desc: "Subscribe to a valid stream with an empty consumer", - stream: fmt.Sprintf("events.%s", stream), - consumer: "", - err: redis.ErrEmptyConsumer, - handler: handler{false}, - }, - { - desc: "Subscribe to another stream", - stream: fmt.Sprintf("events.%s.%d", stream, 1), - consumer: consumer, - err: nil, - handler: handler{false}, - }, - { - desc: "Subscribe to a stream with malformed handler", - stream: fmt.Sprintf("events.%s", stream), - consumer: consumer, - err: nil, - handler: handler{true}, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - subcriber, err := redis.NewSubscriber(redisURL, logger) - if err != nil { - assert.Equal(t, err, tc.err) - - return - } - - cfg := events.SubscriberConfig{ - Stream: tc.stream, - Consumer: tc.consumer, - Handler: tc.handler, - } - switch err := subcriber.Subscribe(context.Background(), cfg); { - case err == nil: - assert.Nil(t, err) - default: - assert.Equal(t, err, tc.err) - } - - err = subcriber.Close() - assert.Nil(t, err) - }) - } -} - -func TestUnavailablePublish(t *testing.T) { - publisher, err := redis.NewPublisher(context.Background(), redisURL, stream, time.Second) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - - subcriber, err := redis.NewSubscriber(redisURL, logger) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store: %s", err)) - - cfg := events.SubscriberConfig{ - Stream: "events." + stream, - Consumer: consumer, - Handler: handler{}, - } - err = subcriber.Subscribe(context.Background(), cfg) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on subscribing to event store: %s", err)) - - err = pool.Client.PauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on pausing container: %s", err)) - - spawnGoroutines(publisher, t) - - time.Sleep(1 * time.Second) - - err = pool.Client.UnpauseContainer(container.Container.ID) - assert.Nil(t, err, fmt.Sprintf("got unexpected error on unpausing container: %s", err)) - - // Wait for the events to be published. - time.Sleep(1 * time.Second) - - err = publisher.Close() - assert.Nil(t, err, fmt.Sprintf("got unexpected error on closing publisher: %s", err)) - - var receivedEvents []map[string]interface{} - for i := 0; i < numEvents; i++ { - event := <-eventsChan - receivedEvents = append(receivedEvents, event) - } - assert.Len(t, receivedEvents, numEvents, "got unexpected number of events") -} - -func generateRandomEvent() testEvent { - return testEvent{ - Data: map[string]interface{}{ - "temperature": fmt.Sprintf("%f", rand.Float64()), - "humidity": fmt.Sprintf("%f", rand.Float64()), - "sensor_id": fmt.Sprintf("%d", rand.Intn(1000)), - "location": fmt.Sprintf("%f", rand.Float64()), - "status": fmt.Sprintf("%d", rand.Intn(1000)), - "timestamp": fmt.Sprintf("%d", time.Now().UnixNano()), - "operation": "create", - }, - } -} - -func spawnGoroutines(publisher events.Publisher, t *testing.T) { - for i := 0; i < numEvents; i++ { - go func() { - err := publisher.Publish(context.Background(), generateRandomEvent()) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - }() - } -} - -type handler struct { - fail bool -} - -func (h handler) Handle(_ context.Context, event events.Event) error { - if h.fail { - return errFailed - } - data, err := event.Encode() - if err != nil { - return err - } - - eventsChan <- data - - return nil -} diff --git a/pkg/events/redis/setup_test.go b/pkg/events/redis/setup_test.go deleted file mode 100644 index 1c98ae8cd..000000000 --- a/pkg/events/redis/setup_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package redis_test - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - "syscall" - "testing" - - "github.com/ory/dockertest/v3" - "github.com/redis/go-redis/v9" -) - -var ( - redisClient *redis.Client - redisURL string - pool *dockertest.Pool - container *dockertest.Resource -) - -func TestMain(m *testing.M) { - var err error - pool, err = dockertest.NewPool("") - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - container, err = pool.RunWithOptions(&dockertest.RunOptions{ - Repository: "redis", - Tag: "7.2.4-alpine", - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - - handleInterrupt(pool, container) - - redisURL = fmt.Sprintf("redis://localhost:%s/0", container.GetPort("6379/tcp")) - ropts, err := redis.ParseURL(redisURL) - if err != nil { - log.Fatalf("Could not parse redis URL: %s", err) - } - - if err := pool.Retry(func() error { - redisClient = redis.NewClient(ropts) - - return redisClient.Ping(context.Background()).Err() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - code := m.Run() - - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} - -func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - - go func() { - <-c - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - os.Exit(0) - }() -} diff --git a/pkg/events/redis/subscriber.go b/pkg/events/redis/subscriber.go deleted file mode 100644 index dc1f981c6..000000000 --- a/pkg/events/redis/subscriber.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package redis - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "log/slog" - - "github.com/absmach/magistrala/pkg/events" - "github.com/redis/go-redis/v9" -) - -const ( - eventsPrefix = "events." - eventCount = 100 - exists = "BUSYGROUP Consumer Group name already exists" - group = "magistrala" -) - -var _ events.Subscriber = (*subEventStore)(nil) - -var ( - // ErrEmptyStream is returned when stream name is empty. - ErrEmptyStream = errors.New("stream name cannot be empty") - - // ErrEmptyConsumer is returned when consumer name is empty. - ErrEmptyConsumer = errors.New("consumer name cannot be empty") -) - -type subEventStore struct { - client *redis.Client - logger *slog.Logger -} - -func NewSubscriber(url string, logger *slog.Logger) (events.Subscriber, error) { - opts, err := redis.ParseURL(url) - if err != nil { - return nil, err - } - - return &subEventStore{ - client: redis.NewClient(opts), - logger: logger, - }, nil -} - -func (es *subEventStore) Subscribe(ctx context.Context, cfg events.SubscriberConfig) error { - if cfg.Stream == "" { - return ErrEmptyStream - } - if cfg.Consumer == "" { - return ErrEmptyConsumer - } - - err := es.client.XGroupCreateMkStream(ctx, cfg.Stream, group, "$").Err() - if err != nil && err.Error() != exists { - return err - } - - go func() { - for { - msgs, err := es.client.XReadGroup(ctx, &redis.XReadGroupArgs{ - Group: group, - Consumer: cfg.Consumer, - Streams: []string{cfg.Stream, ">"}, - Count: eventCount, - }).Result() - if err != nil { - es.logger.Warn(fmt.Sprintf("failed to read from redis stream: %s", err)) - - continue - } - if len(msgs) == 0 { - continue - } - - es.handle(ctx, cfg.Stream, msgs[0].Messages, cfg.Handler) - } - }() - - return nil -} - -func (es *subEventStore) Close() error { - return es.client.Close() -} - -type redisEvent struct { - Data map[string]interface{} -} - -func (re redisEvent) Encode() (map[string]interface{}, error) { - return re.Data, nil -} - -func (es *subEventStore) handle(ctx context.Context, stream string, msgs []redis.XMessage, h events.EventHandler) { - for _, msg := range msgs { - var data map[string]interface{} - if err := json.Unmarshal([]byte(msg.Values["data"].(string)), &data); err != nil { - es.logger.Warn(fmt.Sprintf("failed to unmarshal redis event: %s", err)) - - return - } - - event := redisEvent{ - Data: data, - } - - if err := h.Handle(ctx, event); err != nil { - es.logger.Warn(fmt.Sprintf("failed to handle redis event: %s", err)) - - return - } - - if err := es.client.XAck(ctx, stream, group, msg.ID).Err(); err != nil { - es.logger.Warn(fmt.Sprintf("failed to ack redis event: %s", err)) - - return - } - } -} diff --git a/pkg/events/store/store_nats.go b/pkg/events/store/store_nats.go deleted file mode 100644 index dd9c2d130..000000000 --- a/pkg/events/store/store_nats.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build nats -// +build nats - -package store - -import ( - "context" - "log" - "log/slog" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/nats" -) - -// StreamAllEvents represents subject to subscribe for all the events. -const StreamAllEvents = "events.>" - -func init() { - log.Println("The binary was build using nats as the events store") -} - -func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, error) { - pb, err := nats.NewPublisher(ctx, url, stream) - if err != nil { - return nil, err - } - - return pb, nil -} - -func NewSubscriber(ctx context.Context, url string, logger *slog.Logger) (events.Subscriber, error) { - pb, err := nats.NewSubscriber(ctx, url, logger) - if err != nil { - return nil, err - } - - return pb, nil -} diff --git a/pkg/events/store/store_rabbitmq.go b/pkg/events/store/store_rabbitmq.go deleted file mode 100644 index 233ff78cb..000000000 --- a/pkg/events/store/store_rabbitmq.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build rabbitmq -// +build rabbitmq - -package store - -import ( - "context" - "log" - "log/slog" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/rabbitmq" -) - -// StreamAllEvents represents subject to subscribe for all the events. -const StreamAllEvents = "events.#" - -func init() { - log.Println("The binary was build using rabbitmq as the events store") -} - -func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, error) { - pb, err := rabbitmq.NewPublisher(ctx, url, stream) - if err != nil { - return nil, err - } - - return pb, nil -} - -func NewSubscriber(_ context.Context, url string, logger *slog.Logger) (events.Subscriber, error) { - pb, err := rabbitmq.NewSubscriber(url, logger) - if err != nil { - return nil, err - } - - return pb, nil -} diff --git a/pkg/events/store/store_redis.go b/pkg/events/store/store_redis.go deleted file mode 100644 index 12241c487..000000000 --- a/pkg/events/store/store_redis.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !nats && !rabbitmq -// +build !nats,!rabbitmq - -package store - -import ( - "context" - "log" - "log/slog" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/redis" -) - -// StreamAllEvents represents subject to subscribe for all the events. -const StreamAllEvents = ">" - -func init() { - log.Println("The binary was build using redis as the events store") -} - -func NewPublisher(ctx context.Context, url, stream string) (events.Publisher, error) { - pb, err := redis.NewPublisher(ctx, url, stream, events.UnpublishedEventsCheckInterval) - if err != nil { - return nil, err - } - - return pb, nil -} - -func NewSubscriber(_ context.Context, url string, logger *slog.Logger) (events.Subscriber, error) { - pb, err := redis.NewSubscriber(url, logger) - if err != nil { - return nil, err - } - - return pb, nil -} diff --git a/pkg/groups/doc.go b/pkg/groups/doc.go deleted file mode 100644 index 55e0840d6..000000000 --- a/pkg/groups/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package groups contains the domain concept definitions needed to support -// Magistrala groups functionality. -package groups diff --git a/pkg/groups/errors.go b/pkg/groups/errors.go deleted file mode 100644 index b6665fa0b..000000000 --- a/pkg/groups/errors.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package groups - -import "errors" - -var ( - // ErrInvalidStatus indicates invalid status. - ErrInvalidStatus = errors.New("invalid groups status") - - // ErrEnableGroup indicates error in enabling group. - ErrEnableGroup = errors.New("failed to enable group") - - // ErrDisableGroup indicates error in disabling group. - ErrDisableGroup = errors.New("failed to disable group") -) diff --git a/pkg/groups/groups.go b/pkg/groups/groups.go deleted file mode 100644 index 8719424cf..000000000 --- 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/doc.go b/pkg/groups/mocks/doc.go deleted file mode 100644 index 16ed198af..000000000 --- a/pkg/groups/mocks/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mocks contains mocks for testing purposes. -package mocks diff --git a/pkg/groups/mocks/repository.go b/pkg/groups/mocks/repository.go deleted file mode 100644 index 918b852cb..000000000 --- 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 9fd141891..000000000 --- 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 e49ec6690..000000000 --- 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/groups/status.go b/pkg/groups/status.go deleted file mode 100644 index 273dbdc73..000000000 --- a/pkg/groups/status.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package groups - -import ( - "encoding/json" - "strings" - - svcerr "github.com/absmach/magistrala/pkg/errors/service" -) - -// Status represents User status. -type Status uint8 - -// Possible User status values. -const ( - // EnabledStatus represents enabled User. - EnabledStatus Status = iota - // DisabledStatus represents disabled User. - DisabledStatus - // DeletedStatus represents a user that will be deleted. - DeletedStatus - - // AllStatus is used for querying purposes to list users irrespective - // of their status - both enabled and disabled. It is never stored in the - // database as the actual User status and should always be the largest - // value in this enumeration. - AllStatus -) - -// String representation of the possible status values. -const ( - Disabled = "disabled" - Enabled = "enabled" - Deleted = "deleted" - All = "all" - Unknown = "unknown" -) - -// String converts user/group status to string literal. -func (s Status) String() string { - switch s { - case DisabledStatus: - return Disabled - case EnabledStatus: - return Enabled - case DeletedStatus: - return Deleted - case AllStatus: - return All - default: - return Unknown - } -} - -// ToStatus converts string value to a valid User/Group status. -func ToStatus(status string) (Status, error) { - switch status { - case "", Enabled: - return EnabledStatus, nil - case Disabled: - return DisabledStatus, nil - case Deleted: - return DeletedStatus, nil - case All: - return AllStatus, nil - } - return Status(0), svcerr.ErrInvalidStatus -} - -// Custom Marshaller for Uesr/Groups. -func (s Status) MarshalJSON() ([]byte, error) { - return json.Marshal(s.String()) -} - -// Custom Unmarshaler for User/Groups. -func (s *Status) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToStatus(str) - *s = val - return err -} diff --git a/pkg/grpcclient/client.go b/pkg/grpcclient/client.go deleted file mode 100644 index 5c2957111..000000000 --- a/pkg/grpcclient/client.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -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" - grpchealth "google.golang.org/grpc/health/grpc_health_v1" -) - -// SetupTokenClient loads auth services token gRPC configuration and creates new Token services gRPC client. -// -// For example: -// -// tokenClient, tokenHandler, err := grpcclient.SetupTokenClient(ctx, grpcclient.Config{}). -func SetupTokenClient(ctx context.Context, cfg Config) (magistrala.TokenServiceClient, 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 tokengrpc.NewTokenClient(client.Connection(), cfg.Timeout), client, nil -} - -// SetupDomiansClient loads domains gRPC configuration and creates a new domains gRPC client. -// -// For example: -// -// domainsClient, domainsHandler, err := grpcclient.SetupDomainsClient(ctx, grpcclient.Config{}). -func SetupDomainsClient(ctx context.Context, cfg Config) (magistrala.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 -} - -// SetupThingsClient loads things gRPC configuration and creates new things gRPC client. -// -// For example: -// -// thingClient, thingHandler, err := grpcclient.SetupThings(ctx, grpcclient.Config{}). -func SetupThingsClient(ctx context.Context, cfg Config) (magistrala.ThingsServiceClient, 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 thingsauth.NewClient(client.Connection(), cfg.Timeout), client, nil -} diff --git a/pkg/grpcclient/client_test.go b/pkg/grpcclient/client_test.go deleted file mode 100644 index acc0ebbe3..000000000 --- a/pkg/grpcclient/client_test.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpcclient_test - -import ( - "context" - "fmt" - "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" - 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" -) - -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))) - } - gs := grpcserver.NewServer(ctx, cancel, "auth", server.Config{Port: "12345"}, registerAuthServiceServer, mglog.NewMock()) - go func() { - err := gs.Start() - assert.Nil(t, err, fmt.Sprintf(`"Unexpected error creating server %s"`, err)) - }() - defer func() { - err := gs.Stop() - assert.Nil(t, err, fmt.Sprintf(`"Unexpected error stopping server %s"`, err)) - }() - - cases := []struct { - desc string - config grpcclient.Config - err error - }{ - { - desc: "successful", - config: grpcclient.Config{ - URL: "localhost:12345", - Timeout: time.Second, - }, - err: nil, - }, - { - desc: "failed with empty URL", - config: grpcclient.Config{ - URL: "", - Timeout: time.Second, - }, - err: errors.New("service is not serving"), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - client, handler, err := grpcclient.SetupTokenClient(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) - assert.NotNil(t, handler) - } - }) - } -} - -func TestSetupThingsClient(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - registerThingsServiceServer := func(srv *grpc.Server) { - magistrala.RegisterThingsServiceServer(srv, thingsgrpcapi.NewServer(new(thmocks.Service))) - } - gs := grpcserver.NewServer(ctx, cancel, "things", server.Config{Port: "12345"}, registerThingsServiceServer, mglog.NewMock()) - go func() { - err := gs.Start() - assert.Nil(t, err, fmt.Sprintf(`"Unexpected error creating server %s"`, err)) - }() - defer func() { - err := gs.Stop() - assert.Nil(t, err, fmt.Sprintf(`"Unexpected error stopping server %s"`, err)) - }() - - cases := []struct { - desc string - config grpcclient.Config - err error - }{ - { - desc: "successful", - config: grpcclient.Config{ - URL: "localhost:12345", - Timeout: time.Second, - }, - err: nil, - }, - { - desc: "failed with empty URL", - config: grpcclient.Config{ - URL: "", - Timeout: time.Second, - }, - err: errors.New("service is not serving"), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - client, handler, err := grpcclient.SetupThingsClient(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) - assert.NotNil(t, handler) - } - }) - } -} - -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))) - } - gs := grpcserver.NewServer(ctx, cancel, "auth", server.Config{Port: "12345"}, registerDomainsServiceServer, mglog.NewMock()) - go func() { - err := gs.Start() - assert.Nil(t, err, fmt.Sprintf("Unexpected error creating server %s", err)) - }() - defer func() { - err := gs.Stop() - assert.Nil(t, err, fmt.Sprintf("Unexpected error stopping server %s", err)) - }() - - cases := []struct { - desc string - config grpcclient.Config - err error - }{ - { - desc: "successfully", - config: grpcclient.Config{ - URL: "localhost:12345", - Timeout: time.Second, - }, - err: nil, - }, - { - desc: "failed with empty URL", - config: grpcclient.Config{ - URL: "", - Timeout: time.Second, - }, - err: errors.New("service is not serving"), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - client, handler, err := grpcclient.SetupDomainsClient(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) - assert.NotNil(t, handler) - } - }) - } -} diff --git a/pkg/grpcclient/connect.go b/pkg/grpcclient/connect.go deleted file mode 100644 index e8678ed1b..000000000 --- a/pkg/grpcclient/connect.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpcclient - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "os" - "time" - - "github.com/absmach/magistrala/pkg/errors" - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" -) - -type security int - -const ( - withoutTLS security = iota - withTLS - withmTLS -) -const buffSize = 10 * 1024 * 1024 - -var ( - errGrpcConnect = errors.New("failed to connect to grpc server") - errGrpcClose = errors.New("failed to close grpc connection") - ErrSvcNotServing = errors.New("service is not serving") -) - -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:""` -} - -// Handler is used to handle gRPC connection. -type Handler interface { - // Close closes gRPC connection. - Close() error - - // Secure is used for pretty printing TLS info. - Secure() string - - // Connection returns the gRPC connection. - Connection() *grpc.ClientConn -} - -type client struct { - *grpc.ClientConn - cfg Config - secure security -} - -var _ Handler = (*client)(nil) - -func NewHandler(cfg Config) (Handler, error) { - conn, secure, err := connect(cfg) - if err != nil { - return nil, err - } - - return &client{ - ClientConn: conn, - cfg: cfg, - secure: secure, - }, nil -} - -func (c *client) Close() error { - if err := c.ClientConn.Close(); err != nil { - return errors.Wrap(errGrpcClose, err) - } - - return nil -} - -func (c *client) Connection() *grpc.ClientConn { - return c.ClientConn -} - -// Secure is used for pretty printing TLS info. -func (c *client) Secure() string { - switch c.secure { - case withTLS: - return "with TLS" - case withmTLS: - return "with mTLS" - case withoutTLS: - fallthrough - default: - return "without TLS" - } -} - -// connect creates new gRPC client and connect to gRPC server. -func connect(cfg Config) (*grpc.ClientConn, security, error) { - opts := []grpc.DialOption{ - grpc.WithStatsHandler(otelgrpc.NewClientHandler()), - } - secure := withoutTLS - tc := insecure.NewCredentials() - - if cfg.ServerCAFile != "" { - tlsConfig := &tls.Config{} - - // Loading root ca certificates file - rootCA, err := os.ReadFile(cfg.ServerCAFile) - if err != nil { - return nil, secure, fmt.Errorf("failed to load root ca file: %w", err) - } - if len(rootCA) > 0 { - capool := x509.NewCertPool() - if !capool.AppendCertsFromPEM(rootCA) { - return nil, secure, fmt.Errorf("failed to append root ca to tls.Config") - } - tlsConfig.RootCAs = capool - secure = withTLS - } - - // Loading mtls certificates file - if cfg.ClientCert != "" || cfg.ClientKey != "" { - certificate, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey) - if err != nil { - return nil, secure, fmt.Errorf("failed to client certificate and key %w", err) - } - tlsConfig.Certificates = []tls.Certificate{certificate} - secure = withmTLS - } - - tc = credentials.NewTLS(tlsConfig) - } - - opts = append( - opts, grpc.WithTransportCredentials(tc), - grpc.WithReadBufferSize(buffSize), - grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(buffSize/10), grpc.MaxCallSendMsgSize(buffSize/10)), - grpc.WithWriteBufferSize(buffSize), - ) - - conn, err := grpc.NewClient(cfg.URL, opts...) - if err != nil { - return nil, secure, errors.Wrap(errGrpcConnect, err) - } - - return conn, secure, nil -} diff --git a/pkg/grpcclient/connect_test.go b/pkg/grpcclient/connect_test.go deleted file mode 100644 index 4f5e3045d..000000000 --- a/pkg/grpcclient/connect_test.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpcclient - -import ( - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" -) - -func TestHandler(t *testing.T) { - cases := []struct { - desc string - config Config - err error - secure string - }{ - { - desc: "successful without TLS", - config: Config{ - URL: "localhost:8080", - Timeout: time.Second, - }, - err: nil, - secure: "without TLS", - }, - { - desc: "successful with TLS", - config: Config{ - URL: "localhost:8080", - Timeout: time.Second, - ServerCAFile: "../../docker/ssl/certs/ca.crt", - }, - err: nil, - secure: "with TLS", - }, - { - desc: "successful with mTLS", - config: Config{ - URL: "localhost:8080", - Timeout: time.Second, - ClientCert: "../../docker/ssl/certs/magistrala-server.crt", - ClientKey: "../../docker/ssl/certs/magistrala-server.key", - ServerCAFile: "../../docker/ssl/certs/ca.crt", - }, - err: nil, - secure: "with mTLS", - }, - { - desc: "failed with empty URL", - config: Config{ - URL: "", - Timeout: time.Second, - }, - secure: "without TLS", - }, - { - desc: "failed with invalid server CA file", - config: Config{ - URL: "localhost:8080", - Timeout: time.Second, - ServerCAFile: "invalid", - }, - err: errors.New("failed to load root ca file: open invalid: no such file or directory"), - }, - { - desc: "failed with invalid server CA file as cert key", - config: Config{ - URL: "localhost:8080", - Timeout: time.Second, - ServerCAFile: "../../docker/ssl/certs/magistrala-server.key", - }, - err: errors.New("failed to append root ca to tls.Config"), - }, - { - desc: "failed with invalid client cert", - config: Config{ - URL: "localhost:8080", - Timeout: time.Second, - ClientCert: "invalid", - ClientKey: "../../docker/ssl/certs/magistrala-server.key", - ServerCAFile: "../../docker/ssl/certs/ca.crt", - }, - err: errors.New("failed to client certificate and key open invalid: no such file or directory"), - }, - { - desc: "failed with invalid client key", - config: Config{ - URL: "localhost:8080", - Timeout: time.Second, - ClientCert: "../../docker/ssl/certs/magistrala-server.crt", - ClientKey: "invalid", - ServerCAFile: "../../docker/ssl/certs/ca.crt", - }, - err: errors.New("failed to client certificate and key open invalid: no such file or directory"), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - handler, err := NewHandler(c.config) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s", err, c.err)) - if err == nil { - assert.Equal(t, c.secure, handler.Secure()) - assert.NotNil(t, handler.Connection()) - assert.Nil(t, handler.Close()) - } - }) - } -} diff --git a/pkg/grpcclient/doc.go b/pkg/grpcclient/doc.go deleted file mode 100644 index 1d9ce2fe5..000000000 --- a/pkg/grpcclient/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package auth contains the domain concept definitions needed to support -// Magistrala auth functionality. -package grpcclient diff --git a/pkg/jaeger/doc.go b/pkg/jaeger/doc.go deleted file mode 100644 index 54eb78e67..000000000 --- a/pkg/jaeger/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package jaeger contains the domain concept definitions needed to support -// Magistrala Jaeger tracing functionality. -package jaeger diff --git a/pkg/jaeger/provider.go b/pkg/jaeger/provider.go deleted file mode 100644 index 436c6b2cd..000000000 --- a/pkg/jaeger/provider.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package jaeger - -import ( - "context" - "errors" - "net/url" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/resource" - "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.21.0" -) - -var ( - errNoURL = errors.New("URL is empty") - errNoSvcName = errors.New("service Name is empty") - errUnsupportedTraceURLScheme = errors.New("unsupported tracing url scheme") -) - -// NewProvider initializes Jaeger TraceProvider. -// -// tp, err := jaeger.NewProvider(ctx, "demo-service", "http://localhost:14268/api/traces", "2cb32911-6833-469c-9cad-4d3e93c528d8", "1.0") -func NewProvider(ctx context.Context, svcName string, jaegerUrl url.URL, instanceID string, fraction float64) (*trace.TracerProvider, error) { - if jaegerUrl == (url.URL{}) { - return nil, errNoURL - } - - if svcName == "" { - return nil, errNoSvcName - } - - var client otlptrace.Client - switch jaegerUrl.Scheme { - case "http": - client = otlptracehttp.NewClient(otlptracehttp.WithEndpoint(jaegerUrl.Host), otlptracehttp.WithURLPath(jaegerUrl.Path), otlptracehttp.WithInsecure()) - case "https": - client = otlptracehttp.NewClient(otlptracehttp.WithEndpoint(jaegerUrl.Host), otlptracehttp.WithURLPath(jaegerUrl.Path)) - default: - return nil, errUnsupportedTraceURLScheme - } - - exporter, err := otlptrace.New(ctx, client) - if err != nil { - return nil, err - } - - attributes := []attribute.KeyValue{ - semconv.ServiceNameKey.String(svcName), - attribute.String("host.id", instanceID), - } - - hostAttr, err := resource.New(ctx, resource.WithHost(), resource.WithOSDescription(), resource.WithContainer()) - if err != nil { - return nil, err - } - attributes = append(attributes, hostAttr.Attributes()...) - - tp := trace.NewTracerProvider( - trace.WithSampler(trace.TraceIDRatioBased(fraction)), - trace.WithBatcher(exporter), - trace.WithResource(resource.NewWithAttributes( - semconv.SchemaURL, - attributes..., - )), - ) - otel.SetTracerProvider(tp) - otel.SetTextMapPropagator(propagation.TraceContext{}) - - return tp, nil -} diff --git a/pkg/messaging/README.md b/pkg/messaging/README.md deleted file mode 100644 index f8b07f8eb..000000000 --- a/pkg/messaging/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Messaging - -`messaging` package defines `Publisher`, `Subscriber` and an aggregate `Pubsub` interface. - -`Subscriber` interface defines methods used to subscribe to a message broker such as MQTT or NATS or RabbitMQ. - -`Publisher` interface defines methods used to publish messages to a message broker such as MQTT or NATS or RabbitMQ. - -`Pubsub` interface is composed of `Publisher` and `Subscriber` interface and can be used to send messages to as well as to receive messages from a message broker. diff --git a/pkg/messaging/brokers/brokers_nats.go b/pkg/messaging/brokers/brokers_nats.go deleted file mode 100644 index 1cc25ffe4..000000000 --- a/pkg/messaging/brokers/brokers_nats.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !rabbitmq -// +build !rabbitmq - -package brokers - -import ( - "context" - "log" - "log/slog" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/nats" -) - -// SubjectAllChannels represents subject to subscribe for all the channels. -const SubjectAllChannels = "channels.>" - -func init() { - log.Println("The binary was build using Nats as the message broker") -} - -func NewPublisher(ctx context.Context, url string, opts ...messaging.Option) (messaging.Publisher, error) { - pb, err := nats.NewPublisher(ctx, url, opts...) - if err != nil { - return nil, err - } - - return pb, nil -} - -func NewPubSub(ctx context.Context, url string, logger *slog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { - pb, err := nats.NewPubSub(ctx, url, logger, opts...) - if err != nil { - return nil, err - } - - return pb, nil -} diff --git a/pkg/messaging/brokers/brokers_rabbitmq.go b/pkg/messaging/brokers/brokers_rabbitmq.go deleted file mode 100644 index 4ccaec613..000000000 --- a/pkg/messaging/brokers/brokers_rabbitmq.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build rabbitmq -// +build rabbitmq - -package brokers - -import ( - "context" - "log" - "log/slog" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/rabbitmq" -) - -// SubjectAllChannels represents subject to subscribe for all the channels. -const SubjectAllChannels = "channels.#" - -func init() { - log.Println("The binary was build using RabbitMQ as the message broker") -} - -func NewPublisher(_ context.Context, url string, opts ...messaging.Option) (messaging.Publisher, error) { - pb, err := rabbitmq.NewPublisher(url, opts...) - if err != nil { - return nil, err - } - - return pb, nil -} - -func NewPubSub(_ context.Context, url string, logger *slog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { - pb, err := rabbitmq.NewPubSub(url, logger, opts...) - if err != nil { - return nil, err - } - - return pb, nil -} diff --git a/pkg/messaging/brokers/tracing/brokers_nats.go b/pkg/messaging/brokers/tracing/brokers_nats.go deleted file mode 100644 index 608a9f3a4..000000000 --- a/pkg/messaging/brokers/tracing/brokers_nats.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !rabbitmq -// +build !rabbitmq - -package brokers - -import ( - "log" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/nats/tracing" - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/otel/trace" -) - -// SubjectAllChannels represents subject to subscribe for all the channels. -const SubjectAllChannels = "channels.>" - -func init() { - log.Println("The binary was build using Nats as the message broker") -} - -func NewPublisher(cfg server.Config, tracer trace.Tracer, publisher messaging.Publisher) messaging.Publisher { - return tracing.NewPublisher(cfg, tracer, publisher) -} - -func NewPubSub(cfg server.Config, tracer trace.Tracer, pubsub messaging.PubSub) messaging.PubSub { - return tracing.NewPubSub(cfg, tracer, pubsub) -} diff --git a/pkg/messaging/brokers/tracing/brokers_rabbitmq.go b/pkg/messaging/brokers/tracing/brokers_rabbitmq.go deleted file mode 100644 index c3d07acbf..000000000 --- a/pkg/messaging/brokers/tracing/brokers_rabbitmq.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build rabbitmq -// +build rabbitmq - -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package brokers - -import ( - "log" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/rabbitmq/tracing" - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/otel/trace" -) - -// SubjectAllChannels represents subject to subscribe for all the channels. -const SubjectAllChannels = "channels.#" - -func init() { - log.Println("The binary was build using RabbitMQ as the message broker") -} - -func NewPublisher(cfg server.Config, tracer trace.Tracer, pub messaging.Publisher) messaging.Publisher { - return tracing.NewPublisher(cfg, tracer, pub) -} - -func NewPubSub(cfg server.Config, tracer trace.Tracer, pubsub messaging.PubSub) messaging.PubSub { - return tracing.NewPubSub(cfg, tracer, pubsub) -} diff --git a/pkg/messaging/handler/logging.go b/pkg/messaging/handler/logging.go deleted file mode 100644 index ed379aa27..000000000 --- a/pkg/messaging/handler/logging.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package handler - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/mgate/pkg/session" -) - -var _ session.Handler = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc session.Handler -} - -// AuthConnect implements session.Handler. -func (lm *loggingMiddleware) AuthConnect(ctx context.Context) (err error) { - defer lm.logAction("AuthConnect", nil, time.Now(), err) - return lm.svc.AuthConnect(ctx) -} - -// AuthPublish implements session.Handler. -func (lm *loggingMiddleware) AuthPublish(ctx context.Context, topic *string, payload *[]byte) (err error) { - defer lm.logAction("AuthPublish", &[]string{*topic}, time.Now(), err) - return lm.svc.AuthPublish(ctx, topic, payload) -} - -// AuthSubscribe implements session.Handler. -func (lm *loggingMiddleware) AuthSubscribe(ctx context.Context, topics *[]string) (err error) { - defer lm.logAction("AuthSubscribe", topics, time.Now(), err) - return lm.svc.AuthSubscribe(ctx, topics) -} - -// Connect implements session.Handler. -func (lm *loggingMiddleware) Connect(ctx context.Context) (err error) { - defer lm.logAction("Connect", nil, time.Now(), err) - return lm.svc.Connect(ctx) -} - -// Disconnect implements session.Handler. -func (lm *loggingMiddleware) Disconnect(ctx context.Context) (err error) { - defer lm.logAction("Disconnect", nil, time.Now(), err) - return lm.svc.Disconnect(ctx) -} - -// Publish logs the publish request. It logs the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) Publish(ctx context.Context, topic *string, payload *[]byte) (err error) { - defer lm.logAction("Publish", &[]string{*topic}, time.Now(), err) - return lm.svc.Publish(ctx, topic, payload) -} - -// Subscribe implements session.Handler. -func (lm *loggingMiddleware) Subscribe(ctx context.Context, topics *[]string) (err error) { - defer lm.logAction("Subscribe", topics, time.Now(), err) - return lm.svc.Subscribe(ctx, topics) -} - -// Unsubscribe implements session.Handler. -func (lm *loggingMiddleware) Unsubscribe(ctx context.Context, topics *[]string) (err error) { - defer lm.logAction("Unsubscribe", topics, time.Now(), err) - return lm.svc.Unsubscribe(ctx, topics) -} - -// LoggingMiddleware adds logging facilities to the adapter. -func LoggingMiddleware(svc session.Handler, logger *slog.Logger) session.Handler { - return &loggingMiddleware{logger, svc} -} - -func (lm *loggingMiddleware) logAction(action string, topics *[]string, t time.Time, err error) { - args := []any{ - slog.String("duration", time.Since(t).String()), - } - if topics != nil { - args = append(args, slog.Any("topics", *topics)) - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn(action+" failed", args...) - return - } - lm.logger.Info(action+" completed successfully", args...) -} diff --git a/pkg/messaging/handler/metrics.go b/pkg/messaging/handler/metrics.go deleted file mode 100644 index b92834097..000000000 --- a/pkg/messaging/handler/metrics.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package handler - -import ( - "context" - "time" - - "github.com/absmach/mgate/pkg/session" - "github.com/go-kit/kit/metrics" -) - -var _ session.Handler = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc session.Handler -} - -// MetricsMiddleware instruments adapter by tracking request count and latency. -func MetricsMiddleware(svc session.Handler, counter metrics.Counter, latency metrics.Histogram) session.Handler { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -// AuthConnect implements session.Handler. -func (mm *metricsMiddleware) AuthConnect(ctx context.Context) error { - defer func(begin time.Time) { - mm.counter.With("method", "publish").Add(1) - mm.latency.With("method", "publish").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.AuthConnect(ctx) -} - -// AuthPublish implements session.Handler. -func (mm *metricsMiddleware) AuthPublish(ctx context.Context, topic *string, payload *[]byte) error { - defer func(begin time.Time) { - mm.counter.With("method", "publish").Add(1) - mm.latency.With("method", "publish").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.AuthPublish(ctx, topic, payload) -} - -// AuthSubscribe implements session.Handler. -func (*metricsMiddleware) AuthSubscribe(ctx context.Context, topics *[]string) error { - return nil -} - -// Connect implements session.Handler. -func (*metricsMiddleware) Connect(ctx context.Context) error { - return nil -} - -// Disconnect implements session.Handler. -func (*metricsMiddleware) Disconnect(ctx context.Context) error { - return nil -} - -// Publish instruments Publish method with metrics. -func (mm *metricsMiddleware) Publish(ctx context.Context, topic *string, payload *[]byte) error { - defer func(begin time.Time) { - mm.counter.With("method", "publish").Add(1) - mm.latency.With("method", "publish").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.Publish(ctx, topic, payload) -} - -// Subscribe implements session.Handler. -func (*metricsMiddleware) Subscribe(ctx context.Context, topics *[]string) error { - return nil -} - -// Unsubscribe implements session.Handler. -func (*metricsMiddleware) Unsubscribe(ctx context.Context, topics *[]string) error { - return nil -} diff --git a/pkg/messaging/handler/tracing.go b/pkg/messaging/handler/tracing.go deleted file mode 100644 index 5069180a1..000000000 --- a/pkg/messaging/handler/tracing.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package handler - -import ( - "context" - - "github.com/absmach/mgate/pkg/session" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -const ( - authConnectOP = "auth_connect_op" - authPublishOP = "auth_publish_op" - authSubscribeOP = "auth_subscribe_op" - connectOP = "connect_op" - disconnectOP = "disconnect_op" - subscribeOP = "subscribe_op" - unsubscribeOP = "unsubscribe_op" - publishOP = "publish_op" -) - -var _ session.Handler = (*handlerMiddleware)(nil) - -type handlerMiddleware struct { - handler session.Handler - tracer trace.Tracer -} - -// NewHandler creates a new session.Handler middleware with tracing. -func NewTracing(tracer trace.Tracer, handler session.Handler) session.Handler { - return &handlerMiddleware{ - tracer: tracer, - handler: handler, - } -} - -// AuthConnect traces auth connect operations. -func (h *handlerMiddleware) AuthConnect(ctx context.Context) error { - kvOpts := []attribute.KeyValue{} - s, ok := session.FromContext(ctx) - if ok { - kvOpts = append(kvOpts, attribute.String("client_id", s.ID)) - kvOpts = append(kvOpts, attribute.String("username", s.Username)) - } - ctx, span := h.tracer.Start(ctx, authConnectOP, trace.WithAttributes(kvOpts...)) - defer span.End() - return h.handler.AuthConnect(ctx) -} - -// AuthPublish traces auth publish operations. -func (h *handlerMiddleware) AuthPublish(ctx context.Context, topic *string, payload *[]byte) error { - kvOpts := []attribute.KeyValue{} - s, ok := session.FromContext(ctx) - if ok { - kvOpts = append(kvOpts, attribute.String("client_id", s.ID)) - if topic != nil { - kvOpts = append(kvOpts, attribute.String("topic", *topic)) - } - } - ctx, span := h.tracer.Start(ctx, authPublishOP, trace.WithAttributes(kvOpts...)) - defer span.End() - return h.handler.AuthPublish(ctx, topic, payload) -} - -// AuthSubscribe traces auth subscribe operations. -func (h *handlerMiddleware) AuthSubscribe(ctx context.Context, topics *[]string) error { - kvOpts := []attribute.KeyValue{} - s, ok := session.FromContext(ctx) - if ok { - kvOpts = append(kvOpts, attribute.String("client_id", s.ID)) - if topics != nil { - kvOpts = append(kvOpts, attribute.StringSlice("topics", *topics)) - } - } - ctx, span := h.tracer.Start(ctx, authSubscribeOP, trace.WithAttributes(kvOpts...)) - defer span.End() - return h.handler.AuthSubscribe(ctx, topics) -} - -// Connect traces connect operations. -func (h *handlerMiddleware) Connect(ctx context.Context) error { - ctx, span := h.tracer.Start(ctx, connectOP) - defer span.End() - return h.handler.Connect(ctx) -} - -// Disconnect traces disconnect operations. -func (h *handlerMiddleware) Disconnect(ctx context.Context) error { - ctx, span := h.tracer.Start(ctx, disconnectOP) - defer span.End() - return h.handler.Disconnect(ctx) -} - -// Publish traces publish operations. -func (h *handlerMiddleware) Publish(ctx context.Context, topic *string, payload *[]byte) error { - ctx, span := h.tracer.Start(ctx, publishOP) - defer span.End() - return h.handler.Publish(ctx, topic, payload) -} - -// Subscribe traces subscribe operations. -func (h *handlerMiddleware) Subscribe(ctx context.Context, topics *[]string) error { - ctx, span := h.tracer.Start(ctx, subscribeOP) - defer span.End() - return h.handler.Subscribe(ctx, topics) -} - -// Unsubscribe traces unsubscribe operations. -func (h *handlerMiddleware) Unsubscribe(ctx context.Context, topics *[]string) error { - ctx, span := h.tracer.Start(ctx, unsubscribeOP) - defer span.End() - return h.handler.Unsubscribe(ctx, topics) -} diff --git a/pkg/messaging/message.pb.go b/pkg/messaging/message.pb.go deleted file mode 100644 index 804b02e7d..000000000 --- a/pkg/messaging/message.pb.go +++ /dev/null @@ -1,195 +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: pkg/messaging/message.proto - -package messaging - -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) -) - -// Message represents a message emitted by the Magistrala adapters layer. -type Message struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Channel string `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` - Subtopic string `protobuf:"bytes,2,opt,name=subtopic,proto3" json:"subtopic,omitempty"` - Publisher string `protobuf:"bytes,3,opt,name=publisher,proto3" json:"publisher,omitempty"` - Protocol string `protobuf:"bytes,4,opt,name=protocol,proto3" json:"protocol,omitempty"` - Payload []byte `protobuf:"bytes,5,opt,name=payload,proto3" json:"payload,omitempty"` - Created int64 `protobuf:"varint,6,opt,name=created,proto3" json:"created,omitempty"` // Unix timestamp in nanoseconds -} - -func (x *Message) Reset() { - *x = Message{} - if protoimpl.UnsafeEnabled { - mi := &file_pkg_messaging_message_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Message) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Message) ProtoMessage() {} - -func (x *Message) ProtoReflect() protoreflect.Message { - mi := &file_pkg_messaging_message_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 Message.ProtoReflect.Descriptor instead. -func (*Message) Descriptor() ([]byte, []int) { - return file_pkg_messaging_message_proto_rawDescGZIP(), []int{0} -} - -func (x *Message) GetChannel() string { - if x != nil { - return x.Channel - } - return "" -} - -func (x *Message) GetSubtopic() string { - if x != nil { - return x.Subtopic - } - return "" -} - -func (x *Message) GetPublisher() string { - if x != nil { - return x.Publisher - } - return "" -} - -func (x *Message) GetProtocol() string { - if x != nil { - return x.Protocol - } - return "" -} - -func (x *Message) GetPayload() []byte { - if x != nil { - return x.Payload - } - return nil -} - -func (x *Message) GetCreated() int64 { - if x != nil { - return x.Created - } - return 0 -} - -var File_pkg_messaging_message_proto protoreflect.FileDescriptor - -var file_pkg_messaging_message_proto_rawDesc = []byte{ - 0x0a, 0x1b, 0x70, 0x6b, 0x67, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x2f, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x22, 0xad, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, - 0x0a, 0x08, 0x73, 0x75, 0x62, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x73, 0x75, 0x62, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x18, - 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_pkg_messaging_message_proto_rawDescOnce sync.Once - file_pkg_messaging_message_proto_rawDescData = file_pkg_messaging_message_proto_rawDesc -) - -func file_pkg_messaging_message_proto_rawDescGZIP() []byte { - file_pkg_messaging_message_proto_rawDescOnce.Do(func() { - file_pkg_messaging_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_messaging_message_proto_rawDescData) - }) - return file_pkg_messaging_message_proto_rawDescData -} - -var file_pkg_messaging_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_pkg_messaging_message_proto_goTypes = []any{ - (*Message)(nil), // 0: messaging.Message -} -var file_pkg_messaging_message_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] 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_pkg_messaging_message_proto_init() } -func file_pkg_messaging_message_proto_init() { - if File_pkg_messaging_message_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_pkg_messaging_message_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Message); 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_pkg_messaging_message_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_pkg_messaging_message_proto_goTypes, - DependencyIndexes: file_pkg_messaging_message_proto_depIdxs, - MessageInfos: file_pkg_messaging_message_proto_msgTypes, - }.Build() - File_pkg_messaging_message_proto = out.File - file_pkg_messaging_message_proto_rawDesc = nil - file_pkg_messaging_message_proto_goTypes = nil - file_pkg_messaging_message_proto_depIdxs = nil -} diff --git a/pkg/messaging/message.proto b/pkg/messaging/message.proto deleted file mode 100644 index c1b13b06f..000000000 --- a/pkg/messaging/message.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -syntax = "proto3"; -package messaging; - -option go_package = "./messaging"; - -// Message represents a message emitted by the Magistrala adapters layer. -message Message { - string channel = 1; - string subtopic = 2; - string publisher = 3; - string protocol = 4; - bytes payload = 5; - int64 created = 6; // Unix timestamp in nanoseconds -} diff --git a/pkg/messaging/mocks/pubsub.go b/pkg/messaging/mocks/pubsub.go deleted file mode 100644 index daa32f8e0..000000000 --- a/pkg/messaging/mocks/pubsub.go +++ /dev/null @@ -1,103 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - messaging "github.com/absmach/magistrala/pkg/messaging" - mock "github.com/stretchr/testify/mock" -) - -// PubSub is an autogenerated mock type for the PubSub type -type PubSub struct { - mock.Mock -} - -// Close provides a mock function with given fields: -func (_m *PubSub) Close() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Close") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Publish provides a mock function with given fields: ctx, topic, msg -func (_m *PubSub) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - ret := _m.Called(ctx, topic, msg) - - if len(ret) == 0 { - panic("no return value specified for Publish") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, *messaging.Message) error); ok { - r0 = rf(ctx, topic, msg) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Subscribe provides a mock function with given fields: ctx, cfg -func (_m *PubSub) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) error { - ret := _m.Called(ctx, cfg) - - if len(ret) == 0 { - panic("no return value specified for Subscribe") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, messaging.SubscriberConfig) error); ok { - r0 = rf(ctx, cfg) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Unsubscribe provides a mock function with given fields: ctx, id, topic -func (_m *PubSub) Unsubscribe(ctx context.Context, id string, topic string) error { - ret := _m.Called(ctx, id, topic) - - if len(ret) == 0 { - panic("no return value specified for Unsubscribe") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, id, topic) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewPubSub creates a new instance of PubSub. 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 NewPubSub(t interface { - mock.TestingT - Cleanup(func()) -}) *PubSub { - mock := &PubSub{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/messaging/mqtt/docs.go b/pkg/messaging/mqtt/docs.go deleted file mode 100644 index f799242b2..000000000 --- a/pkg/messaging/mqtt/docs.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mqtt hold the implementation of the Publisher and PubSub -// interfaces for the MQTT messaging system, the internal messaging -// broker of the Magistrala IoT platform. Due to the practical requirements -// implementation Publisher is created alongside PubSub. The reason for -// this is that Subscriber implementation of MQTT brings the burden of -// additional struct fields which are not used by Publisher. Subscriber -// is not implemented separately because PubSub can be used where Subscriber is needed. -package mqtt diff --git a/pkg/messaging/mqtt/publisher.go b/pkg/messaging/mqtt/publisher.go deleted file mode 100644 index 1a2308bac..000000000 --- a/pkg/messaging/mqtt/publisher.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mqtt - -import ( - "context" - "errors" - "time" - - "github.com/absmach/magistrala/pkg/messaging" - mqtt "github.com/eclipse/paho.mqtt.golang" -) - -var errPublishTimeout = errors.New("failed to publish due to timeout reached") - -var _ messaging.Publisher = (*publisher)(nil) - -type publisher struct { - client mqtt.Client - timeout time.Duration - qos uint8 -} - -// NewPublisher returns a new MQTT message publisher. -func NewPublisher(address string, qos uint8, timeout time.Duration) (messaging.Publisher, error) { - client, err := newClient(address, "mqtt-publisher", timeout) - if err != nil { - return nil, err - } - - ret := publisher{ - client: client, - timeout: timeout, - qos: qos, - } - return ret, nil -} - -func (pub publisher) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - if topic == "" { - return ErrEmptyTopic - } - - // Publish only the payload and not the whole message. - token := pub.client.Publish(topic, byte(pub.qos), false, msg.GetPayload()) - if token.Error() != nil { - return token.Error() - } - - if ok := token.WaitTimeout(pub.timeout); !ok { - return errPublishTimeout - } - - return nil -} - -func (pub publisher) Close() error { - pub.client.Disconnect(uint(pub.timeout)) - return nil -} diff --git a/pkg/messaging/mqtt/pubsub.go b/pkg/messaging/mqtt/pubsub.go deleted file mode 100644 index 4b642283e..000000000 --- a/pkg/messaging/mqtt/pubsub.go +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mqtt - -import ( - "context" - "errors" - "fmt" - "log/slog" - "sync" - "time" - - "github.com/absmach/magistrala/pkg/messaging" - mqtt "github.com/eclipse/paho.mqtt.golang" - "google.golang.org/protobuf/proto" -) - -const username = "magistrala-mqtt" - -var ( - // ErrConnect indicates that connection to MQTT broker failed. - ErrConnect = errors.New("failed to connect to MQTT broker") - - // errSubscribeTimeout indicates that the subscription failed due to timeout. - errSubscribeTimeout = errors.New("failed to subscribe due to timeout reached") - - // errUnsubscribeTimeout indicates that unsubscribe failed due to timeout. - errUnsubscribeTimeout = errors.New("failed to unsubscribe due to timeout reached") - - // errUnsubscribeDeleteTopic indicates that unsubscribe failed because the topic was deleted. - errUnsubscribeDeleteTopic = errors.New("failed to unsubscribe due to deletion of topic") - - // ErrNotSubscribed indicates that the topic is not subscribed to. - ErrNotSubscribed = errors.New("not subscribed") - - // ErrEmptyTopic indicates the absence of topic. - ErrEmptyTopic = errors.New("empty topic") - - // ErrEmptyID indicates the absence of ID. - ErrEmptyID = errors.New("empty ID") -) - -var _ messaging.PubSub = (*pubsub)(nil) - -type subscription struct { - client mqtt.Client - topics []string - cancel func() error -} - -type pubsub struct { - publisher - logger *slog.Logger - mu sync.RWMutex - address string - timeout time.Duration - subscriptions map[string]subscription -} - -// NewPubSub returns MQTT message publisher/subscriber. -func NewPubSub(url string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { - client, err := newClient(url, "mqtt-publisher", timeout) - if err != nil { - return nil, err - } - ret := &pubsub{ - publisher: publisher{ - client: client, - timeout: timeout, - qos: qos, - }, - address: url, - timeout: timeout, - logger: logger, - subscriptions: make(map[string]subscription), - } - return ret, nil -} - -func (ps *pubsub) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) error { - if cfg.ID == "" { - return ErrEmptyID - } - if cfg.Topic == "" { - return ErrEmptyTopic - } - ps.mu.Lock() - defer ps.mu.Unlock() - - s, ok := ps.subscriptions[cfg.ID] - // If the client exists, check if it's subscribed to the topic and unsubscribe if needed. - switch ok { - case true: - if ok := s.contains(cfg.Topic); ok { - if err := s.unsubscribe(cfg.Topic, ps.timeout); err != nil { - return err - } - } - default: - client, err := newClient(ps.address, cfg.ID, ps.timeout) - if err != nil { - return err - } - s = subscription{ - client: client, - topics: []string{}, - cancel: cfg.Handler.Cancel, - } - } - s.topics = append(s.topics, cfg.Topic) - ps.subscriptions[cfg.ID] = s - - token := s.client.Subscribe(cfg.Topic, byte(ps.qos), ps.mqttHandler(cfg.Handler)) - if token.Error() != nil { - return token.Error() - } - if ok := token.WaitTimeout(ps.timeout); !ok { - return errSubscribeTimeout - } - - return nil -} - -func (ps *pubsub) Unsubscribe(ctx context.Context, id, topic string) error { - if id == "" { - return ErrEmptyID - } - if topic == "" { - return ErrEmptyTopic - } - ps.mu.Lock() - defer ps.mu.Unlock() - - s, ok := ps.subscriptions[id] - if !ok || !s.contains(topic) { - return ErrNotSubscribed - } - - if err := s.unsubscribe(topic, ps.timeout); err != nil { - return err - } - ps.subscriptions[id] = s - - if len(s.topics) == 0 { - delete(ps.subscriptions, id) - } - return nil -} - -func (s *subscription) unsubscribe(topic string, timeout time.Duration) error { - if s.cancel != nil { - if err := s.cancel(); err != nil { - return err - } - } - - token := s.client.Unsubscribe(topic) - if token.Error() != nil { - return token.Error() - } - - if ok := token.WaitTimeout(timeout); !ok { - return errUnsubscribeTimeout - } - if ok := s.delete(topic); !ok { - return errUnsubscribeDeleteTopic - } - return token.Error() -} - -func newClient(address, id string, timeout time.Duration) (mqtt.Client, error) { - opts := mqtt.NewClientOptions(). - SetUsername(username). - AddBroker(address). - SetClientID(id) - client := mqtt.NewClient(opts) - token := client.Connect() - if token.Error() != nil { - return nil, token.Error() - } - - if ok := token.WaitTimeout(timeout); !ok { - return nil, ErrConnect - } - - return client, nil -} - -func (ps *pubsub) mqttHandler(h messaging.MessageHandler) mqtt.MessageHandler { - return func(_ mqtt.Client, m mqtt.Message) { - var msg messaging.Message - if err := proto.Unmarshal(m.Payload(), &msg); err != nil { - ps.logger.Warn(fmt.Sprintf("Failed to unmarshal received message: %s", err)) - return - } - - if err := h.Handle(&msg); err != nil { - ps.logger.Warn(fmt.Sprintf("Failed to handle Magistrala message: %s", err)) - } - } -} - -// Contains checks if a topic is present. -func (s subscription) contains(topic string) bool { - return s.indexOf(topic) != -1 -} - -// Finds the index of an item in the topics. -func (s subscription) indexOf(element string) int { - for k, v := range s.topics { - if element == v { - return k - } - } - return -1 -} - -// Deletes a topic from the slice. -func (s *subscription) delete(topic string) bool { - index := s.indexOf(topic) - if index == -1 { - return false - } - topics := make([]string, len(s.topics)-1) - copy(topics[:index], s.topics[:index]) - copy(topics[index:], s.topics[index+1:]) - s.topics = topics - return true -} diff --git a/pkg/messaging/mqtt/pubsub_test.go b/pkg/messaging/mqtt/pubsub_test.go deleted file mode 100644 index d0bdafc45..000000000 --- a/pkg/messaging/mqtt/pubsub_test.go +++ /dev/null @@ -1,474 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mqtt_test - -import ( - "context" - "errors" - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/pkg/messaging" - mqttpubsub "github.com/absmach/magistrala/pkg/messaging/mqtt" - mqtt "github.com/eclipse/paho.mqtt.golang" - "github.com/stretchr/testify/assert" - "google.golang.org/protobuf/proto" -) - -const ( - topic = "topic" - chansPrefix = "channels" - channel = "9b7b1b3f-b1b0-46a8-a717-b8213f9eda3b" - subtopic = "engine" - tokenTimeout = 100 * time.Millisecond -) - -var data = []byte("payload") - -// ErrFailedHandleMessage indicates that the message couldn't be handled. -var errFailedHandleMessage = errors.New("failed to handle magistrala message") - -func TestPublisher(t *testing.T) { - msgChan := make(chan []byte) - - // Subscribing with topic, and with subtopic, so that we can publish messages. - client, err := newClient(address, "clientID1", brokerTimeout) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - token := client.Subscribe(topic, qos, func(_ mqtt.Client, m mqtt.Message) { - msgChan <- m.Payload() - }) - if ok := token.WaitTimeout(tokenTimeout); !ok { - assert.Fail(t, fmt.Sprintf("failed to subscribe to topic %s", topic)) - } - assert.Nil(t, token.Error(), fmt.Sprintf("got unexpected error: %s", token.Error())) - - token = client.Subscribe(fmt.Sprintf("%s.%s", topic, subtopic), qos, func(_ mqtt.Client, m mqtt.Message) { - msgChan <- m.Payload() - }) - if ok := token.WaitTimeout(tokenTimeout); !ok { - assert.Fail(t, fmt.Sprintf("failed to subscribe to topic %s", fmt.Sprintf("%s.%s", topic, subtopic))) - } - assert.Nil(t, token.Error(), fmt.Sprintf("got unexpected error: %s", token.Error())) - - t.Cleanup(func() { - token := client.Unsubscribe(topic, fmt.Sprintf("%s.%s", topic, subtopic)) - token.WaitTimeout(tokenTimeout) - assert.Nil(t, token.Error(), fmt.Sprintf("got unexpected error: %s", token.Error())) - - client.Disconnect(100) - }) - - // Test publish with an empty topic. - err = pubsub.Publish(context.TODO(), "", &messaging.Message{Payload: data}) - assert.Equal(t, err, mqttpubsub.ErrEmptyTopic, fmt.Sprintf("Publish with empty topic: expected: %s, got: %s", mqttpubsub.ErrEmptyTopic, err)) - - cases := []struct { - desc string - channel string - subtopic string - payload []byte - }{ - { - desc: "publish message with nil payload", - payload: nil, - }, - { - desc: "publish message with string payload", - payload: data, - }, - { - desc: "publish message with channel", - payload: data, - channel: channel, - }, - { - desc: "publish message with subtopic", - payload: data, - subtopic: subtopic, - }, - { - desc: "publish message with channel and subtopic", - payload: data, - channel: channel, - subtopic: subtopic, - }, - } - for _, tc := range cases { - expectedMsg := messaging.Message{ - Publisher: "clientID11", - Channel: tc.channel, - Subtopic: tc.subtopic, - Payload: tc.payload, - } - - err := pubsub.Publish(context.TODO(), topic, &expectedMsg) - assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error: %s\n", tc.desc, err)) - - data, err := proto.Marshal(&expectedMsg) - assert.Nil(t, err, fmt.Sprintf("%s: failed to serialize protobuf error: %s\n", tc.desc, err)) - - receivedMsg := <-msgChan - if tc.payload != nil { - assert.Equal(t, expectedMsg.GetPayload(), receivedMsg, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, data, receivedMsg)) - } - } -} - -func TestSubscribe(t *testing.T) { - msgChan := make(chan *messaging.Message) - - // Creating client to Publish messages to subscribed topic. - client, err := newClient(address, "magistrala", brokerTimeout) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - t.Cleanup(func() { - client.Unsubscribe() - client.Disconnect(100) - }) - - cases := []struct { - desc string - topic string - clientID string - err error - handler messaging.MessageHandler - }{ - { - desc: "Subscribe to a topic with an ID", - topic: topic, - clientID: "clientid1", - err: nil, - handler: handler{false, "clientid1", msgChan}, - }, - { - desc: "Subscribe to the same topic with a different ID", - topic: topic, - clientID: "clientid2", - err: nil, - handler: handler{false, "clientid2", msgChan}, - }, - { - desc: "Subscribe to an already subscribed topic with an ID", - topic: topic, - clientID: "clientid1", - err: nil, - handler: handler{false, "clientid1", msgChan}, - }, - { - desc: "Subscribe to a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s", topic, subtopic), - clientID: "clientid1", - err: nil, - handler: handler{false, "clientid1", msgChan}, - }, - { - desc: "Subscribe to an already subscribed topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s", topic, subtopic), - clientID: "clientid1", - err: nil, - handler: handler{false, "clientid1", msgChan}, - }, - { - desc: "Subscribe to an empty topic with an ID", - topic: "", - clientID: "clientid1", - err: mqttpubsub.ErrEmptyTopic, - handler: handler{false, "clientid1", msgChan}, - }, - { - desc: "Subscribe to a topic with empty id", - topic: topic, - clientID: "", - err: mqttpubsub.ErrEmptyID, - handler: handler{false, "", msgChan}, - }, - } - for _, tc := range cases { - subCfg := messaging.SubscriberConfig{ - ID: tc.clientID, - Topic: tc.topic, - Handler: tc.handler, - } - err = pubsub.Subscribe(context.TODO(), subCfg) - assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, err, tc.err)) - - if tc.err == nil { - expectedMsg := messaging.Message{ - Publisher: "clientID1", - Channel: channel, - Subtopic: subtopic, - Payload: data, - } - data, err := proto.Marshal(&expectedMsg) - assert.Nil(t, err, fmt.Sprintf("%s: failed to serialize protobuf error: %s\n", tc.desc, err)) - - token := client.Publish(tc.topic, qos, false, data) - token.WaitTimeout(tokenTimeout) - assert.Nil(t, token.Error(), fmt.Sprintf("got unexpected error: %s", token.Error())) - - receivedMsg := <-msgChan - assert.Equal(t, expectedMsg.Channel, receivedMsg.Channel, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Created, receivedMsg.Created, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Protocol, receivedMsg.Protocol, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Publisher, receivedMsg.Publisher, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Subtopic, receivedMsg.Subtopic, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - } - } -} - -func TestPubSub(t *testing.T) { - msgChan := make(chan *messaging.Message) - - cases := []struct { - desc string - topic string - clientID string - err error - handler messaging.MessageHandler - }{ - { - desc: "Subscribe to a topic with an ID", - topic: topic, - clientID: "clientid7", - err: nil, - handler: handler{false, "clientid7", msgChan}, - }, - { - desc: "Subscribe to the same topic with a different ID", - topic: topic, - clientID: "clientid8", - err: nil, - handler: handler{false, "clientid8", msgChan}, - }, - { - desc: "Subscribe to a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s", topic, subtopic), - clientID: "clientid7", - err: nil, - handler: handler{false, "clientid7", msgChan}, - }, - { - desc: "Subscribe to an empty topic with an ID", - topic: "", - clientID: "clientid7", - err: mqttpubsub.ErrEmptyTopic, - handler: handler{false, "clientid7", msgChan}, - }, - { - desc: "Subscribe to a topic with empty id", - topic: topic, - clientID: "", - err: mqttpubsub.ErrEmptyID, - handler: handler{false, "", msgChan}, - }, - } - for _, tc := range cases { - subCfg := messaging.SubscriberConfig{ - ID: tc.clientID, - Topic: tc.topic, - Handler: tc.handler, - } - err := pubsub.Subscribe(context.TODO(), subCfg) - assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, err, tc.err)) - - if tc.err == nil { - // Use pubsub to subscribe to a topic, and then publish messages to that topic. - expectedMsg := messaging.Message{ - Publisher: "clientID", - Channel: channel, - Subtopic: subtopic, - Payload: data, - } - data, err := proto.Marshal(&expectedMsg) - assert.Nil(t, err, fmt.Sprintf("%s: failed to serialize protobuf error: %s\n", tc.desc, err)) - - msg := messaging.Message{ - Payload: data, - } - // Publish message, and then receive it on message channel. - err = pubsub.Publish(context.TODO(), topic, &msg) - assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error: %s\n", tc.desc, err)) - - receivedMsg := <-msgChan - assert.Equal(t, expectedMsg.Channel, receivedMsg.Channel, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Created, receivedMsg.Created, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Protocol, receivedMsg.Protocol, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Publisher, receivedMsg.Publisher, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Subtopic, receivedMsg.Subtopic, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - } - } -} - -func TestUnsubscribe(t *testing.T) { - msgChan := make(chan *messaging.Message) - - cases := []struct { - desc string - topic string - clientID string - err error - subscribe bool // True for subscribe and false for unsubscribe. - handler messaging.MessageHandler - }{ - { - desc: "Subscribe to a topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid4", - err: nil, - subscribe: true, - handler: handler{false, "clientid4", msgChan}, - }, - { - desc: "Subscribe to the same topic with a different ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid9", - err: nil, - subscribe: true, - handler: handler{false, "clientid9", msgChan}, - }, - { - desc: "Unsubscribe from a topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid4", - err: nil, - subscribe: false, - handler: handler{false, "clientid4", msgChan}, - }, - { - desc: "Unsubscribe from same topic with different ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid9", - err: nil, - subscribe: false, - handler: handler{false, "clientid9", msgChan}, - }, - { - desc: "Unsubscribe from a non-existent topic with an ID", - topic: "h", - clientID: "clientid4", - err: mqttpubsub.ErrNotSubscribed, - subscribe: false, - handler: handler{false, "clientid4", msgChan}, - }, - { - desc: "Unsubscribe from an already unsubscribed topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid4", - err: mqttpubsub.ErrNotSubscribed, - subscribe: false, - handler: handler{false, "clientid4", msgChan}, - }, - { - desc: "Subscribe to a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientidd4", - err: nil, - subscribe: true, - handler: handler{false, "clientidd4", msgChan}, - }, - { - desc: "Unsubscribe from a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientidd4", - err: nil, - subscribe: false, - handler: handler{false, "clientidd4", msgChan}, - }, - { - desc: "Unsubscribe from an already unsubscribed topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientid4", - err: mqttpubsub.ErrNotSubscribed, - subscribe: false, - handler: handler{false, "clientid4", msgChan}, - }, - { - desc: "Unsubscribe from an empty topic with an ID", - topic: "", - clientID: "clientid4", - err: mqttpubsub.ErrEmptyTopic, - subscribe: false, - handler: handler{false, "clientid4", msgChan}, - }, - { - desc: "Unsubscribe from a topic with empty ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "", - err: mqttpubsub.ErrEmptyID, - subscribe: false, - handler: handler{false, "", msgChan}, - }, - { - desc: "Subscribe to a new topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic+"2"), - clientID: "clientid55", - err: nil, - subscribe: true, - handler: handler{true, "clientid5", msgChan}, - }, - { - desc: "Unsubscribe from a topic with an ID with failing handler", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic+"2"), - clientID: "clientid55", - err: errFailedHandleMessage, - subscribe: false, - handler: handler{true, "clientid5", msgChan}, - }, - { - desc: "Subscribe to a new topic with subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic+"2", subtopic), - clientID: "clientid55", - err: nil, - subscribe: true, - handler: handler{true, "clientid5", msgChan}, - }, - { - desc: "Unsubscribe from a topic with subtopic with an ID with failing handler", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic+"2", subtopic), - clientID: "clientid55", - err: errFailedHandleMessage, - subscribe: false, - handler: handler{true, "clientid5", msgChan}, - }, - } - for _, tc := range cases { - subCfg := messaging.SubscriberConfig{ - ID: tc.clientID, - Topic: tc.topic, - Handler: tc.handler, - } - switch tc.subscribe { - case true: - err := pubsub.Subscribe(context.TODO(), subCfg) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, tc.err, err)) - default: - err := pubsub.Unsubscribe(context.TODO(), tc.clientID, tc.topic) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, tc.err, err)) - } - } -} - -type handler struct { - fail bool - publisher string - msgChan chan *messaging.Message -} - -func (h handler) Handle(msg *messaging.Message) error { - if msg.GetPublisher() != h.publisher { - h.msgChan <- msg - } - return nil -} - -func (h handler) Cancel() error { - if h.fail { - return errFailedHandleMessage - } - return nil -} diff --git a/pkg/messaging/mqtt/setup_test.go b/pkg/messaging/mqtt/setup_test.go deleted file mode 100644 index faa8ddfb9..000000000 --- a/pkg/messaging/mqtt/setup_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mqtt_test - -import ( - "fmt" - "log" - "log/slog" - "os" - "os/signal" - "syscall" - "testing" - "time" - - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/messaging" - mqttpubsub "github.com/absmach/magistrala/pkg/messaging/mqtt" - mqtt "github.com/eclipse/paho.mqtt.golang" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" -) - -var ( - pubsub messaging.PubSub - logger *slog.Logger - address string -) - -const ( - username = "magistrala-mqtt" - qos = 2 - port = "1883/tcp" - brokerTimeout = 30 * time.Second - poolMaxWait = 120 * time.Second -) - -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: "eclipse-mosquitto", - Tag: "1.6.15", - }, func(config *docker.HostConfig) { - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{Name: "no"} - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - - handleInterrupt(pool, container) - - address = fmt.Sprintf("%s:%s", "localhost", container.GetPort(port)) - pool.MaxWait = poolMaxWait - - logger, err = mglog.New(os.Stdout, "debug") - if err != nil { - log.Fatal(err.Error()) - } - - if err := pool.Retry(func() error { - pubsub, err = mqttpubsub.NewPubSub(address, 2, brokerTimeout, logger) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - code := m.Run() - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) - - defer func() { - err = pubsub.Close() - if err != nil { - log.Fatal(err.Error()) - } - }() -} - -func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) { - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - os.Exit(0) - }() -} - -func newClient(address, id string, timeout time.Duration) (mqtt.Client, error) { - opts := mqtt.NewClientOptions(). - SetUsername(username). - AddBroker(address). - SetClientID(id) - - client := mqtt.NewClient(opts) - token := client.Connect() - if token.Error() != nil { - return nil, token.Error() - } - - ok := token.WaitTimeout(timeout) - if !ok { - return nil, mqttpubsub.ErrConnect - } - - if token.Error() != nil { - return nil, token.Error() - } - - return client, nil -} diff --git a/pkg/messaging/nats/doc.go b/pkg/messaging/nats/doc.go deleted file mode 100644 index 5c9d84778..000000000 --- a/pkg/messaging/nats/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package nats hold the implementation of the Publisher and PubSub -// interfaces for the NATS messaging system, the internal messaging -// broker of the Magistrala IoT platform. Due to the practical requirements -// implementation Publisher is created alongside PubSub. The reason for -// this is that Subscriber implementation of NATS brings the burden of -// additional struct fields which are not used by Publisher. Subscriber -// is not implemented separately because PubSub can be used where Subscriber is needed. -package nats diff --git a/pkg/messaging/nats/options.go b/pkg/messaging/nats/options.go deleted file mode 100644 index 713682905..000000000 --- a/pkg/messaging/nats/options.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats - -import ( - "errors" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/nats-io/nats.go/jetstream" -) - -// ErrInvalidType is returned when the provided value is not of the expected type. -var ErrInvalidType = errors.New("invalid type") - -// Prefix sets the prefix for the publisher. -func Prefix(prefix string) messaging.Option { - return func(val interface{}) error { - p, ok := val.(*publisher) - if !ok { - return ErrInvalidType - } - - p.prefix = prefix - - return nil - } -} - -// JSStream sets the JetStream for the publisher. -func JSStream(stream jetstream.JetStream) messaging.Option { - return func(val interface{}) error { - p, ok := val.(*publisher) - if !ok { - return ErrInvalidType - } - - p.js = stream - - return nil - } -} - -// Stream sets the Stream for the subscriber. -func Stream(stream jetstream.Stream) messaging.Option { - return func(val interface{}) error { - p, ok := val.(*pubsub) - if !ok { - return ErrInvalidType - } - - p.stream = stream - - return nil - } -} diff --git a/pkg/messaging/nats/publisher.go b/pkg/messaging/nats/publisher.go deleted file mode 100644 index 2aca0b843..000000000 --- a/pkg/messaging/nats/publisher.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats - -import ( - "context" - "fmt" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/messaging" - broker "github.com/nats-io/nats.go" - "github.com/nats-io/nats.go/jetstream" - "google.golang.org/protobuf/proto" -) - -const ( - // A maximum number of reconnect attempts before NATS connection closes permanently. - // Value -1 represents an unlimited number of reconnect retries, i.e. the client - // will never give up on retrying to re-establish connection to NATS server. - maxReconnects = -1 - - // reconnectBufSize is obtained from the maximum number of unpublished events - // multiplied by the approximate maximum size of a single event. - reconnectBufSize = events.MaxUnpublishedEvents * (1024 * 1024) -) - -var _ messaging.Publisher = (*publisher)(nil) - -type publisher struct { - js jetstream.JetStream - conn *broker.Conn - prefix string -} - -// NewPublisher returns NATS message Publisher. -func NewPublisher(ctx context.Context, url string, opts ...messaging.Option) (messaging.Publisher, error) { - conn, err := broker.Connect(url, broker.MaxReconnects(maxReconnects), broker.ReconnectBufSize(int(reconnectBufSize))) - if err != nil { - return nil, err - } - js, err := jetstream.New(conn) - if err != nil { - return nil, err - } - if _, err := js.CreateStream(ctx, jsStreamConfig); err != nil { - return nil, err - } - - ret := &publisher{ - js: js, - conn: conn, - prefix: chansPrefix, - } - - for _, opt := range opts { - if err := opt(ret); err != nil { - return nil, err - } - } - - return ret, nil -} - -func (pub *publisher) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - if topic == "" { - return ErrEmptyTopic - } - - data, err := proto.Marshal(msg) - if err != nil { - return err - } - - subject := fmt.Sprintf("%s.%s", pub.prefix, topic) - if msg.GetSubtopic() != "" { - subject = fmt.Sprintf("%s.%s", subject, msg.GetSubtopic()) - } - - _, err = pub.js.Publish(ctx, subject, data) - - return err -} - -func (pub *publisher) Close() error { - pub.conn.Close() - return nil -} diff --git a/pkg/messaging/nats/pubsub.go b/pkg/messaging/nats/pubsub.go deleted file mode 100644 index 7161a0d99..000000000 --- a/pkg/messaging/nats/pubsub.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats - -import ( - "context" - "errors" - "fmt" - "log/slog" - "strings" - "time" - - "github.com/absmach/magistrala/pkg/messaging" - broker "github.com/nats-io/nats.go" - "github.com/nats-io/nats.go/jetstream" - "google.golang.org/protobuf/proto" -) - -const chansPrefix = "channels" - -// Publisher and Subscriber errors. -var ( - ErrNotSubscribed = errors.New("not subscribed") - ErrEmptyTopic = errors.New("empty topic") - ErrEmptyID = errors.New("empty id") - - jsStreamConfig = jetstream.StreamConfig{ - Name: "channels", - Description: "Magistrala stream for sending and receiving messages in between Magistrala channels", - Subjects: []string{"channels.>"}, - Retention: jetstream.LimitsPolicy, - MaxMsgsPerSubject: 1e6, - MaxAge: time.Hour * 24, - MaxMsgSize: 1024 * 1024, - Discard: jetstream.DiscardOld, - Storage: jetstream.FileStorage, - } -) - -var _ messaging.PubSub = (*pubsub)(nil) - -type pubsub struct { - publisher - logger *slog.Logger - stream jetstream.Stream -} - -// NewPubSub returns NATS message publisher/subscriber. -// Parameter queue specifies the queue for the Subscribe method. -// If queue is specified (is not an empty string), Subscribe method -// will execute NATS QueueSubscribe which is conceptually different -// from ordinary subscribe. For more information, please take a look -// here: https://docs.nats.io/developing-with-nats/receiving/queues. -// If the queue is empty, Subscribe will be used. -func NewPubSub(ctx context.Context, url string, logger *slog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { - conn, err := broker.Connect(url, broker.MaxReconnects(maxReconnects)) - if err != nil { - return nil, err - } - js, err := jetstream.New(conn) - if err != nil { - return nil, err - } - stream, err := js.CreateStream(ctx, jsStreamConfig) - if err != nil { - return nil, err - } - - ret := &pubsub{ - publisher: publisher{ - js: js, - conn: conn, - prefix: chansPrefix, - }, - stream: stream, - logger: logger, - } - - for _, opt := range opts { - if err := opt(ret); err != nil { - return nil, err - } - } - - return ret, nil -} - -func (ps *pubsub) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) error { - if cfg.ID == "" { - return ErrEmptyID - } - if cfg.Topic == "" { - return ErrEmptyTopic - } - - nh := ps.natsHandler(cfg.Handler) - - consumerConfig := jetstream.ConsumerConfig{ - Name: formatConsumerName(cfg.Topic, cfg.ID), - Durable: formatConsumerName(cfg.Topic, cfg.ID), - Description: fmt.Sprintf("Magistrala consumer of id %s for cfg.Topic %s", cfg.ID, cfg.Topic), - DeliverPolicy: jetstream.DeliverNewPolicy, - FilterSubject: cfg.Topic, - } - - switch cfg.DeliveryPolicy { - case messaging.DeliverNewPolicy: - consumerConfig.DeliverPolicy = jetstream.DeliverNewPolicy - case messaging.DeliverAllPolicy: - consumerConfig.DeliverPolicy = jetstream.DeliverAllPolicy - } - - consumer, err := ps.stream.CreateOrUpdateConsumer(ctx, consumerConfig) - if err != nil { - return fmt.Errorf("failed to create consumer: %w", err) - } - - if _, err = consumer.Consume(nh); err != nil { - return fmt.Errorf("failed to consume: %w", err) - } - - return nil -} - -func (ps *pubsub) Unsubscribe(ctx context.Context, id, topic string) error { - if id == "" { - return ErrEmptyID - } - if topic == "" { - return ErrEmptyTopic - } - - err := ps.stream.DeleteConsumer(ctx, formatConsumerName(topic, id)) - switch { - case errors.Is(err, jetstream.ErrConsumerNotFound): - return ErrNotSubscribed - default: - return err - } -} - -func (ps *pubsub) natsHandler(h messaging.MessageHandler) func(m jetstream.Msg) { - return func(m jetstream.Msg) { - var msg messaging.Message - if err := proto.Unmarshal(m.Data(), &msg); err != nil { - ps.logger.Warn(fmt.Sprintf("Failed to unmarshal received message: %s", err)) - - return - } - - if err := h.Handle(&msg); err != nil { - ps.logger.Warn(fmt.Sprintf("Failed to handle Magistrala message: %s", err)) - } - if err := m.Ack(); err != nil { - ps.logger.Warn(fmt.Sprintf("Failed to ack message: %s", err)) - } - } -} - -func formatConsumerName(topic, id string) string { - // A durable name cannot contain whitespace, ., *, >, path separators (forward or backwards slash), and non-printable characters. - chars := []string{ - " ", "_", - ".", "_", - "*", "_", - ">", "_", - "/", "_", - "\\", "_", - } - topic = strings.NewReplacer(chars...).Replace(topic) - - return fmt.Sprintf("%s-%s", topic, id) -} diff --git a/pkg/messaging/nats/pubsub_test.go b/pkg/messaging/nats/pubsub_test.go deleted file mode 100644 index d9e49b49d..000000000 --- a/pkg/messaging/nats/pubsub_test.go +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/nats" - "github.com/stretchr/testify/assert" -) - -const ( - topic = "topic" - chansPrefix = "channels" - channel = "9b7b1b3f-b1b0-46a8-a717-b8213f9eda3b" - subtopic = "engine" - clientID = "9b7b1b3f-b1b0-46a8-a717-b8213f9eda3b" -) - -var ( - msgChan = make(chan *messaging.Message) - message = &messaging.Message{ - Channel: channel, - Subtopic: subtopic, - Publisher: "9b7b1b3f-b1b0-46a8-a717-b8213f9eda3b", - Protocol: "mqtt", - Payload: []byte("payload"), - Created: time.Now().UnixNano(), - } -) - -func TestPublisher(t *testing.T) { - subCfg := messaging.SubscriberConfig{ - ID: clientID, - Topic: fmt.Sprintf("%s.>", chansPrefix), - Handler: handler{}, - } - err := pubsub.Subscribe(context.TODO(), subCfg) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - cases := []struct { - desc string - topic string - subtopic string - message *messaging.Message - error error - }{ - { - desc: "publish message with empty message", - topic: channel, - subtopic: subtopic, - message: &messaging.Message{}, - error: nil, - }, - { - desc: "publish message with message", - topic: channel, - subtopic: subtopic, - message: message, - error: nil, - }, - { - desc: "publish message with topic and empty subtopic", - topic: channel, - subtopic: "", - message: message, - error: nil, - }, - { - desc: "publish message with subtopic and empty topic", - topic: "", - subtopic: subtopic, - message: message, - error: nats.ErrEmptyTopic, - }, - { - desc: "publish message with topic and subtopic", - topic: channel, - subtopic: subtopic, - message: message, - error: nil, - }, - } - - for _, tc := range cases { - tc.message.Subtopic = tc.subtopic - err := pubsub.Publish(context.TODO(), tc.topic, tc.message) - assert.Equal(t, tc.error, err, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, tc.error, err)) - - if err == nil { - receivedMsg := <-msgChan - assert.Equal(t, tc.message.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, tc.message.Payload, receivedMsg)) - assert.Equal(t, tc.message.Channel, receivedMsg.Channel, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &tc.message, receivedMsg)) - assert.Equal(t, tc.message.Created, receivedMsg.Created, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &tc.message, receivedMsg)) - assert.Equal(t, tc.message.Protocol, receivedMsg.Protocol, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &tc.message, receivedMsg)) - assert.Equal(t, tc.message.Publisher, receivedMsg.Publisher, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &tc.message, receivedMsg)) - assert.Equal(t, tc.message.Subtopic, receivedMsg.Subtopic, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &tc.message, receivedMsg)) - assert.Equal(t, tc.message.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &tc.message, receivedMsg)) - } - } -} - -func TestPubsub(t *testing.T) { - // Test Subscribe and Unsubscribe. - subcases := []struct { - desc string - topic string - clientID string - errorMessage error - pubsub bool // true for subscribe and false for unsubscribe. - handler messaging.MessageHandler - }{ - { - desc: "Subscribe to a topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid1", - errorMessage: nil, - pubsub: true, - handler: handler{}, - }, - { - desc: "Subscribe using malformed topic and ID", - topic: fmt.Sprintf("%s.>", chansPrefix), - clientID: "clientid1", - errorMessage: nil, - pubsub: true, - handler: handler{}, - }, - { - desc: "Subscribe using malformed topic and ID", - topic: fmt.Sprintf("%s.*", chansPrefix), - clientID: "clientid1", - errorMessage: nil, - pubsub: true, - handler: handler{}, - }, - { - desc: "Subscribe to the same topic with a different ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid2", - errorMessage: nil, - pubsub: true, - handler: handler{}, - }, - { - desc: "Subscribe to an already subscribed topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid1", - errorMessage: nil, - pubsub: true, - handler: handler{}, - }, - { - desc: "Unsubscribe from a topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid1", - errorMessage: nil, - pubsub: false, - handler: handler{}, - }, - { - desc: "Unsubscribe from a non-existent topic with an ID", - topic: "h", - clientID: "clientid1", - errorMessage: nats.ErrNotSubscribed, - pubsub: false, - handler: handler{}, - }, - { - desc: "Unsubscribe from the same topic with a different ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientidd2", - errorMessage: nats.ErrNotSubscribed, - pubsub: false, - handler: handler{}, - }, - { - desc: "Unsubscribe from the same topic with a different ID not subscribed", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientidd3", - errorMessage: nats.ErrNotSubscribed, - pubsub: false, - handler: handler{}, - }, - { - desc: "Unsubscribe from an already unsubscribed topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid1", - errorMessage: nats.ErrNotSubscribed, - pubsub: false, - handler: handler{}, - }, - { - desc: "Subscribe to a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientidd1", - errorMessage: nil, - pubsub: true, - handler: handler{}, - }, - { - desc: "Subscribe to an already subscribed topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientidd1", - errorMessage: nil, - pubsub: true, - handler: handler{}, - }, - { - desc: "Unsubscribe from a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientidd1", - errorMessage: nil, - pubsub: false, - handler: handler{}, - }, - { - desc: "Unsubscribe from an already unsubscribed topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientid1", - errorMessage: nats.ErrNotSubscribed, - pubsub: false, - handler: handler{}, - }, - { - desc: "Subscribe to an empty topic with an ID", - topic: "", - clientID: "clientid1", - errorMessage: nats.ErrEmptyTopic, - pubsub: true, - handler: handler{}, - }, - { - desc: "Unsubscribe from an empty topic with an ID", - topic: "", - clientID: "clientid1", - errorMessage: nats.ErrEmptyTopic, - pubsub: false, - handler: handler{}, - }, - { - desc: "Subscribe to a topic with empty id", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "", - errorMessage: nats.ErrEmptyID, - pubsub: true, - handler: handler{}, - }, - { - desc: "Unsubscribe from a topic with empty id", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "", - errorMessage: nats.ErrEmptyID, - pubsub: false, - handler: handler{}, - }, - } - - for _, pc := range subcases { - subCfg := messaging.SubscriberConfig{ - ID: pc.clientID, - Topic: pc.topic, - Handler: pc.handler, - } - if pc.pubsub == true { - err := pubsub.Subscribe(context.TODO(), subCfg) - if pc.errorMessage == nil { - assert.Nil(t, err, fmt.Sprintf("%s expected %+v got %+v\n", pc.desc, pc.errorMessage, err)) - } else { - assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s expected %+v got %+v\n", pc.desc, pc.errorMessage, err)) - } - } else { - err := pubsub.Unsubscribe(context.TODO(), pc.clientID, pc.topic) - if pc.errorMessage == nil { - assert.Nil(t, err, fmt.Sprintf("%s expected %+v got %+v\n", pc.desc, pc.errorMessage, err)) - } else { - assert.Equal(t, err, pc.errorMessage, fmt.Sprintf("%s expected %+v got %+v\n", pc.desc, pc.errorMessage, err)) - } - } - } -} - -type handler struct{} - -func (h handler) Handle(msg *messaging.Message) error { - msgChan <- msg - - return nil -} - -func (h handler) Cancel() error { - return nil -} diff --git a/pkg/messaging/nats/setup_test.go b/pkg/messaging/nats/setup_test.go deleted file mode 100644 index f140197b7..000000000 --- a/pkg/messaging/nats/setup_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package nats_test - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - "syscall" - "testing" - - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/nats" - "github.com/ory/dockertest/v3" -) - -var ( - publisher messaging.Publisher - pubsub messaging.PubSub -) - -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: "nats", - Tag: "2.10.9-alpine", - Cmd: []string{"-DVV", "-js"}, - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - handleInterrupt(pool, container) - - address := fmt.Sprintf("nats://%s:%s", "localhost", container.GetPort("4222/tcp")) - if err := pool.Retry(func() error { - publisher, err = nats.NewPublisher(context.Background(), address) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - logger, err := mglog.New(os.Stdout, "error") - if err != nil { - log.Fatal(err.Error()) - } - if err := pool.Retry(func() error { - pubsub, err = nats.NewPubSub(context.Background(), address, logger) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - code := m.Run() - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} - -func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) { - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - - go func() { - <-c - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - os.Exit(0) - }() -} diff --git a/pkg/messaging/nats/tracing/doc.go b/pkg/messaging/nats/tracing/doc.go deleted file mode 100644 index 5f8df0d9e..000000000 --- a/pkg/messaging/nats/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala things policies service. -// -// This package provides tracing middleware for Magistrala things policies service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala things policies 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/pkg/messaging/nats/tracing/publisher.go b/pkg/messaging/nats/tracing/publisher.go deleted file mode 100644 index 84c2bc5b8..000000000 --- a/pkg/messaging/nats/tracing/publisher.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/tracing" - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -// Traced operations. -const publishOP = "publish" - -var defaultAttributes = []attribute.KeyValue{ - attribute.String("messaging.system", "nats"), - attribute.String("network.protocol.name", "nats"), - attribute.String("network.protocol.version", "2.2.4"), -} - -var _ messaging.Publisher = (*publisherMiddleware)(nil) - -type publisherMiddleware struct { - publisher messaging.Publisher - tracer trace.Tracer - host server.Config -} - -func NewPublisher(config server.Config, tracer trace.Tracer, publisher messaging.Publisher) messaging.Publisher { - pub := &publisherMiddleware{ - publisher: publisher, - tracer: tracer, - host: config, - } - - return pub -} - -func (pm *publisherMiddleware) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - ctx, span := tracing.CreateSpan(ctx, publishOP, msg.GetPublisher(), topic, msg.GetSubtopic(), len(msg.GetPayload()), pm.host, trace.SpanKindClient, pm.tracer) - defer span.End() - span.SetAttributes(defaultAttributes...) - - return pm.publisher.Publish(ctx, topic, msg) -} - -func (pm *publisherMiddleware) Close() error { - return pm.publisher.Close() -} diff --git a/pkg/messaging/nats/tracing/pubsub.go b/pkg/messaging/nats/tracing/pubsub.go deleted file mode 100644 index c8f6b0cf7..000000000 --- a/pkg/messaging/nats/tracing/pubsub.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/tracing" - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/otel/trace" -) - -// Constants to define different operations to be traced. -const ( - subscribeOP = "receive" - unsubscribeOp = "unsubscribe" // This is not specified in the open telemetry spec. - processOp = "process" -) - -var _ messaging.PubSub = (*pubsubMiddleware)(nil) - -type pubsubMiddleware struct { - publisherMiddleware - pubsub messaging.PubSub - host server.Config -} - -// NewPubSub creates a new pubsub middleware that traces pubsub operations. -func NewPubSub(config server.Config, tracer trace.Tracer, pubsub messaging.PubSub) messaging.PubSub { - pb := &pubsubMiddleware{ - publisherMiddleware: publisherMiddleware{ - publisher: pubsub, - tracer: tracer, - host: config, - }, - pubsub: pubsub, - host: config, - } - - return pb -} - -// Subscribe creates a new subscription and traces the operation. -func (pm *pubsubMiddleware) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) error { - ctx, span := tracing.CreateSpan(ctx, subscribeOP, cfg.ID, cfg.Topic, "", 0, pm.host, trace.SpanKindClient, pm.tracer) - defer span.End() - - span.SetAttributes(defaultAttributes...) - - cfg.Handler = &traceHandler{ - ctx: ctx, - handler: cfg.Handler, - tracer: pm.tracer, - host: pm.host, - topic: cfg.Topic, - clientID: cfg.ID, - } - - return pm.pubsub.Subscribe(ctx, cfg) -} - -// Unsubscribe removes an existing subscription and traces the operation. -func (pm *pubsubMiddleware) Unsubscribe(ctx context.Context, id, topic string) error { - ctx, span := tracing.CreateSpan(ctx, unsubscribeOp, id, topic, "", 0, pm.host, trace.SpanKindInternal, pm.tracer) - defer span.End() - - span.SetAttributes(defaultAttributes...) - - return pm.pubsub.Unsubscribe(ctx, id, topic) -} - -// TraceHandler is used to trace the message handling operation. -type traceHandler struct { - ctx context.Context - handler messaging.MessageHandler - tracer trace.Tracer - host server.Config - topic string - clientID string -} - -// Handle instruments the message handling operation. -func (h *traceHandler) Handle(msg *messaging.Message) error { - _, span := tracing.CreateSpan(h.ctx, processOp, h.clientID, h.topic, msg.GetSubtopic(), len(msg.GetPayload()), h.host, trace.SpanKindConsumer, h.tracer) - defer span.End() - - span.SetAttributes(defaultAttributes...) - - return h.handler.Handle(msg) -} - -// Cancel cancels the message handling operation. -func (h *traceHandler) Cancel() error { - return h.handler.Cancel() -} diff --git a/pkg/messaging/pubsub.go b/pkg/messaging/pubsub.go deleted file mode 100644 index 08ea63815..000000000 --- a/pkg/messaging/pubsub.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package messaging - -import "context" - -type DeliveryPolicy uint8 - -const ( - // DeliverNewPolicy will only deliver new messages that are sent after the consumer is created. - // This is the default policy. - DeliverNewPolicy DeliveryPolicy = iota - - // DeliverAllPolicy starts delivering messages from the very beginning of a stream. - DeliverAllPolicy -) - -// Publisher specifies message publishing API. -type Publisher interface { - // Publishes message to the stream. - Publish(ctx context.Context, topic string, msg *Message) error - - // Close gracefully closes message publisher's connection. - Close() error -} - -// MessageHandler represents Message handler for Subscriber. -type MessageHandler interface { - // Handle handles messages passed by underlying implementation. - Handle(msg *Message) error - - // Cancel is used for cleanup during unsubscribing and it's optional. - Cancel() error -} - -type SubscriberConfig struct { - ID string - Topic string - Handler MessageHandler - DeliveryPolicy DeliveryPolicy -} - -// Subscriber specifies message subscription API. -type Subscriber interface { - // Subscribe subscribes to the message stream and consumes messages. - Subscribe(ctx context.Context, cfg SubscriberConfig) error - - // Unsubscribe unsubscribes from the message stream and - // stops consuming messages. - Unsubscribe(ctx context.Context, id, topic string) error - - // Close gracefully closes message subscriber's connection. - Close() error -} - -// PubSub represents aggregation interface for publisher and subscriber. -// -//go:generate mockery --name PubSub --filename pubsub.go --quiet --note "Copyright (c) Abstract Machines" -type PubSub interface { - Publisher - Subscriber -} - -// Option represents optional configuration for message broker. -// -// This is used to provide optional configuration parameters to the -// underlying publisher and pubsub implementation so that it can be -// configured to meet the specific needs. -// -// For example, it can be used to set the message prefix so that -// brokers can be used for event sourcing as well as internal message broker. -// Using value of type interface is not recommended but is the most suitable -// for this use case as options should be compiled with respect to the -// underlying broker which can either be RabbitMQ or NATS. -// -// The example below shows how to set the prefix and jetstream stream for NATS. -// -// Example: -// -// broker.NewPublisher(ctx, url, broker.Prefix(eventsPrefix), broker.JSStream(js)) -type Option func(vals interface{}) error diff --git a/pkg/messaging/rabbitmq/doc.go b/pkg/messaging/rabbitmq/doc.go deleted file mode 100644 index e331069f0..000000000 --- a/pkg/messaging/rabbitmq/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package rabbitmq holds the implementation of the Publisher and PubSub -// interfaces for the RabbitMQ messaging system, the internal messaging -// broker of the Magistrala IoT platform. Due to the practical requirements -// implementation Publisher is created alongside PubSub. The reason for -// this is that Subscriber implementation of RabbitMQ brings the burden of -// additional struct fields which are not used by Publisher. Subscriber -// is not implemented separately because PubSub can be used where Subscriber is needed. -package rabbitmq diff --git a/pkg/messaging/rabbitmq/options.go b/pkg/messaging/rabbitmq/options.go deleted file mode 100644 index b0727b340..000000000 --- a/pkg/messaging/rabbitmq/options.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq - -import ( - "errors" - - "github.com/absmach/magistrala/pkg/messaging" - amqp "github.com/rabbitmq/amqp091-go" -) - -// ErrInvalidType is returned when the provided value is not of the expected type. -var ErrInvalidType = errors.New("invalid type") - -// Prefix sets the prefix for the publisher. -func Prefix(prefix string) messaging.Option { - return func(val interface{}) error { - p, ok := val.(*publisher) - if !ok { - return ErrInvalidType - } - - p.prefix = prefix - - return nil - } -} - -// Channel sets the channel for the publisher or subscriber. -func Channel(channel *amqp.Channel) messaging.Option { - return func(val interface{}) error { - switch v := val.(type) { - case *publisher: - v.channel = channel - case *pubsub: - v.channel = channel - default: - return ErrInvalidType - } - - return nil - } -} - -// Exchange sets the exchange for the publisher or subscriber. -func Exchange(exchange string) messaging.Option { - return func(val interface{}) error { - switch v := val.(type) { - case *publisher: - v.exchange = exchange - case *pubsub: - v.exchange = exchange - default: - return ErrInvalidType - } - - return nil - } -} diff --git a/pkg/messaging/rabbitmq/publisher.go b/pkg/messaging/rabbitmq/publisher.go deleted file mode 100644 index 3f52d38f1..000000000 --- a/pkg/messaging/rabbitmq/publisher.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq - -import ( - "context" - "fmt" - "strings" - - "github.com/absmach/magistrala/pkg/messaging" - amqp "github.com/rabbitmq/amqp091-go" - "google.golang.org/protobuf/proto" -) - -var _ messaging.Publisher = (*publisher)(nil) - -type publisher struct { - conn *amqp.Connection - channel *amqp.Channel - prefix string - exchange string -} - -// NewPublisher returns RabbitMQ message Publisher. -func NewPublisher(url string, opts ...messaging.Option) (messaging.Publisher, error) { - conn, err := amqp.Dial(url) - if err != nil { - return nil, err - } - ch, err := conn.Channel() - if err != nil { - return nil, err - } - if err := ch.ExchangeDeclare(exchangeName, amqp.ExchangeTopic, true, false, false, false, nil); err != nil { - return nil, err - } - - ret := &publisher{ - conn: conn, - channel: ch, - prefix: chansPrefix, - exchange: exchangeName, - } - - for _, opt := range opts { - if err := opt(ret); err != nil { - return nil, err - } - } - - return ret, nil -} - -func (pub *publisher) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - if topic == "" { - return ErrEmptyTopic - } - data, err := proto.Marshal(msg) - if err != nil { - return err - } - - subject := fmt.Sprintf("%s.%s", pub.prefix, topic) - if msg.GetSubtopic() != "" { - subject = fmt.Sprintf("%s.%s", subject, msg.GetSubtopic()) - } - subject = formatTopic(subject) - - err = pub.channel.PublishWithContext( - ctx, - pub.exchange, - subject, - false, - false, - amqp.Publishing{ - Headers: amqp.Table{}, - ContentType: "application/octet-stream", - AppId: "magistrala-publisher", - Body: data, - }) - if err != nil { - return err - } - - return nil -} - -func (pub *publisher) Close() error { - return pub.conn.Close() -} - -func formatTopic(topic string) string { - return strings.ReplaceAll(topic, ">", "#") -} diff --git a/pkg/messaging/rabbitmq/pubsub.go b/pkg/messaging/rabbitmq/pubsub.go deleted file mode 100644 index 59b06a496..000000000 --- a/pkg/messaging/rabbitmq/pubsub.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq - -import ( - "context" - "errors" - "fmt" - "log/slog" - "sync" - - "github.com/absmach/magistrala/pkg/messaging" - amqp "github.com/rabbitmq/amqp091-go" - "google.golang.org/protobuf/proto" -) - -const ( - // SubjectAllChannels represents subject to subscribe for all the channels. - SubjectAllChannels = "channels.#" - - exchangeName = "messages" - chansPrefix = "channels" -) - -var ( - // ErrNotSubscribed indicates that the topic is not subscribed to. - ErrNotSubscribed = errors.New("not subscribed") - - // ErrEmptyTopic indicates the absence of topic. - ErrEmptyTopic = errors.New("empty topic") - - // ErrEmptyID indicates the absence of ID. - ErrEmptyID = errors.New("empty ID") -) -var _ messaging.PubSub = (*pubsub)(nil) - -type subscription struct { - cancel func() error -} -type pubsub struct { - publisher - logger *slog.Logger - subscriptions map[string]map[string]subscription - mu sync.Mutex -} - -// NewPubSub returns RabbitMQ message publisher/subscriber. -func NewPubSub(url string, logger *slog.Logger, opts ...messaging.Option) (messaging.PubSub, error) { - conn, err := amqp.Dial(url) - if err != nil { - return nil, err - } - ch, err := conn.Channel() - if err != nil { - return nil, err - } - if err := ch.ExchangeDeclare(exchangeName, amqp.ExchangeTopic, true, false, false, false, nil); err != nil { - return nil, err - } - - ret := &pubsub{ - publisher: publisher{ - conn: conn, - channel: ch, - exchange: exchangeName, - prefix: chansPrefix, - }, - logger: logger, - subscriptions: make(map[string]map[string]subscription), - } - - for _, opt := range opts { - if err := opt(ret); err != nil { - return nil, err - } - } - - return ret, nil -} - -func (ps *pubsub) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) error { - if cfg.ID == "" { - return ErrEmptyID - } - if cfg.Topic == "" { - return ErrEmptyTopic - } - ps.mu.Lock() - - cfg.Topic = formatTopic(cfg.Topic) - // Check topic - s, ok := ps.subscriptions[cfg.Topic] - if ok { - // Check client ID - if _, ok := s[cfg.ID]; ok { - // Unlocking, so that Unsubscribe() can access ps.subscriptions - ps.mu.Unlock() - if err := ps.Unsubscribe(ctx, cfg.ID, cfg.Topic); err != nil { - return err - } - - ps.mu.Lock() - // value of s can be changed while ps.mu is unlocked - s = ps.subscriptions[cfg.Topic] - } - } - defer ps.mu.Unlock() - if s == nil { - s = make(map[string]subscription) - ps.subscriptions[cfg.Topic] = s - } - - clientID := fmt.Sprintf("%s-%s", cfg.Topic, cfg.ID) - - queue, err := ps.channel.QueueDeclare(clientID, true, false, false, false, nil) - if err != nil { - return err - } - - if err := ps.channel.QueueBind(queue.Name, cfg.Topic, ps.exchange, false, nil); err != nil { - return err - } - - msgs, err := ps.channel.Consume(queue.Name, clientID, true, false, false, false, nil) - if err != nil { - return err - } - go ps.handle(msgs, cfg.Handler) - s[cfg.ID] = subscription{ - cancel: func() error { - if err := ps.channel.Cancel(clientID, false); err != nil { - return err - } - return cfg.Handler.Cancel() - }, - } - - return nil -} - -func (ps *pubsub) Unsubscribe(ctx context.Context, id, topic string) error { - if id == "" { - return ErrEmptyID - } - if topic == "" { - return ErrEmptyTopic - } - ps.mu.Lock() - defer ps.mu.Unlock() - - topic = formatTopic(topic) - // Check topic - s, ok := ps.subscriptions[topic] - if !ok { - return ErrNotSubscribed - } - // Check topic ID - current, ok := s[id] - if !ok { - return ErrNotSubscribed - } - if current.cancel != nil { - if err := current.cancel(); err != nil { - return err - } - } - if err := ps.channel.QueueUnbind(topic, topic, exchangeName, nil); err != nil { - return err - } - - delete(s, id) - if len(s) == 0 { - delete(ps.subscriptions, topic) - } - return nil -} - -func (ps *pubsub) handle(deliveries <-chan amqp.Delivery, h messaging.MessageHandler) { - for d := range deliveries { - var msg messaging.Message - if err := proto.Unmarshal(d.Body, &msg); err != nil { - ps.logger.Warn(fmt.Sprintf("Failed to unmarshal received message: %s", err)) - return - } - if err := h.Handle(&msg); err != nil { - ps.logger.Warn(fmt.Sprintf("Failed to handle Magistrala message: %s", err)) - return - } - } -} diff --git a/pkg/messaging/rabbitmq/pubsub_test.go b/pkg/messaging/rabbitmq/pubsub_test.go deleted file mode 100644 index 2dcf3ecf9..000000000 --- a/pkg/messaging/rabbitmq/pubsub_test.go +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq_test - -import ( - "context" - "errors" - "fmt" - "testing" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/rabbitmq" - amqp "github.com/rabbitmq/amqp091-go" - "github.com/stretchr/testify/assert" - "google.golang.org/protobuf/proto" -) - -const ( - topic = "topic" - chansPrefix = "channels" - channel = "9b7b1b3f-b1b0-46a8-a717-b8213f9eda3b" - subtopic = "engine" - clientID = "9b7b1b3f-b1b0-46a8-a717-b8213f9eda3b" - exchangeName = "messages" -) - -var ( - msgChan = make(chan *messaging.Message) - data = []byte("payload") -) - -var errFailedHandleMessage = errors.New("failed to handle magistrala message") - -func TestPublisher(t *testing.T) { - // Subscribing with topic, and with subtopic, so that we can publish messages. - conn, ch, err := newConn() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - topicChan := subscribe(t, ch, fmt.Sprintf("%s.%s", chansPrefix, topic)) - subtopicChan := subscribe(t, ch, fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic)) - - go rabbitHandler(topicChan, handler{}) - go rabbitHandler(subtopicChan, handler{}) - - t.Cleanup(func() { - conn.Close() - ch.Close() - }) - - cases := []struct { - desc string - channel string - subtopic string - payload []byte - }{ - { - desc: "publish message with nil payload", - payload: nil, - }, - { - desc: "publish message with string payload", - payload: data, - }, - { - desc: "publish message with channel", - payload: data, - channel: channel, - }, - { - desc: "publish message with subtopic", - payload: data, - subtopic: subtopic, - }, - { - desc: "publish message with channel and subtopic", - payload: data, - channel: channel, - subtopic: subtopic, - }, - } - - for _, tc := range cases { - expectedMsg := messaging.Message{ - Publisher: clientID, - Channel: tc.channel, - Subtopic: tc.subtopic, - Payload: tc.payload, - } - err = pubsub.Publish(context.TODO(), topic, &expectedMsg) - assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error: %s", tc.desc, err)) - - receivedMsg := <-msgChan - assert.Equal(t, expectedMsg.Channel, receivedMsg.Channel, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Created, receivedMsg.Created, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Protocol, receivedMsg.Protocol, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Publisher, receivedMsg.Publisher, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Subtopic, receivedMsg.Subtopic, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - } -} - -func TestSubscribe(t *testing.T) { - // Creating rabbitmq connection and channel, so that we can publish messages. - conn, ch, err := newConn() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - t.Cleanup(func() { - conn.Close() - ch.Close() - }) - - cases := []struct { - desc string - topic string - clientID string - err error - handler messaging.MessageHandler - }{ - { - desc: "Subscribe to a topic with an ID", - topic: topic, - clientID: "clientid1", - err: nil, - handler: handler{false, "clientid1"}, - }, - { - desc: "Subscribe to the same topic with a different ID", - topic: topic, - clientID: "clientid2", - err: nil, - handler: handler{false, "clientid2"}, - }, - { - desc: "Subscribe to an already subscribed topic with an ID", - topic: topic, - clientID: "clientid1", - err: nil, - handler: handler{false, "clientid1"}, - }, - { - desc: "Subscribe to a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s", topic, subtopic), - clientID: "clientid1", - err: nil, - handler: handler{false, "clientid1"}, - }, - { - desc: "Subscribe to an already subscribed topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s", topic, subtopic), - clientID: "clientid1", - err: nil, - handler: handler{false, "clientid1"}, - }, - { - desc: "Subscribe to an empty topic with an ID", - topic: "", - clientID: "clientid1", - err: rabbitmq.ErrEmptyTopic, - handler: handler{false, "clientid1"}, - }, - { - desc: "Subscribe to a topic with empty id", - topic: topic, - clientID: "", - err: rabbitmq.ErrEmptyID, - handler: handler{false, ""}, - }, - } - for _, tc := range cases { - subCfg := messaging.SubscriberConfig{ - ID: tc.clientID, - Topic: tc.topic, - Handler: tc.handler, - } - err := pubsub.Subscribe(context.TODO(), subCfg) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, tc.err, err)) - - if tc.err == nil { - expectedMsg := messaging.Message{ - Publisher: "CLIENTID", - Channel: channel, - Subtopic: subtopic, - Payload: data, - } - - data, err := proto.Marshal(&expectedMsg) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - err = ch.PublishWithContext( - context.Background(), - exchangeName, - tc.topic, - false, - false, - amqp.Publishing{ - Headers: amqp.Table{}, - ContentType: "application/octet-stream", - AppId: "magistrala-publisher", - Body: data, - }) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - receivedMsg := <-msgChan - assert.Equal(t, expectedMsg.Channel, receivedMsg.Channel, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Created, receivedMsg.Created, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Protocol, receivedMsg.Protocol, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Publisher, receivedMsg.Publisher, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Subtopic, receivedMsg.Subtopic, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - } - } -} - -func TestUnsubscribe(t *testing.T) { - // Test Subscribe and Unsubscribe - cases := []struct { - desc string - topic string - clientID string - err error - subscribe bool // True for subscribe and false for unsubscribe. - handler messaging.MessageHandler - }{ - { - desc: "Subscribe to a topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid4", - err: nil, - subscribe: true, - handler: handler{false, "clientid4"}, - }, - { - desc: "Subscribe to the same topic with a different ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid9", - err: nil, - subscribe: true, - handler: handler{false, "clientid9"}, - }, - { - desc: "Unsubscribe from a topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid4", - err: nil, - subscribe: false, - handler: handler{false, "clientid4"}, - }, - { - desc: "Unsubscribe from same topic with different ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid9", - err: nil, - subscribe: false, - handler: handler{false, "clientid9"}, - }, - { - desc: "Unsubscribe from a non-existent topic with an ID", - topic: "h", - clientID: "clientid4", - err: rabbitmq.ErrNotSubscribed, - subscribe: false, - handler: handler{false, "clientid4"}, - }, - { - desc: "Unsubscribe from an already unsubscribed topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "clientid4", - err: rabbitmq.ErrNotSubscribed, - subscribe: false, - handler: handler{false, "clientid4"}, - }, - { - desc: "Subscribe to a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientidd4", - err: nil, - subscribe: true, - handler: handler{false, "clientidd4"}, - }, - { - desc: "Unsubscribe from a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientidd4", - err: nil, - subscribe: false, - handler: handler{false, "clientidd4"}, - }, - { - desc: "Unsubscribe from an already unsubscribed topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), - clientID: "clientid4", - err: rabbitmq.ErrNotSubscribed, - subscribe: false, - handler: handler{false, "clientid4"}, - }, - { - desc: "Unsubscribe from an empty topic with an ID", - topic: "", - clientID: "clientid4", - err: rabbitmq.ErrEmptyTopic, - subscribe: false, - handler: handler{false, "clientid4"}, - }, - { - desc: "Unsubscribe from a topic with empty ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic), - clientID: "", - err: rabbitmq.ErrEmptyID, - subscribe: false, - handler: handler{false, ""}, - }, - { - desc: "Subscribe to a new topic with an ID", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic+"2"), - clientID: "clientid55", - err: nil, - subscribe: true, - handler: handler{true, "clientid5"}, - }, - { - desc: "Unsubscribe from a topic with an ID with failing handler", - topic: fmt.Sprintf("%s.%s", chansPrefix, topic+"2"), - clientID: "clientid55", - err: errFailedHandleMessage, - subscribe: false, - handler: handler{true, "clientid5"}, - }, - { - desc: "Subscribe to a new topic with subtopic with an ID", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic+"2", subtopic), - clientID: "clientid55", - err: nil, - subscribe: true, - handler: handler{true, "clientid5"}, - }, - { - desc: "Unsubscribe from a topic with subtopic with an ID with failing handler", - topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic+"2", subtopic), - clientID: "clientid55", - err: errFailedHandleMessage, - subscribe: false, - handler: handler{true, "clientid5"}, - }, - } - - for _, tc := range cases { - subCfg := messaging.SubscriberConfig{ - ID: tc.clientID, - Topic: tc.topic, - Handler: tc.handler, - } - switch tc.subscribe { - case true: - err := pubsub.Subscribe(context.TODO(), subCfg) - assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, tc.err, err)) - default: - err := pubsub.Unsubscribe(context.TODO(), tc.clientID, tc.topic) - assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, tc.err, err)) - } - } -} - -func TestPubSub(t *testing.T) { - cases := []struct { - desc string - topic string - clientID string - err error - handler messaging.MessageHandler - }{ - { - desc: "Subscribe to a topic with an ID", - topic: topic, - clientID: clientID, - err: nil, - handler: handler{false, clientID}, - }, - { - desc: "Subscribe to the same topic with a different ID", - topic: topic, - clientID: clientID + "1", - err: nil, - handler: handler{false, clientID + "1"}, - }, - { - desc: "Subscribe to a topic with a subtopic with an ID", - topic: fmt.Sprintf("%s.%s", topic, subtopic), - clientID: clientID + "2", - err: nil, - handler: handler{false, clientID + "2"}, - }, - { - desc: "Subscribe to an empty topic with an ID", - topic: "", - clientID: clientID, - err: rabbitmq.ErrEmptyTopic, - handler: handler{false, clientID}, - }, - { - desc: "Subscribe to a topic with empty id", - topic: topic, - clientID: "", - err: rabbitmq.ErrEmptyID, - handler: handler{false, ""}, - }, - } - for _, tc := range cases { - subject := "" - if tc.topic != "" { - subject = fmt.Sprintf("%s.%s", chansPrefix, tc.topic) - } - subCfg := messaging.SubscriberConfig{ - ID: tc.clientID, - Topic: subject, - Handler: tc.handler, - } - err := pubsub.Subscribe(context.TODO(), subCfg) - - switch tc.err { - case nil: - // If no error, publish message, and receive after subscribing. - expectedMsg := messaging.Message{ - Channel: channel, - Payload: data, - } - - err = pubsub.Publish(context.TODO(), tc.topic, &expectedMsg) - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", tc.desc, err)) - - receivedMsg := <-msgChan - assert.Equal(t, expectedMsg.Channel, receivedMsg.Channel, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - assert.Equal(t, expectedMsg.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) - - err = pubsub.Unsubscribe(context.TODO(), tc.clientID, fmt.Sprintf("%s.%s", chansPrefix, tc.topic)) - assert.Nil(t, err, fmt.Sprintf("%s got unexpected error: %s", tc.desc, err)) - default: - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, err, tc.err)) - } - } -} - -type handler struct { - fail bool - publisher string -} - -func (h handler) Handle(msg *messaging.Message) error { - if msg.GetPublisher() != h.publisher { - msgChan <- msg - } - return nil -} - -func (h handler) Cancel() error { - if h.fail { - return errFailedHandleMessage - } - return nil -} diff --git a/pkg/messaging/rabbitmq/setup_test.go b/pkg/messaging/rabbitmq/setup_test.go deleted file mode 100644 index af8328ac7..000000000 --- a/pkg/messaging/rabbitmq/setup_test.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package rabbitmq_test - -import ( - "fmt" - "log" - "log/slog" - "os" - "os/signal" - "syscall" - "testing" - - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/rabbitmq" - "github.com/ory/dockertest/v3" - amqp "github.com/rabbitmq/amqp091-go" - "github.com/stretchr/testify/assert" - "google.golang.org/protobuf/proto" -) - -const ( - port = "5672/tcp" - brokerName = "rabbitmq" - brokerVersion = "3.12.12-alpine" -) - -var ( - publisher messaging.Publisher - pubsub messaging.PubSub - logger *slog.Logger - address string -) - -func TestMain(m *testing.M) { - pool, err := dockertest.NewPool("") - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - container, err := pool.Run(brokerName, brokerVersion, []string{}) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - handleInterrupt(pool, container) - - address = fmt.Sprintf("amqp://%s:%s", "localhost", container.GetPort(port)) - if err := pool.Retry(func() error { - publisher, err = rabbitmq.NewPublisher(address) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - logger, err = mglog.New(os.Stdout, "debug") - if err != nil { - log.Fatal(err.Error()) - } - if err := pool.Retry(func() error { - pubsub, err = rabbitmq.NewPubSub(address, logger) - return err - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - code := m.Run() - - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} - -func newConn() (*amqp.Connection, *amqp.Channel, error) { - conn, err := amqp.Dial(address) - if err != nil { - return nil, nil, err - } - ch, err := conn.Channel() - if err != nil { - return nil, nil, err - } - if err := ch.ExchangeDeclare(exchangeName, amqp.ExchangeTopic, true, false, false, false, nil); err != nil { - return nil, nil, err - } - - return conn, ch, nil -} - -func rabbitHandler(deliveries <-chan amqp.Delivery, h messaging.MessageHandler) { - for d := range deliveries { - var msg messaging.Message - if err := proto.Unmarshal(d.Body, &msg); err != nil { - logger.Warn(fmt.Sprintf("Failed to unmarshal received message: %s", err)) - return - } - if err := h.Handle(&msg); err != nil { - logger.Warn(fmt.Sprintf("Failed to handle Magistrala message: %s", err)) - return - } - } -} - -func subscribe(t *testing.T, ch *amqp.Channel, topic string) <-chan amqp.Delivery { - _, err := ch.QueueDeclare(topic, true, true, true, false, nil) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - err = ch.QueueBind(topic, topic, exchangeName, false, nil) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - clientID := fmt.Sprintf("%s-%s", topic, clientID) - msgs, err := ch.Consume(topic, clientID, true, false, false, false, nil) - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - return msgs -} - -func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) { - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - os.Exit(0) - }() -} diff --git a/pkg/messaging/rabbitmq/tracing/doc.go b/pkg/messaging/rabbitmq/tracing/doc.go deleted file mode 100644 index 5f8df0d9e..000000000 --- a/pkg/messaging/rabbitmq/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala things policies service. -// -// This package provides tracing middleware for Magistrala things policies service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala things policies 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/pkg/messaging/rabbitmq/tracing/publisher.go b/pkg/messaging/rabbitmq/tracing/publisher.go deleted file mode 100644 index 6998bf889..000000000 --- a/pkg/messaging/rabbitmq/tracing/publisher.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/tracing" - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -// Traced operations. -const publishOP = "publish" - -var defaultAttributes = []attribute.KeyValue{ - attribute.String("messaging.system", "rabbitmq"), - attribute.String("network.protocol.name", "amqp"), - attribute.String("network.protocol.version", "3.9.20"), - attribute.String("messaging.rabbitmq.destination.routing_key", "magistrala"), -} - -var _ messaging.Publisher = (*publisherMiddleware)(nil) - -type publisherMiddleware struct { - publisher messaging.Publisher - tracer trace.Tracer - host server.Config -} - -func NewPublisher(config server.Config, tracer trace.Tracer, publisher messaging.Publisher) messaging.Publisher { - pub := &publisherMiddleware{ - publisher: publisher, - tracer: tracer, - host: config, - } - - return pub -} - -func (pm *publisherMiddleware) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - ctx, span := tracing.CreateSpan(ctx, publishOP, msg.GetPublisher(), topic, msg.GetSubtopic(), len(msg.GetPayload()), pm.host, trace.SpanKindClient, pm.tracer) - defer span.End() - - span.SetAttributes(defaultAttributes...) - - return pm.publisher.Publish(ctx, topic, msg) -} - -func (pm *publisherMiddleware) Close() error { - return pm.publisher.Close() -} diff --git a/pkg/messaging/rabbitmq/tracing/pubsub.go b/pkg/messaging/rabbitmq/tracing/pubsub.go deleted file mode 100644 index c8f6b0cf7..000000000 --- a/pkg/messaging/rabbitmq/tracing/pubsub.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/messaging/tracing" - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/otel/trace" -) - -// Constants to define different operations to be traced. -const ( - subscribeOP = "receive" - unsubscribeOp = "unsubscribe" // This is not specified in the open telemetry spec. - processOp = "process" -) - -var _ messaging.PubSub = (*pubsubMiddleware)(nil) - -type pubsubMiddleware struct { - publisherMiddleware - pubsub messaging.PubSub - host server.Config -} - -// NewPubSub creates a new pubsub middleware that traces pubsub operations. -func NewPubSub(config server.Config, tracer trace.Tracer, pubsub messaging.PubSub) messaging.PubSub { - pb := &pubsubMiddleware{ - publisherMiddleware: publisherMiddleware{ - publisher: pubsub, - tracer: tracer, - host: config, - }, - pubsub: pubsub, - host: config, - } - - return pb -} - -// Subscribe creates a new subscription and traces the operation. -func (pm *pubsubMiddleware) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) error { - ctx, span := tracing.CreateSpan(ctx, subscribeOP, cfg.ID, cfg.Topic, "", 0, pm.host, trace.SpanKindClient, pm.tracer) - defer span.End() - - span.SetAttributes(defaultAttributes...) - - cfg.Handler = &traceHandler{ - ctx: ctx, - handler: cfg.Handler, - tracer: pm.tracer, - host: pm.host, - topic: cfg.Topic, - clientID: cfg.ID, - } - - return pm.pubsub.Subscribe(ctx, cfg) -} - -// Unsubscribe removes an existing subscription and traces the operation. -func (pm *pubsubMiddleware) Unsubscribe(ctx context.Context, id, topic string) error { - ctx, span := tracing.CreateSpan(ctx, unsubscribeOp, id, topic, "", 0, pm.host, trace.SpanKindInternal, pm.tracer) - defer span.End() - - span.SetAttributes(defaultAttributes...) - - return pm.pubsub.Unsubscribe(ctx, id, topic) -} - -// TraceHandler is used to trace the message handling operation. -type traceHandler struct { - ctx context.Context - handler messaging.MessageHandler - tracer trace.Tracer - host server.Config - topic string - clientID string -} - -// Handle instruments the message handling operation. -func (h *traceHandler) Handle(msg *messaging.Message) error { - _, span := tracing.CreateSpan(h.ctx, processOp, h.clientID, h.topic, msg.GetSubtopic(), len(msg.GetPayload()), h.host, trace.SpanKindConsumer, h.tracer) - defer span.End() - - span.SetAttributes(defaultAttributes...) - - return h.handler.Handle(msg) -} - -// Cancel cancels the message handling operation. -func (h *traceHandler) Cancel() error { - return h.handler.Cancel() -} diff --git a/pkg/messaging/tracing/doc.go b/pkg/messaging/tracing/doc.go deleted file mode 100644 index 5f8df0d9e..000000000 --- a/pkg/messaging/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala things policies service. -// -// This package provides tracing middleware for Magistrala things policies service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala things policies 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/pkg/messaging/tracing/tracing.go b/pkg/messaging/tracing/tracing.go deleted file mode 100644 index e3b925149..000000000 --- a/pkg/messaging/tracing/tracing.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package tracing - -import ( - "context" - "fmt" - - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var defaultAttributes = []attribute.KeyValue{ - attribute.Bool("messaging.destination.anonymous", false), - attribute.String("messaging.destination.template", "channels/{channelID}/messages/*"), - attribute.Bool("messaging.destination.temporary", true), - attribute.String("network.transport", "tcp"), - attribute.String("network.type", "ipv4"), -} - -func CreateSpan(ctx context.Context, operation, clientID, topic, subTopic string, msgSize int, cfg server.Config, spanKind trace.SpanKind, tracer trace.Tracer) (context.Context, trace.Span) { - subject := fmt.Sprintf("channels.%s.messages", topic) - if subTopic != "" { - subject = fmt.Sprintf("%s.%s", subject, subTopic) - } - spanName := fmt.Sprintf("%s %s", subject, operation) - - kvOpts := []attribute.KeyValue{ - attribute.String("messaging.operation", operation), - attribute.String("messaging.client_id", clientID), - attribute.String("messaging.destination.name", subject), - attribute.String("server.address", cfg.Host), - attribute.String("server.socket.port", cfg.Port), - } - - if msgSize > 0 { - kvOpts = append(kvOpts, attribute.Int("messaging.message.payload_size_bytes", msgSize)) - } - - kvOpts = append(kvOpts, defaultAttributes...) - - return tracer.Start(ctx, spanName, trace.WithAttributes(kvOpts...), trace.WithSpanKind(spanKind)) -} diff --git a/pkg/oauth2/doc.go b/pkg/oauth2/doc.go deleted file mode 100644 index 2d7e006f5..000000000 --- a/pkg/oauth2/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package oauth2 contains the domain concept definitions needed to support -// Magistrala ui service OAuth2 functionality. -package oauth2 diff --git a/pkg/oauth2/google/doc.go b/pkg/oauth2/google/doc.go deleted file mode 100644 index 74f7ada5e..000000000 --- a/pkg/oauth2/google/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package google contains the domain concept definitions needed to support -// Magistrala services for Google OAuth2 functionality. -package google diff --git a/pkg/oauth2/google/provider.go b/pkg/oauth2/google/provider.go deleted file mode 100644 index 0c3c531c8..000000000 --- a/pkg/oauth2/google/provider.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package google - -import ( - "context" - "encoding/json" - "io" - "net/http" - "net/url" - "time" - - svcerr "github.com/absmach/magistrala/pkg/errors/service" - mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" - uclient "github.com/absmach/magistrala/users" - "golang.org/x/oauth2" - googleoauth2 "golang.org/x/oauth2/google" -) - -const ( - providerName = "google" - defTimeout = 1 * time.Minute - userInfoURL = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" - tokenInfoURL = "https://oauth2.googleapis.com/tokeninfo?access_token=" -) - -var scopes = []string{ - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/userinfo.profile", -} - -var _ mgoauth2.Provider = (*config)(nil) - -type config struct { - config *oauth2.Config - state string - uiRedirectURL string - errorURL string -} - -// NewProvider returns a new Google OAuth provider. -func NewProvider(cfg mgoauth2.Config, uiRedirectURL, errorURL string) mgoauth2.Provider { - return &config{ - config: &oauth2.Config{ - ClientID: cfg.ClientID, - ClientSecret: cfg.ClientSecret, - Endpoint: googleoauth2.Endpoint, - RedirectURL: cfg.RedirectURL, - Scopes: scopes, - }, - state: cfg.State, - uiRedirectURL: uiRedirectURL, - errorURL: errorURL, - } -} - -func (cfg *config) Name() string { - return providerName -} - -func (cfg *config) State() string { - return cfg.state -} - -func (cfg *config) RedirectURL() string { - return cfg.uiRedirectURL -} - -func (cfg *config) ErrorURL() string { - return cfg.errorURL -} - -func (cfg *config) IsEnabled() bool { - return cfg.config.ClientID != "" && cfg.config.ClientSecret != "" -} - -func (cfg *config) Exchange(ctx context.Context, code string) (oauth2.Token, error) { - token, err := cfg.config.Exchange(ctx, code) - if err != nil { - return oauth2.Token{}, err - } - - return *token, nil -} - -func (cfg *config) UserInfo(accessToken string) (uclient.User, error) { - resp, err := http.Get(userInfoURL + url.QueryEscape(accessToken)) - if err != nil { - return uclient.User{}, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return uclient.User{}, svcerr.ErrAuthentication - } - - data, err := io.ReadAll(resp.Body) - if err != nil { - return uclient.User{}, err - } - - var user struct { - ID string `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Username string `json:"username"` - Email string `json:"email"` - Picture string `json:"picture"` - } - if err := json.Unmarshal(data, &user); err != nil { - return uclient.User{}, err - } - - if user.ID == "" || user.FirstName == "" || user.LastName == "" || user.Email == "" { - return uclient.User{}, svcerr.ErrAuthentication - } - - client := uclient.User{ - ID: user.ID, - FirstName: user.FirstName, - LastName: user.LastName, - Email: user.Email, - Metadata: map[string]interface{}{ - "oauth_provider": providerName, - "profile_picture": user.Picture, - }, - Status: uclient.EnabledStatus, - } - - return client, nil -} diff --git a/pkg/oauth2/mocks/provider.go b/pkg/oauth2/mocks/provider.go deleted file mode 100644 index 1f911984e..000000000 --- a/pkg/oauth2/mocks/provider.go +++ /dev/null @@ -1,180 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - users "github.com/absmach/magistrala/users" - - xoauth2 "golang.org/x/oauth2" -) - -// Provider is an autogenerated mock type for the Provider type -type Provider struct { - mock.Mock -} - -// ErrorURL provides a mock function with given fields: -func (_m *Provider) ErrorURL() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for ErrorURL") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Exchange provides a mock function with given fields: ctx, code -func (_m *Provider) Exchange(ctx context.Context, code string) (xoauth2.Token, error) { - ret := _m.Called(ctx, code) - - if len(ret) == 0 { - panic("no return value specified for Exchange") - } - - var r0 xoauth2.Token - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (xoauth2.Token, error)); ok { - return rf(ctx, code) - } - if rf, ok := ret.Get(0).(func(context.Context, string) xoauth2.Token); ok { - r0 = rf(ctx, code) - } else { - r0 = ret.Get(0).(xoauth2.Token) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, code) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// IsEnabled provides a mock function with given fields: -func (_m *Provider) IsEnabled() bool { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for IsEnabled") - } - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// Name provides a mock function with given fields: -func (_m *Provider) Name() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Name") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// RedirectURL provides a mock function with given fields: -func (_m *Provider) RedirectURL() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for RedirectURL") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// State provides a mock function with given fields: -func (_m *Provider) State() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for State") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// UserInfo provides a mock function with given fields: accessToken -func (_m *Provider) UserInfo(accessToken string) (users.User, error) { - ret := _m.Called(accessToken) - - if len(ret) == 0 { - panic("no return value specified for UserInfo") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(string) (users.User, error)); ok { - return rf(accessToken) - } - if rf, ok := ret.Get(0).(func(string) users.User); ok { - r0 = rf(accessToken) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(accessToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewProvider creates a new instance of Provider. 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 NewProvider(t interface { - mock.TestingT - Cleanup(func()) -}) *Provider { - mock := &Provider{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/oauth2/oauth2.go b/pkg/oauth2/oauth2.go deleted file mode 100644 index f788ef9f1..000000000 --- a/pkg/oauth2/oauth2.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package oauth2 - -import ( - "context" - - "github.com/absmach/magistrala/users" - "golang.org/x/oauth2" -) - -// Config is the configuration for the OAuth2 provider. -type Config struct { - ClientID string `env:"CLIENT_ID" envDefault:""` - ClientSecret string `env:"CLIENT_SECRET" envDefault:""` - State string `env:"STATE" envDefault:""` - RedirectURL string `env:"REDIRECT_URL" envDefault:""` -} - -// Provider is an interface that provides the OAuth2 flow for a specific provider -// (e.g. Google, GitHub, etc.) -// -//go:generate mockery --name Provider --output=./mocks --filename provider.go --quiet --note "Copyright (c) Abstract Machines" -type Provider interface { - // Name returns the name of the OAuth2 provider. - Name() string - - // State returns the current state for the OAuth2 flow. - State() string - - // RedirectURL returns the URL to redirect the user to after completing the OAuth2 flow. - RedirectURL() string - - // ErrorURL returns the URL to redirect the user to in case of an error during the OAuth2 flow. - ErrorURL() string - - // IsEnabled checks if the OAuth2 provider is enabled. - IsEnabled() bool - - // Exchange converts an authorization code into a token. - Exchange(ctx context.Context, code string) (oauth2.Token, error) - - // UserInfo retrieves the user's information using the access token. - UserInfo(accessToken string) (users.User, error) -} diff --git a/pkg/policies/doc.go b/pkg/policies/doc.go deleted file mode 100644 index 59958f849..000000000 --- a/pkg/policies/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package policies contains Magistrala policy definitions. -package policies diff --git a/pkg/policies/evaluator.go b/pkg/policies/evaluator.go deleted file mode 100644 index c6288697c..000000000 --- a/pkg/policies/evaluator.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package policies - -import ( - "context" -) - -const ( - TokenKind = "token" - GroupsKind = "groups" - NewGroupKind = "new_group" - ChannelsKind = "channels" - NewChannelKind = "new_channel" - ThingsKind = "things" - NewThingKind = "new_thing" - UsersKind = "users" - DomainsKind = "domains" - PlatformKind = "platform" -) - -const ( - GroupType = "group" - ThingType = "thing" - UserType = "user" - DomainType = "domain" - PlatformType = "platform" -) - -const ( - AdministratorRelation = "administrator" - EditorRelation = "editor" - ContributorRelation = "contributor" - MemberRelation = "member" - DomainRelation = "domain" - ParentGroupRelation = "parent_group" - RoleGroupRelation = "role_group" - GroupRelation = "group" - PlatformRelation = "platform" - GuestRelation = "guest" -) - -const ( - AdminPermission = "admin" - DeletePermission = "delete" - EditPermission = "edit" - ViewPermission = "view" - MembershipPermission = "membership" - SharePermission = "share" - PublishPermission = "publish" - SubscribePermission = "subscribe" - CreatePermission = "create" -) - -const MagistralaObject = "magistrala" - -//go:generate mockery --name Evaluator --output=./mocks --filename evaluator.go --quiet --note "Copyright (c) Abstract Machines" -type Evaluator interface { - // CheckPolicy checks if the subject has a relation on the object. - // It returns a non-nil error if the subject has no relation on - // the object (which simply means the operation is denied). - CheckPolicy(ctx context.Context, pr Policy) error -} diff --git a/pkg/policies/mocks/evaluator.go b/pkg/policies/mocks/evaluator.go deleted file mode 100644 index 82afcc378..000000000 --- a/pkg/policies/mocks/evaluator.go +++ /dev/null @@ -1,49 +0,0 @@ -// 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" -) - -// Evaluator is an autogenerated mock type for the Evaluator type -type Evaluator struct { - mock.Mock -} - -// CheckPolicy provides a mock function with given fields: ctx, pr -func (_m *Evaluator) CheckPolicy(ctx context.Context, pr policies.Policy) error { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for CheckPolicy") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) error); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewEvaluator creates a new instance of Evaluator. 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 NewEvaluator(t interface { - mock.TestingT - Cleanup(func()) -}) *Evaluator { - mock := &Evaluator{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/policies/mocks/service.go b/pkg/policies/mocks/service.go deleted file mode 100644 index 7cfddcc8b..000000000 --- a/pkg/policies/mocks/service.go +++ /dev/null @@ -1,301 +0,0 @@ -// 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" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// AddPolicies provides a mock function with given fields: ctx, prs -func (_m *Service) AddPolicies(ctx context.Context, prs []policies.Policy) error { - ret := _m.Called(ctx, prs) - - if len(ret) == 0 { - panic("no return value specified for AddPolicies") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []policies.Policy) error); ok { - r0 = rf(ctx, prs) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// AddPolicy provides a mock function with given fields: ctx, pr -func (_m *Service) AddPolicy(ctx context.Context, pr policies.Policy) error { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for AddPolicy") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) error); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// CountObjects provides a mock function with given fields: ctx, pr -func (_m *Service) CountObjects(ctx context.Context, pr policies.Policy) (uint64, error) { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for CountObjects") - } - - var r0 uint64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) (uint64, error)); ok { - return rf(ctx, pr) - } - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) uint64); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Get(0).(uint64) - } - - if rf, ok := ret.Get(1).(func(context.Context, policies.Policy) error); ok { - r1 = rf(ctx, pr) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CountSubjects provides a mock function with given fields: ctx, pr -func (_m *Service) CountSubjects(ctx context.Context, pr policies.Policy) (uint64, error) { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for CountSubjects") - } - - var r0 uint64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) (uint64, error)); ok { - return rf(ctx, pr) - } - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) uint64); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Get(0).(uint64) - } - - if rf, ok := ret.Get(1).(func(context.Context, policies.Policy) error); ok { - r1 = rf(ctx, pr) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DeletePolicies provides a mock function with given fields: ctx, prs -func (_m *Service) DeletePolicies(ctx context.Context, prs []policies.Policy) error { - ret := _m.Called(ctx, prs) - - if len(ret) == 0 { - panic("no return value specified for DeletePolicies") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []policies.Policy) error); ok { - r0 = rf(ctx, prs) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// DeletePolicyFilter provides a mock function with given fields: ctx, pr -func (_m *Service) DeletePolicyFilter(ctx context.Context, pr policies.Policy) error { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for DeletePolicyFilter") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) error); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ListAllObjects provides a mock function with given fields: ctx, pr -func (_m *Service) ListAllObjects(ctx context.Context, pr policies.Policy) (policies.PolicyPage, error) { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for ListAllObjects") - } - - var r0 policies.PolicyPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) (policies.PolicyPage, error)); ok { - return rf(ctx, pr) - } - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) policies.PolicyPage); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Get(0).(policies.PolicyPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, policies.Policy) error); ok { - r1 = rf(ctx, pr) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListAllSubjects provides a mock function with given fields: ctx, pr -func (_m *Service) ListAllSubjects(ctx context.Context, pr policies.Policy) (policies.PolicyPage, error) { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for ListAllSubjects") - } - - var r0 policies.PolicyPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) (policies.PolicyPage, error)); ok { - return rf(ctx, pr) - } - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy) policies.PolicyPage); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Get(0).(policies.PolicyPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, policies.Policy) error); ok { - r1 = rf(ctx, pr) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListObjects provides a mock function with given fields: ctx, pr, nextPageToken, limit -func (_m *Service) ListObjects(ctx context.Context, pr policies.Policy, nextPageToken string, limit uint64) (policies.PolicyPage, error) { - ret := _m.Called(ctx, pr, nextPageToken, limit) - - if len(ret) == 0 { - panic("no return value specified for ListObjects") - } - - var r0 policies.PolicyPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy, string, uint64) (policies.PolicyPage, error)); ok { - return rf(ctx, pr, nextPageToken, limit) - } - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy, string, uint64) policies.PolicyPage); ok { - r0 = rf(ctx, pr, nextPageToken, limit) - } else { - r0 = ret.Get(0).(policies.PolicyPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, policies.Policy, string, uint64) error); ok { - r1 = rf(ctx, pr, nextPageToken, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListPermissions provides a mock function with given fields: ctx, pr, permissionsFilter -func (_m *Service) ListPermissions(ctx context.Context, pr policies.Policy, permissionsFilter []string) (policies.Permissions, error) { - ret := _m.Called(ctx, pr, permissionsFilter) - - if len(ret) == 0 { - panic("no return value specified for ListPermissions") - } - - var r0 policies.Permissions - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy, []string) (policies.Permissions, error)); ok { - return rf(ctx, pr, permissionsFilter) - } - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy, []string) policies.Permissions); ok { - r0 = rf(ctx, pr, permissionsFilter) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(policies.Permissions) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, policies.Policy, []string) error); ok { - r1 = rf(ctx, pr, permissionsFilter) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListSubjects provides a mock function with given fields: ctx, pr, nextPageToken, limit -func (_m *Service) ListSubjects(ctx context.Context, pr policies.Policy, nextPageToken string, limit uint64) (policies.PolicyPage, error) { - ret := _m.Called(ctx, pr, nextPageToken, limit) - - if len(ret) == 0 { - panic("no return value specified for ListSubjects") - } - - var r0 policies.PolicyPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy, string, uint64) (policies.PolicyPage, error)); ok { - return rf(ctx, pr, nextPageToken, limit) - } - if rf, ok := ret.Get(0).(func(context.Context, policies.Policy, string, uint64) policies.PolicyPage); ok { - r0 = rf(ctx, pr, nextPageToken, limit) - } else { - r0 = ret.Get(0).(policies.PolicyPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, policies.Policy, string, uint64) error); ok { - r1 = rf(ctx, pr, nextPageToken, limit) - } 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/policies/service.go b/pkg/policies/service.go deleted file mode 100644 index 446926c13..000000000 --- a/pkg/policies/service.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package policies - -import ( - "context" - "encoding/json" -) - -type Policy struct { - // Domain contains the domain ID. - Domain string `json:"domain,omitempty"` - - // Subject contains the subject ID or Token. - Subject string `json:"subject"` - - // SubjectType contains the subject type. Supported subject types are - // platform, group, domain, thing, users. - SubjectType string `json:"subject_type"` - - // SubjectKind contains the subject kind. Supported subject kinds are - // token, users, platform, things, channels, groups, domain. - SubjectKind string `json:"subject_kind"` - - // SubjectRelation contains subject relations. - SubjectRelation string `json:"subject_relation,omitempty"` - - // Object contains the object ID. - Object string `json:"object"` - - // ObjectKind contains the object kind. Supported object kinds are - // users, platform, things, channels, groups, domain. - ObjectKind string `json:"object_kind"` - - // ObjectType contains the object type. Supported object types are - // platform, group, domain, thing, users. - ObjectType string `json:"object_type"` - - // Relation contains the relation. Supported relations are administrator, editor, contributor, member, guest, parent_group,group,domain. - Relation string `json:"relation,omitempty"` - - // Permission contains the permission. Supported permissions are admin, delete, edit, share, view, - // membership, create, admin_only, edit_only, view_only, membership_only, ext_admin, ext_edit, ext_view. - Permission string `json:"permission,omitempty"` -} - -func (pr Policy) String() string { - data, err := json.Marshal(pr) - if err != nil { - return "" - } - return string(data) -} - -type PolicyPage struct { - Policies []string - NextPageToken string -} - -type Permissions []string - -// PolicyService facilitates the communication to authorization -// services and implements Authz functionalities for spicedb -// -//go:generate mockery --name Service --filename service.go --quiet --note "Copyright (c) Abstract Machines" -type Service interface { - // AddPolicy creates a policy for the given subject, so that, after - // AddPolicy, `subject` has a `relation` on `object`. Returns a non-nil - // error in case of failures. - AddPolicy(ctx context.Context, pr Policy) error - - // AddPolicies adds new policies for given subjects. This method is - // only allowed to use as an admin. - AddPolicies(ctx context.Context, prs []Policy) error - - // DeletePolicyFilter removes policy for given policy filter request. - DeletePolicyFilter(ctx context.Context, pr Policy) error - - // DeletePolicies deletes policies for given subjects. This method is - // only allowed to use as an admin. - DeletePolicies(ctx context.Context, prs []Policy) error - - // ListObjects lists policies based on the given Policy structure. - ListObjects(ctx context.Context, pr Policy, nextPageToken string, limit uint64) (PolicyPage, error) - - // ListAllObjects lists all policies based on the given Policy structure. - ListAllObjects(ctx context.Context, pr Policy) (PolicyPage, error) - - // CountObjects count policies based on the given Policy structure. - CountObjects(ctx context.Context, pr Policy) (uint64, error) - - // ListSubjects lists subjects based on the given Policy structure. - ListSubjects(ctx context.Context, pr Policy, nextPageToken string, limit uint64) (PolicyPage, error) - - // ListAllSubjects lists all subjects based on the given Policy structure. - ListAllSubjects(ctx context.Context, pr Policy) (PolicyPage, error) - - // CountSubjects count policies based on the given Policy structure. - CountSubjects(ctx context.Context, pr Policy) (uint64, error) - - // ListPermissions lists permission betweeen given subject and object . - ListPermissions(ctx context.Context, pr Policy, permissionsFilter []string) (Permissions, error) -} diff --git a/pkg/policies/spicedb/doc.go b/pkg/policies/spicedb/doc.go deleted file mode 100644 index beac26947..000000000 --- a/pkg/policies/spicedb/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package server contains the HTTP, gRPC and CoAP server implementation. -package spicedb diff --git a/pkg/policies/spicedb/evaluator.go b/pkg/policies/spicedb/evaluator.go deleted file mode 100644 index e40b7207b..000000000 --- a/pkg/policies/spicedb/evaluator.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package spicedb - -import ( - "context" - "log/slog" - - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/policies" - v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" - "github.com/authzed/authzed-go/v1" -) - -type policyEvaluator struct { - client *authzed.ClientWithExperimental - permissionClient v1.PermissionsServiceClient - logger *slog.Logger -} - -func NewPolicyEvaluator(client *authzed.ClientWithExperimental, logger *slog.Logger) policies.Evaluator { - return &policyEvaluator{ - client: client, - permissionClient: client.PermissionsServiceClient, - logger: logger, - } -} - -func (pe *policyEvaluator) CheckPolicy(ctx context.Context, pr policies.Policy) error { - checkReq := v1.CheckPermissionRequest{ - // FullyConsistent means little caching will be available, which means performance will suffer. - // Only use if a ZedToken is not available or absolutely latest information is required. - // If we want to avoid FullyConsistent and to improve the performance of spicedb, then we need to cache the ZEDTOKEN whenever RELATIONS is created or updated. - // Instead of using FullyConsistent we need to use Consistency_AtLeastAsFresh, code looks like below one. - // Consistency: &v1.Consistency{ - // Requirement: &v1.Consistency_AtLeastAsFresh{ - // AtLeastAsFresh: getRelationTupleZedTokenFromCache() , - // } - // }, - // Reference: https://authzed.com/docs/reference/api-consistency - Consistency: &v1.Consistency{ - Requirement: &v1.Consistency_FullyConsistent{ - FullyConsistent: true, - }, - }, - Resource: &v1.ObjectReference{ObjectType: pr.ObjectType, ObjectId: pr.Object}, - Permission: pr.Permission, - Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: pr.SubjectType, ObjectId: pr.Subject}, OptionalRelation: pr.SubjectRelation}, - } - - resp, err := pe.permissionClient.CheckPermission(ctx, &checkReq) - if err != nil { - return handleSpicedbError(err) - } - if resp.Permissionship == v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { - return nil - } - if reason, ok := v1.CheckPermissionResponse_Permissionship_name[int32(resp.Permissionship)]; ok { - return errors.Wrap(svcerr.ErrAuthorization, errors.New(reason)) - } - return svcerr.ErrAuthorization -} diff --git a/pkg/policies/spicedb/service.go b/pkg/policies/spicedb/service.go deleted file mode 100644 index 6abbf5965..000000000 --- a/pkg/policies/spicedb/service.go +++ /dev/null @@ -1,950 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package spicedb - -import ( - "context" - "fmt" - "io" - "log/slog" - - "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" - v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" - "github.com/authzed/authzed-go/v1" - gstatus "google.golang.org/genproto/googleapis/rpc/status" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -const defRetrieveAllLimit = 1000 - -var ( - errInvalidSubject = errors.New("invalid subject kind") - errAddPolicies = errors.New("failed to add policies") - errRetrievePolicies = errors.New("failed to retrieve policies") - errRemovePolicies = errors.New("failed to remove the policies") - errNoPolicies = errors.New("no policies provided") - errInternal = errors.New("spicedb internal error") - errPlatform = errors.New("invalid platform id") -) - -var ( - defThingsFilterPermissions = []string{ - policies.AdminPermission, - policies.DeletePermission, - policies.EditPermission, - policies.ViewPermission, - policies.SharePermission, - policies.PublishPermission, - policies.SubscribePermission, - } - - defGroupsFilterPermissions = []string{ - policies.AdminPermission, - policies.DeletePermission, - policies.EditPermission, - policies.ViewPermission, - policies.MembershipPermission, - policies.SharePermission, - } - - defDomainsFilterPermissions = []string{ - policies.AdminPermission, - policies.EditPermission, - policies.ViewPermission, - policies.MembershipPermission, - policies.SharePermission, - } - - defPlatformFilterPermissions = []string{ - policies.AdminPermission, - policies.MembershipPermission, - } -) - -type policyService struct { - client *authzed.ClientWithExperimental - permissionClient v1.PermissionsServiceClient - logger *slog.Logger -} - -func NewPolicyService(client *authzed.ClientWithExperimental, logger *slog.Logger) policies.Service { - return &policyService{ - client: client, - permissionClient: client.PermissionsServiceClient, - logger: logger, - } -} - -func (ps *policyService) AddPolicy(ctx context.Context, pr policies.Policy) error { - if err := ps.policyValidation(pr); err != nil { - return errors.Wrap(svcerr.ErrInvalidPolicy, err) - } - precond, err := ps.addPolicyPreCondition(ctx, pr) - if err != nil { - return err - } - - updates := []*v1.RelationshipUpdate{ - { - Operation: v1.RelationshipUpdate_OPERATION_CREATE, - Relationship: &v1.Relationship{ - Resource: &v1.ObjectReference{ObjectType: pr.ObjectType, ObjectId: pr.Object}, - Relation: pr.Relation, - Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: pr.SubjectType, ObjectId: pr.Subject}, OptionalRelation: pr.SubjectRelation}, - }, - }, - } - _, err = ps.permissionClient.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{Updates: updates, OptionalPreconditions: precond}) - if err != nil { - return errors.Wrap(errAddPolicies, handleSpicedbError(err)) - } - - return nil -} - -func (ps *policyService) AddPolicies(ctx context.Context, prs []policies.Policy) error { - updates := []*v1.RelationshipUpdate{} - var preconds []*v1.Precondition - for _, pr := range prs { - if err := ps.policyValidation(pr); err != nil { - return errors.Wrap(svcerr.ErrInvalidPolicy, err) - } - precond, err := ps.addPolicyPreCondition(ctx, pr) - if err != nil { - return err - } - preconds = append(preconds, precond...) - updates = append(updates, &v1.RelationshipUpdate{ - Operation: v1.RelationshipUpdate_OPERATION_CREATE, - Relationship: &v1.Relationship{ - Resource: &v1.ObjectReference{ObjectType: pr.ObjectType, ObjectId: pr.Object}, - Relation: pr.Relation, - Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: pr.SubjectType, ObjectId: pr.Subject}, OptionalRelation: pr.SubjectRelation}, - }, - }) - } - if len(updates) == 0 { - return errors.Wrap(errors.ErrMalformedEntity, errNoPolicies) - } - _, err := ps.permissionClient.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{Updates: updates, OptionalPreconditions: preconds}) - if err != nil { - return errors.Wrap(errAddPolicies, handleSpicedbError(err)) - } - - return nil -} - -func (ps *policyService) DeletePolicyFilter(ctx context.Context, pr policies.Policy) error { - req := &v1.DeleteRelationshipsRequest{ - RelationshipFilter: &v1.RelationshipFilter{ - ResourceType: pr.ObjectType, - OptionalResourceId: pr.Object, - }, - } - - if pr.Relation != "" { - req.RelationshipFilter.OptionalRelation = pr.Relation - } - - if pr.SubjectType != "" { - req.RelationshipFilter.OptionalSubjectFilter = &v1.SubjectFilter{ - SubjectType: pr.SubjectType, - } - if pr.Subject != "" { - req.RelationshipFilter.OptionalSubjectFilter.OptionalSubjectId = pr.Subject - } - if pr.SubjectRelation != "" { - req.RelationshipFilter.OptionalSubjectFilter.OptionalRelation = &v1.SubjectFilter_RelationFilter{ - Relation: pr.SubjectRelation, - } - } - } - - if _, err := ps.permissionClient.DeleteRelationships(ctx, req); err != nil { - return errors.Wrap(errRemovePolicies, handleSpicedbError(err)) - } - - return nil -} - -func (ps *policyService) DeletePolicies(ctx context.Context, prs []policies.Policy) error { - updates := []*v1.RelationshipUpdate{} - for _, pr := range prs { - if err := ps.policyValidation(pr); err != nil { - return errors.Wrap(svcerr.ErrInvalidPolicy, err) - } - updates = append(updates, &v1.RelationshipUpdate{ - Operation: v1.RelationshipUpdate_OPERATION_DELETE, - Relationship: &v1.Relationship{ - Resource: &v1.ObjectReference{ObjectType: pr.ObjectType, ObjectId: pr.Object}, - Relation: pr.Relation, - Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: pr.SubjectType, ObjectId: pr.Subject}, OptionalRelation: pr.SubjectRelation}, - }, - }) - } - if len(updates) == 0 { - return errors.Wrap(errors.ErrMalformedEntity, errNoPolicies) - } - _, err := ps.permissionClient.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{Updates: updates}) - if err != nil { - return errors.Wrap(errRemovePolicies, handleSpicedbError(err)) - } - - return nil -} - -func (ps *policyService) ListObjects(ctx context.Context, pr policies.Policy, nextPageToken string, limit uint64) (policies.PolicyPage, error) { - if limit <= 0 { - limit = 100 - } - res, npt, err := ps.retrieveObjects(ctx, pr, nextPageToken, limit) - if err != nil { - return policies.PolicyPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - var page policies.PolicyPage - for _, tuple := range res { - page.Policies = append(page.Policies, tuple.Object) - } - page.NextPageToken = npt - - return page, nil -} - -func (ps *policyService) ListAllObjects(ctx context.Context, pr policies.Policy) (policies.PolicyPage, error) { - res, err := ps.retrieveAllObjects(ctx, pr) - if err != nil { - return policies.PolicyPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - var page policies.PolicyPage - for _, tuple := range res { - page.Policies = append(page.Policies, tuple.Object) - } - - return page, nil -} - -func (ps *policyService) CountObjects(ctx context.Context, pr policies.Policy) (uint64, error) { - var count uint64 - nextPageToken := "" - for { - relationTuples, npt, err := ps.retrieveObjects(ctx, pr, nextPageToken, defRetrieveAllLimit) - if err != nil { - return count, err - } - count = count + uint64(len(relationTuples)) - if npt == "" { - break - } - nextPageToken = npt - } - - return count, nil -} - -func (ps *policyService) ListSubjects(ctx context.Context, pr policies.Policy, nextPageToken string, limit uint64) (policies.PolicyPage, error) { - if limit <= 0 { - limit = 100 - } - res, npt, err := ps.retrieveSubjects(ctx, pr, nextPageToken, limit) - if err != nil { - return policies.PolicyPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - var page policies.PolicyPage - for _, tuple := range res { - page.Policies = append(page.Policies, tuple.Subject) - } - page.NextPageToken = npt - - return page, nil -} - -func (ps *policyService) ListAllSubjects(ctx context.Context, pr policies.Policy) (policies.PolicyPage, error) { - res, err := ps.retrieveAllSubjects(ctx, pr) - if err != nil { - return policies.PolicyPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - var page policies.PolicyPage - for _, tuple := range res { - page.Policies = append(page.Policies, tuple.Subject) - } - - return page, nil -} - -func (ps *policyService) CountSubjects(ctx context.Context, pr policies.Policy) (uint64, error) { - var count uint64 - nextPageToken := "" - for { - relationTuples, npt, err := ps.retrieveSubjects(ctx, pr, nextPageToken, defRetrieveAllLimit) - if err != nil { - return count, err - } - count = count + uint64(len(relationTuples)) - if npt == "" { - break - } - nextPageToken = npt - } - - return count, nil -} - -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.GroupType: - permissionsFilter = defGroupsFilterPermissions - case policies.PlatformType: - permissionsFilter = defPlatformFilterPermissions - case policies.DomainType: - permissionsFilter = defDomainsFilterPermissions - default: - return nil, svcerr.ErrMalformedEntity - } - } - pers, err := ps.retrievePermissions(ctx, pr, permissionsFilter) - if err != nil { - return []string{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - return pers, nil -} - -func (ps *policyService) policyValidation(pr policies.Policy) error { - if pr.ObjectType == policies.PlatformType && pr.Object != policies.MagistralaObject { - return errPlatform - } - - return nil -} - -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 - // 3.) group -> group (both for adding parent_group and channels) - // 4.) group (channel) -> thing - // 5.) user -> domain - - switch { - // 1.) user -> group (both user groups and channels) - // Checks : - // - USER with ANY RELATION to DOMAIN - // - GROUP with DOMAIN RELATION to DOMAIN - case pr.SubjectType == policies.UserType && pr.ObjectType == policies.GroupType: - return ps.userGroupPreConditions(ctx, pr) - - // 2.) user -> thing - // 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) - - // 3.) group -> group (both for adding parent_group and channels) - // Checks : - // - CHILD_GROUP with out PARENT_GROUP RELATION with any GROUP - case pr.SubjectType == policies.GroupType && pr.ObjectType == policies.GroupType: - return groupPreConditions(pr) - - // 4.) group (channel) -> thing - // 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) - - // 5.) user -> domain - // Checks : - // - User doesn't have any relation with domain - 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): - preconds := []*v1.Precondition{ - { - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: pr.ObjectType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - }, - }, - }, - } - return preconds, nil - } - - return nil, nil -} - -func (ps *policyService) userGroupPreConditions(ctx context.Context, pr policies.Policy) ([]*v1.Precondition, error) { - var preconds []*v1.Precondition - - // user should not have any relation with group - preconds = append(preconds, &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalResourceId: pr.Object, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.UserType, - OptionalSubjectId: pr.Subject, - }, - }, - }) - isSuperAdmin := false - if err := ps.checkPolicy(ctx, policies.Policy{ - Subject: pr.Subject, - SubjectType: pr.SubjectType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err == nil { - isSuperAdmin = true - } - - if !isSuperAdmin { - preconds = append(preconds, &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.DomainType, - OptionalResourceId: pr.Domain, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.UserType, - OptionalSubjectId: pr.Subject, - }, - }, - }) - } - switch { - case pr.ObjectKind == policies.NewGroupKind || pr.ObjectKind == policies.NewChannelKind: - preconds = append(preconds, - &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - }, - }, - }, - ) - default: - preconds = append(preconds, - &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - OptionalSubjectId: pr.Domain, - }, - }, - }, - ) - } - - return preconds, nil -} - -func (ps *policyService) userThingPreConditions(ctx context.Context, pr policies.Policy) ([]*v1.Precondition, error) { - var preconds []*v1.Precondition - - // user should not have any relation with thing - preconds = append(preconds, &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.ThingType, - OptionalResourceId: pr.Object, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.UserType, - OptionalSubjectId: pr.Subject, - }, - }, - }) - - isSuperAdmin := false - if err := ps.checkPolicy(ctx, policies.Policy{ - Subject: pr.Subject, - SubjectType: pr.SubjectType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err == nil { - isSuperAdmin = true - } - - if !isSuperAdmin { - preconds = append(preconds, &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.DomainType, - OptionalResourceId: pr.Domain, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.UserType, - OptionalSubjectId: pr.Subject, - }, - }, - }) - } - switch { - // For New thing - // - THING without DOMAIN RELATION to ANY DOMAIN - case pr.ObjectKind == policies.NewThingKind: - preconds = append(preconds, - &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.ThingType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - }, - }, - }, - ) - default: - // For existing thing - // - THING without DOMAIN RELATION to ANY DOMAIN - preconds = append(preconds, - &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.ThingType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - OptionalSubjectId: pr.Domain, - }, - }, - }, - ) - } - - return preconds, nil -} - -func (ps *policyService) userDomainPreConditions(ctx context.Context, pr policies.Policy) ([]*v1.Precondition, error) { - var preconds []*v1.Precondition - - if err := ps.checkPolicy(ctx, policies.Policy{ - Subject: pr.Subject, - SubjectType: pr.SubjectType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err == nil { - return preconds, fmt.Errorf("use already exists in domain") - } - - // user should not have any relation with domain. - preconds = append(preconds, &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.DomainType, - OptionalResourceId: pr.Object, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.UserType, - OptionalSubjectId: pr.Subject, - }, - }, - }) - - return preconds, nil -} - -func (ps *policyService) checkPolicy(ctx context.Context, pr policies.Policy) error { - checkReq := v1.CheckPermissionRequest{ - // FullyConsistent means little caching will be available, which means performance will suffer. - // Only use if a ZedToken is not available or absolutely latest information is required. - // If we want to avoid FullyConsistent and to improve the performance of spicedb, then we need to cache the ZEDTOKEN whenever RELATIONS is created or updated. - // Instead of using FullyConsistent we need to use Consistency_AtLeastAsFresh, code looks like below one. - // Consistency: &v1.Consistency{ - // Requirement: &v1.Consistency_AtLeastAsFresh{ - // AtLeastAsFresh: getRelationTupleZedTokenFromCache() , - // } - // }, - // Reference: https://authzed.com/docs/reference/api-consistency - Consistency: &v1.Consistency{ - Requirement: &v1.Consistency_FullyConsistent{ - FullyConsistent: true, - }, - }, - Resource: &v1.ObjectReference{ObjectType: pr.ObjectType, ObjectId: pr.Object}, - Permission: pr.Permission, - Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: pr.SubjectType, ObjectId: pr.Subject}, OptionalRelation: pr.SubjectRelation}, - } - - resp, err := ps.permissionClient.CheckPermission(ctx, &checkReq) - if err != nil { - return handleSpicedbError(err) - } - if resp.Permissionship == v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { - return nil - } - if reason, ok := v1.CheckPermissionResponse_Permissionship_name[int32(resp.Permissionship)]; ok { - return errors.Wrap(svcerr.ErrAuthorization, errors.New(reason)) - } - return svcerr.ErrAuthorization -} - -func (ps *policyService) retrieveObjects(ctx context.Context, pr policies.Policy, nextPageToken string, limit uint64) ([]policies.Policy, string, error) { - resourceReq := &v1.LookupResourcesRequest{ - Consistency: &v1.Consistency{ - Requirement: &v1.Consistency_FullyConsistent{ - FullyConsistent: true, - }, - }, - ResourceObjectType: pr.ObjectType, - Permission: pr.Permission, - Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: pr.SubjectType, ObjectId: pr.Subject}, OptionalRelation: pr.SubjectRelation}, - OptionalLimit: uint32(limit), - } - if nextPageToken != "" { - resourceReq.OptionalCursor = &v1.Cursor{Token: nextPageToken} - } - stream, err := ps.permissionClient.LookupResources(ctx, resourceReq) - if err != nil { - return nil, "", errors.Wrap(errRetrievePolicies, handleSpicedbError(err)) - } - resources := []*v1.LookupResourcesResponse{} - var token string - for { - resp, err := stream.Recv() - switch err { - case nil: - resources = append(resources, resp) - case io.EOF: - if len(resources) > 0 && resources[len(resources)-1].AfterResultCursor != nil { - token = resources[len(resources)-1].AfterResultCursor.Token - } - return objectsToAuthPolicies(resources), token, nil - default: - if len(resources) > 0 && resources[len(resources)-1].AfterResultCursor != nil { - token = resources[len(resources)-1].AfterResultCursor.Token - } - return []policies.Policy{}, token, errors.Wrap(errRetrievePolicies, handleSpicedbError(err)) - } - } -} - -func (ps *policyService) retrieveAllObjects(ctx context.Context, pr policies.Policy) ([]policies.Policy, error) { - resourceReq := &v1.LookupResourcesRequest{ - Consistency: &v1.Consistency{ - Requirement: &v1.Consistency_FullyConsistent{ - FullyConsistent: true, - }, - }, - ResourceObjectType: pr.ObjectType, - Permission: pr.Permission, - Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: pr.SubjectType, ObjectId: pr.Subject}, OptionalRelation: pr.SubjectRelation}, - } - stream, err := ps.permissionClient.LookupResources(ctx, resourceReq) - if err != nil { - return nil, errors.Wrap(errRetrievePolicies, handleSpicedbError(err)) - } - tuples := []policies.Policy{} - for { - resp, err := stream.Recv() - switch { - case errors.Contains(err, io.EOF): - return tuples, nil - case err != nil: - return tuples, errors.Wrap(errRetrievePolicies, handleSpicedbError(err)) - default: - tuples = append(tuples, policies.Policy{Object: resp.ResourceObjectId}) - } - } -} - -func (ps *policyService) retrieveSubjects(ctx context.Context, pr policies.Policy, nextPageToken string, limit uint64) ([]policies.Policy, string, error) { - subjectsReq := v1.LookupSubjectsRequest{ - Consistency: &v1.Consistency{ - Requirement: &v1.Consistency_FullyConsistent{ - FullyConsistent: true, - }, - }, - Resource: &v1.ObjectReference{ObjectType: pr.ObjectType, ObjectId: pr.Object}, - Permission: pr.Permission, - SubjectObjectType: pr.SubjectType, - OptionalSubjectRelation: pr.SubjectRelation, - OptionalConcreteLimit: uint32(limit), - WildcardOption: v1.LookupSubjectsRequest_WILDCARD_OPTION_INCLUDE_WILDCARDS, - } - if nextPageToken != "" { - subjectsReq.OptionalCursor = &v1.Cursor{Token: nextPageToken} - } - stream, err := ps.permissionClient.LookupSubjects(ctx, &subjectsReq) - if err != nil { - return nil, "", errors.Wrap(errRetrievePolicies, handleSpicedbError(err)) - } - subjects := []*v1.LookupSubjectsResponse{} - var token string - for { - resp, err := stream.Recv() - - switch err { - case nil: - subjects = append(subjects, resp) - case io.EOF: - if len(subjects) > 0 && subjects[len(subjects)-1].AfterResultCursor != nil { - token = subjects[len(subjects)-1].AfterResultCursor.Token - } - return subjectsToAuthPolicies(subjects), token, nil - default: - if len(subjects) > 0 && subjects[len(subjects)-1].AfterResultCursor != nil { - token = subjects[len(subjects)-1].AfterResultCursor.Token - } - return []policies.Policy{}, token, errors.Wrap(errRetrievePolicies, handleSpicedbError(err)) - } - } -} - -func (ps *policyService) retrieveAllSubjects(ctx context.Context, pr policies.Policy) ([]policies.Policy, error) { - var tuples []policies.Policy - nextPageToken := "" - for i := 0; ; i++ { - relationTuples, npt, err := ps.retrieveSubjects(ctx, pr, nextPageToken, defRetrieveAllLimit) - if err != nil { - return tuples, err - } - tuples = append(tuples, relationTuples...) - if npt == "" || (len(tuples) < defRetrieveAllLimit) { - break - } - nextPageToken = npt - } - return tuples, nil -} - -func (ps *policyService) retrievePermissions(ctx context.Context, pr policies.Policy, filterPermission []string) (policies.Permissions, error) { - var permissionChecks []*v1.CheckBulkPermissionsRequestItem - for _, fp := range filterPermission { - permissionChecks = append(permissionChecks, &v1.CheckBulkPermissionsRequestItem{ - Resource: &v1.ObjectReference{ - ObjectType: pr.ObjectType, - ObjectId: pr.Object, - }, - Permission: fp, - Subject: &v1.SubjectReference{ - Object: &v1.ObjectReference{ - ObjectType: pr.SubjectType, - ObjectId: pr.Subject, - }, - OptionalRelation: pr.SubjectRelation, - }, - }) - } - resp, err := ps.client.PermissionsServiceClient.CheckBulkPermissions(ctx, &v1.CheckBulkPermissionsRequest{ - Consistency: &v1.Consistency{ - Requirement: &v1.Consistency_FullyConsistent{ - FullyConsistent: true, - }, - }, - Items: permissionChecks, - }) - if err != nil { - return policies.Permissions{}, errors.Wrap(errRetrievePolicies, handleSpicedbError(err)) - } - - permissions := []string{} - for _, pair := range resp.Pairs { - if pair.GetError() != nil { - s := pair.GetError() - return policies.Permissions{}, errors.Wrap(errRetrievePolicies, convertGRPCStatusToError(convertToGrpcStatus(s))) - } - item := pair.GetItem() - req := pair.GetRequest() - if item != nil && req != nil && item.Permissionship == v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { - permissions = append(permissions, req.GetPermission()) - } - } - return permissions, nil -} - -func groupPreConditions(pr policies.Policy) ([]*v1.Precondition, error) { - // - PARENT_GROUP (subject) with DOMAIN RELATION to DOMAIN - precond := []*v1.Precondition{ - { - Operation: v1.Precondition_OPERATION_MUST_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalResourceId: pr.Subject, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - OptionalSubjectId: pr.Domain, - }, - }, - }, - } - if pr.ObjectKind != policies.ChannelsKind { - precond = append(precond, - &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.ParentGroupRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.GroupType, - }, - }, - }, - ) - } - switch { - // - NEW CHILD_GROUP (object) with out DOMAIN RELATION to ANY DOMAIN - case pr.ObjectType == policies.GroupType && pr.ObjectKind == policies.NewGroupKind: - precond = append(precond, - &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - }, - }, - }, - ) - default: - // - CHILD_GROUP (object) with DOMAIN RELATION to DOMAIN - precond = append(precond, - &v1.Precondition{ - Operation: v1.Precondition_OPERATION_MUST_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - OptionalSubjectId: pr.Domain, - }, - }, - }, - ) - } - return precond, nil -} - -func channelThingPreCondition(pr policies.Policy) ([]*v1.Precondition, error) { - if pr.SubjectKind != policies.ChannelsKind { - return nil, errors.Wrap(errors.ErrMalformedEntity, errInvalidSubject) - } - precond := []*v1.Precondition{ - { - Operation: v1.Precondition_OPERATION_MUST_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalResourceId: pr.Subject, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - OptionalSubjectId: pr.Domain, - }, - }, - }, - { - Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.GroupType, - OptionalRelation: policies.ParentGroupRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.GroupType, - OptionalSubjectId: pr.Subject, - }, - }, - }, - { - Operation: v1.Precondition_OPERATION_MUST_MATCH, - Filter: &v1.RelationshipFilter{ - ResourceType: policies.ThingType, - OptionalResourceId: pr.Object, - OptionalRelation: policies.DomainRelation, - OptionalSubjectFilter: &v1.SubjectFilter{ - SubjectType: policies.DomainType, - OptionalSubjectId: pr.Domain, - }, - }, - }, - } - return precond, nil -} - -func objectsToAuthPolicies(objects []*v1.LookupResourcesResponse) []policies.Policy { - var policyList []policies.Policy - for _, obj := range objects { - policyList = append(policyList, policies.Policy{ - Object: obj.GetResourceObjectId(), - }) - } - return policyList -} - -func subjectsToAuthPolicies(subjects []*v1.LookupSubjectsResponse) []policies.Policy { - var policyList []policies.Policy - for _, sub := range subjects { - policyList = append(policyList, policies.Policy{ - Subject: sub.Subject.GetSubjectObjectId(), - }) - } - return policyList -} - -func handleSpicedbError(err error) error { - if st, ok := status.FromError(err); ok { - return convertGRPCStatusToError(st) - } - return err -} - -func convertToGrpcStatus(gst *gstatus.Status) *status.Status { - st := status.New(codes.Code(gst.Code), gst.GetMessage()) - return st -} - -func convertGRPCStatusToError(st *status.Status) error { - switch st.Code() { - case codes.NotFound: - return errors.Wrap(repoerr.ErrNotFound, errors.New(st.Message())) - case codes.InvalidArgument: - return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) - case codes.AlreadyExists: - return errors.Wrap(repoerr.ErrConflict, errors.New(st.Message())) - case codes.Unauthenticated: - return errors.Wrap(svcerr.ErrAuthentication, errors.New(st.Message())) - case codes.Internal: - return errors.Wrap(errInternal, errors.New(st.Message())) - case codes.OK: - if msg := st.Message(); msg != "" { - return errors.Wrap(errors.ErrUnidentified, errors.New(msg)) - } - return nil - case codes.FailedPrecondition: - return errors.Wrap(errors.ErrMalformedEntity, errors.New(st.Message())) - case codes.PermissionDenied: - return errors.Wrap(svcerr.ErrAuthorization, errors.New(st.Message())) - default: - return errors.Wrap(fmt.Errorf("unexpected gRPC status: %s (status code:%v)", st.Code().String(), st.Code()), errors.New(st.Message())) - } -} diff --git a/pkg/postgres/common.go b/pkg/postgres/common.go deleted file mode 100644 index 3f394f772..000000000 --- a/pkg/postgres/common.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "encoding/json" - "fmt" -) - -// CreateMetadataQuery creates a query to filter by metadata. -// -// For example: -// -// query, param, err := CreateMetadataQuery("", map[string]interface{}{ -// "key": "value", -// }) -func CreateMetadataQuery(entity string, um map[string]interface{}) (string, []byte, error) { - if len(um) == 0 { - return "", nil, nil - } - - param, err := json.Marshal(um) - if err != nil { - return "", nil, err - } - query := fmt.Sprintf("%smetadata @> :metadata", entity) - - return query, param, nil -} - -// Total returns the total number of rows. -// -// For example: -// -// total, err := Total(ctx, db, "SELECT COUNT(*) FROM table", nil) -func Total(ctx context.Context, db Database, query string, params interface{}) (uint64, error) { - rows, err := db.NamedQueryContext(ctx, query, params) - if err != nil { - return 0, err - } - defer rows.Close() - - total := uint64(0) - if rows.Next() { - if err := rows.Scan(&total); err != nil { - return 0, err - } - } - - return total, nil -} diff --git a/pkg/postgres/doc.go b/pkg/postgres/doc.go deleted file mode 100644 index 58e340579..000000000 --- a/pkg/postgres/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres contains the domain concept definitions needed to support -// Magistrala PostgreSQL database functionality. -// -// It provides the abstraction of the PostgreSQL database service, which is used -// to configure, setup and connect to the PostgreSQL database. -package postgres diff --git a/pkg/postgres/errors.go b/pkg/postgres/errors.go deleted file mode 100644 index 541f7f2eb..000000000 --- a/pkg/postgres/errors.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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" - "github.com/jackc/pgx/v5/pgconn" -) - -// Postgres error codes: -// https://www.postgresql.org/docs/current/errcodes-appendix.html -const ( - errDuplicate = "23505" // unique_violation - errTruncation = "22001" // string_data_right_truncation - errFK = "23503" // foreign_key_violation - errInvalid = "22P02" // invalid_text_representation - errUntranslatable = "22P05" // untranslatable_character - errInvalidChar = "22021" // character_not_in_repertoire -) - -// HandleError handles the error and returns a wrapped error. -// It checks the error code and returns a specific error. -func HandleError(wrapper, err error) error { - pqErr, ok := err.(*pgconn.PgError) - if ok { - switch pqErr.Code { - case errDuplicate: - return errors.Wrap(repoerr.ErrConflict, err) - case errInvalid, errInvalidChar, errTruncation, errUntranslatable: - return errors.Wrap(repoerr.ErrMalformedEntity, err) - case errFK: - return errors.Wrap(repoerr.ErrCreateEntity, err) - } - } - - return errors.Wrap(wrapper, err) -} diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go deleted file mode 100644 index 975ed1ee0..000000000 --- a/pkg/postgres/postgres.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "fmt" - - "github.com/absmach/magistrala/pkg/errors" - _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access - "github.com/jmoiron/sqlx" - migrate "github.com/rubenv/sql-migrate" -) - -var ( - errConnect = errors.New("failed to connect to postgresql server") - errMigration = errors.New("failed to apply migrations") -) - -type Config struct { - Host string `env:"HOST" envDefault:"localhost"` - Port string `env:"PORT" envDefault:"5432"` - User string `env:"USER" envDefault:"magistrala"` - Pass string `env:"PASS" envDefault:"magistrala"` - Name string `env:"NAME" envDefault:""` - SSLMode string `env:"SSL_MODE" envDefault:"disable"` - SSLCert string `env:"SSL_CERT" envDefault:""` - SSLKey string `env:"SSL_KEY" envDefault:""` - SSLRootCert string `env:"SSL_ROOT_CERT" envDefault:""` -} - -// Setup creates a connection to the PostgreSQL instance and applies any -// unapplied database migrations. A non-nil error is returned to indicate failure. -// -// For example: -// -// db, err := postgres.Setup(postgres.Config{}, migrate.MemoryMigrationSource{}) -func Setup(cfg Config, migrations migrate.MemoryMigrationSource) (*sqlx.DB, error) { - db, err := Connect(cfg) - if err != nil { - return nil, err - } - - if _, err = migrate.Exec(db.DB, "postgres", migrations, migrate.Up); err != nil { - return nil, errors.Wrap(errMigration, err) - } - - return db, nil -} - -// Connect creates a connection to the PostgreSQL instance. -// -// For example: -// -// db, err := postgres.Connect(postgres.Config{}) -func Connect(cfg Config) (*sqlx.DB, error) { - url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert) - - db, err := sqlx.Open("pgx", url) - if err != nil { - return nil, errors.Wrap(errConnect, err) - } - - return db, nil -} diff --git a/pkg/postgres/tracing.go b/pkg/postgres/tracing.go deleted file mode 100644 index dfd4e934b..000000000 --- a/pkg/postgres/tracing.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "database/sql" - "fmt" - "strings" - - "github.com/jmoiron/sqlx" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ Database = (*database)(nil) - -type database struct { - Config - db *sqlx.DB - tracer trace.Tracer -} - -// Database provides a database interface. -type Database interface { - // NamedQueryContext executes a named query against the database and returns - NamedQueryContext(context.Context, string, interface{}) (*sqlx.Rows, error) - - // NamedExecContext executes a named query against the database and returns - NamedExecContext(context.Context, string, interface{}) (sql.Result, error) - - // QueryRowxContext queries the database and returns an *sqlx.Row. - QueryRowxContext(context.Context, string, ...interface{}) *sqlx.Row - - // QueryxContext queries the database and returns an *sqlx.Rows and an error. - QueryxContext(context.Context, string, ...interface{}) (*sqlx.Rows, error) - - // QueryContext queries the database and returns an *sql.Rows and an error. - QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) - - // ExecContext executes a query without returning any rows. - ExecContext(context.Context, string, ...interface{}) (sql.Result, error) - - // BeginTxx begins a transaction and returns an *sqlx.Tx. - BeginTxx(ctx context.Context, opts *sql.TxOptions) (*sqlx.Tx, error) -} - -// NewDatabase creates a Clients'Database instance. -func NewDatabase(db *sqlx.DB, config Config, tracer trace.Tracer) Database { - database := &database{ - Config: config, - db: db, - tracer: tracer, - } - - return database -} - -func (d *database) NamedQueryContext(ctx context.Context, query string, args interface{}) (*sqlx.Rows, error) { - ctx, span := d.addSpanTags(ctx, query) - defer span.End() - - return d.db.NamedQueryContext(ctx, query, args) -} - -func (d *database) NamedExecContext(ctx context.Context, query string, args interface{}) (sql.Result, error) { - ctx, span := d.addSpanTags(ctx, query) - defer span.End() - - return d.db.NamedExecContext(ctx, query, args) -} - -func (d *database) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - ctx, span := d.addSpanTags(ctx, query) - defer span.End() - - return d.db.ExecContext(ctx, query, args...) -} - -func (d *database) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row { - ctx, span := d.addSpanTags(ctx, query) - defer span.End() - - return d.db.QueryRowxContext(ctx, query, args...) -} - -func (d *database) QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) { - ctx, span := d.addSpanTags(ctx, query) - defer span.End() - - return d.db.QueryxContext(ctx, query, args...) -} - -func (d database) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { - ctx, span := d.addSpanTags(ctx, query) - defer span.End() - return d.db.QueryContext(ctx, query, args...) -} - -func (d database) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*sqlx.Tx, error) { - ctx, span := d.addSpanTags(ctx, "BeginTxx") - defer span.End() - - return d.db.BeginTxx(ctx, opts) -} - -func (d *database) addSpanTags(ctx context.Context, query string) (context.Context, trace.Span) { - operation := strings.Replace(strings.Split(query, " ")[0], "(", "", 1) - - ctx, span := d.tracer.Start(ctx, - fmt.Sprintf("%s %s", operation, d.Name), - trace.WithAttributes( - // Related to the database instance (informational) - attribute.String("db.system", "postgresql"), - attribute.String("db.user", d.User), - attribute.String("network.transport", "tcp"), - attribute.String("network.type", "ipv4"), - attribute.String("server.address", d.Host), - attribute.String("server.port", d.Port), - attribute.String("db.name", d.Name), - attribute.String("db.statement", query), - - // General Span tags - attribute.String("span.kind", "client"), - ), - ) - - return ctx, span -} diff --git a/pkg/sdk/README.md b/pkg/sdk/README.md deleted file mode 100644 index c5a945c7d..000000000 --- a/pkg/sdk/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 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. - -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 deleted file mode 100644 index f82f782f9..000000000 --- a/pkg/sdk/go/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# Magistrala Go SDK - -Go SDK, a Go driver for Magistrala HTTP API. - -Does both system administration (provisioning) and messaging. - -## Installation - -Import `"github.com/absmach/magistrala/sdk/go"` in your Go package. - -```` -import "github.com/absmach/magistrala/pkg/sdk/go"``` - -Then call SDK Go functions to interact with the system. - -## API Reference - -```go -FUNCTIONS - -func NewMgxSDK(host, port string, tls bool) *MgxSDK - -func (sdk *MgxSDK) Channel(id, token string) (things.Channel, error) - Channel - gets channel by ID - -func (sdk *MgxSDK) Channels(token string) ([]things.Channel, error) - Channels - gets all channels - -func (sdk *MgxSDK) Connect(struct{[]string, []string}, token string) error - Connect - connect things 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) CreateToken(user, pwd string) (string, error) - CreateToken - create user token - -func (sdk *MgxSDK) CreateUser(user, pwd string) error - CreateUser - create user - -func (sdk *MgxSDK) User(pwd string) (user, error) - User - gets user - -func (sdk *MgxSDK) UpdateUser(user, pwd string) error - UpdateUser - update user - -func (sdk *MgxSDK) UpdatePassword(user, pwd string) error - UpdatePassword - update user password - -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) DisconnectThing(thingID, chanID, token string) error - DisconnectThing - connect thing to a channel - -func (sdk *MgxSDK) SendMessage(chanID, msg, token string) error - SendMessage - send message on Magistrala channel - -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) Things(token string) ([]Thing, error) - Things - gets all things - -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) Health() (magistrala.Health, error) - Health - things service health check -```` diff --git a/pkg/sdk/go/bootstrap.go b/pkg/sdk/go/bootstrap.go deleted file mode 100644 index 7fd9ba960..000000000 --- a/pkg/sdk/go/bootstrap.go +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" -) - -const ( - configsEndpoint = "things/configs" - bootstrapEndpoint = "things/bootstrap" - whitelistEndpoint = "things/state" - bootstrapCertsEndpoint = "things/configs/certs" - bootstrapConnEndpoint = "things/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. -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"` -} - -func (ts *BootstrapConfig) UnmarshalJSON(data []byte) error { - var rawData map[string]json.RawMessage - if err := json.Unmarshal(data, &rawData); err != nil { - return err - } - - if channelData, ok := rawData["channels"]; ok { - var stringData []string - if err := json.Unmarshal(channelData, &stringData); err == nil { - ts.Channels = stringData - } else { - var channels []Channel - if err := json.Unmarshal(channelData, &channels); err == nil { - ts.Channels = channels - } else { - return fmt.Errorf("unsupported channel data type") - } - } - } - - 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: &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, - }); err != nil { - return err - } - - return nil -} - -func (sdk mgSDK) AddBootstrap(cfg BootstrapConfig, domainID, token string) (string, errors.SDKError) { - data, err := json.Marshal(cfg) - if err != nil { - return "", errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint) - - headers, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusOK, http.StatusCreated) - if sdkerr != nil { - return "", sdkerr - } - - id := strings.TrimPrefix(headers.Get("Location"), "/things/configs/") - - return id, nil -} - -func (sdk mgSDK) Bootstraps(pm PageMetadata, domainID, token string) (BootstrapPage, errors.SDKError) { - endpoint := fmt.Sprintf("%s/%s", domainID, configsEndpoint) - url, err := sdk.withQueryParams(sdk.bootstrapURL, endpoint, pm) - if err != nil { - return BootstrapPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return BootstrapPage{}, sdkerr - } - - var bb BootstrapPage - if err = json.Unmarshal(body, &bb); err != nil { - return BootstrapPage{}, errors.NewSDKError(err) - } - - return bb, nil -} - -func (sdk mgSDK) Whitelist(thingID string, state int, domainID, token string) errors.SDKError { - if thingID == "" { - return errors.NewSDKError(apiutil.ErrMissingID) - } - - data, err := json.Marshal(BootstrapConfig{State: state}) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, whitelistEndpoint, thingID) - - _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusCreated, http.StatusOK) - - return sdkerr -} - -func (sdk mgSDK) ViewBootstrap(id, domainID, token string) (BootstrapConfig, errors.SDKError) { - if id == "" { - return BootstrapConfig{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint, id) - - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return BootstrapConfig{}, err - } - - var bc BootstrapConfig - if err := json.Unmarshal(body, &bc); err != nil { - return BootstrapConfig{}, errors.NewSDKError(err) - } - - return bc, nil -} - -func (sdk mgSDK) UpdateBootstrap(cfg BootstrapConfig, domainID, token string) errors.SDKError { - if cfg.ThingID == "" { - return errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint, cfg.ThingID) - - data, err := json.Marshal(cfg) - if err != nil { - return errors.NewSDKError(err) - } - - _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusOK) - - return sdkerr -} - -func (sdk mgSDK) UpdateBootstrapCerts(id, clientCert, clientKey, ca, domainID, token string) (BootstrapConfig, errors.SDKError) { - if id == "" { - return BootstrapConfig{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, bootstrapCertsEndpoint, id) - request := BootstrapConfig{ - ClientCert: clientCert, - ClientKey: clientKey, - CACert: ca, - } - - data, err := json.Marshal(request) - if err != nil { - return BootstrapConfig{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return BootstrapConfig{}, sdkerr - } - - var bc BootstrapConfig - if err := json.Unmarshal(body, &bc); err != nil { - return BootstrapConfig{}, errors.NewSDKError(err) - } - - return bc, nil -} - -func (sdk mgSDK) UpdateBootstrapConnection(id string, channels []string, domainID, token string) errors.SDKError { - if id == "" { - return errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, bootstrapConnEndpoint, id) - request := map[string][]string{ - "channels": channels, - } - data, err := json.Marshal(request) - if err != nil { - return errors.NewSDKError(err) - } - - _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusOK) - return sdkerr -} - -func (sdk mgSDK) RemoveBootstrap(id, domainID, token string) errors.SDKError { - if id == "" { - return errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint, id) - - _, _, err := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - return err -} - -func (sdk mgSDK) Bootstrap(externalID, externalKey string) (BootstrapConfig, errors.SDKError) { - if externalID == "" { - return BootstrapConfig{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, bootstrapEndpoint, externalID) - - _, body, err := sdk.processRequest(http.MethodGet, url, ThingPrefix+externalKey, nil, nil, http.StatusOK) - if err != nil { - return BootstrapConfig{}, err - } - - var bc BootstrapConfig - if err := json.Unmarshal(body, &bc); err != nil { - return BootstrapConfig{}, errors.NewSDKError(err) - } - - return bc, nil -} - -func (sdk mgSDK) BootstrapSecure(externalID, externalKey, cryptoKey string) (BootstrapConfig, errors.SDKError) { - if externalID == "" { - return BootstrapConfig{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, bootstrapEndpoint, secureEndpoint, externalID) - - encExtKey, err := bootstrapEncrypt([]byte(externalKey), cryptoKey) - if err != nil { - return BootstrapConfig{}, errors.NewSDKError(err) - } - - _, body, sdkErr := sdk.processRequest(http.MethodGet, url, ThingPrefix+encExtKey, nil, nil, http.StatusOK) - if sdkErr != nil { - return BootstrapConfig{}, sdkErr - } - - decBody, decErr := bootstrapDecrypt(body, cryptoKey) - if decErr != nil { - return BootstrapConfig{}, errors.NewSDKError(decErr) - } - var bc BootstrapConfig - if err := json.Unmarshal(decBody, &bc); err != nil { - return BootstrapConfig{}, errors.NewSDKError(err) - } - - return bc, nil -} - -func bootstrapEncrypt(in []byte, cryptoKey string) (string, error) { - block, err := aes.NewCipher([]byte(cryptoKey)) - if err != nil { - return "", err - } - ciphertext := make([]byte, aes.BlockSize+len(in)) - iv := ciphertext[:aes.BlockSize] - - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return "", err - } - stream := cipher.NewCFBEncrypter(block, iv) - stream.XORKeyStream(ciphertext[aes.BlockSize:], in) - return hex.EncodeToString(ciphertext), nil -} - -func bootstrapDecrypt(in []byte, cryptoKey string) ([]byte, error) { - ciphertext := in - - block, err := aes.NewCipher([]byte(cryptoKey)) - if err != nil { - return nil, err - } - if len(ciphertext) < aes.BlockSize { - return nil, err - } - iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] - stream := cipher.NewCFBDecrypter(block, iv) - stream.XORKeyStream(ciphertext, ciphertext) - return ciphertext, nil -} diff --git a/pkg/sdk/go/bootstrap_test.go b/pkg/sdk/go/bootstrap_test.go deleted file mode 100644 index b091bc976..000000000 --- a/pkg/sdk/go/bootstrap_test.go +++ /dev/null @@ -1,1347 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/bootstrap/api" - bmocks "github.com/absmach/magistrala/bootstrap/mocks" - "github.com/absmach/magistrala/internal/testsutil" - 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" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - externalId = testsutil.GenerateUUID(&testing.T{}) - externalKey = testsutil.GenerateUUID(&testing.T{}) - thingId = testsutil.GenerateUUID(&testing.T{}) - thingKey = testsutil.GenerateUUID(&testing.T{}) - channel1Id = testsutil.GenerateUUID(&testing.T{}) - channel2Id = testsutil.GenerateUUID(&testing.T{}) - clientCert = "newcert" - clientKey = "newkey" - caCert = "newca" - content = "newcontent" - state = 1 - bsName = "test" - encKey = []byte("1234567891011121") - bootstrapConfig = bootstrap.Config{ - ThingID: thingId, - Name: "test", - ClientCert: clientCert, - ClientKey: clientKey, - CACert: caCert, - Channels: []bootstrap.Channel{ - { - ID: channel1Id, - }, - { - ID: channel2Id, - }, - }, - ExternalID: externalId, - ExternalKey: externalKey, - Content: content, - 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, - } - sdkBootsrapConfigRes = sdk.BootstrapConfig{ - ThingID: thingId, - ThingKey: thingKey, - Channels: []sdk.Channel{ - { - ID: channel1Id, - }, - { - ID: channel2Id, - }, - }, - ClientCert: clientCert, - ClientKey: clientKey, - 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"` - }{ - ThingID: thingId, - ThingKey: thingKey, - Channels: []readerChannelRes{ - { - ID: channel1Id, - }, - { - ID: channel2Id, - }, - }, - ClientCert: clientCert, - ClientKey: clientKey, - CACert: caCert, - } -) - -var ( - errMarshalChan = errors.New("json: unsupported type: chan int") - errJsonEOF = errors.New("unexpected end of JSON input") -) - -type readerChannelRes struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Metadata interface{} `json:"metadata,omitempty"` -} - -func setupBootstrap() (*httptest.Server, *bmocks.Service, *bmocks.ConfigReader, *authnmocks.Authentication) { - bsvc := new(bmocks.Service) - reader := new(bmocks.ConfigReader) - logger := mglog.NewMock() - authn := new(authnmocks.Authentication) - mux := api.MakeHandler(bsvc, authn, reader, logger, "") - - return httptest.NewServer(mux), bsvc, reader, authn -} - -func TestAddBootstrap(t *testing.T) { - bs, bsvc, _, auth := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - neID := sdkBootstrapConfig - neID.ThingID = "non-existent" - - neReqId := bootstrapConfig - neReqId.ThingID = "non-existent" - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - cfg sdk.BootstrapConfig - svcReq bootstrap.Config - svcRes bootstrap.Config - svcErr error - authenticateErr error - response string - err errors.SDKError - }{ - { - desc: "add successfully", - domainID: domainID, - token: validToken, - cfg: sdkBootstrapConfig, - svcReq: bootstrapConfig, - svcRes: bootstrapConfig, - svcErr: nil, - err: nil, - }, - { - desc: "add with invalid token", - domainID: domainID, - token: invalidToken, - cfg: sdkBootstrapConfig, - svcReq: bootstrapConfig, - svcRes: bootstrap.Config{}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "add with config that cannot be marshalled", - domainID: domainID, - token: validToken, - cfg: sdk.BootstrapConfig{ - 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, - }, - svcReq: bootstrap.Config{}, - svcRes: bootstrap.Config{}, - svcErr: nil, - err: errors.NewSDKError(errMarshalChan), - }, - { - desc: "add an existing config", - domainID: domainID, - token: validToken, - cfg: sdkBootstrapConfig, - svcReq: bootstrapConfig, - svcRes: bootstrap.Config{}, - svcErr: svcerr.ErrConflict, - err: errors.NewSDKErrorWithStatus(svcerr.ErrConflict, http.StatusConflict), - }, - { - desc: "add empty config", - domainID: domainID, - token: validToken, - cfg: sdk.BootstrapConfig{}, - svcReq: bootstrap.Config{}, - svcRes: bootstrap.Config{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "add with non-existent thing Id", - domainID: domainID, - token: validToken, - cfg: neID, - svcReq: neReqId, - svcRes: bootstrap.Config{}, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("Add", mock.Anything, tc.session, tc.token, tc.svcReq).Return(tc.svcRes, tc.svcErr) - 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) - ok := svcCall.Parent.AssertCalled(t, "Add", mock.Anything, tc.session, tc.token, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListBootstraps(t *testing.T) { - bs, bsvc, _, auth := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - configRes := sdk.BootstrapConfig{ - Channels: []sdk.Channel{ - { - ID: channel1Id, - }, - { - ID: channel2Id, - }, - }, - ThingID: thingId, - Name: bsName, - ExternalID: externalId, - ExternalKey: externalKey, - Content: content, - } - unmarshalableConfig := bootstrapConfig - unmarshalableConfig.Channels = []bootstrap.Channel{ - { - ID: channel1Id, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - } - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - pageMeta sdk.PageMetadata - svcResp bootstrap.ConfigsPage - svcErr error - authenticateErr error - response sdk.BootstrapPage - err errors.SDKError - }{ - { - desc: "list successfully", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcResp: bootstrap.ConfigsPage{ - Total: 1, - Offset: 0, - Configs: []bootstrap.Config{bootstrapConfig}, - }, - response: sdk.BootstrapPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Configs: []sdk.BootstrapConfig{configRes}, - }, - err: nil, - }, - { - desc: "list with invalid token", - domainID: domainID, - token: invalidToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcResp: bootstrap.ConfigsPage{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.BootstrapPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list with empty token", - domainID: domainID, - token: "", - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcResp: bootstrap.ConfigsPage{}, - svcErr: nil, - response: sdk.BootstrapPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list with invalid query params", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 1, - Limit: 10, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcResp: bootstrap.ConfigsPage{}, - svcErr: nil, - response: sdk.BootstrapPage{}, - err: errors.NewSDKError(errMarshalChan), - }, - { - desc: "list with response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcResp: bootstrap.ConfigsPage{ - Total: 1, - Offset: 0, - Configs: []bootstrap.Config{unmarshalableConfig}, - }, - svcErr: nil, - response: sdk.BootstrapPage{}, - err: errors.NewSDKError(errJsonEOF), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("List", mock.Anything, tc.session, mock.Anything, tc.pageMeta.Offset, tc.pageMeta.Limit).Return(tc.svcResp, tc.svcErr) - resp, err := mgsdk.Bootstraps(tc.pageMeta, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if err == nil { - ok := svcCall.Parent.AssertCalled(t, "List", mock.Anything, tc.session, mock.Anything, tc.pageMeta.Offset, tc.pageMeta.Limit) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestWhiteList(t *testing.T) { - bs, bsvc, _, auth := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - active := 1 - inactive := 0 - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - state int - svcReq bootstrap.State - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "whitelist to active state successfully", - domainID: domainID, - token: validToken, - thingID: thingId, - state: active, - svcReq: bootstrap.Active, - svcErr: nil, - err: nil, - }, - { - desc: "whitelist to inactive state successfully", - domainID: domainID, - token: validToken, - thingID: thingId, - state: inactive, - svcReq: bootstrap.Inactive, - svcErr: nil, - err: nil, - }, - { - desc: "whitelist with invalid token", - domainID: domainID, - token: invalidToken, - thingID: thingId, - state: active, - svcReq: bootstrap.Active, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "whitelist with empty token", - domainID: domainID, - token: "", - thingID: thingId, - state: active, - svcReq: bootstrap.Active, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "whitelist with invalid state", - domainID: domainID, - token: validToken, - thingID: thingId, - state: -1, - svcReq: bootstrap.Active, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBootstrapState), http.StatusBadRequest), - }, - { - desc: "whitelist with empty thing Id", - domainID: domainID, - token: validToken, - thingID: "", - state: 1, - svcReq: bootstrap.Active, - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewBootstrap(t *testing.T) { - bs, bsvc, _, auth := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - viewBoostrapRes := sdk.BootstrapConfig{ - ThingID: thingId, - Channels: sdkBootsrapConfigRes.Channels, - ExternalID: externalId, - ExternalKey: externalKey, - Name: bsName, - Content: content, - State: 0, - } - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - id string - svcResp bootstrap.Config - svcErr error - authenticateErr error - response sdk.BootstrapConfig - err errors.SDKError - }{ - { - desc: "view successfully", - domainID: domainID, - token: validToken, - id: thingId, - svcResp: bootstrapConfig, - svcErr: nil, - response: viewBoostrapRes, - err: nil, - }, - { - desc: "view with invalid token", - domainID: domainID, - token: invalidToken, - id: thingId, - svcResp: bootstrap.Config{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.BootstrapConfig{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view with empty token", - domainID: domainID, - token: "", - id: thingId, - svcResp: bootstrap.Config{}, - svcErr: nil, - response: sdk.BootstrapConfig{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view with non-existent thing Id", - domainID: domainID, - token: validToken, - id: invalid, - svcResp: bootstrap.Config{}, - svcErr: svcerr.ErrViewEntity, - response: sdk.BootstrapConfig{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "view with response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - id: thingId, - svcResp: bootstrap.Config{ - ThingID: thingId, - Channels: []bootstrap.Channel{ - { - ID: channel1Id, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - }, - }, - svcErr: nil, - response: sdk.BootstrapConfig{}, - err: errors.NewSDKError(errJsonEOF), - }, - { - desc: "view with empty thing Id", - domainID: domainID, - token: validToken, - id: "", - svcResp: bootstrap.Config{}, - svcErr: nil, - response: sdk.BootstrapConfig{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("View", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr) - resp, err := mgsdk.ViewBootstrap(tc.id, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if err == nil { - ok := svcCall.Parent.AssertCalled(t, "View", mock.Anything, tc.session, tc.id) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateBootstrap(t *testing.T) { - bs, bsvc, _, auth := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - cfg sdk.BootstrapConfig - svcReq bootstrap.Config - svcErr error - authenticationErr error - err errors.SDKError - }{ - { - desc: "update successfully", - domainID: domainID, - token: validToken, - cfg: sdkBootstrapConfig, - svcReq: bootstrap.Config{ - ThingID: thingId, - Name: bsName, - Content: content, - }, - svcErr: nil, - err: nil, - }, - { - desc: "update with invalid token", - domainID: domainID, - token: invalidToken, - cfg: sdkBootstrapConfig, - svcReq: bootstrap.Config{ - ThingID: thingId, - Name: bsName, - Content: content, - }, - authenticationErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update with empty token", - domainID: domainID, - token: "", - cfg: sdkBootstrapConfig, - svcReq: bootstrap.Config{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update with config that cannot be marshalled", - domainID: domainID, - token: validToken, - cfg: sdk.BootstrapConfig{ - 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, - }, - svcReq: bootstrap.Config{ - ThingID: thingId, - Name: bsName, - Content: content, - }, - svcErr: nil, - err: errors.NewSDKError(errMarshalChan), - }, - { - desc: "update with non-existent thing Id", - domainID: domainID, - token: validToken, - cfg: sdk.BootstrapConfig{ - ThingID: invalid, - Channels: []sdk.Channel{ - { - ID: channel1Id, - }, - }, - ExternalID: externalId, - ExternalKey: externalKey, - Content: content, - Name: bsName, - }, - svcReq: bootstrap.Config{ - ThingID: invalid, - Name: bsName, - Content: content, - }, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "update with empty thing Id", - domainID: domainID, - token: validToken, - cfg: sdk.BootstrapConfig{ - ThingID: "", - Channels: []sdk.Channel{ - { - ID: channel1Id, - }, - }, - ExternalID: externalId, - ExternalKey: externalKey, - Content: content, - Name: bsName, - }, - svcReq: bootstrap.Config{ - ThingID: "", - Name: bsName, - Content: content, - }, - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "update with config with only thing Id", - domainID: domainID, - token: validToken, - cfg: sdk.BootstrapConfig{ - ThingID: thingId, - }, - svcReq: bootstrap.Config{ - ThingID: thingId, - }, - svcErr: nil, - err: nil, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticationErr) - svcCall := bsvc.On("Update", mock.Anything, tc.session, tc.svcReq).Return(tc.svcErr) - err := mgsdk.UpdateBootstrap(tc.cfg, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Update", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateBootstrapCerts(t *testing.T) { - bs, bsvc, _, auth := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - updateconfigRes := sdk.BootstrapConfig{ - ThingID: thingId, - ClientCert: clientCert, - CACert: caCert, - ClientKey: clientKey, - } - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - id string - clientCert string - clientKey string - caCert string - svcResp bootstrap.Config - svcErr error - authenticateErr error - response sdk.BootstrapConfig - err errors.SDKError - }{ - { - desc: "update certs successfully", - domainID: domainID, - token: validToken, - id: thingId, - clientCert: clientCert, - clientKey: clientKey, - caCert: caCert, - svcResp: bootstrapConfig, - svcErr: nil, - response: updateconfigRes, - err: nil, - }, - { - desc: "update certs with invalid token", - domainID: domainID, - token: validToken, - id: thingId, - clientCert: clientCert, - clientKey: clientKey, - caCert: caCert, - svcResp: bootstrap.Config{}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update certs with empty token", - domainID: domainID, - token: "", - id: thingId, - clientCert: clientCert, - clientKey: clientKey, - caCert: caCert, - svcResp: bootstrap.Config{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update certs with non-existent thing Id", - domainID: domainID, - token: validToken, - id: invalid, - clientCert: clientCert, - clientKey: clientKey, - caCert: caCert, - svcResp: bootstrap.Config{}, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "update certs with empty certs", - domainID: domainID, - token: validToken, - id: thingId, - clientCert: "", - clientKey: "", - caCert: "", - svcResp: bootstrap.Config{}, - svcErr: nil, - err: nil, - }, - { - desc: "update certs with empty id", - domainID: domainID, - token: validToken, - id: "", - clientCert: clientCert, - clientKey: clientKey, - caCert: caCert, - svcResp: bootstrap.Config{}, - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("UpdateCert", mock.Anything, tc.session, tc.id, tc.clientCert, tc.clientKey, tc.caCert).Return(tc.svcResp, tc.svcErr) - resp, err := mgsdk.UpdateBootstrapCerts(tc.id, tc.clientCert, tc.clientKey, tc.caCert, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if err == nil { - assert.Equal(t, tc.response, resp) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateBootstrapConnection(t *testing.T) { - bs, bsvc, _, auth := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - id string - channels []string - svcRes bootstrap.Config - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "update connection successfully", - domainID: domainID, - token: validToken, - id: thingId, - channels: []string{channel1Id, channel2Id}, - svcErr: nil, - err: nil, - }, - { - desc: "update connection with invalid token", - domainID: domainID, - token: invalidToken, - id: thingId, - channels: []string{channel1Id, channel2Id}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update connection with empty token", - domainID: domainID, - token: "", - id: thingId, - channels: []string{channel1Id, channel2Id}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update connection with non-existent thing Id", - domainID: domainID, - token: validToken, - id: invalid, - channels: []string{channel1Id, channel2Id}, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "update connection with non-existent channel Id", - domainID: domainID, - token: validToken, - id: thingId, - channels: []string{invalid}, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "update connection with empty channels", - domainID: domainID, - token: validToken, - id: thingId, - channels: []string{}, - svcErr: svcerr.ErrUpdateEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update connection with empty id", - domainID: domainID, - token: validToken, - id: "", - channels: []string{channel1Id, channel2Id}, - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("UpdateConnections", mock.Anything, tc.session, tc.token, tc.id, tc.channels).Return(tc.svcErr) - err := mgsdk.UpdateBootstrapConnection(tc.id, tc.channels, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UpdateConnections", mock.Anything, tc.session, tc.token, tc.id, tc.channels) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestRemoveBootstrap(t *testing.T) { - bs, bsvc, _, auth := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - id string - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "remove successfully", - domainID: domainID, - token: validToken, - id: thingId, - svcErr: nil, - err: nil, - }, - { - desc: "remove with invalid token", - domainID: domainID, - token: invalidToken, - id: thingId, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "remove with non-existent thing Id", - domainID: domainID, - token: validToken, - id: invalid, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "remove removed bootstrap", - domainID: domainID, - token: validToken, - id: thingId, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "remove with empty token", - domainID: domainID, - token: "", - id: thingId, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "remove with empty id", - domainID: domainID, - token: validToken, - id: "", - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("Remove", mock.Anything, tc.session, tc.id).Return(tc.svcErr) - err := mgsdk.RemoveBootstrap(tc.id, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Remove", mock.Anything, tc.session, tc.id) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestBoostrap(t *testing.T) { - bs, bsvc, reader, _ := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - externalID string - externalKey string - svcResp bootstrap.Config - svcErr error - readerResp interface{} - readerErr error - response sdk.BootstrapConfig - err errors.SDKError - }{ - { - desc: "bootstrap successfully", - token: validToken, - externalID: externalId, - externalKey: externalKey, - svcResp: bootstrapConfig, - svcErr: nil, - readerResp: readConfigResponse, - readerErr: nil, - response: sdkBootsrapConfigRes, - err: nil, - }, - { - desc: "bootstrap with invalid token", - token: invalidToken, - externalID: externalId, - externalKey: externalKey, - svcResp: bootstrap.Config{}, - svcErr: svcerr.ErrAuthentication, - readerResp: bootstrap.Config{}, - readerErr: nil, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "bootstrap with error in reader", - token: validToken, - externalID: externalId, - externalKey: externalKey, - svcResp: bootstrapConfig, - svcErr: nil, - readerResp: []byte{0}, - readerErr: errJsonEOF, - err: errors.NewSDKErrorWithStatus(errJsonEOF, http.StatusInternalServerError), - }, - { - desc: "boostrap with response that cannot be unmarshalled", - token: validToken, - externalID: externalId, - externalKey: externalKey, - svcResp: bootstrapConfig, - svcErr: nil, - readerResp: []byte{0}, - readerErr: nil, - err: errors.NewSDKError(errors.New("json: cannot unmarshal string into Go value of type map[string]json.RawMessage")), - }, - { - desc: "bootstrap with empty id", - token: validToken, - externalID: "", - externalKey: externalKey, - svcResp: bootstrap.Config{}, - svcErr: nil, - readerResp: bootstrap.Config{}, - readerErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "boostrap with empty key", - token: validToken, - externalID: externalId, - externalKey: "", - svcResp: bootstrap.Config{}, - svcErr: nil, - readerResp: bootstrap.Config{}, - readerErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerKey), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := bsvc.On("Bootstrap", mock.Anything, tc.externalKey, tc.externalID, false).Return(tc.svcResp, tc.svcErr) - readerCall := reader.On("ReadConfig", tc.svcResp, false).Return(tc.readerResp, tc.readerErr) - resp, err := mgsdk.Bootstrap(tc.externalID, tc.externalKey) - assert.Equal(t, tc.err, err) - if err == nil { - assert.Equal(t, tc.response, resp) - ok := svcCall.Parent.AssertCalled(t, "Bootstrap", mock.Anything, tc.externalKey, tc.externalID, false) - assert.True(t, ok) - } - svcCall.Unset() - readerCall.Unset() - }) - } -} - -func TestBootstrapSecure(t *testing.T) { - bs, bsvc, reader, _ := setupBootstrap() - defer bs.Close() - - conf := sdk.Config{ - BootstrapURL: bs.URL, - } - mgsdk := sdk.NewSDK(conf) - - b, err := json.Marshal(readConfigResponse) - assert.Nil(t, err, fmt.Sprintf("Marshalling bootstrap response expected to succeed: %s.\n", err)) - encResponse, err := encrypt(b, encKey) - assert.Nil(t, err, fmt.Sprintf("Encrypting bootstrap response expected to succeed: %s.\n", err)) - - cases := []struct { - desc string - token string - externalID string - externalKey string - cryptoKey string - svcResp bootstrap.Config - svcErr error - readerResp []byte - readerErr error - response sdk.BootstrapConfig - err errors.SDKError - }{ - { - desc: "bootstrap successfully", - token: validToken, - externalID: externalId, - externalKey: externalKey, - cryptoKey: string(encKey), - svcResp: bootstrapConfig, - svcErr: nil, - readerResp: encResponse, - readerErr: nil, - response: sdkBootsrapConfigRes, - err: nil, - }, - { - desc: "bootstrap with invalid token", - token: invalidToken, - externalID: externalId, - externalKey: externalKey, - cryptoKey: string(encKey), - svcResp: bootstrap.Config{}, - svcErr: svcerr.ErrAuthentication, - readerResp: []byte{0}, - readerErr: nil, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "booostrap with invalid crypto key", - token: validToken, - externalID: externalId, - externalKey: externalKey, - cryptoKey: invalid, - svcResp: bootstrap.Config{}, - svcErr: nil, - readerResp: []byte{0}, - readerErr: nil, - err: errors.NewSDKError(errors.New("crypto/aes: invalid key size 7")), - }, - { - desc: "bootstrap with error in reader", - token: validToken, - externalID: externalId, - externalKey: externalKey, - cryptoKey: string(encKey), - svcResp: bootstrapConfig, - svcErr: nil, - readerResp: []byte{0}, - readerErr: errJsonEOF, - err: errors.NewSDKErrorWithStatus(errJsonEOF, http.StatusInternalServerError), - }, - { - desc: "bootstrap with response that cannot be unmarshalled", - token: validToken, - externalID: externalId, - externalKey: externalKey, - cryptoKey: string(encKey), - svcResp: bootstrapConfig, - svcErr: nil, - readerResp: []byte{0}, - readerErr: nil, - err: errors.NewSDKError(errJsonEOF), - }, - { - desc: "bootstrap with empty id", - token: validToken, - externalID: "", - externalKey: externalKey, - svcResp: bootstrap.Config{}, - svcErr: nil, - readerResp: []byte{0}, - readerErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := bsvc.On("Bootstrap", mock.Anything, mock.Anything, tc.externalID, true).Return(tc.svcResp, tc.svcErr) - readerCall := reader.On("ReadConfig", tc.svcResp, true).Return(tc.readerResp, tc.readerErr) - resp, err := mgsdk.BootstrapSecure(tc.externalID, tc.externalKey, tc.cryptoKey) - assert.Equal(t, tc.err, err) - if err == nil { - assert.Equal(t, sdkBootsrapConfigRes, resp) - ok := svcCall.Parent.AssertCalled(t, "Bootstrap", mock.Anything, mock.Anything, tc.externalID, true) - assert.True(t, ok) - } - svcCall.Unset() - readerCall.Unset() - }) - } -} - -func encrypt(in, encKey []byte) ([]byte, error) { - block, err := aes.NewCipher(encKey) - if err != nil { - return nil, err - } - ciphertext := make([]byte, aes.BlockSize+len(in)) - iv := ciphertext[:aes.BlockSize] - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return nil, err - } - stream := cipher.NewCFBEncrypter(block, iv) - stream.XORKeyStream(ciphertext[aes.BlockSize:], in) - return ciphertext, nil -} diff --git a/pkg/sdk/go/certs.go b/pkg/sdk/go/certs.go deleted file mode 100644 index 35d68509a..000000000 --- a/pkg/sdk/go/certs.go +++ /dev/null @@ -1,108 +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 ( - certsEndpoint = "certs" - serialsEndpoint = "serials" -) - -// Cert represents certs data. -type Cert struct { - SerialNumber string `json:"serial_number,omitempty"` - Certificate string `json:"certificate,omitempty"` - Key string `json:"key,omitempty"` - Revoked bool `json:"revoked,omitempty"` - ExpiryTime time.Time `json:"expiry_time,omitempty"` - ThingID string `json:"thing_id,omitempty"` -} - -func (sdk mgSDK) IssueCert(thingID, validity, domainID, token string) (Cert, errors.SDKError) { - r := certReq{ - ThingID: thingID, - Validity: validity, - } - d, err := json.Marshal(r) - if err != nil { - return Cert{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s", sdk.certsURL, domainID, certsEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, d, nil, http.StatusCreated) - if sdkerr != nil { - return Cert{}, sdkerr - } - - var c Cert - if err := json.Unmarshal(body, &c); err != nil { - return Cert{}, errors.NewSDKError(err) - } - return c, nil -} - -func (sdk mgSDK) ViewCert(id, domainID, token string) (Cert, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.certsURL, domainID, certsEndpoint, id) - - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return Cert{}, err - } - - var cert Cert - if err := json.Unmarshal(body, &cert); err != nil { - return Cert{}, errors.NewSDKError(err) - } - - return cert, nil -} - -func (sdk mgSDK) ViewCertByThing(thingID, domainID, token string) (CertSerials, errors.SDKError) { - if thingID == "" { - return CertSerials{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.certsURL, domainID, serialsEndpoint, thingID) - - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return CertSerials{}, err - } - var cs CertSerials - if err := json.Unmarshal(body, &cs); err != nil { - return CertSerials{}, errors.NewSDKError(err) - } - - return cs, nil -} - -func (sdk mgSDK) RevokeCert(id, domainID, token string) (time.Time, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.certsURL, domainID, certsEndpoint, id) - - _, body, err := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusOK) - if err != nil { - return time.Time{}, err - } - - var rcr revokeCertsRes - if err := json.Unmarshal(body, &rcr); err != nil { - return time.Time{}, errors.NewSDKError(err) - } - - return rcr.RevocationTime, nil -} - -type certReq struct { - ThingID string `json:"thing_id"` - Validity string `json:"ttl"` -} diff --git a/pkg/sdk/go/certs_test.go b/pkg/sdk/go/certs_test.go deleted file mode 100644 index 13055db6e..000000000 --- a/pkg/sdk/go/certs_test.go +++ /dev/null @@ -1,463 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/absmach/magistrala/certs" - httpapi "github.com/absmach/magistrala/certs/api" - "github.com/absmach/magistrala/certs/mocks" - "github.com/absmach/magistrala/internal/testsutil" - 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" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" - -var ( - valid = "valid" - thingID = testsutil.GenerateUUID(&testing.T{}) - OwnerID = testsutil.GenerateUUID(&testing.T{}) - serial = testsutil.GenerateUUID(&testing.T{}) - ttl = "10h" - cert, sdkCert = generateTestCerts(&testing.T{}) - defOffset uint64 = 0 - defLimit uint64 = 10 - defRevoke = "false" -) - -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, - SerialNumber: serial, - ExpiryTime: expirationTime, - Certificate: valid, - } - sc := sdk.Cert{ - ThingID: thingID, - SerialNumber: serial, - Key: valid, - Certificate: valid, - ExpiryTime: expirationTime, - } - - return c, sc -} - -func setupCerts() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - svc := new(mocks.Service) - logger := mglog.NewMock() - authn := new(authnmocks.Authentication) - mux := httpapi.MakeHandler(svc, authn, logger, instanceID) - - return httptest.NewServer(mux), svc, authn -} - -func TestIssueCert(t *testing.T) { - ts, svc, auth := setupCerts() - defer ts.Close() - - sdkConf := sdk.Config{ - CertsURL: ts.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - thingID string - duration string - domainID string - token string - session mgauthn.Session - authenticateErr error - svcRes certs.Cert - svcErr error - err errors.SDKError - }{ - { - desc: "create new cert with thing id and duration", - thingID: thingID, - duration: ttl, - domainID: validID, - token: validToken, - svcRes: certs.Cert{SerialNumber: serial}, - svcErr: nil, - err: nil, - }, - { - desc: "create new cert with empty thing id and duration", - thingID: "", - duration: ttl, - domainID: validID, - token: validToken, - svcRes: certs.Cert{}, - svcErr: errors.Wrap(certs.ErrFailedCertCreation, apiutil.ErrMissingID), - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "create new cert with invalid thing id and duration", - thingID: invalid, - duration: ttl, - domainID: validID, - token: validToken, - svcRes: certs.Cert{}, - svcErr: errors.Wrap(certs.ErrFailedCertCreation, apiutil.ErrValidation), - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, certs.ErrFailedCertCreation), http.StatusBadRequest), - }, - { - desc: "create new cert with thing id and empty duration", - thingID: thingID, - duration: "", - domainID: validID, - token: validToken, - svcRes: certs.Cert{}, - svcErr: errors.Wrap(certs.ErrFailedCertCreation, apiutil.ErrMissingCertData), - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingCertData), http.StatusBadRequest), - }, - { - desc: "create new cert with thing id and malformed duration", - thingID: thingID, - duration: invalid, - domainID: validID, - token: validToken, - svcRes: certs.Cert{}, - svcErr: errors.Wrap(certs.ErrFailedCertCreation, apiutil.ErrInvalidCertData), - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidCertData), http.StatusBadRequest), - }, - { - desc: "create new cert with empty token", - thingID: thingID, - duration: ttl, - domainID: validID, - token: "", - svcRes: certs.Cert{}, - svcErr: errors.Wrap(certs.ErrFailedCertCreation, svcerr.ErrAuthentication), - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "create new cert with invalid token", - thingID: thingID, - domainID: domainID, - duration: ttl, - token: invalidToken, - svcRes: certs.Cert{}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "create new empty cert", - thingID: "", - duration: "", - domainID: validID, - token: validToken, - svcRes: certs.Cert{}, - svcErr: errors.Wrap(certs.ErrFailedCertCreation, certs.ErrFailedCertCreation), - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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("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) - 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewCert(t *testing.T) { - ts, svc, auth := setupCerts() - defer ts.Close() - - sdkConf := sdk.Config{ - CertsURL: ts.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - - viewCertRes := sdkCert - viewCertRes.Key = "" - - cases := []struct { - desc string - certID string - domainID string - token string - session mgauthn.Session - authenticateErr error - svcRes certs.Cert - svcErr error - err errors.SDKError - }{ - { - desc: "view existing cert", - certID: validID, - domainID: validID, - token: validToken, - svcRes: cert, - svcErr: nil, - err: nil, - }, - { - desc: "view non-existent cert", - certID: invalid, - domainID: validID, - token: validToken, - svcRes: certs.Cert{}, - svcErr: errors.Wrap(svcerr.ErrNotFound, repoerr.ErrNotFound), - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, svcerr.ErrNotFound), http.StatusNotFound), - }, - { - desc: "view cert with invalid token", - certID: validID, - domainID: domainID, - token: invalidToken, - svcRes: certs.Cert{}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view cert with empty token", - certID: validID, - domainID: domainID, - token: "", - svcRes: certs.Cert{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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("ViewCert", mock.Anything, tc.certID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ViewCert(tc.certID, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if err == nil { - assert.Equal(t, viewCertRes, resp) - ok := svcCall.Parent.AssertCalled(t, "ViewCert", mock.Anything, tc.certID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewCertByThing(t *testing.T) { - ts, svc, auth := setupCerts() - defer ts.Close() - - sdkConf := sdk.Config{ - CertsURL: ts.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - - viewCertThingRes := sdk.CertSerials{ - Certs: []sdk.Cert{{ - SerialNumber: serial, - }}, - } - cases := []struct { - desc string - thingID string - domainID string - token string - session mgauthn.Session - authenticateErr error - svcRes certs.CertPage - svcErr error - err errors.SDKError - }{ - { - desc: "view existing cert", - thingID: thingID, - domainID: domainID, - token: validToken, - svcRes: certs.CertPage{Certificates: []certs.Cert{{SerialNumber: serial}}}, - svcErr: nil, - err: nil, - }, - { - desc: "view non-existent cert", - thingID: invalid, - domainID: domainID, - token: validToken, - svcRes: certs.CertPage{Certificates: []certs.Cert{}}, - svcErr: errors.Wrap(svcerr.ErrNotFound, repoerr.ErrNotFound), - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, svcerr.ErrNotFound), http.StatusNotFound), - }, - { - desc: "view cert with invalid token", - thingID: thingID, - domainID: domainID, - token: invalidToken, - svcRes: certs.CertPage{Certificates: []certs.Cert{}}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view cert with empty token", - thingID: thingID, - domainID: domainID, - token: "", - svcRes: certs.CertPage{Certificates: []certs.Cert{}}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view cert with empty thing id", - thingID: "", - domainID: domainID, - token: validToken, - svcRes: certs.CertPage{Certificates: []certs.Cert{}}, - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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: defRevoke, Offset: defOffset, Limit: defLimit}).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ViewCertByThing(tc.thingID, 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.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestRevokeCert(t *testing.T) { - ts, svc, auth := setupCerts() - defer ts.Close() - - sdkConf := sdk.Config{ - CertsURL: ts.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - thingID string - domainID string - token string - session mgauthn.Session - svcResp certs.Revoke - authenticateErr error - svcErr error - err errors.SDKError - }{ - { - desc: "revoke cert successfully", - thingID: thingID, - domainID: validID, - token: validToken, - svcResp: certs.Revoke{RevocationTime: time.Now()}, - svcErr: nil, - err: nil, - }, - { - desc: "revoke cert with invalid token", - thingID: thingID, - domainID: validID, - token: invalidToken, - svcResp: certs.Revoke{}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "revoke non-existing cert", - thingID: invalid, - domainID: validID, - token: validToken, - svcResp: certs.Revoke{}, - svcErr: errors.Wrap(certs.ErrFailedCertRevocation, svcerr.ErrNotFound), - err: errors.NewSDKErrorWithStatus(certs.ErrFailedCertRevocation, http.StatusNotFound), - }, - { - desc: "revoke cert with empty token", - thingID: thingID, - domainID: validID, - token: "", - svcResp: certs.Revoke{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "revoke deleted cert", - thingID: thingID, - domainID: validID, - token: validToken, - svcResp: certs.Revoke{}, - svcErr: errors.Wrap(certs.ErrFailedToRemoveCertFromDB, svcerr.ErrNotFound), - err: errors.NewSDKErrorWithStatus(certs.ErrFailedToRemoveCertFromDB, http.StatusNotFound), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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("RevokeCert", mock.Anything, tc.domainID, tc.token, tc.thingID).Return(tc.svcResp, tc.svcErr) - resp, err := mgsdk.RevokeCert(tc.thingID, 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} diff --git a/pkg/sdk/go/channels.go b/pkg/sdk/go/channels.go deleted file mode 100644 index d68b92c84..000000000 --- a/pkg/sdk/go/channels.go +++ /dev/null @@ -1,307 +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 channelsEndpoint = "channels" - -// Channel represents magistrala channel. -type Channel struct { - ID string `json:"id,omitempty"` - DomainID string `json:"domain_id,omitempty"` - ParentID string `json:"parent_id,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Level int `json:"level,omitempty"` - Path string `json:"path,omitempty"` - Children []*Channel `json:"children,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"` -} - -func (sdk mgSDK) CreateChannel(c Channel, domainID, token string) (Channel, errors.SDKError) { - data, err := json.Marshal(c) - if err != nil { - return Channel{}, errors.NewSDKError(err) - } - url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return Channel{}, sdkerr - } - - c = Channel{} - if err := json.Unmarshal(body, &c); err != nil { - return Channel{}, errors.NewSDKError(err) - } - - return c, nil -} - -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) - if err != nil { - return ChannelsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return ChannelsPage{}, sdkerr - } - - var cp ChannelsPage - if err = json.Unmarshal(body, &cp); err != nil { - return ChannelsPage{}, errors.NewSDKError(err) - } - - 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) - if err != nil { - return ChannelsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return ChannelsPage{}, sdkerr - } - - var cp ChannelsPage - if err := json.Unmarshal(body, &cp); err != nil { - return ChannelsPage{}, errors.NewSDKError(err) - } - - return cp, nil -} - -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) - - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return Channel{}, err - } - - var c Channel - if err := json.Unmarshal(body, &c); err != nil { - return Channel{}, errors.NewSDKError(err) - } - - return c, nil -} - -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) - - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return Channel{}, err - } - - var c Channel - if err := json.Unmarshal(body, &c); err != nil { - return Channel{}, errors.NewSDKError(err) - } - - return c, nil -} - -func (sdk mgSDK) UpdateChannel(c Channel, domainID, token string) (Channel, errors.SDKError) { - if c.ID == "" { - return Channel{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.thingsURL, domainID, channelsEndpoint, c.ID) - - data, err := json.Marshal(c) - if err != nil { - return Channel{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return Channel{}, sdkerr - } - - c = Channel{} - if err := json.Unmarshal(body, &c); err != nil { - return Channel{}, errors.NewSDKError(err) - } - - return c, nil -} - -func (sdk mgSDK) AddUserToChannel(channelID 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/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, usersEndpoint, assignEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - return sdkerr -} - -func (sdk mgSDK) RemoveUserFromChannel(channelID 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/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, usersEndpoint, unassignEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - return sdkerr -} - -func (sdk mgSDK) ListChannelUsers(channelID string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", domainID, channelsEndpoint, channelID, 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) AddUserGroupToChannel(channelID string, req UserGroupsRequest, 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/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, groupsEndpoint, assignEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - return sdkerr -} - -func (sdk mgSDK) RemoveUserGroupFromChannel(channelID string, req UserGroupsRequest, 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/%s", sdk.thingsURL, domainID, channelsEndpoint, channelID, groupsEndpoint, unassignEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - return sdkerr -} - -func (sdk mgSDK) ListChannelUserGroups(channelID string, pm PageMetadata, domainID, token string) (GroupsPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", domainID, channelsEndpoint, channelID, groupsEndpoint), pm) - if err != nil { - return GroupsPage{}, errors.NewSDKError(err) - } - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return GroupsPage{}, sdkerr - } - gp := GroupsPage{} - if err := json.Unmarshal(body, &gp); err != nil { - return GroupsPage{}, errors.NewSDKError(err) - } - - return gp, nil -} - -func (sdk mgSDK) Connect(conn Connection, domainID, token string) errors.SDKError { - data, err := json.Marshal(conn) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, domainID, connectEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - - return sdkerr -} - -func (sdk mgSDK) Disconnect(connIDs Connection, domainID, token string) errors.SDKError { - data, err := json.Marshal(connIDs) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, 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) - - _, _, 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) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mgSDK) EnableChannel(id, domainID, token string) (Channel, errors.SDKError) { - return sdk.changeChannelStatus(id, enableEndpoint, domainID, token) -} - -func (sdk mgSDK) DisableChannel(id, domainID, token string) (Channel, errors.SDKError) { - return sdk.changeChannelStatus(id, disableEndpoint, domainID, token) -} - -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) - _, _, 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) - - _, body, err := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusOK) - if err != nil { - return Channel{}, err - } - c := Channel{} - if err := json.Unmarshal(body, &c); err != nil { - return Channel{}, errors.NewSDKError(err) - } - - return c, nil -} diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go deleted file mode 100644 index d4b02dc68..000000000 --- a/pkg/sdk/go/channels_test.go +++ /dev/null @@ -1,2900 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - authmocks "github.com/absmach/magistrala/auth/mocks" - "github.com/absmach/magistrala/internal/testsutil" - 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" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - channelName = "channelName" - newName = "newName" - newDescription = "newDescription" - channel = generateTestChannel(&testing.T{}) -) - -func setupChannels() (*httptest.Server, *gmocks.Service, *authnmocks.Authentication) { - tsvc := new(thmocks.Service) - usvc := new(usmocks.Service) - gsvc := new(gmocks.Service) - logger := mglog.NewMock() - provider := new(oauth2mocks.Provider) - provider.On("Name").Return("test") - authn := new(authnmocks.Authentication) - token := new(authmocks.TokenServiceClient) - - mux := chi.NewRouter() - - thapi.MakeHandler(tsvc, gsvc, authn, mux, logger, "") - usapi.MakeHandler(usvc, authn, token, true, gsvc, mux, logger, "", passRegex, provider) - return httptest.NewServer(mux), gsvc, authn -} - -func TestCreateChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - group := convertChannel(channel) - createGroupReq := groups.Group{ - Name: channel.Name, - Metadata: groups.Metadata{"role": "client"}, - Status: groups.EnabledStatus, - } - - channelReq := sdk.Channel{ - Name: channel.Name, - Metadata: validMetadata, - Status: groups.EnabledStatus.String(), - } - - channelKind := "new_channel" - parentID := testsutil.GenerateUUID(&testing.T{}) - pGroup := group - pGroup.Parent = parentID - pChannel := channel - pChannel.ParentID = parentID - - iGroup := group - iGroup.Metadata = groups.Metadata{ - "test": make(chan int), - } - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - cases := []struct { - desc string - channelReq sdk.Channel - domainID string - token string - session mgauthn.Session - createGroupReq groups.Group - svcRes groups.Group - svcErr error - authenticateRes mgauthn.Session - authenticateErr error - response sdk.Channel - err errors.SDKError - }{ - { - desc: "create channel successfully", - channelReq: channelReq, - domainID: domainID, - token: validToken, - createGroupReq: createGroupReq, - svcRes: group, - svcErr: nil, - response: channel, - err: nil, - }, - { - desc: "create channel with existing name", - channelReq: channelReq, - domainID: domainID, - token: validToken, - createGroupReq: createGroupReq, - svcRes: groups.Group{}, - svcErr: svcerr.ErrCreateEntity, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "create channel that can't be marshalled", - channelReq: sdk.Channel{ - Name: "test", - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - domainID: domainID, - token: validToken, - createGroupReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "create channel with parent", - channelReq: sdk.Channel{ - Name: channel.Name, - ParentID: parentID, - Status: groups.EnabledStatus.String(), - }, - domainID: domainID, - token: validToken, - createGroupReq: groups.Group{ - Name: channel.Name, - Parent: parentID, - Status: groups.EnabledStatus, - }, - svcRes: pGroup, - svcErr: nil, - response: pChannel, - err: nil, - }, - { - desc: "create channel with invalid parent", - channelReq: sdk.Channel{ - Name: channel.Name, - ParentID: wrongID, - Status: groups.EnabledStatus.String(), - }, - domainID: domainID, - token: validToken, - createGroupReq: groups.Group{ - Name: channel.Name, - Parent: wrongID, - Status: groups.EnabledStatus, - }, - svcRes: groups.Group{}, - svcErr: svcerr.ErrCreateEntity, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "create channel with missing name", - channelReq: sdk.Channel{ - Status: groups.EnabledStatus.String(), - }, - domainID: domainID, - token: validToken, - createGroupReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), - }, - { - desc: "create a channel with every field defined", - channelReq: sdk.Channel{ - ID: group.ID, - ParentID: parentID, - Name: channel.Name, - Description: description, - Metadata: validMetadata, - CreatedAt: group.CreatedAt, - UpdatedAt: group.UpdatedAt, - Status: groups.EnabledStatus.String(), - }, - domainID: domainID, - token: validToken, - createGroupReq: groups.Group{ - ID: group.ID, - Parent: parentID, - Name: channel.Name, - Description: description, - Metadata: groups.Metadata{"role": "client"}, - CreatedAt: group.CreatedAt, - UpdatedAt: group.UpdatedAt, - Status: groups.EnabledStatus, - }, - svcRes: pGroup, - svcErr: nil, - response: pChannel, - err: nil, - }, - { - desc: "create channel with response that can't be unmarshalled", - channelReq: channelReq, - domainID: domainID, - token: validToken, - createGroupReq: createGroupReq, - svcRes: iGroup, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("CreateGroup", mock.Anything, tc.session, channelKind, tc.createGroupReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.CreateChannel(tc.channelReq, 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, "CreateGroup", mock.Anything, tc.session, channelKind, tc.createGroupReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListChannels(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - var chs []sdk.Channel - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - for i := 10; i < 100; i++ { - 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(), - } - chs = append(chs, gr) - } - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - status groups.Status - total uint64 - offset uint64 - limit uint64 - level int - name string - metadata sdk.Metadata - groupsPageMeta groups.Page - svcRes groups.Page - svcErr error - authenticateRes mgauthn.Session - authenticateErr error - response sdk.ChannelsPage - err errors.SDKError - }{ - { - desc: "list channels successfully", - token: validToken, - domainID: domainID, - limit: limit, - offset: offset, - total: total, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(chs[offset:limit])), - }, - Groups: convertChannels(chs[offset:limit]), - }, - response: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(chs[offset:limit])), - }, - Channels: chs[offset:limit], - }, - err: nil, - }, - { - desc: "list channels with invalid token", - token: invalidToken, - domainID: domainID, - offset: offset, - limit: limit, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list channels with empty token", - token: "", - domainID: validID, - offset: offset, - limit: limit, - groupsPageMeta: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list channels with zero limit", - token: validToken, - domainID: domainID, - offset: offset, - limit: 0, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(chs[offset:])), - }, - Groups: convertChannels(chs[offset:limit]), - }, - svcErr: nil, - response: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(chs[offset:])), - }, - Channels: chs[offset:limit], - }, - err: nil, - }, - { - desc: "list channels with limit greater than max", - token: validToken, - domainID: domainID, - offset: offset, - limit: 110, - groupsPageMeta: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "list channels with level", - token: validToken, - domainID: domainID, - offset: 0, - limit: 1, - level: 1, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: 1, - }, - Level: 1, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: convertChannels(chs[0:1]), - }, - svcErr: nil, - response: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Channels: chs[0:1], - }, - err: nil, - }, - { - desc: "list channels with metadata", - token: validToken, - domainID: domainID, - offset: 0, - limit: 10, - metadata: sdk.Metadata{"name": "thing_89"}, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: 10, - Metadata: groups.Metadata{"name": "thing_89"}, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: convertChannels([]sdk.Channel{chs[89]}), - }, - svcErr: nil, - response: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Channels: []sdk.Channel{chs[89]}, - }, - err: nil, - }, - { - desc: "list channels with invalid metadata", - token: validToken, - domainID: domainID, - offset: 0, - limit: 10, - metadata: sdk.Metadata{ - "test": make(chan int), - }, - groupsPageMeta: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list channels with service response that can't be unmarshalled", - token: validToken, - domainID: domainID, - offset: 0, - limit: 10, - groupsPageMeta: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{{ - ID: generateUUID(t), - Metadata: groups.Metadata{ - "test": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - pm := sdk.PageMetadata{ - Offset: tc.offset, - Limit: tc.limit, - Level: uint64(tc.level), - Metadata: tc.metadata, - } - if tc.token == validToken { - 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.UsersKind, "", tc.groupsPageMeta).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Channels(pm, 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.UsersKind, "", tc.groupsPageMeta) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - groupRes := convertChannel(channel) - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Channel - err errors.SDKError - }{ - { - desc: "view channel successfully", - domainID: domainID, - token: validToken, - channelID: groupRes.ID, - svcRes: groupRes, - svcErr: nil, - response: channel, - err: nil, - }, - { - desc: "view channel with invalid token", - domainID: domainID, - token: invalidToken, - channelID: groupRes.ID, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view channel with empty token", - domainID: domainID, - token: "", - channelID: groupRes.ID, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view channel for wrong id", - domainID: domainID, - token: validToken, - channelID: wrongID, - svcRes: groups.Group{}, - svcErr: svcerr.ErrViewEntity, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "view channel with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "view channel with service response that can't be unmarshalled", - domainID: domainID, - token: validToken, - channelID: groupRes.ID, - svcRes: groups.Group{ - ID: generateUUID(t), - Metadata: groups.Metadata{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("ViewGroup", mock.Anything, tc.session, tc.channelID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Channel(tc.channelID, 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, "ViewGroup", mock.Anything, tc.session, tc.channelID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - group := convertChannel(channel) - nGroup := group - nGroup.Name = newName - nChannel := channel - nChannel.Name = newName - - dGroup := group - dGroup.Description = newDescription - dChannel := channel - dChannel.Description = newDescription - - mGroup := group - mGroup.Metadata = groups.Metadata{ - "field": "value2", - } - mChannel := channel - mChannel.Metadata = sdk.Metadata{ - "field": "value2", - } - - aGroup := group - aGroup.Name = newName - aGroup.Description = newDescription - aGroup.Metadata = groups.Metadata{"field": "value2"} - aChannel := channel - aChannel.Name = newName - aChannel.Description = newDescription - aChannel.Metadata = sdk.Metadata{"field": "value2"} - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelReq sdk.Channel - updateGroupReq groups.Group - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Channel - err errors.SDKError - }{ - { - desc: "update channel name", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: channel.ID, - Name: newName, - }, - updateGroupReq: groups.Group{ - ID: group.ID, - Name: newName, - }, - svcRes: nGroup, - svcErr: nil, - response: nChannel, - err: nil, - }, - { - desc: "update channel description", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: channel.ID, - Description: newDescription, - }, - updateGroupReq: groups.Group{ - ID: group.ID, - Description: newDescription, - }, - svcRes: dGroup, - svcErr: nil, - response: dChannel, - err: nil, - }, - { - desc: "update channel metadata", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: channel.ID, - Metadata: sdk.Metadata{ - "field": "value2", - }, - }, - updateGroupReq: groups.Group{ - ID: group.ID, - Metadata: groups.Metadata{"field": "value2"}, - }, - svcRes: mGroup, - svcErr: nil, - response: mChannel, - err: nil, - }, - { - desc: "update channel with every field defined", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: channel.ID, - Name: newName, - Description: newDescription, - Metadata: sdk.Metadata{"field": "value2"}, - }, - updateGroupReq: groups.Group{ - ID: group.ID, - Name: newName, - Description: newDescription, - Metadata: groups.Metadata{"field": "value2"}, - }, - svcRes: aGroup, - svcErr: nil, - response: aChannel, - err: nil, - }, - { - desc: "update channel name with invalid channel id", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: wrongID, - Name: newName, - }, - updateGroupReq: groups.Group{ - ID: wrongID, - Name: newName, - }, - svcRes: groups.Group{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "update channel description with invalid channel id", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: wrongID, - Description: newDescription, - }, - updateGroupReq: groups.Group{ - ID: wrongID, - Description: newDescription, - }, - svcRes: groups.Group{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "update channel metadata with invalid channel id", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: wrongID, - Metadata: sdk.Metadata{ - "field": "value2", - }, - }, - updateGroupReq: groups.Group{ - ID: wrongID, - Metadata: groups.Metadata{"field": "value2"}, - }, - svcRes: groups.Group{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "update channel with invalid token", - domainID: domainID, - token: invalidToken, - channelReq: sdk.Channel{ - ID: channel.ID, - Name: newName, - }, - updateGroupReq: groups.Group{ - ID: group.ID, - Name: newName, - }, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update channel with empty token", - domainID: domainID, - token: "", - channelReq: sdk.Channel{ - ID: channel.ID, - Name: newName, - }, - updateGroupReq: groups.Group{ - ID: group.ID, - Name: newName, - }, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update channel with name that is too long", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: channel.ID, - Name: strings.Repeat("a", 1025), - }, - updateGroupReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), - }, - { - desc: "update channel that can't be marshalled", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: channel.ID, - Name: "test", - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - updateGroupReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "update channel with service response that can't be unmarshalled", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - ID: channel.ID, - Name: newName, - }, - updateGroupReq: groups.Group{ - ID: group.ID, - Name: newName, - }, - svcRes: groups.Group{ - ID: generateUUID(t), - Metadata: groups.Metadata{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - { - desc: "update channel with empty channel id", - domainID: domainID, - token: validToken, - channelReq: sdk.Channel{ - Name: newName, - }, - updateGroupReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("UpdateGroup", mock.Anything, tc.session, tc.updateGroupReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateChannel(tc.channelReq, 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, "UpdateGroup", mock.Anything, tc.session, tc.updateGroupReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListChannelsByThing(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - nChannels := uint64(10) - aChannels := []sdk.Channel{} - - for i := uint64(1); i < nChannels; i++ { - channel := sdk.Channel{ - ID: generateUUID(t), - Name: fmt.Sprintf("membership_%d@example.com", i), - Metadata: sdk.Metadata{"role": "channel"}, - Status: groups.EnabledStatus.String(), - } - aChannels = append(aChannels, channel) - } - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - pageMeta sdk.PageMetadata - listGroupsReq groups.Page - svcRes groups.Page - svcErr error - authenticateErr error - response sdk.ChannelsPage - err errors.SDKError - }{ - { - desc: "list channels successfully", - domainID: domainID, - token: validToken, - thingID: testsutil.GenerateUUID(t), - pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: nChannels, - }, - Groups: convertChannels(aChannels), - }, - svcErr: nil, - response: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: nChannels, - }, - Channels: aChannels, - }, - err: nil, - }, - { - desc: "list channel with offset and limit", - domainID: domainID, - token: validToken, - thingID: testsutil.GenerateUUID(t), - pageMeta: sdk.PageMetadata{ - Offset: 6, - Limit: nChannels, - }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 6, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(aChannels[6 : nChannels-1])), - }, - Groups: convertChannels(aChannels[6 : nChannels-1]), - }, - svcErr: nil, - response: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(aChannels[6 : nChannels-1])), - }, - Channels: aChannels[6 : nChannels-1], - }, - err: nil, - }, - { - desc: "list channel with given name", - domainID: domainID, - token: validToken, - thingID: testsutil.GenerateUUID(t), - pageMeta: sdk.PageMetadata{ - Name: "membership_8@example.com", - Offset: 0, - Limit: nChannels, - }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Name: "membership_8@example.com", - Offset: 0, - Limit: nChannels, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: convertChannels([]sdk.Channel{aChannels[8]}), - }, - svcErr: nil, - response: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Channels: aChannels[8:9], - }, - err: nil, - }, - { - desc: "list channels with invalid token", - domainID: domainID, - token: invalidToken, - thingID: testsutil.GenerateUUID(t), - pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list channels with empty token", - domainID: domainID, - token: "", - thingID: testsutil.GenerateUUID(t), - pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list channels with limit greater than max", - domainID: domainID, - token: validToken, - thingID: testsutil.GenerateUUID(t), - pageMeta: sdk.PageMetadata{ - Limit: 110, - }, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "list channels with invalid metadata", - domainID: domainID, - token: validToken, - thingID: testsutil.GenerateUUID(t), - pageMeta: sdk.PageMetadata{ - Metadata: sdk.Metadata{ - "test": make(chan int), - }, - }, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list channels with service response that can't be unmarshalled", - domainID: domainID, - token: validToken, - thingID: testsutil.GenerateUUID(t), - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{{ - ID: generateUUID(t), - Metadata: groups.Metadata{ - "test": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestEnableChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - group := convertChannel(channel) - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Channel - err errors.SDKError - }{ - { - desc: "enable channel successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - svcRes: group, - svcErr: nil, - response: channel, - err: nil, - }, - { - desc: "enable channel with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "enable channel with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "enable channel with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - svcRes: groups.Group{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "enable channel with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "enable channel with service response that can't be unmarshalled", - domainID: domainID, - token: validToken, - channelID: channel.ID, - svcRes: groups.Group{ - ID: generateUUID(t), - Metadata: groups.Metadata{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("EnableGroup", mock.Anything, tc.session, tc.channelID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.EnableChannel(tc.channelID, 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, "EnableGroup", mock.Anything, tc.session, tc.channelID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDisableChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - group := convertChannel(channel) - dGroup := group - dGroup.Status = groups.DisabledStatus - dChannel := channel - dChannel.Status = groups.DisabledStatus.String() - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Channel - err errors.SDKError - }{ - { - desc: "disable channel successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - svcRes: dGroup, - svcErr: nil, - response: dChannel, - err: nil, - }, - { - desc: "disable channel with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "disable channel with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "disable channel with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - svcRes: groups.Group{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "disable channel with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "disable channel with service response that can't be unmarshalled", - domainID: domainID, - token: validToken, - channelID: channel.ID, - svcRes: groups.Group{ - ID: generateUUID(t), - Metadata: groups.Metadata{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("DisableGroup", mock.Anything, tc.session, tc.channelID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.DisableChannel(tc.channelID, 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, "DisableGroup", mock.Anything, tc.session, tc.channelID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDeleteChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "delete channel successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - svcErr: nil, - err: nil, - }, - { - desc: "delete channel with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "delete channel with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "delete channel with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - svcErr: svcerr.ErrRemoveEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrRemoveEntity, http.StatusUnprocessableEntity), - }, - { - desc: "delete channel with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - svcErr: svcerr.ErrRemoveEntity, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("DeleteGroup", mock.Anything, tc.session, tc.channelID).Return(tc.svcErr) - err := mgsdk.DeleteChannel(tc.channelID, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "DeleteGroup", mock.Anything, tc.session, tc.channelID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestChannelPermissions(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - svcRes []string - svcErr error - authenticateErr error - response sdk.Channel - err errors.SDKError - }{ - { - desc: "view channel permissions successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - svcRes: []string{"view"}, - svcErr: nil, - response: sdk.Channel{ - Permissions: []string{"view"}, - }, - err: nil, - }, - { - desc: "view channel permissions with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - svcRes: []string{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view channel permissions with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - svcRes: []string{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view channel permissions with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - svcRes: []string{}, - svcErr: svcerr.ErrAuthorization, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "view channel permissions with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - svcRes: []string{}, - svcErr: nil, - response: sdk.Channel{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("ViewGroupPerms", mock.Anything, tc.session, tc.channelID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ChannelPermissions(tc.channelID, 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, "ViewGroupPerms", mock.Anything, tc.session, tc.channelID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestAddUserToChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - addUserReq sdk.UsersRelationRequest - authenticateErr error - svcErr error - err errors.SDKError - }{ - { - desc: "add user to channel successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "add user to channel with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "add user to channel with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "add user to channel with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "add user to channel with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "add users to channel with empty relation", - domainID: domainID, - token: validToken, - channelID: channel.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingRelation), http.StatusBadRequest), - }, - { - desc: "add users to channel with empty user ids", - domainID: domainID, - token: validToken, - channelID: channel.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyList), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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, tc.addUserReq.Relation, policies.UsersKind, tc.addUserReq.UserIDs).Return(tc.svcErr) - err := mgsdk.AddUserToChannel(tc.channelID, tc.addUserReq, 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, tc.addUserReq.Relation, policies.UsersKind, tc.addUserReq.UserIDs) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestRemoveUserFromChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - removeUserReq sdk.UsersRelationRequest - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "remove user from channel successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "remove user from channel with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "remove user from channel with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "remove user from channel with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "remove user from channel with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "remove users from channel with empty user ids", - domainID: domainID, - token: validToken, - channelID: channel.ID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyList), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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, tc.removeUserReq.Relation, policies.UsersKind, tc.removeUserReq.UserIDs).Return(tc.svcErr) - err := mgsdk.RemoveUserFromChannel(tc.channelID, tc.removeUserReq, 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, tc.removeUserReq.Relation, policies.UsersKind, tc.removeUserReq.UserIDs) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestAddUserGroupToChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - relation := "parent_group" - - groupID := generateUUID(t) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - addUserGroupReq sdk.UserGroupsRequest - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "add user group to channel successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - addUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "add user group to channel with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - addUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "add user group to channel with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - addUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "add user group to channel with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - addUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "add user group to channel with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - addUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "add user group to channel with empty group ids", - domainID: domainID, - token: validToken, - channelID: channel.ID, - addUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyList), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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, relation, policies.ChannelsKind, tc.addUserGroupReq.UserGroupIDs).Return(tc.svcErr) - err := mgsdk.AddUserGroupToChannel(tc.channelID, tc.addUserGroupReq, 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, relation, policies.ChannelsKind, tc.addUserGroupReq.UserGroupIDs) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestRemoveUserGroupFromChannel(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - relation := "parent_group" - - groupID := generateUUID(t) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - removeUserGroupReq sdk.UserGroupsRequest - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "remove user group from channel successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - removeUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "remove user group from channel with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - removeUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "remove user group from channel with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - removeUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "remove user group from channel with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - removeUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "remove user group from channel with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - removeUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{groupID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "remove user group from channel with empty group ids", - domainID: domainID, - token: validToken, - channelID: channel.ID, - removeUserGroupReq: sdk.UserGroupsRequest{ - UserGroupIDs: []string{}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyList), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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, relation, policies.ChannelsKind, tc.removeUserGroupReq.UserGroupIDs).Return(tc.svcErr) - err := mgsdk.RemoveUserGroupFromChannel(tc.channelID, tc.removeUserGroupReq, 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, relation, policies.ChannelsKind, tc.removeUserGroupReq.UserGroupIDs) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListChannelUserGroups(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - nGroups := uint64(10) - aGroups := []sdk.Group{} - - for i := uint64(1); i < nGroups; i++ { - group := sdk.Group{ - ID: generateUUID(t), - Name: fmt.Sprintf("group_%d", i), - Metadata: sdk.Metadata{"role": "group"}, - Status: groups.EnabledStatus.String(), - } - aGroups = append(aGroups, group) - } - - cases := []struct { - desc string - token string - domainID string - session mgauthn.Session - channelID string - pageMeta sdk.PageMetadata - listGroupsReq groups.Page - svcRes groups.Page - svcErr error - authenticateErr error - response sdk.GroupsPage - err errors.SDKError - }{ - { - desc: "list user groups successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - 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", - domainID: domainID, - token: validToken, - channelID: channel.ID, - pageMeta: sdk.PageMetadata{ - Offset: 6, - Limit: nGroups, - }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 6, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - 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", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list user groups with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list user groups with limit greater than max", - domainID: domainID, - token: validToken, - channelID: channel.ID, - pageMeta: sdk.PageMetadata{ - Limit: 110, - }, - listGroupsReq: groups.Page{}, - svcRes: groups.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", - domainID: domainID, - token: validToken, - channelID: wrongID, - pageMeta: sdk.PageMetadata{ - DomainID: domainID, - }, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{}, - svcErr: svcerr.ErrAuthorization, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "list users groups with level exceeding max", - domainID: domainID, - token: validToken, - channelID: channel.ID, - pageMeta: sdk.PageMetadata{ - Level: 10, - }, - listGroupsReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidLevel), http.StatusBadRequest), - }, - { - desc: "list users with invalid page metadata", - token: validToken, - channelID: channel.ID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - Metadata: sdk.Metadata{ - "test": make(chan int), - }, - }, - listGroupsReq: groups.Page{}, - svcRes: groups.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", - domainID: domainID, - token: validToken, - channelID: channel.ID, - pageMeta: sdk.PageMetadata{}, - listGroupsReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{ - { - ID: generateUUID(t), - Metadata: groups.Metadata{"test": make(chan int)}, - }, - }, - }, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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.ChannelsKind, tc.channelID, tc.listGroupsReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListChannelUserGroups(tc.channelID, 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.ChannelsKind, tc.channelID, tc.listGroupsReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestConnect(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - thingID := generateUUID(t) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - connection sdk.Connection - svcErr error - authenticateRes mgauthn.Session - authenticateErr error - err errors.SDKError - }{ - { - desc: "connect successfully", - domainID: domainID, - token: validToken, - connection: sdk.Connection{ - ChannelID: channel.ID, - ThingID: thingID, - }, - svcErr: nil, - err: nil, - }, - { - desc: "connect with invalid token", - domainID: domainID, - token: invalidToken, - connection: sdk.Connection{ - ChannelID: channel.ID, - ThingID: thingID, - }, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "connect with empty token", - domainID: domainID, - token: "", - connection: sdk.Connection{ - ChannelID: channel.ID, - ThingID: thingID, - }, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "connect with invalid channel id", - domainID: domainID, - token: validToken, - connection: sdk.Connection{ - ChannelID: wrongID, - ThingID: thingID, - }, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "connect with empty channel id", - domainID: domainID, - token: validToken, - connection: sdk.Connection{ - ChannelID: "", - ThingID: thingID, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "connect with empty thing id", - domainID: domainID, - token: validToken, - connection: sdk.Connection{ - ChannelID: channel.ID, - ThingID: "", - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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}) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDisconnect(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - thingID := generateUUID(t) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - disconnect sdk.Connection - svcErr error - authenticateRes mgauthn.Session - authenticateErr error - err errors.SDKError - }{ - { - desc: "disconnect successfully", - domainID: domainID, - token: validToken, - disconnect: sdk.Connection{ - ChannelID: channel.ID, - ThingID: thingID, - }, - svcErr: nil, - err: nil, - }, - { - desc: "disconnect with invalid token", - domainID: domainID, - token: invalidToken, - disconnect: sdk.Connection{ - ChannelID: channel.ID, - ThingID: thingID, - }, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "disconnect with empty token", - domainID: domainID, - token: "", - disconnect: sdk.Connection{ - ChannelID: channel.ID, - ThingID: thingID, - }, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "disconnect with invalid channel id", - domainID: domainID, - token: validToken, - disconnect: sdk.Connection{ - ChannelID: wrongID, - ThingID: thingID, - }, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "disconnect with empty channel id", - domainID: domainID, - token: validToken, - disconnect: sdk.Connection{ - ChannelID: "", - ThingID: thingID, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "disconnect with empty thing id", - domainID: domainID, - token: validToken, - disconnect: sdk.Connection{ - ChannelID: channel.ID, - ThingID: "", - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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}) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestConnectThing(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - thingID := generateUUID(t) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - thingID string - svcErr error - authenticateRes mgauthn.Session - authenticateErr error - err errors.SDKError - }{ - { - desc: "connect successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - thingID: thingID, - svcErr: nil, - err: nil, - }, - { - desc: "connect with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - thingID: thingID, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "connect with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - thingID: thingID, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "connect with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - thingID: thingID, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "connect with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - thingID: thingID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "connect with empty thing id", - domainID: domainID, - token: validToken, - channelID: channel.ID, - thingID: "", - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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}) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDisconnectThing(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - thingID := generateUUID(t) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - channelID string - thingID string - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "disconnect successfully", - domainID: domainID, - token: validToken, - channelID: channel.ID, - thingID: thingID, - svcErr: nil, - err: nil, - }, - { - desc: "disconnect with invalid token", - domainID: domainID, - token: invalidToken, - channelID: channel.ID, - thingID: thingID, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "disconnect with empty token", - domainID: domainID, - token: "", - channelID: channel.ID, - thingID: thingID, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "disconnect with invalid channel id", - domainID: domainID, - token: validToken, - channelID: wrongID, - thingID: thingID, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "disconnect with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - thingID: thingID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "disconnect with empty thing id", - domainID: domainID, - token: validToken, - channelID: channel.ID, - thingID: "", - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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}) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListGroupChannels(t *testing.T) { - ts, gsvc, auth := setupChannels() - defer ts.Close() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - groupChannel := sdk.Channel{ - ID: testsutil.GenerateUUID(t), - Name: "group_channel", - Metadata: sdk.Metadata{"role": "group"}, - Status: groups.EnabledStatus.String(), - } - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupID string - pageMeta sdk.PageMetadata - svcReq groups.Page - svcRes groups.Page - svcErr error - authenticateErr error - response sdk.ChannelsPage - err errors.SDKError - }{ - { - desc: "list group channels successfully", - domainID: domainID, - token: validToken, - groupID: group.ID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{convertChannel(groupChannel)}, - }, - svcErr: nil, - response: sdk.ChannelsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Channels: []sdk.Channel{groupChannel}, - }, - err: nil, - }, - { - desc: "list group channels with invalid token", - domainID: domainID, - token: invalidToken, - groupID: group.ID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list group channels with empty token", - domainID: domainID, - token: "", - groupID: group.ID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list group channels with invalid group id", - domainID: domainID, - token: validToken, - groupID: wrongID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{}, - svcErr: svcerr.ErrAuthorization, - response: sdk.ChannelsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "list group channels with invalid page metadata", - domainID: domainID, - token: validToken, - groupID: group.ID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Metadata: sdk.Metadata{ - "test": make(chan int), - }, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list group channels with service response that can't be unmarshalled", - domainID: domainID, - token: validToken, - groupID: group.ID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: "view", - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{ - { - ID: generateUUID(t), - Metadata: groups.Metadata{"test": make(chan int)}, - }, - }, - }, - svcErr: nil, - response: sdk.ChannelsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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.GroupsKind, tc.groupID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListGroupChannels(tc.groupID, 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.GroupsKind, tc.groupID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func generateTestChannel(t *testing.T) sdk.Channel { - createdAt, err := time.Parse(time.RFC3339, "2023-03-03T00:00:00Z") - assert.Nil(t, err, fmt.Sprintf("unexpected error %s", err)) - updatedAt := createdAt - ch := sdk.Channel{ - ID: testsutil.GenerateUUID(&testing.T{}), - DomainID: testsutil.GenerateUUID(&testing.T{}), - Name: channelName, - Description: description, - Metadata: sdk.Metadata{"role": "client"}, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - Status: groups.EnabledStatus.String(), - } - return ch -} diff --git a/pkg/sdk/go/consumers.go b/pkg/sdk/go/consumers.go deleted file mode 100644 index ad3cdb3b0..000000000 --- a/pkg/sdk/go/consumers.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/absmach/magistrala/pkg/errors" -) - -const ( - subscriptionEndpoint = "subscriptions" -) - -type Subscription struct { - ID string `json:"id,omitempty"` - OwnerID string `json:"owner_id,omitempty"` - Topic string `json:"topic,omitempty"` - Contact string `json:"contact,omitempty"` -} - -func (sdk mgSDK) CreateSubscription(topic, contact, token string) (string, errors.SDKError) { - sub := Subscription{ - Topic: topic, - Contact: contact, - } - data, err := json.Marshal(sub) - if err != nil { - return "", errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.usersURL, subscriptionEndpoint) - - headers, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return "", sdkerr - } - - id := strings.TrimPrefix(headers.Get("Location"), fmt.Sprintf("/%s/", subscriptionEndpoint)) - - return id, nil -} - -func (sdk mgSDK) ListSubscriptions(pm PageMetadata, token string) (SubscriptionPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, subscriptionEndpoint, pm) - if err != nil { - return SubscriptionPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return SubscriptionPage{}, sdkerr - } - - var sp SubscriptionPage - if err := json.Unmarshal(body, &sp); err != nil { - return SubscriptionPage{}, errors.NewSDKError(err) - } - - return sp, nil -} - -func (sdk mgSDK) ViewSubscription(id, token string) (Subscription, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, subscriptionEndpoint, id) - - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return Subscription{}, err - } - - var sub Subscription - if err := json.Unmarshal(body, &sub); err != nil { - return Subscription{}, errors.NewSDKError(err) - } - - return sub, nil -} - -func (sdk mgSDK) DeleteSubscription(id, token string) errors.SDKError { - url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, subscriptionEndpoint, id) - - _, _, err := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - - return err -} diff --git a/pkg/sdk/go/consumers_test.go b/pkg/sdk/go/consumers_test.go deleted file mode 100644 index f2ce28912..000000000 --- a/pkg/sdk/go/consumers_test.go +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/absmach/magistrala/consumers/notifiers" - httpapi "github.com/absmach/magistrala/consumers/notifiers/api" - notmocks "github.com/absmach/magistrala/consumers/notifiers/mocks" - "github.com/absmach/magistrala/internal/testsutil" - mglog "github.com/absmach/magistrala/logger" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - ownerID = testsutil.GenerateUUID(&testing.T{}) - subID = testsutil.GenerateUUID(&testing.T{}) - sdkSubReq = sdk.Subscription{ - Topic: "topic", - Contact: "contact", - } - sdkSubRes = sdk.Subscription{ - Topic: "topic", - Contact: "contact", - OwnerID: ownerID, - ID: subID, - } - notSubReq = notifiers.Subscription{ - Contact: "contact", - Topic: "topic", - } - notSubRes = notifiers.Subscription{ - Contact: "contact", - Topic: "topic", - OwnerID: ownerID, - ID: subID, - } -) - -func setupSubscriptions() (*httptest.Server, *notmocks.Service) { - nsvc := new(notmocks.Service) - logger := mglog.NewMock() - mux := httpapi.MakeHandler(nsvc, logger, instanceID) - - return httptest.NewServer(mux), nsvc -} - -func TestCreateSubscription(t *testing.T) { - ts, nsvc := setupSubscriptions() - defer ts.Close() - - sdkConf := sdk.Config{ - UsersURL: ts.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - subscription sdk.Subscription - token string - empty bool - id string - svcReq notifiers.Subscription - svcErr error - svcRes string - err errors.SDKError - }{ - { - desc: "create new subscription", - subscription: sdkSubReq, - token: validToken, - empty: false, - svcReq: notSubReq, - svcRes: subID, - svcErr: nil, - err: nil, - }, - { - desc: "create new subscription with empty token", - subscription: sdkSubReq, - token: "", - empty: true, - svcReq: notifiers.Subscription{}, - svcRes: "", - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - }, - { - desc: "create new subscription with invalid token", - subscription: sdkSubReq, - token: invalidToken, - empty: true, - svcReq: notSubReq, - svcRes: "", - svcErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "create new subscription with empty topic", - subscription: sdk.Subscription{ - Topic: "", - Contact: "contact", - }, - token: validToken, - empty: true, - svcReq: notifiers.Subscription{}, - svcErr: nil, - svcRes: "", - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTopic), http.StatusBadRequest), - }, - { - desc: "create new subscription with empty contact", - subscription: sdk.Subscription{ - Topic: "topic", - Contact: "", - }, - token: validToken, - empty: true, - svcReq: notifiers.Subscription{}, - svcErr: nil, - svcRes: "", - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidContact), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := nsvc.On("CreateSubscription", mock.Anything, tc.token, tc.svcReq).Return(tc.svcRes, tc.svcErr) - loc, err := mgsdk.CreateSubscription(tc.subscription.Topic, tc.subscription.Contact, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.empty, loc == "") - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "CreateSubscription", mock.Anything, tc.token, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestViewSubscription(t *testing.T) { - ts, nsvc := setupSubscriptions() - defer ts.Close() - sdkConf := sdk.Config{ - UsersURL: ts.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - subID string - token string - svcRes notifiers.Subscription - svcErr error - response sdk.Subscription - err errors.SDKError - }{ - { - desc: "view existing subscription", - subID: subID, - token: validToken, - svcRes: notSubRes, - svcErr: nil, - response: sdkSubRes, - err: nil, - }, - { - desc: "view non-existent subscription", - subID: wrongID, - token: validToken, - svcRes: notifiers.Subscription{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Subscription{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "view subscription with invalid token", - subID: subID, - token: invalidToken, - svcRes: notifiers.Subscription{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.Subscription{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view subscription with empty token", - subID: subID, - token: "", - svcRes: notifiers.Subscription{}, - svcErr: nil, - response: sdk.Subscription{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := nsvc.On("ViewSubscription", mock.Anything, tc.token, tc.subID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ViewSubscription(tc.subID, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ViewSubscription", mock.Anything, tc.token, tc.subID) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestListSubscription(t *testing.T) { - ts, nsvc := setupSubscriptions() - defer ts.Close() - sdkConf := sdk.Config{ - UsersURL: ts.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - nSubs := 10 - noSubs := []notifiers.Subscription{} - sdSubs := []sdk.Subscription{} - for i := 0; i < nSubs; i++ { - nosub := notifiers.Subscription{ - OwnerID: ownerID, - Topic: fmt.Sprintf("topic_%d", i), - Contact: fmt.Sprintf("contact_%d", i), - } - noSubs = append(noSubs, nosub) - sdsub := sdk.Subscription{ - OwnerID: ownerID, - Topic: fmt.Sprintf("topic_%d", i), - Contact: fmt.Sprintf("contact_%d", i), - } - sdSubs = append(sdSubs, sdsub) - } - - cases := []struct { - desc string - token string - pageMeta sdk.PageMetadata - svcReq notifiers.PageMetadata - svcRes notifiers.Page - svcErr error - response sdk.SubscriptionPage - err errors.SDKError - }{ - { - desc: "list all subscription", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: notifiers.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcRes: notifiers.Page{ - Total: 10, - Subscriptions: noSubs, - }, - svcErr: nil, - response: sdk.SubscriptionPage{ - PageRes: sdk.PageRes{ - Total: 10, - }, - Subscriptions: sdSubs, - }, - err: nil, - }, - { - desc: "list subscription with specific topic", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Topic: "topic_1", - }, - svcReq: notifiers.PageMetadata{ - Offset: 0, - Limit: 10, - Topic: "topic_1", - }, - svcRes: notifiers.Page{ - Total: uint(len(noSubs[1:2])), - Subscriptions: noSubs[1:2], - }, - svcErr: nil, - response: sdk.SubscriptionPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(sdSubs[1:2])), - }, - Subscriptions: sdSubs[1:2], - }, - err: nil, - }, - { - desc: "list subscription with specific contact", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Contact: "contact_1", - }, - svcReq: notifiers.PageMetadata{ - Offset: 0, - Limit: 10, - Contact: "contact_1", - }, - svcRes: notifiers.Page{ - Total: uint(len(noSubs[1:2])), - Subscriptions: noSubs[1:2], - }, - svcErr: nil, - response: sdk.SubscriptionPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(sdSubs[1:2])), - }, - Subscriptions: sdSubs[1:2], - }, - err: nil, - }, - { - desc: "list subscription with invalid token", - token: invalidToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: notifiers.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcRes: notifiers.Page{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.SubscriptionPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list subscription with empty token", - token: "", - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: notifiers.PageMetadata{}, - svcRes: notifiers.Page{}, - svcErr: nil, - response: sdk.SubscriptionPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - }, - { - desc: "list subscription with invalid page metadata", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcReq: notifiers.PageMetadata{}, - svcRes: notifiers.Page{}, - svcErr: nil, - response: sdk.SubscriptionPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := nsvc.On("ListSubscriptions", mock.Anything, tc.token, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListSubscriptions(tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListSubscriptions", mock.Anything, tc.token, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestDeleteSubscription(t *testing.T) { - ts, nsvc := setupSubscriptions() - defer ts.Close() - sdkConf := sdk.Config{ - UsersURL: ts.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - subID string - token string - svcErr error - err errors.SDKError - }{ - { - desc: "delete existing subscription", - subID: subID, - token: validToken, - svcErr: nil, - err: nil, - }, - { - desc: "delete non-existent subscription", - subID: wrongID, - token: validToken, - svcErr: svcerr.ErrRemoveEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrRemoveEntity, http.StatusUnprocessableEntity), - }, - { - desc: "delete subscription with invalid token", - subID: subID, - token: invalidToken, - svcErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "delete subscription with empty token", - subID: subID, - token: "", - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - }, - { - desc: "delete subscription with empty subID", - subID: "", - token: validToken, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := nsvc.On("RemoveSubscription", mock.Anything, tc.token, tc.subID).Return(tc.svcErr) - err := mgsdk.DeleteSubscription(tc.subID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "RemoveSubscription", mock.Anything, tc.token, tc.subID) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} diff --git a/pkg/sdk/go/doc.go b/pkg/sdk/go/doc.go deleted file mode 100644 index b060484b3..000000000 --- a/pkg/sdk/go/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package sdk contains Magistrala SDK. -package sdk diff --git a/pkg/sdk/go/domains.go b/pkg/sdk/go/domains.go deleted file mode 100644 index 1732cded9..000000000 --- a/pkg/sdk/go/domains.go +++ /dev/null @@ -1,204 +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 domainsEndpoint = "domains" - -// Domain represents magistrala domain. -type Domain struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` - Alias string `json:"alias,omitempty"` - Status string `json:"status,omitempty"` - Permission string `json:"permission,omitempty"` - CreatedBy string `json:"created_by,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - Permissions []string `json:"permissions,omitempty"` -} - -func (sdk mgSDK) CreateDomain(domain Domain, token string) (Domain, errors.SDKError) { - data, err := json.Marshal(domain) - if err != nil { - return Domain{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.domainsURL, domainsEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return Domain{}, sdkerr - } - - var d Domain - if err := json.Unmarshal(body, &d); err != nil { - return Domain{}, errors.NewSDKError(err) - } - return d, nil -} - -func (sdk mgSDK) UpdateDomain(domain Domain, token string) (Domain, errors.SDKError) { - if domain.ID == "" { - return Domain{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s", sdk.domainsURL, domainsEndpoint, domain.ID) - - data, err := json.Marshal(domain) - if err != nil { - return Domain{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return Domain{}, sdkerr - } - - var d Domain - if err := json.Unmarshal(body, &d); err != nil { - return Domain{}, errors.NewSDKError(err) - } - return d, nil -} - -func (sdk mgSDK) Domain(domainID, token string) (Domain, errors.SDKError) { - if domainID == "" { - return Domain{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s", sdk.domainsURL, domainsEndpoint, domainID) - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return Domain{}, sdkerr - } - - var domain Domain - if err := json.Unmarshal(body, &domain); err != nil { - return Domain{}, errors.NewSDKError(err) - } - - return domain, nil -} - -func (sdk mgSDK) DomainPermissions(domainID, token string) (Domain, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.domainsURL, domainsEndpoint, domainID, permissionsEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return Domain{}, sdkerr - } - - var domain Domain - if err := json.Unmarshal(body, &domain); err != nil { - return Domain{}, errors.NewSDKError(err) - } - - return domain, nil -} - -func (sdk mgSDK) Domains(pm PageMetadata, token string) (DomainsPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.domainsURL, domainsEndpoint, pm) - if err != nil { - return DomainsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return DomainsPage{}, sdkerr - } - - var dp DomainsPage - if err := json.Unmarshal(body, &dp); err != nil { - return DomainsPage{}, errors.NewSDKError(err) - } - - return dp, nil -} - -func (sdk mgSDK) ListDomainUsers(domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s", domainID, 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 - } - var up UsersPage - if err := json.Unmarshal(body, &up); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return up, nil -} - -func (sdk mgSDK) ListUserDomains(userID string, pm PageMetadata, token string) (DomainsPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.domainsURL, fmt.Sprintf("%s/%s/%s", usersEndpoint, userID, domainsEndpoint), pm) - if err != nil { - return DomainsPage{}, errors.NewSDKError(err) - } - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return DomainsPage{}, sdkerr - } - var dp DomainsPage - if err := json.Unmarshal(body, &dp); err != nil { - return DomainsPage{}, errors.NewSDKError(err) - } - - return dp, nil -} - -func (sdk mgSDK) EnableDomain(domainID, token string) errors.SDKError { - return sdk.changeDomainStatus(token, domainID, enableEndpoint) -} - -func (sdk mgSDK) DisableDomain(domainID, token string) errors.SDKError { - return sdk.changeDomainStatus(token, domainID, disableEndpoint) -} - -func (sdk mgSDK) changeDomainStatus(token, id, status string) errors.SDKError { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.domainsURL, domainsEndpoint, id, status) - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusOK) - return sdkerr -} - -func (sdk mgSDK) AddUserToDomain(domainID string, req UsersRelationRequest, 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.domainsURL, domainsEndpoint, domainID, usersEndpoint, assignEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - return sdkerr -} - -func (sdk mgSDK) RemoveUserFromDomain(domainID, userID, token string) errors.SDKError { - req := map[string]string{ - "user_id": userID, - } - data, err := json.Marshal(req) - if err != nil { - return errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.domainsURL, domainsEndpoint, domainID, usersEndpoint, unassignEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - return sdkerr -} diff --git a/pkg/sdk/go/domains_test.go b/pkg/sdk/go/domains_test.go deleted file mode 100644 index ea1c484ec..000000000 --- a/pkg/sdk/go/domains_test.go +++ /dev/null @@ -1,1136 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/absmach/magistrala/auth" - httpapi "github.com/absmach/magistrala/auth/api/http/domains" - authmocks "github.com/absmach/magistrala/auth/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" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - policies "github.com/absmach/magistrala/pkg/policies" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/go-chi/chi/v5" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - authDomain, sdkDomain = generateTestDomain(&testing.T{}) - authDomainReq = auth.Domain{ - Name: authDomain.Name, - Metadata: authDomain.Metadata, - Tags: authDomain.Tags, - Alias: authDomain.Alias, - } - sdkDomainReq = sdk.Domain{ - Name: sdkDomain.Name, - Metadata: sdkDomain.Metadata, - Tags: sdkDomain.Tags, - Alias: sdkDomain.Alias, - } - updatedDomianName = "updated-domain" -) - -func setupDomains() (*httptest.Server, *authmocks.Service) { - svc := new(authmocks.Service) - logger := mglog.NewMock() - mux := chi.NewRouter() - - mux = httpapi.MakeHandler(svc, mux, logger) - return httptest.NewServer(mux), svc -} - -func TestCreateDomain(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - token string - domain sdk.Domain - svcReq auth.Domain - svcRes auth.Domain - svcErr error - response sdk.Domain - err error - }{ - { - desc: "create domain successfully", - token: validToken, - domain: sdkDomainReq, - svcReq: authDomainReq, - svcRes: authDomain, - svcErr: nil, - response: sdkDomain, - err: nil, - }, - { - desc: "create domain with invalid token", - token: invalidToken, - domain: sdkDomainReq, - svcReq: authDomainReq, - svcRes: auth.Domain{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "create domain with empty token", - token: "", - domain: sdkDomainReq, - svcReq: authDomainReq, - svcRes: auth.Domain{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "create domain with empty name", - token: validToken, - domain: sdk.Domain{ - Name: "", - Metadata: sdkDomain.Metadata, - Tags: sdkDomain.Tags, - Alias: sdkDomain.Alias, - }, - svcReq: auth.Domain{}, - svcRes: auth.Domain{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingName, http.StatusBadRequest), - }, - { - desc: "create domain with request that cannot be marshalled", - token: validToken, - domain: sdk.Domain{ - Name: sdkDomain.Name, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcReq: auth.Domain{}, - svcRes: auth.Domain{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "create domain with response that cannot be unmarshalled", - token: validToken, - domain: sdkDomainReq, - svcReq: authDomainReq, - svcRes: auth.Domain{ - ID: authDomain.ID, - Name: authDomain.Name, - Metadata: auth.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("CreateDomain", mock.Anything, tc.token, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.CreateDomain(tc.domain, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "CreateDomain", mock.Anything, tc.token, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestUpdateDomain(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - - upDomainSDK := sdkDomain - upDomainSDK.Name = updatedDomianName - upDomainAuth := authDomain - upDomainAuth.Name = updatedDomianName - - cases := []struct { - desc string - token string - domainID string - domain sdk.Domain - svcRes auth.Domain - svcErr error - response sdk.Domain - err error - }{ - { - desc: "update domain successfully", - token: validToken, - domainID: sdkDomain.ID, - domain: sdk.Domain{ - ID: sdkDomain.ID, - Name: updatedDomianName, - }, - svcRes: upDomainAuth, - svcErr: nil, - response: upDomainSDK, - err: nil, - }, - { - desc: "update domain with invalid token", - token: invalidToken, - domainID: sdkDomain.ID, - domain: sdk.Domain{ - ID: sdkDomain.ID, - Name: updatedDomianName, - }, - svcRes: auth.Domain{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update domain with empty token", - token: "", - domainID: sdkDomain.ID, - domain: sdk.Domain{ - ID: sdkDomain.ID, - Name: updatedDomianName, - }, - svcRes: auth.Domain{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update domain with invalid domain ID", - token: validToken, - domainID: wrongID, - domain: sdk.Domain{ - ID: wrongID, - Name: updatedDomianName, - }, - svcRes: auth.Domain{}, - svcErr: svcerr.ErrAuthorization, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "update domain with empty id", - token: validToken, - domainID: "", - domain: sdk.Domain{ - Name: sdkDomain.Name, - }, - svcRes: auth.Domain{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "update domain with request that cannot be marshalled", - token: validToken, - domainID: sdkDomain.ID, - domain: sdk.Domain{ - ID: sdkDomain.ID, - Name: sdkDomain.Name, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcRes: auth.Domain{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "update domain with response that cannot be unmarshalled", - token: validToken, - domainID: sdkDomain.ID, - domain: sdk.Domain{ - ID: sdkDomain.ID, - Name: sdkDomain.Name, - }, - svcRes: auth.Domain{ - ID: authDomain.ID, - Name: authDomain.Name, - Metadata: auth.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("UpdateDomain", mock.Anything, tc.token, tc.domainID, mock.Anything).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateDomain(tc.domain, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UpdateDomain", mock.Anything, tc.token, tc.domainID, mock.Anything) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestViewDomain(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - token string - domainID string - svcRes auth.Domain - svcErr error - response sdk.Domain - err error - }{ - { - desc: "view domain successfully", - token: validToken, - domainID: sdkDomain.ID, - svcRes: authDomain, - svcErr: nil, - response: sdkDomain, - err: nil, - }, - { - desc: "view domain with invalid token", - token: invalidToken, - domainID: sdkDomain.ID, - svcRes: auth.Domain{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view domain with empty token", - token: "", - domainID: sdkDomain.ID, - svcRes: auth.Domain{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view domain with invalid domain ID", - token: validToken, - domainID: wrongID, - svcRes: auth.Domain{}, - svcErr: svcerr.ErrAuthorization, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "view domain with empty id", - token: validToken, - domainID: "", - svcRes: auth.Domain{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "view domain with response that cannot be unmarshalled", - token: validToken, - domainID: sdkDomain.ID, - svcRes: auth.Domain{ - ID: authDomain.ID, - Name: authDomain.Name, - Metadata: auth.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("RetrieveDomain", mock.Anything, tc.token, tc.domainID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Domain(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, "RetrieveDomain", mock.Anything, tc.token, tc.domainID) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestDomainPermissions(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - token string - domainID string - svcRes policies.Permissions - svcErr error - response sdk.Domain - err error - }{ - { - desc: "retrieve domain permissions successfully", - token: validToken, - domainID: sdkDomain.ID, - svcRes: policies.Permissions{policies.ViewPermission}, - svcErr: nil, - response: sdk.Domain{ - Permissions: []string{policies.ViewPermission}, - }, - err: nil, - }, - { - desc: "retrieve domain permissions with invalid token", - token: invalidToken, - domainID: sdkDomain.ID, - svcRes: policies.Permissions{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "retrieve domain permissions with empty token", - token: "", - domainID: sdkDomain.ID, - svcRes: policies.Permissions{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "retrieve domain permissions with empty domain id", - token: validToken, - domainID: "", - svcRes: policies.Permissions{}, - svcErr: nil, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), - }, - { - desc: "retrieve domain permissions with invalid domain id", - token: validToken, - domainID: wrongID, - svcRes: policies.Permissions{}, - svcErr: svcerr.ErrAuthorization, - response: sdk.Domain{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("RetrieveDomainPermissions", mock.Anything, tc.token, tc.domainID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.DomainPermissions(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, "RetrieveDomainPermissions", mock.Anything, tc.token, tc.domainID) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestListDomians(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - token string - pageMeta sdk.PageMetadata - svcReq auth.Page - svcRes auth.DomainsPage - svcErr error - response sdk.DomainsPage - err error - }{ - { - desc: "list domains successfully", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{ - Offset: 0, - Limit: 10, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{authDomain}, - }, - svcErr: nil, - response: sdk.DomainsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Domains: []sdk.Domain{sdkDomain}, - }, - err: nil, - }, - { - desc: "list domains with invalid token", - token: invalidToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{ - Offset: 0, - Limit: 10, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: auth.DomainsPage{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.DomainsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list domains with empty token", - token: "", - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, - svcErr: nil, - response: sdk.DomainsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - }, - { - desc: "list domains with invalid page metadata", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, - svcErr: nil, - response: sdk.DomainsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list domains with request that cannot be marshalled", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{ - Offset: 0, - Limit: 10, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{{ - Name: authDomain.Name, - Metadata: auth.Metadata{"key": make(chan int)}, - }}, - }, - svcErr: nil, - response: sdk.DomainsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("ListDomains", mock.Anything, tc.token, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Domains(tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListDomains", mock.Anything, tc.token, mock.Anything) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestListUserDomains(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - token string - userID string - pageMeta sdk.PageMetadata - svcReq auth.Page - svcRes auth.DomainsPage - svcErr error - response sdk.DomainsPage - err error - }{ - { - desc: "list user domains successfully", - token: validToken, - userID: sdkDomain.CreatedBy, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{ - Offset: 0, - Limit: 10, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{authDomain}, - }, - svcErr: nil, - response: sdk.DomainsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Domains: []sdk.Domain{sdkDomain}, - }, - err: nil, - }, - { - desc: "list user domains with invalid token", - token: invalidToken, - userID: sdkDomain.CreatedBy, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{ - Offset: 0, - Limit: 10, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: auth.DomainsPage{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.DomainsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list user domains with empty token", - token: "", - userID: sdkDomain.CreatedBy, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, - svcErr: nil, - response: sdk.DomainsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list user domains with empty user id", - token: validToken, - userID: "", - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, - svcErr: nil, - response: sdk.DomainsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), - }, - { - desc: "list user domains with request that cannot be marshalled", - token: validToken, - userID: sdkDomain.CreatedBy, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: auth.Page{ - Offset: 0, - Limit: 10, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: auth.DomainsPage{ - Total: 1, - Domains: []auth.Domain{{ - Name: authDomain.Name, - Metadata: auth.Metadata{"key": make(chan int)}, - }}, - }, - svcErr: nil, - response: sdk.DomainsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - { - desc: "list user domains with invalid page metadata", - token: validToken, - userID: sdkDomain.CreatedBy, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcReq: auth.Page{}, - svcRes: auth.DomainsPage{}, - svcErr: nil, - response: sdk.DomainsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("ListUserDomains", mock.Anything, tc.token, tc.userID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListUserDomains(tc.userID, tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListUserDomains", mock.Anything, tc.token, tc.userID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestEnableDomain(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - - enable := auth.EnabledStatus - - cases := []struct { - desc string - token string - domainID string - svcReq auth.DomainReq - svcRes auth.Domain - svcErr error - err error - }{ - { - desc: "enable domain successfully", - token: validToken, - domainID: sdkDomain.ID, - svcReq: auth.DomainReq{ - Status: &enable, - }, - svcRes: authDomain, - svcErr: nil, - err: nil, - }, - { - desc: "enable domain with invalid token", - token: invalidToken, - domainID: sdkDomain.ID, - svcReq: auth.DomainReq{ - Status: &enable, - }, - svcRes: auth.Domain{}, - svcErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "enable domain with empty token", - token: "", - domainID: sdkDomain.ID, - svcReq: auth.DomainReq{}, - svcRes: auth.Domain{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "enable domain with empty domain id", - token: validToken, - domainID: "", - svcReq: auth.DomainReq{}, - svcRes: auth.Domain{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domainID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - err := mgsdk.EnableDomain(tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ChangeDomainStatus", mock.Anything, tc.token, tc.domainID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestDisableDomain(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - - disable := auth.DisabledStatus - - cases := []struct { - desc string - token string - domainID string - svcReq auth.DomainReq - svcRes auth.Domain - svcErr error - err error - }{ - { - desc: "disable domain successfully", - token: validToken, - domainID: sdkDomain.ID, - svcReq: auth.DomainReq{ - Status: &disable, - }, - svcRes: authDomain, - svcErr: nil, - err: nil, - }, - { - desc: "disable domain with invalid token", - token: invalidToken, - domainID: sdkDomain.ID, - svcReq: auth.DomainReq{ - Status: &disable, - }, - svcRes: auth.Domain{}, - svcErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "disable domain with empty token", - token: "", - domainID: sdkDomain.ID, - svcReq: auth.DomainReq{}, - svcRes: auth.Domain{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "disable domain with empty domain id", - token: validToken, - domainID: "", - svcReq: auth.DomainReq{}, - svcRes: auth.Domain{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domainID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - err := mgsdk.DisableDomain(tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ChangeDomainStatus", mock.Anything, tc.token, tc.domainID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestAddUserToDomain(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - newUser := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - token string - domainID string - addUserDomainReq sdk.UsersRelationRequest - svcErr error - err error - }{ - { - desc: "add user to domain successfully", - token: validToken, - domainID: sdkDomain.ID, - addUserDomainReq: sdk.UsersRelationRequest{ - UserIDs: []string{newUser}, - Relation: policies.MemberRelation, - }, - svcErr: nil, - err: nil, - }, - { - desc: "add user to domain with invalid token", - token: invalidToken, - domainID: sdkDomain.ID, - addUserDomainReq: sdk.UsersRelationRequest{ - UserIDs: []string{newUser}, - Relation: policies.MemberRelation, - }, - svcErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "add user to domain with empty token", - token: "", - domainID: sdkDomain.ID, - addUserDomainReq: sdk.UsersRelationRequest{ - UserIDs: []string{newUser}, - Relation: policies.MemberRelation, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "add user to domain with empty domain id", - token: validToken, - domainID: "", - addUserDomainReq: sdk.UsersRelationRequest{ - UserIDs: []string{newUser}, - Relation: policies.MemberRelation, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), - }, - { - desc: "add user to domain with empty user id", - token: validToken, - domainID: sdkDomain.ID, - addUserDomainReq: sdk.UsersRelationRequest{ - UserIDs: []string{}, - Relation: policies.MemberRelation, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), - }, - { - desc: "add user to domain with empty relation", - token: validToken, - domainID: sdkDomain.ID, - addUserDomainReq: sdk.UsersRelationRequest{ - UserIDs: []string{newUser}, - Relation: "", - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingRelation, http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("AssignUsers", mock.Anything, tc.token, tc.domainID, tc.addUserDomainReq.UserIDs, tc.addUserDomainReq.Relation).Return(tc.svcErr) - err := mgsdk.AddUserToDomain(tc.domainID, tc.addUserDomainReq, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "AssignUsers", mock.Anything, tc.token, tc.domainID, tc.addUserDomainReq.UserIDs, tc.addUserDomainReq.Relation) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestRemoveUserFromDomain(t *testing.T) { - ds, svc := setupDomains() - defer ds.Close() - - sdkConf := sdk.Config{ - DomainsURL: ds.URL, - MsgContentType: contentType, - } - - mgsdk := sdk.NewSDK(sdkConf) - removeUserID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - token string - domainID string - userID string - svcErr error - err error - }{ - { - desc: "remove user from domain successfully", - token: validToken, - domainID: sdkDomain.ID, - userID: removeUserID, - svcErr: nil, - err: nil, - }, - { - desc: "remove user from domain with invalid token", - token: invalidToken, - domainID: sdkDomain.ID, - userID: removeUserID, - svcErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "remove user from domain with empty token", - token: "", - domainID: sdkDomain.ID, - userID: removeUserID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "remove user from domain with empty domain id", - token: validToken, - domainID: "", - userID: removeUserID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), - }, - { - desc: "remove user from domain with empty user id", - token: validToken, - domainID: sdkDomain.ID, - userID: "", - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicy, http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("UnassignUser", mock.Anything, tc.token, tc.domainID, tc.userID).Return(tc.svcErr) - err := mgsdk.RemoveUserFromDomain(tc.domainID, tc.userID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UnassignUser", mock.Anything, tc.token, tc.domainID, tc.userID) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func generateTestDomain(t *testing.T) (auth.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{ - ID: testsutil.GenerateUUID(t), - Name: "test-domain", - Metadata: auth.Metadata(validMetadata), - Tags: []string{"tag1", "tag2"}, - Alias: "test-alias", - Status: auth.EnabledStatus, - CreatedBy: ownerID, - CreatedAt: createdAt, - UpdatedBy: ownerID, - UpdatedAt: createdAt, - } - - sd := sdk.Domain{ - ID: ad.ID, - Name: ad.Name, - Metadata: validMetadata, - Tags: ad.Tags, - Alias: ad.Alias, - Status: ad.Status.String(), - CreatedBy: ad.CreatedBy, - CreatedAt: ad.CreatedAt, - UpdatedBy: ad.UpdatedBy, - UpdatedAt: ad.UpdatedAt, - } - return ad, sd -} diff --git a/pkg/sdk/go/groups.go b/pkg/sdk/go/groups.go deleted file mode 100644 index 0dcb0ee0d..000000000 --- a/pkg/sdk/go/groups.go +++ /dev/null @@ -1,256 +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 ( - groupsEndpoint = "groups" - MaxLevel = uint64(5) - MinLevel = uint64(1) -) - -// Group represents the group of Clients. -// Indicates a level in tree hierarchy. Root node is level 1. -// Path in a tree consisting of group IDs -// Paths are unique per owner. -type Group struct { - ID string `json:"id,omitempty"` - DomainID string `json:"domain_id,omitempty"` - ParentID string `json:"parent_id,omitempty"` - Name string `json:"name,omitempty"` - 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,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - Status string `json:"status,omitempty"` - Permissions []string `json:"permissions,omitempty"` -} - -func (sdk mgSDK) CreateGroup(g Group, domainID, token string) (Group, errors.SDKError) { - data, err := json.Marshal(g) - if err != nil { - return Group{}, errors.NewSDKError(err) - } - url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, domainID, groupsEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return Group{}, sdkerr - } - - g = Group{} - if err := json.Unmarshal(body, &g); err != nil { - return Group{}, errors.NewSDKError(err) - } - - return g, nil -} - -func (sdk mgSDK) Groups(pm PageMetadata, domainID, token string) (GroupsPage, errors.SDKError) { - endpoint := fmt.Sprintf("%s/%s", domainID, groupsEndpoint) - url, err := sdk.withQueryParams(sdk.usersURL, endpoint, pm) - if err != nil { - return GroupsPage{}, errors.NewSDKError(err) - } - - return sdk.getGroups(url, token) -} - -func (sdk mgSDK) Parents(id string, pm PageMetadata, domainID, token string) (GroupsPage, errors.SDKError) { - pm.Level = MaxLevel - endpoint := fmt.Sprintf("%s/%s", domainID, groupsEndpoint) - url, err := sdk.withQueryParams(fmt.Sprintf("%s/%s/%s", sdk.usersURL, endpoint, id), "parents", pm) - if err != nil { - return GroupsPage{}, errors.NewSDKError(err) - } - - return sdk.getGroups(url, token) -} - -func (sdk mgSDK) Children(id string, pm PageMetadata, domainID, token string) (GroupsPage, errors.SDKError) { - pm.Level = MaxLevel - endpoint := fmt.Sprintf("%s/%s", domainID, groupsEndpoint) - url, err := sdk.withQueryParams(fmt.Sprintf("%s/%s/%s", sdk.usersURL, endpoint, id), "children", pm) - if err != nil { - return GroupsPage{}, errors.NewSDKError(err) - } - - return sdk.getGroups(url, token) -} - -func (sdk mgSDK) getGroups(url, token string) (GroupsPage, errors.SDKError) { - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return GroupsPage{}, err - } - - var tp GroupsPage - if err := json.Unmarshal(body, &tp); err != nil { - return GroupsPage{}, errors.NewSDKError(err) - } - - return tp, nil -} - -func (sdk mgSDK) Group(id, domainID, token string) (Group, errors.SDKError) { - if id == "" { - return Group{}, errors.NewSDKError(apiutil.ErrMissingID) - } - - url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, domainID, groupsEndpoint, id) - - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return Group{}, err - } - - var t Group - if err := json.Unmarshal(body, &t); err != nil { - return Group{}, errors.NewSDKError(err) - } - - return t, nil -} - -func (sdk mgSDK) GroupPermissions(id, domainID, token string) (Group, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.usersURL, domainID, groupsEndpoint, id, permissionsEndpoint) - - _, body, err := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if err != nil { - return Group{}, err - } - - var t Group - if err := json.Unmarshal(body, &t); err != nil { - return Group{}, errors.NewSDKError(err) - } - - return t, nil -} - -func (sdk mgSDK) UpdateGroup(g Group, domainID, token string) (Group, errors.SDKError) { - data, err := json.Marshal(g) - if err != nil { - return Group{}, errors.NewSDKError(err) - } - - if g.ID == "" { - return Group{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, domainID, groupsEndpoint, g.ID) - - _, body, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return Group{}, sdkerr - } - - g = Group{} - if err := json.Unmarshal(body, &g); err != nil { - return Group{}, errors.NewSDKError(err) - } - - return g, nil -} - -func (sdk mgSDK) EnableGroup(id, domainID, token string) (Group, errors.SDKError) { - return sdk.changeGroupStatus(id, enableEndpoint, domainID, token) -} - -func (sdk mgSDK) DisableGroup(id, domainID, token string) (Group, errors.SDKError) { - return sdk.changeGroupStatus(id, disableEndpoint, domainID, token) -} - -func (sdk mgSDK) AddUserToGroup(groupID 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/%s", sdk.usersURL, domainID, groupsEndpoint, groupID, usersEndpoint, assignEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - return sdkerr -} - -func (sdk mgSDK) RemoveUserFromGroup(groupID 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/%s", sdk.usersURL, domainID, groupsEndpoint, groupID, usersEndpoint, unassignEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - return sdkerr -} - -func (sdk mgSDK) ListGroupUsers(groupID string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", domainID, groupsEndpoint, groupID, 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) 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) - if err != nil { - return ChannelsPage{}, errors.NewSDKError(err) - } - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return ChannelsPage{}, sdkerr - } - cp := ChannelsPage{} - if err := json.Unmarshal(body, &cp); err != nil { - return ChannelsPage{}, errors.NewSDKError(err) - } - - return cp, nil -} - -func (sdk mgSDK) DeleteGroup(id, domainID, token string) errors.SDKError { - if id == "" { - return errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, domainID, groupsEndpoint, id) - _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - return sdkerr -} - -func (sdk mgSDK) changeGroupStatus(id, status, domainID, token string) (Group, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.usersURL, domainID, groupsEndpoint, id, status) - - _, body, err := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusOK) - if err != nil { - return Group{}, err - } - g := Group{} - if err := json.Unmarshal(body, &g); err != nil { - return Group{}, errors.NewSDKError(err) - } - - return g, nil -} diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go deleted file mode 100644 index 82271465e..000000000 --- a/pkg/sdk/go/groups_test.go +++ /dev/null @@ -1,2038 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - authmocks "github.com/absmach/magistrala/auth/mocks" - "github.com/absmach/magistrala/internal/testsutil" - 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" - "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" -) - -var ( - sdkGroup = generateTestGroup(&testing.T{}) - group = convertGroup(sdkGroup) - updatedName = "updated_name" - updatedDescription = "updated_description" -) - -func setupGroups() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - usvc := new(umocks.Service) - gsvc := 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) - - return httptest.NewServer(mux), gsvc, authn -} - -func TestCreateGroup(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - createGroupReq := sdk.Group{ - Name: gName, - Description: description, - Metadata: validMetadata, - } - pGroup := group - pGroup.Parent = testsutil.GenerateUUID(t) - psdkGroup := sdkGroup - psdkGroup.ParentID = pGroup.Parent - - uGroup := group - uGroup.Metadata = groups.Metadata{ - "key": make(chan int), - } - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupReq sdk.Group - svcReq groups.Group - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Group - err errors.SDKError - }{ - { - desc: "create group successfully", - domainID: domainID, - token: validToken, - groupReq: createGroupReq, - svcReq: groups.Group{ - Name: gName, - Description: description, - Metadata: groups.Metadata{"role": "client"}, - }, - svcRes: group, - svcErr: nil, - response: sdkGroup, - err: nil, - }, - { - desc: "create group with existing name", - domainID: domainID, - token: validToken, - groupReq: createGroupReq, - svcReq: groups.Group{ - Name: gName, - Description: description, - Metadata: groups.Metadata{"role": "client"}, - }, - svcRes: group, - svcErr: nil, - response: sdkGroup, - err: nil, - }, - { - desc: "create group with parent", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - Name: gName, - Description: description, - Metadata: validMetadata, - ParentID: pGroup.Parent, - }, - svcReq: groups.Group{ - Name: gName, - Description: description, - Metadata: groups.Metadata{"role": "client"}, - Parent: pGroup.Parent, - }, - svcRes: pGroup, - svcErr: nil, - response: psdkGroup, - err: nil, - }, - { - desc: "create group with invalid parent", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - Name: gName, - Description: description, - Metadata: validMetadata, - ParentID: wrongID, - }, - svcReq: groups.Group{ - Name: gName, - Description: description, - Metadata: groups.Metadata{"role": "client"}, - Parent: wrongID, - }, - svcRes: groups.Group{}, - svcErr: svcerr.ErrAuthorization, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "create group with invalid token", - domainID: domainID, - token: invalidToken, - groupReq: sdk.Group{ - Name: gName, - Description: description, - Metadata: validMetadata, - }, - svcReq: groups.Group{ - Name: gName, - Description: description, - Metadata: groups.Metadata{"role": "client"}, - }, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "create group with empty token", - domainID: domainID, - token: "", - groupReq: sdk.Group{ - Name: gName, - Description: description, - Metadata: validMetadata, - }, - svcReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "create group with missing name", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - Description: description, - Metadata: validMetadata, - }, - svcReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), - }, - { - desc: "create group with name that is too long", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - Name: strings.Repeat("a", 1025), - Description: description, - Metadata: validMetadata, - }, - svcReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), - }, - { - desc: "create group with request that cannot be marshalled", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - Name: gName, - Description: description, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "create group with service response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - Name: gName, - Description: description, - Metadata: validMetadata, - }, - svcReq: groups.Group{ - Name: gName, - Description: description, - Metadata: groups.Metadata{"role": "client"}, - }, - svcRes: uGroup, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("CreateGroup", mock.Anything, tc.session, policies.NewGroupKind, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.CreateGroup(tc.groupReq, 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, "CreateGroup", mock.Anything, tc.session, policies.NewGroupKind, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListGroups(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - var grps []sdk.Group - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - for i := 10; i < 100; i++ { - gr := sdk.Group{ - ID: generateUUID(t), - Name: fmt.Sprintf("group_%d", i), - Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, - Status: groups.EnabledStatus.String(), - } - grps = append(grps, gr) - } - - cases := []struct { - desc string - token string - domainID string - session mgauthn.Session - pageMeta sdk.PageMetadata - svcReq groups.Page - svcRes groups.Page - svcErr error - authenticateErr error - response sdk.GroupsPage - err errors.SDKError - }{ - { - desc: "list groups successfully", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 100, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: 100, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(grps)), - }, - Groups: convertGroups(grps), - }, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(grps)), - }, - Groups: grps, - }, - err: nil, - }, - { - desc: "list groups with invalid token", - token: invalidToken, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 100, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: 100, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list groups with empty token", - domainID: domainID, - token: "", - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 100, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list groups with zero limit", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 0, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: 10, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(grps[0:10])), - }, - Groups: convertGroups(grps[0:10]), - }, - svcErr: nil, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(grps[0:10])), - }, - Groups: grps[0:10], - }, - err: nil, - }, - { - desc: "list groups with limit greater than max", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 110, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "list groups with given name", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Metadata: sdk.Metadata{ - "name": "user_89", - }, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - Metadata: groups.Metadata{ - "name": "user_89", - }, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: convertGroups([]sdk.Group{grps[89]}), - }, - svcErr: nil, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Groups: []sdk.Group{grps[89]}, - }, - err: nil, - }, - { - desc: "list groups with invalid level", - token: validToken, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 100, - Level: 6, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidLevel), http.StatusBadRequest), - }, - { - desc: "list groups with invalid page metadata", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list groups with service response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{{ - ID: generateUUID(t), - Name: "group_1", - Metadata: groups.Metadata{ - "key": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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.UsersKind, "", tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Groups(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.UsersKind, "", tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListParentGroups(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - var grps []sdk.Group - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - parentID := "" - for i := 10; i < 100; i++ { - gr := sdk.Group{ - ID: generateUUID(t), - Name: fmt.Sprintf("group_%d", i), - Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, - Status: groups.EnabledStatus.String(), - ParentID: parentID, - Level: 1, - } - parentID = gr.ID - grps = append(grps, gr) - } - - cases := []struct { - desc string - token string - domainID string - session mgauthn.Session - pageMeta sdk.PageMetadata - parentID string - svcReq groups.Page - svcRes groups.Page - svcErr error - authenticateErr error - response sdk.GroupsPage - err errors.SDKError - }{ - { - desc: "list parent groups successfully", - domainID: domainID, - token: validToken, - parentID: parentID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - ParentID: parentID, - Permission: policies.ViewPermission, - Direction: 1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(grps[offset:limit])), - }, - Groups: convertGroups(grps[offset:limit]), - }, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(grps[offset:limit])), - }, - Groups: grps[offset:limit], - }, - err: nil, - }, - { - desc: "list parent groups with invalid token", - domainID: domainID, - token: invalidToken, - parentID: parentID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - ParentID: parentID, - Permission: policies.ViewPermission, - Direction: 1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list parent groups with empty token", - domainID: domainID, - token: "", - parentID: parentID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list parent groups with zero limit", - domainID: domainID, - token: validToken, - parentID: parentID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 0, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: 10, - }, - ParentID: parentID, - Permission: policies.ViewPermission, - Direction: 1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(grps[offset:10])), - }, - Groups: convertGroups(grps[offset:10]), - }, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(grps[offset:10])), - }, - Groups: grps[offset:10], - }, - err: nil, - }, - { - desc: "list parent groups with limit greater than max", - domainID: domainID, - token: validToken, - parentID: parentID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 110, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "list parent groups with given metadata", - domainID: domainID, - token: validToken, - parentID: parentID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Metadata: sdk.Metadata{ - "name": "user_89", - }, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - Metadata: groups.Metadata{ - "name": "user_89", - }, - }, - ParentID: parentID, - Permission: policies.ViewPermission, - Direction: 1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: convertGroups([]sdk.Group{grps[89]}), - }, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Groups: []sdk.Group{grps[89]}, - }, - err: nil, - }, - { - desc: "list parent groups with invalid page metadata", - domainID: domainID, - token: validToken, - parentID: parentID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list parent groups with service response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - parentID: parentID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - DomainID: domainID, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - ParentID: parentID, - Permission: policies.ViewPermission, - Direction: 1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{{ - ID: generateUUID(t), - Name: "group_1", - Metadata: groups.Metadata{ - "key": make(chan int), - }, - Level: 1, - }}, - }, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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.UsersKind, "", tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Parents(tc.parentID, 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.UsersKind, "", tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListChildrenGroups(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - var grps []sdk.Group - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - parentID := "" - for i := 10; i < 100; i++ { - gr := sdk.Group{ - ID: generateUUID(t), - Name: fmt.Sprintf("group_%d", i), - Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, - Status: groups.EnabledStatus.String(), - ParentID: parentID, - Level: -1, - } - parentID = gr.ID - grps = append(grps, gr) - } - childID := grps[0].ID - - cases := []struct { - desc string - token string - domainID string - session mgauthn.Session - childID string - pageMeta sdk.PageMetadata - svcReq groups.Page - svcRes groups.Page - svcErr error - authenticateErr error - response sdk.GroupsPage - err errors.SDKError - }{ - { - desc: "list children groups successfully", - domainID: domainID, - token: validToken, - childID: childID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - ParentID: childID, - Permission: policies.ViewPermission, - Direction: -1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(grps[offset:limit])), - }, - Groups: convertGroups(grps[offset:limit]), - }, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(grps[offset:limit])), - }, - Groups: grps[offset:limit], - }, - err: nil, - }, - { - desc: "list children groups with invalid token", - domainID: domainID, - token: invalidToken, - childID: childID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - ParentID: childID, - Permission: policies.ViewPermission, - Direction: -1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list children groups with empty token", - domainID: domainID, - token: "", - childID: childID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list children groups with zero limit", - domainID: domainID, - token: validToken, - childID: childID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 0, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: 10, - }, - ParentID: childID, - Permission: policies.ViewPermission, - Direction: -1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: uint64(len(grps[offset:10])), - }, - Groups: convertGroups(grps[offset:10]), - }, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(grps[offset:10])), - }, - Groups: grps[offset:10], - }, - err: nil, - }, - { - desc: "list children groups with limit greater than max", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 110, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "list children groups with given metadata", - domainID: domainID, - token: validToken, - childID: childID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Metadata: sdk.Metadata{ - "name": "user_89", - }, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - Metadata: groups.Metadata{ - "name": "user_89", - }, - }, - ParentID: childID, - Permission: policies.ViewPermission, - Direction: -1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: convertGroups([]sdk.Group{grps[89]}), - }, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Groups: []sdk.Group{grps[89]}, - }, - err: nil, - }, - { - desc: "list children groups with invalid page metadata", - domainID: domainID, - token: validToken, - childID: childID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Metadata: sdk.Metadata{ - "key": make(chan int), - }, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list children groups with service response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - childID: childID, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: offset, - Limit: limit, - }, - ParentID: childID, - Permission: policies.ViewPermission, - Direction: -1, - Level: sdk.MaxLevel, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{{ - ID: generateUUID(t), - Name: "group_1", - Metadata: groups.Metadata{ - "key": make(chan int), - }, - Level: -1, - }}, - }, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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.UsersKind, "", tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Children(tc.childID, 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.UsersKind, "", tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewGroup(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupID string - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Group - err errors.SDKError - }{ - { - desc: "view group successfully", - domainID: domainID, - token: validToken, - groupID: group.ID, - svcRes: group, - svcErr: nil, - response: sdkGroup, - err: nil, - }, - { - desc: "view group with invalid token", - domainID: domainID, - token: invalidToken, - groupID: group.ID, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view group with empty token", - domainID: domainID, - token: "", - groupID: group.ID, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view group with invalid group id", - domainID: domainID, - token: validToken, - groupID: wrongID, - svcRes: groups.Group{}, - svcErr: svcerr.ErrViewEntity, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "view group with service response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - groupID: group.ID, - svcRes: groups.Group{ - ID: group.ID, - Name: "group_1", - Metadata: groups.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - { - desc: "view group with empty id", - domainID: domainID, - token: validToken, - groupID: "", - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("ViewGroup", mock.Anything, tc.session, tc.groupID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Group(tc.groupID, 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, "ViewGroup", mock.Anything, tc.session, tc.groupID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewGroupPermissions(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupID string - svcRes []string - svcErr error - authenticateErr error - response sdk.Group - err errors.SDKError - }{ - { - desc: "view group permissions successfully", - domainID: domainID, - token: validToken, - groupID: group.ID, - svcRes: []string{policies.ViewPermission, policies.MembershipPermission}, - svcErr: nil, - response: sdk.Group{ - Permissions: []string{policies.ViewPermission, policies.MembershipPermission}, - }, - err: nil, - }, - { - desc: "view group permissions with invalid token", - domainID: domainID, - token: invalidToken, - groupID: group.ID, - svcRes: []string{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view group permissions with empty token", - domainID: domainID, - token: "", - groupID: group.ID, - svcRes: []string{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view group permissions with invalid group id", - domainID: domainID, - token: validToken, - groupID: wrongID, - svcRes: []string{}, - svcErr: svcerr.ErrAuthorization, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "view group permissions with empty id", - domainID: domainID, - token: validToken, - groupID: "", - svcRes: []string{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("ViewGroupPerms", mock.Anything, tc.session, tc.groupID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.GroupPermissions(tc.groupID, 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, "ViewGroupPerms", mock.Anything, tc.session, tc.groupID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateGroup(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - upGroup := sdkGroup - upGroup.Name = updatedName - upGroup.Description = updatedDescription - upGroup.Metadata = sdk.Metadata{"key": "value"} - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - group.ID = generateUUID(t) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupReq sdk.Group - svcReq groups.Group - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Group - err errors.SDKError - }{ - { - desc: "update group successfully", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - ID: group.ID, - Name: updatedName, - Description: updatedDescription, - Metadata: sdk.Metadata{"key": "value"}, - }, - svcReq: groups.Group{ - ID: group.ID, - Name: updatedName, - Description: updatedDescription, - Metadata: groups.Metadata{"key": "value"}, - }, - svcRes: convertGroup(upGroup), - svcErr: nil, - response: upGroup, - err: nil, - }, - { - desc: "update group name with invalid group id", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - ID: wrongID, - Name: updatedName, - Description: updatedDescription, - Metadata: sdk.Metadata{"key": "value"}, - }, - svcReq: groups.Group{ - ID: wrongID, - Name: updatedName, - Description: updatedDescription, - Metadata: groups.Metadata{"key": "value"}, - }, - svcRes: groups.Group{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "update group name with invalid token", - domainID: domainID, - token: invalidToken, - groupReq: sdk.Group{ - ID: group.ID, - Name: updatedName, - Description: updatedDescription, - Metadata: sdk.Metadata{"key": "value"}, - }, - svcReq: groups.Group{ - ID: group.ID, - Name: updatedName, - Description: updatedDescription, - Metadata: groups.Metadata{"key": "value"}, - }, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update group name with empty token", - domainID: domainID, - token: "", - groupReq: sdk.Group{ - ID: group.ID, - Name: updatedName, - Description: updatedDescription, - Metadata: sdk.Metadata{"key": "value"}, - }, - svcReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update group with empty id", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - ID: "", - Name: updatedName, - Description: updatedDescription, - Metadata: sdk.Metadata{"key": "value"}, - }, - svcReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "update group with request that can't be marshalled", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - ID: group.ID, - Name: updatedName, - Description: updatedDescription, - Metadata: sdk.Metadata{"key": make(chan int)}, - }, - svcReq: groups.Group{}, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "update group with service response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - groupReq: sdk.Group{ - ID: group.ID, - Name: updatedName, - Description: updatedDescription, - Metadata: sdk.Metadata{"key": "value"}, - }, - svcReq: groups.Group{ - ID: group.ID, - Name: updatedName, - Description: updatedDescription, - Metadata: groups.Metadata{"key": "value"}, - }, - svcRes: groups.Group{ - ID: group.ID, - Name: updatedName, - Metadata: groups.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("UpdateGroup", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateGroup(tc.groupReq, 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, "UpdateGroup", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestEnableGroup(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - enGroup := sdkGroup - enGroup.Status = groups.EnabledStatus.String() - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupID string - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Group - err errors.SDKError - }{ - { - desc: "enable group successfully", - domainID: domainID, - token: validToken, - groupID: group.ID, - svcRes: convertGroup(enGroup), - svcErr: nil, - response: enGroup, - err: nil, - }, - { - desc: "enable group with invalid group id", - domainID: domainID, - token: validToken, - groupID: wrongID, - svcRes: groups.Group{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "enable group with invalid token", - domainID: domainID, - token: invalidToken, - groupID: group.ID, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "enable group with empty token", - domainID: domainID, - token: "", - groupID: group.ID, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "enable group with empty id", - domainID: domainID, - token: validToken, - groupID: "", - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "enable group with service response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - groupID: group.ID, - svcRes: groups.Group{ - ID: group.ID, - Name: "group_1", - Metadata: groups.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("EnableGroup", mock.Anything, tc.session, tc.groupID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.EnableGroup(tc.groupID, 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, "EnableGroup", mock.Anything, tc.session, tc.groupID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDisableGroup(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - disGroup := sdkGroup - disGroup.Status = groups.DisabledStatus.String() - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupID string - svcRes groups.Group - svcErr error - authenticateErr error - response sdk.Group - err errors.SDKError - }{ - { - desc: "disable group successfully", - domainID: domainID, - token: validToken, - groupID: group.ID, - svcRes: convertGroup(disGroup), - svcErr: nil, - response: disGroup, - err: nil, - }, - { - desc: "disable group with invalid group id", - domainID: domainID, - token: validToken, - groupID: wrongID, - svcRes: groups.Group{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - { - desc: "disable group with invalid token", - domainID: domainID, - token: invalidToken, - groupID: group.ID, - svcRes: groups.Group{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "disable group with empty token", - domainID: domainID, - token: "", - groupID: group.ID, - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "disable group with empty id", - domainID: domainID, - token: validToken, - groupID: "", - svcRes: groups.Group{}, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "disable group with service response that cannot be unmarshalled", - domainID: domainID, - token: validToken, - groupID: group.ID, - svcRes: groups.Group{ - ID: group.ID, - Name: "group_1", - Metadata: groups.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Group{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("DisableGroup", mock.Anything, tc.session, tc.groupID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.DisableGroup(tc.groupID, 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, "DisableGroup", mock.Anything, tc.session, tc.groupID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDeleteGroup(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupID string - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "delete group successfully", - domainID: domainID, - token: validToken, - groupID: group.ID, - svcErr: nil, - err: nil, - }, - { - desc: "delete group with invalid group id", - domainID: domainID, - token: validToken, - groupID: wrongID, - svcErr: svcerr.ErrRemoveEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrRemoveEntity, http.StatusUnprocessableEntity), - }, - { - desc: "delete group with invalid token", - domainID: domainID, - token: invalidToken, - groupID: group.ID, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "delete group with empty token", - domainID: domainID, - token: "", - groupID: group.ID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "delete group with empty id", - domainID: domainID, - token: validToken, - groupID: "", - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("DeleteGroup", mock.Anything, tc.session, tc.groupID).Return(tc.svcErr) - err := mgsdk.DeleteGroup(tc.groupID, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "DeleteGroup", mock.Anything, tc.session, tc.groupID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestAddUserToGroup(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupID string - addUserReq sdk.UsersRelationRequest - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "add user to group successfully", - domainID: domainID, - token: validToken, - groupID: group.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "add user to group with invalid token", - domainID: domainID, - token: invalidToken, - groupID: group.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "add user to group with empty token", - domainID: domainID, - token: "", - groupID: group.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "add user to group with invalid group id", - domainID: domainID, - token: validToken, - groupID: wrongID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "add user to group with empty group id", - domainID: domainID, - token: validToken, - groupID: "", - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "add users to group with empty relation", - domainID: domainID, - token: validToken, - groupID: group.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingRelation), http.StatusBadRequest), - }, - { - desc: "add users to group with empty user ids", - domainID: domainID, - token: validToken, - groupID: group.ID, - addUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyList), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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.groupID, tc.addUserReq.Relation, policies.UsersKind, tc.addUserReq.UserIDs).Return(tc.svcErr) - err := mgsdk.AddUserToGroup(tc.groupID, tc.addUserReq, 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.groupID, tc.addUserReq.Relation, policies.UsersKind, tc.addUserReq.UserIDs) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestRemoveUserFromGroup(t *testing.T) { - ts, gsvc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - groupID string - removeUserReq sdk.UsersRelationRequest - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "remove user from group successfully", - domainID: domainID, - token: validToken, - groupID: group.ID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: nil, - }, - { - desc: "remove user from group with invalid token", - domainID: domainID, - token: invalidToken, - groupID: group.ID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "remove user from group with empty token", - domainID: domainID, - token: "", - groupID: group.ID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "remove user from group with invalid group id", - domainID: domainID, - token: validToken, - groupID: wrongID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "remove user from group with empty group id", - domainID: domainID, - token: validToken, - groupID: "", - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{user.ID}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "remove users from group with empty user ids", - domainID: domainID, - token: validToken, - groupID: group.ID, - removeUserReq: sdk.UsersRelationRequest{ - Relation: "member", - UserIDs: []string{}, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyList), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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.groupID, tc.removeUserReq.Relation, policies.UsersKind, tc.removeUserReq.UserIDs).Return(tc.svcErr) - err := mgsdk.RemoveUserFromGroup(tc.groupID, tc.removeUserReq, 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.groupID, tc.removeUserReq.Relation, policies.UsersKind, tc.removeUserReq.UserIDs) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func generateTestGroup(t *testing.T) sdk.Group { - createdAt, err := time.Parse(time.RFC3339, "2023-03-03T00:00:00Z") - assert.Nil(t, err, fmt.Sprintf("unexpected error %s", err)) - updatedAt := createdAt - gr := sdk.Group{ - ID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Name: gName, - Description: description, - Metadata: sdk.Metadata{"role": "client"}, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - Status: groups.EnabledStatus.String(), - } - return gr -} diff --git a/pkg/sdk/go/health.go b/pkg/sdk/go/health.go deleted file mode 100644 index 4334b2940..000000000 --- a/pkg/sdk/go/health.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/absmach/magistrala/pkg/errors" -) - -// HealthInfo contains version endpoint response. -type HealthInfo struct { - // Status contains service status. - Status string `json:"status"` - - // Version contains current service version. - Version string `json:"version"` - - // Commit represents the git hash commit. - Commit string `json:"commit"` - - // Description contains service description. - Description string `json:"description"` - - // BuildTime contains service build time. - BuildTime string `json:"build_time"` -} - -func (sdk mgSDK) Health(service string) (HealthInfo, errors.SDKError) { - var url string - switch service { - case "things": - url = fmt.Sprintf("%s/health", sdk.thingsURL) - case "users": - url = fmt.Sprintf("%s/health", sdk.usersURL) - case "bootstrap": - url = fmt.Sprintf("%s/health", sdk.bootstrapURL) - case "certs": - url = fmt.Sprintf("%s/health", sdk.certsURL) - case "reader": - url = fmt.Sprintf("%s/health", sdk.readerURL) - case "http-adapter": - url = fmt.Sprintf("%s/health", sdk.httpAdapterURL) - } - - resp, err := sdk.client.Get(url) - if err != nil { - return HealthInfo{}, errors.NewSDKError(err) - } - defer resp.Body.Close() - - if err := errors.CheckError(resp, http.StatusOK); err != nil { - return HealthInfo{}, err - } - - var h HealthInfo - if err := json.NewDecoder(resp.Body).Decode(&h); err != nil { - return HealthInfo{}, errors.NewSDKError(err) - } - - return h, nil -} diff --git a/pkg/sdk/go/health_test.go b/pkg/sdk/go/health_test.go deleted file mode 100644 index f30cf045d..000000000 --- a/pkg/sdk/go/health_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http/httptest" - "testing" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/bootstrap/api" - bmocks "github.com/absmach/magistrala/bootstrap/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() - - usersTs, _, _ := setupUsers() - defer usersTs.Close() - - certsTs, _, _ := setupCerts() - defer certsTs.Close() - - bootstrapTs := setupMinimalBootstrap() - defer bootstrapTs.Close() - - readerTs := setupMinimalReader() - defer readerTs.Close() - - httpAdapterTs, _, _ := setupMessages() - defer httpAdapterTs.Close() - - sdkConf := sdk.Config{ - ThingsURL: thingsTs.URL, - UsersURL: usersTs.URL, - CertsURL: certsTs.URL, - BootstrapURL: bootstrapTs.URL, - ReaderURL: readerTs.URL, - HTTPAdapterURL: httpAdapterTs.URL, - MsgContentType: contentType, - TLSVerification: false, - } - - mgsdk := sdk.NewSDK(sdkConf) - cases := []struct { - desc string - service string - empty bool - description string - status string - err errors.SDKError - }{ - { - desc: "get things service health check", - service: "things", - empty: false, - err: nil, - description: "things service", - status: "pass", - }, - { - desc: "get users service health check", - service: "users", - empty: false, - err: nil, - description: "users service", - status: "pass", - }, - { - desc: "get certs service health check", - service: "certs", - empty: false, - err: nil, - description: "certs service", - status: "pass", - }, - { - desc: "get bootstrap service health check", - service: "bootstrap", - empty: false, - err: nil, - description: "bootstrap service", - status: "pass", - }, - { - desc: "get reader service health check", - service: "reader", - empty: false, - err: nil, - description: "test service", - status: "pass", - }, - { - desc: "get http-adapter service health check", - service: "http-adapter", - empty: false, - err: nil, - description: "http service", - status: "pass", - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - h, err := mgsdk.Health(tc.service) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) - assert.Equal(t, tc.status, h.Status, fmt.Sprintf("%s: expected %s status, got %s", tc.desc, tc.status, h.Status)) - assert.Equal(t, tc.empty, h.Version == "", fmt.Sprintf("%s: expected non-empty version", tc.desc)) - assert.Equal(t, magistrala.Commit, h.Commit, fmt.Sprintf("%s: expected non-empty commit", tc.desc)) - assert.Equal(t, tc.description, h.Description, fmt.Sprintf("%s: expected proper description, got %s", tc.desc, h.Description)) - assert.Equal(t, magistrala.BuildTime, h.BuildTime, fmt.Sprintf("%s: expected default epoch date, got %s", tc.desc, h.BuildTime)) - }) - } -} - -func setupMinimalBootstrap() *httptest.Server { - bsvc := new(bmocks.Service) - reader := new(bmocks.ConfigReader) - logger := mglog.NewMock() - authn := new(authnmocks.Authentication) - mux := api.MakeHandler(bsvc, authn, reader, logger, "") - - return httptest.NewServer(mux) -} - -func setupMinimalReader() *httptest.Server { - repo := new(readersmocks.MessageRepository) - authz := new(authzmocks.Authorization) - authn := new(authnmocks.Authentication) - things := new(thmocks.ThingsServiceClient) - - mux := readersapi.MakeHandler(repo, authn, authz, things, "test", "") - return httptest.NewServer(mux) -} diff --git a/pkg/sdk/go/invitations.go b/pkg/sdk/go/invitations.go deleted file mode 100644 index 97c42255f..000000000 --- a/pkg/sdk/go/invitations.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/absmach/magistrala/pkg/errors" -) - -const ( - invitationsEndpoint = "invitations" - acceptEndpoint = "accept" - rejectEndpoint = "reject" -) - -type Invitation struct { - InvitedBy string `json:"invited_by"` - UserID string `json:"user_id"` - DomainID string `json:"domain_id"` - Token string `json:"token,omitempty"` - Relation string `json:"relation,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - ConfirmedAt time.Time `json:"confirmed_at,omitempty"` - RejectedAt time.Time `json:"rejected_at,omitempty"` - Resend bool `json:"resend,omitempty"` -} - -type InvitationPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Invitations []Invitation `json:"invitations"` -} - -func (sdk mgSDK) SendInvitation(invitation Invitation, token string) (err error) { - data, err := json.Marshal(invitation) - if err != nil { - return errors.NewSDKError(err) - } - - url := sdk.invitationsURL + "/" + invitationsEndpoint - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - - return sdkerr -} - -func (sdk mgSDK) Invitation(userID, domainID, token string) (invitation Invitation, err error) { - url := sdk.invitationsURL + "/" + invitationsEndpoint + "/" + userID + "/" + domainID - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return Invitation{}, sdkerr - } - - if err := json.Unmarshal(body, &invitation); err != nil { - return Invitation{}, errors.NewSDKError(err) - } - - return invitation, nil -} - -func (sdk mgSDK) Invitations(pm PageMetadata, token string) (invitations InvitationPage, err error) { - url, err := sdk.withQueryParams(sdk.invitationsURL, invitationsEndpoint, pm) - if err != nil { - return InvitationPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return InvitationPage{}, sdkerr - } - - var invPage InvitationPage - if err := json.Unmarshal(body, &invPage); err != nil { - return InvitationPage{}, errors.NewSDKError(err) - } - - return invPage, nil -} - -func (sdk mgSDK) AcceptInvitation(domainID, token string) (err error) { - req := struct { - DomainID string `json:"domain_id"` - }{ - DomainID: domainID, - } - data, err := json.Marshal(req) - if err != nil { - return errors.NewSDKError(err) - } - - url := sdk.invitationsURL + "/" + invitationsEndpoint + "/" + acceptEndpoint - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mgSDK) RejectInvitation(domainID, token string) (err error) { - req := struct { - DomainID string `json:"domain_id"` - }{ - DomainID: domainID, - } - data, err := json.Marshal(req) - if err != nil { - return errors.NewSDKError(err) - } - - url := sdk.invitationsURL + "/" + invitationsEndpoint + "/" + rejectEndpoint - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusNoContent) - - return sdkerr -} - -func (sdk mgSDK) DeleteInvitation(userID, domainID, token string) (err error) { - url := sdk.invitationsURL + "/" + invitationsEndpoint + "/" + userID + "/" + domainID - - _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - - return sdkerr -} diff --git a/pkg/sdk/go/invitations_test.go b/pkg/sdk/go/invitations_test.go deleted file mode 100644 index cc662a378..000000000 --- a/pkg/sdk/go/invitations_test.go +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/invitations/api" - "github.com/absmach/magistrala/invitations/mocks" - 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" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - policies "github.com/absmach/magistrala/pkg/policies" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - sdkInvitation = generateTestInvitation(&testing.T{}) - invitation = convertInvitation(sdkInvitation) -) - -func setupInvitations() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - svc := new(mocks.Service) - logger := mglog.NewMock() - authn := new(authnmocks.Authentication) - mux := api.MakeHandler(svc, logger, authn, "test") - - return httptest.NewServer(mux), svc, authn -} - -func TestSendInvitation(t *testing.T) { - is, svc, auth := setupInvitations() - defer is.Close() - - conf := sdk.Config{ - InvitationsURL: is.URL, - } - mgsdk := sdk.NewSDK(conf) - - sendInvitationReq := sdk.Invitation{ - UserID: invitation.UserID, - DomainID: invitation.DomainID, - Relation: invitation.Relation, - Resend: invitation.Resend, - } - - cases := []struct { - desc string - token string - session mgauthn.Session - sendInvitationReq sdk.Invitation - svcReq invitations.Invitation - authenticateErr error - svcErr error - err error - }{ - { - desc: "send invitation successfully", - token: validToken, - sendInvitationReq: sendInvitationReq, - svcReq: convertInvitation(sendInvitationReq), - svcErr: nil, - err: nil, - }, - { - desc: "send invitation with invalid token", - token: invalidToken, - sendInvitationReq: sendInvitationReq, - svcReq: convertInvitation(sendInvitationReq), - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "send invitation with empty token", - token: "", - sendInvitationReq: sendInvitationReq, - svcReq: invitations.Invitation{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "send invitation with empty userID", - token: validToken, - sendInvitationReq: sdk.Invitation{ - UserID: "", - DomainID: invitation.DomainID, - Relation: invitation.Relation, - Resend: invitation.Resend, - }, - svcReq: invitations.Invitation{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "send invitation with invalid relation", - token: validToken, - sendInvitationReq: sdk.Invitation{ - UserID: invitation.UserID, - DomainID: invitation.DomainID, - Relation: "invalid", - Resend: invitation.Resend, - }, - svcReq: invitations.Invitation{}, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidRelation), http.StatusInternalServerError), - }, - { - desc: "send inviation with invalid domainID", - token: validToken, - sendInvitationReq: sdk.Invitation{ - UserID: invitation.UserID, - DomainID: wrongID, - Relation: invitation.Relation, - Resend: invitation.Resend, - }, - svcReq: invitations.Invitation{ - UserID: invitation.UserID, - DomainID: wrongID, - Relation: invitation.Relation, - Resend: invitation.Resend, - }, - svcErr: svcerr.ErrCreateEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == valid { - tc.session = mgauthn.Session{UserID: tc.sendInvitationReq.UserID, DomainID: tc.sendInvitationReq.DomainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("SendInvitation", mock.Anything, tc.session, tc.svcReq).Return(tc.svcErr) - err := mgsdk.SendInvitation(tc.sendInvitationReq, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "SendInvitation", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewInvitation(t *testing.T) { - is, svc, auth := setupInvitations() - defer is.Close() - - conf := sdk.Config{ - InvitationsURL: is.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - domainID string - svcRes invitations.Invitation - svcErr error - authenticateErr error - response sdk.Invitation - err error - }{ - { - desc: "view invitation successfully", - token: validToken, - userID: invitation.UserID, - domainID: invitation.DomainID, - svcRes: invitation, - svcErr: nil, - response: sdkInvitation, - err: nil, - }, - { - desc: "view invitation with invalid token", - token: invalidToken, - userID: invitation.UserID, - domainID: invitation.DomainID, - svcRes: invitations.Invitation{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Invitation{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view invitation with empty token", - token: "", - userID: invitation.UserID, - domainID: invitation.DomainID, - svcRes: invitations.Invitation{}, - svcErr: nil, - response: sdk.Invitation{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view invitation with empty userID", - token: validToken, - userID: "", - domainID: invitation.DomainID, - svcRes: invitations.Invitation{}, - svcErr: nil, - response: sdk.Invitation{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "view invitation with invalid domainID", - token: validToken, - userID: invitation.UserID, - domainID: wrongID, - svcRes: invitations.Invitation{}, - svcErr: svcerr.ErrNotFound, - response: sdk.Invitation{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == valid { - tc.session = mgauthn.Session{UserID: tc.userID, DomainID: tc.domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ViewInvitation", mock.Anything, tc.session, tc.userID, tc.domainID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Invitation(tc.userID, 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, "ViewInvitation", mock.Anything, tc.session, tc.userID, tc.domainID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListInvitation(t *testing.T) { - is, svc, auth := setupInvitations() - defer is.Close() - - conf := sdk.Config{ - InvitationsURL: is.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - pageMeta sdk.PageMetadata - svcReq invitations.Page - svcRes invitations.InvitationPage - svcErr error - authenticateErr error - response sdk.InvitationPage - err error - }{ - { - desc: "list invitations successfully", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: invitations.Page{ - Offset: 0, - Limit: 10, - }, - svcRes: invitations.InvitationPage{ - Total: 1, - Invitations: []invitations.Invitation{invitation}, - }, - svcErr: nil, - response: sdk.InvitationPage{ - Total: 1, - Invitations: []sdk.Invitation{sdkInvitation}, - }, - err: nil, - }, - { - desc: "list invitations with invalid token", - token: invalidToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: invitations.Page{ - Offset: 0, - Limit: 10, - }, - svcRes: invitations.InvitationPage{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.InvitationPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list invitations with empty token", - token: "", - pageMeta: sdk.PageMetadata{}, - svcRes: invitations.InvitationPage{}, - svcErr: nil, - response: sdk.InvitationPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list invitations with limit greater than max limit", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 101, - }, - svcReq: invitations.Page{}, - svcRes: invitations.InvitationPage{}, - svcErr: nil, - response: sdk.InvitationPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == valid { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ListInvitations", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Invitations(tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListInvitations", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestAcceptInvitation(t *testing.T) { - is, svc, auth := setupInvitations() - defer is.Close() - - conf := sdk.Config{ - InvitationsURL: is.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - domainID string - authenticateErr error - svcErr error - err error - }{ - { - desc: "accept invitation successfully", - token: validToken, - domainID: invitation.DomainID, - svcErr: nil, - err: nil, - }, - { - desc: "accept invitation with invalid token", - token: invalidToken, - domainID: invitation.DomainID, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "accept invitation with empty token", - token: "", - domainID: invitation.DomainID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "accept invitation with invalid domainID", - token: validToken, - domainID: wrongID, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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("AcceptInvitation", mock.Anything, tc.session, tc.domainID).Return(tc.svcErr) - err := mgsdk.AcceptInvitation(tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "AcceptInvitation", mock.Anything, tc.session, tc.domainID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestRejectInvitation(t *testing.T) { - is, svc, auth := setupInvitations() - defer is.Close() - - conf := sdk.Config{ - InvitationsURL: is.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - domainID string - authenticateErr error - svcErr error - err error - }{ - { - desc: "reject invitation successfully", - token: validToken, - domainID: invitation.DomainID, - svcErr: nil, - err: nil, - }, - { - desc: "reject invitation with invalid token", - token: invalidToken, - domainID: invitation.DomainID, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "reject invitation with empty token", - token: "", - domainID: invitation.DomainID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "reject invitation with invalid domainID", - token: validToken, - domainID: wrongID, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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("RejectInvitation", mock.Anything, tc.session, tc.domainID).Return(tc.svcErr) - err := mgsdk.RejectInvitation(tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "RejectInvitation", mock.Anything, tc.session, tc.domainID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDeleteInvitation(t *testing.T) { - is, svc, auth := setupInvitations() - defer is.Close() - - conf := sdk.Config{ - InvitationsURL: is.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - domainID string - authenticateErr error - svcErr error - err error - }{ - { - desc: "delete invitation successfully", - token: validToken, - userID: invitation.UserID, - domainID: invitation.DomainID, - svcErr: nil, - err: nil, - }, - { - desc: "delete invitation with invalid token", - token: invalidToken, - userID: invitation.UserID, - domainID: invitation.DomainID, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "delete invitation with empty token", - token: "", - userID: invitation.UserID, - domainID: invitation.DomainID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "delete invitation with empty userID", - token: validToken, - userID: "", - domainID: invitation.DomainID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "delete invitation with invalid domainID", - token: validToken, - userID: invitation.UserID, - domainID: wrongID, - svcErr: svcerr.ErrNotFound, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == valid { - tc.session = mgauthn.Session{UserID: tc.userID, DomainID: tc.domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("DeleteInvitation", mock.Anything, tc.session, tc.userID, tc.domainID).Return(tc.svcErr) - err := mgsdk.DeleteInvitation(tc.userID, tc.domainID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "DeleteInvitation", mock.Anything, tc.session, tc.userID, tc.domainID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func generateTestInvitation(t *testing.T) sdk.Invitation { - createdAt, err := time.Parse(time.RFC3339, "2024-01-01T00:00:00Z") - assert.Nil(t, err, fmt.Sprintf("Unexpected error parsing time: %v", err)) - return sdk.Invitation{ - InvitedBy: testsutil.GenerateUUID(t), - UserID: testsutil.GenerateUUID(t), - DomainID: testsutil.GenerateUUID(t), - Token: validToken, - Relation: policies.MemberRelation, - CreatedAt: createdAt, - UpdatedAt: createdAt, - Resend: false, - } -} diff --git a/pkg/sdk/go/journal.go b/pkg/sdk/go/journal.go deleted file mode 100644 index 19f0a580c..000000000 --- a/pkg/sdk/go/journal.go +++ /dev/null @@ -1,62 +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 journalEndpoint = "journal" - -type Journal struct { - ID string `json:"id,omitempty"` - Operation string `json:"operation,omitempty"` - OccurredAt time.Time `json:"occurred_at,omitempty"` - Attributes Metadata `json:"attributes,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` -} - -type JournalsPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Journals []Journal `json:"journals"` -} - -func (sdk mgSDK) Journal(entityType, entityID, domainID string, pm PageMetadata, token string) (journals JournalsPage, err error) { - if entityID == "" { - return JournalsPage{}, errors.NewSDKError(apiutil.ErrMissingID) - } - if entityType == "" { - return JournalsPage{}, errors.NewSDKError(apiutil.ErrMissingEntityType) - } - - reqUrl := fmt.Sprintf("%s/%s/%s/%s", domainID, journalEndpoint, entityType, entityID) - if entityType == "user" { - reqUrl = fmt.Sprintf("%s/%s/%s", journalEndpoint, entityType, entityID) - } - - url, err := sdk.withQueryParams(sdk.journalURL, reqUrl, pm) - if err != nil { - return JournalsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return JournalsPage{}, sdkerr - } - - var journalsPage JournalsPage - if err := json.Unmarshal(body, &journalsPage); err != nil { - return JournalsPage{}, errors.NewSDKError(err) - } - - return journalsPage, nil -} diff --git a/pkg/sdk/go/journal_test.go b/pkg/sdk/go/journal_test.go deleted file mode 100644 index 3a572c9a6..000000000 --- a/pkg/sdk/go/journal_test.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/journal/api" - "github.com/absmach/magistrala/journal/mocks" - 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" - "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func setupJournal() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - svc := new(mocks.Service) - authn := new(authnmocks.Authentication) - logger := mglog.NewMock() - mux := api.MakeHandler(svc, authn, logger, "journal-log", "test") - - return httptest.NewServer(mux), svc, authn -} - -func TestRetrieveJournal(t *testing.T) { - js, svc, authn := setupJournal() - defer js.Close() - - testJournal := generateTestJournal(t) - validEntityType := "group" - - sdkConf := sdk.Config{ - JournalURL: js.URL, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - token string - session mgauthn.Session - entityType string - entityID string - domainID string - pageMeta sdk.PageMetadata - svcReq journal.Page - svcRes journal.JournalsPage - svcErr error - authnErr error - response sdk.JournalsPage - err error - }{ - { - desc: "retrieve user journal successfully", - token: validToken, - entityType: "user", - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: validID, - EntityType: journal.UserEntity, - Direction: "desc", - }, - svcRes: journal.JournalsPage{ - Total: 1, - Journals: []journal.Journal{convertJournal(testJournal)}, - }, - svcErr: nil, - response: sdk.JournalsPage{ - Total: 1, - Journals: []sdk.Journal{testJournal}, - }, - err: nil, - }, - { - desc: "retrieve channel journal successfully", - token: validToken, - entityType: "channel", - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: validID, - EntityType: journal.ChannelEntity, - Direction: "desc", - }, - svcRes: journal.JournalsPage{ - Total: 1, - Journals: []journal.Journal{convertJournal(testJournal)}, - }, - svcErr: nil, - response: sdk.JournalsPage{ - Total: 1, - Journals: []sdk.Journal{testJournal}, - }, - err: nil, - }, - { - desc: "retrieve group journal successfully", - token: validToken, - entityType: "group", - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: validID, - EntityType: journal.GroupEntity, - Direction: "desc", - }, - svcRes: journal.JournalsPage{ - Total: 1, - Journals: []journal.Journal{convertJournal(testJournal)}, - }, - svcErr: nil, - response: sdk.JournalsPage{ - Total: 1, - Journals: []sdk.Journal{testJournal}, - }, - err: nil, - }, - { - desc: "retrieve thing journal successfully", - token: validToken, - entityType: "thing", - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: validID, - EntityType: journal.ThingEntity, - Direction: "desc", - }, - svcRes: journal.JournalsPage{ - Total: 1, - Journals: []journal.Journal{convertJournal(testJournal)}, - }, - svcErr: nil, - response: sdk.JournalsPage{ - Total: 1, - Journals: []sdk.Journal{testJournal}, - }, - err: nil, - }, - { - desc: "retrieve journal with invalid token", - token: invalidToken, - entityType: validEntityType, - entityID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: validID, - EntityType: journal.GroupEntity, - Direction: "desc", - }, - svcRes: journal.JournalsPage{}, - authnErr: svcerr.ErrAuthentication, - response: sdk.JournalsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "retrieve journal with empty token", - token: "", - entityType: validEntityType, - entityID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{}, - svcRes: journal.JournalsPage{}, - svcErr: nil, - response: sdk.JournalsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "retrieve journal with invalid entity type", - token: validToken, - entityType: "invalid", - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{}, - svcRes: journal.JournalsPage{}, - svcErr: nil, - response: sdk.JournalsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidEntityType), http.StatusBadRequest), - }, - { - desc: "retrieve journal with empty entity ID", - token: validToken, - entityType: validEntityType, - entityID: "", - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{}, - svcRes: journal.JournalsPage{}, - svcErr: nil, - response: sdk.JournalsPage{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "retrieve journal with empty entity type", - token: validToken, - entityType: "", - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{}, - svcRes: journal.JournalsPage{}, - svcErr: nil, - response: sdk.JournalsPage{}, - err: errors.NewSDKError(apiutil.ErrMissingEntityType), - }, - { - desc: "retrieve journal with limit greater than default", - token: validToken, - entityType: validEntityType, - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 1000, - }, - svcReq: journal.Page{}, - svcRes: journal.JournalsPage{}, - svcErr: nil, - response: sdk.JournalsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "retrieve journal with invalid page metadata", - token: validToken, - entityType: validEntityType, - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }, - svcReq: journal.Page{}, - svcRes: journal.JournalsPage{}, - svcErr: nil, - response: sdk.JournalsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "retrieve journal with response that cannot be unmarshalled", - token: validToken, - entityType: validEntityType, - entityID: validID, - domainID: domainID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - svcReq: journal.Page{ - Offset: 0, - Limit: 10, - EntityID: validID, - EntityType: journal.GroupEntity, - Direction: "desc", - }, - svcRes: journal.JournalsPage{ - Total: 1, - Journals: []journal.Journal{{ - ID: validID, - Operation: "create", - OccurredAt: time.Now(), - Attributes: validMetadata, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.JournalsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID} - } - authCall := authn.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authnErr) - svcCall := svc.On("RetrieveAll", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Journal(tc.entityType, tc.entityID, tc.domainID, tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "RetrieveAll", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func generateTestJournal(t *testing.T) sdk.Journal { - occuredAt, err := time.Parse(time.RFC3339, "2024-01-01T00:00:00Z") - assert.Nil(t, err, fmt.Sprintf("Unexpected error parsing time: %v", err)) - return sdk.Journal{ - ID: validID, - Operation: "create", - OccurredAt: occuredAt, - Attributes: validMetadata, - Metadata: validMetadata, - } -} diff --git a/pkg/sdk/go/message.go b/pkg/sdk/go/message.go deleted file mode 100644 index 0ff16e8d1..000000000 --- a/pkg/sdk/go/message.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" -) - -const channelParts = 2 - -func (sdk mgSDK) SendMessage(chanName, msg, key string) errors.SDKError { - chanNameParts := strings.SplitN(chanName, ".", channelParts) - chanID := chanNameParts[0] - subtopicPart := "" - if len(chanNameParts) == channelParts { - subtopicPart = fmt.Sprintf("/%s", strings.ReplaceAll(chanNameParts[1], ".", "/")) - } - - 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) - - return err -} - -func (sdk mgSDK) ReadMessages(pm MessagePageMetadata, chanName, domainID, token string) (MessagesPage, errors.SDKError) { - chanNameParts := strings.SplitN(chanName, ".", channelParts) - chanID := chanNameParts[0] - subtopicPart := "" - if len(chanNameParts) == channelParts { - subtopicPart = fmt.Sprintf("?subtopic=%s", chanNameParts[1]) - } - - readMessagesEndpoint := fmt.Sprintf("%s/channels/%s/messages%s", domainID, chanID, subtopicPart) - msgURL, err := sdk.withMessageQueryParams(sdk.readerURL, readMessagesEndpoint, pm) - if err != nil { - return MessagesPage{}, errors.NewSDKError(err) - } - - header := make(map[string]string) - header["Content-Type"] = string(sdk.msgContentType) - - _, body, sdkerr := sdk.processRequest(http.MethodGet, msgURL, token, nil, header, http.StatusOK) - if sdkerr != nil { - return MessagesPage{}, sdkerr - } - - var mp MessagesPage - if err := json.Unmarshal(body, &mp); err != nil { - return MessagesPage{}, errors.NewSDKError(err) - } - - return mp, nil -} - -func (sdk *mgSDK) SetContentType(ct ContentType) errors.SDKError { - if ct != CTJSON && ct != CTJSONSenML && ct != CTBinary { - return errors.NewSDKError(apiutil.ErrUnsupportedContentType) - } - - sdk.msgContentType = ct - - return nil -} - -func (sdk mgSDK) withMessageQueryParams(baseURL, endpoint string, mpm MessagePageMetadata) (string, error) { - b, err := json.Marshal(mpm) - if err != nil { - return "", err - } - q := map[string]interface{}{} - if err := json.Unmarshal(b, &q); err != nil { - return "", err - } - ret := url.Values{} - for k, v := range q { - switch t := v.(type) { - case string: - ret.Add(k, t) - case float64: - ret.Add(k, strconv.FormatFloat(t, 'f', -1, 64)) - case uint64: - ret.Add(k, strconv.FormatUint(t, 10)) - case int64: - ret.Add(k, strconv.FormatInt(t, 10)) - case json.Number: - ret.Add(k, t.String()) - case bool: - ret.Add(k, strconv.FormatBool(t)) - } - } - qs := ret.Encode() - - return fmt.Sprintf("%s/%s?%s", baseURL, endpoint, qs), nil -} diff --git a/pkg/sdk/go/message_test.go b/pkg/sdk/go/message_test.go deleted file mode 100644 index 3f5ad3df6..000000000 --- a/pkg/sdk/go/message_test.go +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/absmach/magistrala" - adapter "github.com/absmach/magistrala/http" - "github.com/absmach/magistrala/http/api" - 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" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/absmach/magistrala/pkg/transformers/senml" - "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) - pub := new(pubsub.PubSub) - handler := adapter.NewHandler(pub, mglog.NewMock(), things) - - mux := api.MakeHandler(mglog.NewMock(), "") - target := httptest.NewServer(mux) - - config := mgate.Config{ - Address: "", - Target: target.URL, - } - mp, err := proxy.NewProxy(config, handler, mglog.NewMock()) - if err != nil { - return nil, nil, nil - } - - return httptest.NewServer(http.HandlerFunc(mp.ServeHTTP)), things, pub -} - -func setupReader() (*httptest.Server, *authzmocks.Authorization, *authnmocks.Authentication, *readersmocks.MessageRepository) { - repo := new(readersmocks.MessageRepository) - authz := new(authzmocks.Authorization) - authn := new(authnmocks.Authentication) - things := new(thmocks.ThingsServiceClient) - - mux := readersapi.MakeHandler(repo, authn, authz, things, "test", "") - return httptest.NewServer(mux), authz, authn, repo -} - -func TestSendMessage(t *testing.T) { - ts, things, pub := setupMessages() - defer ts.Close() - - msg := `[{"n":"current","t":-1,"v":1.6}]` - thingKey := "thingKey" - channelID := "channelID" - - sdkConf := sdk.Config{ - HTTPAdapterURL: ts.URL, - MsgContentType: "application/senml+json", - TLSVerification: false, - } - - 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: "publish message successfully", - chanName: channelID, - msg: msg, - thingKey: thingKey, - authRes: &magistrala.ThingsAuthzRes{Authorized: 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 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 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 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 channel subtopic", - chanName: channelID + ".subtopic", - msg: msg, - thingKey: thingKey, - authRes: &magistrala.ThingsAuthzRes{Authorized: 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) - svcCall := pub.On("Publish", mock.Anything, channelID, mock.Anything).Return(tc.svcErr) - err := mgsdk.SendMessage(tc.chanName, tc.msg, tc.thingKey) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Publish", mock.Anything, channelID, mock.Anything) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestSetContentType(t *testing.T) { - ts, _, _ := setupMessages() - defer ts.Close() - - sdkConf := sdk.Config{ - HTTPAdapterURL: ts.URL, - MsgContentType: "application/senml+json", - TLSVerification: false, - } - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - cType sdk.ContentType - err errors.SDKError - }{ - { - desc: "set senml+json content type", - cType: "application/senml+json", - err: nil, - }, - { - desc: "set invalid content type", - cType: "invalid", - err: errors.NewSDKError(apiutil.ErrUnsupportedContentType), - }, - } - for _, tc := range cases { - err := mgsdk.SetContentType(tc.cType) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) - } -} - -func TestReadMessages(t *testing.T) { - ts, authz, authn, repo := setupReader() - defer ts.Close() - - channelID := "channelID" - msgValue := 1.6 - boolVal := true - msg := senml.Message{ - Name: "current", - Time: 1720000000, - Value: &msgValue, - Publisher: validID, - } - invalidMsg := "[{\"n\":\"current\",\"t\":-1,\"v\":1.6}]" - - sdkConf := sdk.Config{ - ReaderURL: ts.URL, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - token string - chanName string - domainID string - messagePageMeta sdk.MessagePageMetadata - authzErr error - authnErr error - repoRes readers.MessagesPage - repoErr error - response sdk.MessagesPage - err errors.SDKError - }{ - { - desc: "read messages successfully", - token: validToken, - chanName: channelID, - domainID: validID, - messagePageMeta: sdk.MessagePageMetadata{ - PageMetadata: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Level: 0, - }, - Publisher: validID, - BoolValue: &boolVal, - }, - repoRes: readers.MessagesPage{ - Total: 1, - Messages: []readers.Message{msg}, - }, - repoErr: nil, - response: sdk.MessagesPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Messages: []senml.Message{msg}, - }, - err: nil, - }, - { - desc: "read messages successfully with subtopic", - token: validToken, - chanName: channelID + ".subtopic", - domainID: validID, - messagePageMeta: sdk.MessagePageMetadata{ - PageMetadata: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - Publisher: validID, - }, - repoRes: readers.MessagesPage{ - Total: 1, - Messages: []readers.Message{msg}, - }, - repoErr: nil, - response: sdk.MessagesPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Messages: []senml.Message{msg}, - }, - err: nil, - }, - { - desc: "read messages with invalid token", - token: invalidToken, - chanName: channelID, - domainID: validID, - messagePageMeta: sdk.MessagePageMetadata{ - PageMetadata: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - Subtopic: "subtopic", - Publisher: validID, - }, - authzErr: svcerr.ErrAuthorization, - repoRes: readers.MessagesPage{}, - response: sdk.MessagesPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusUnauthorized), - }, - { - desc: "read messages with empty token", - token: "", - chanName: channelID, - domainID: validID, - messagePageMeta: sdk.MessagePageMetadata{ - PageMetadata: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - Subtopic: "subtopic", - Publisher: validID, - }, - authnErr: svcerr.ErrAuthentication, - repoRes: readers.MessagesPage{}, - response: sdk.MessagesPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - }, - { - desc: "read messages with empty channel ID", - token: validToken, - chanName: "", - domainID: validID, - messagePageMeta: sdk.MessagePageMetadata{ - PageMetadata: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - Subtopic: "subtopic", - Publisher: validID, - }, - repoRes: readers.MessagesPage{}, - repoErr: nil, - response: sdk.MessagesPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "read messages with invalid message page metadata", - token: validToken, - chanName: channelID, - domainID: validID, - messagePageMeta: sdk.MessagePageMetadata{ - PageMetadata: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }, - Subtopic: "subtopic", - Publisher: validID, - }, - repoRes: readers.MessagesPage{}, - repoErr: nil, - response: sdk.MessagesPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "read messages with response that cannot be unmarshalled", - token: validToken, - chanName: channelID, - domainID: validID, - messagePageMeta: sdk.MessagePageMetadata{ - PageMetadata: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - }, - Subtopic: "subtopic", - Publisher: validID, - }, - repoRes: readers.MessagesPage{ - Total: 1, - Messages: []readers.Message{invalidMsg}, - }, - repoErr: nil, - response: sdk.MessagesPage{}, - err: errors.NewSDKError(errors.New("json: cannot unmarshal string into Go struct field MessagesPage.messages of type senml.Message")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authCall := authz.On("Authorize", mock.Anything, mock.Anything).Return(tc.authzErr) - authCall1 := authn.On("Authenticate", mock.Anything, tc.token).Return(mgauthn.Session{UserID: validID}, tc.authnErr) - repoCall := repo.On("ReadAll", channelID, mock.Anything).Return(tc.repoRes, tc.repoErr) - response, err := mgsdk.ReadMessages(tc.messagePageMeta, tc.chanName, tc.domainID, tc.token) - fmt.Println(err) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, response) - if tc.err == nil { - ok := repoCall.Parent.AssertCalled(t, "ReadAll", channelID, mock.Anything) - assert.True(t, ok) - } - authCall.Unset() - authCall1.Unset() - repoCall.Unset() - }) - } -} diff --git a/pkg/sdk/go/metadata.go b/pkg/sdk/go/metadata.go deleted file mode 100644 index b9341560c..000000000 --- a/pkg/sdk/go/metadata.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -type Metadata map[string]interface{} diff --git a/pkg/sdk/go/requests.go b/pkg/sdk/go/requests.go deleted file mode 100644 index 21e8f62a7..000000000 --- a/pkg/sdk/go/requests.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -// updateUserSecretReq is used to update the user secret. -type updateUserSecretReq struct { - OldSecret string `json:"old_secret,omitempty"` - NewSecret string `json:"new_secret,omitempty"` -} - -type resetPasswordRequestreq struct { - Email string `json:"email"` - Host string `json:"host"` -} - -type resetPasswordReq struct { - Token string `json:"token"` - Password string `json:"password"` - ConfPass string `json:"confirm_password"` -} - -type updateThingSecretReq struct { - Secret string `json:"secret,omitempty"` -} - -// updateUserEmailReq is used to update the user email. -type updateUserEmailReq struct { - token string - id string - Email string `json:"email,omitempty"` -} - -// UserPasswordReq contains old and new passwords. -type UserPasswordReq struct { - OldPassword string `json:"old_password,omitempty"` - Password string `json:"password,omitempty"` -} - -// Connection contains thing and channel ID that are connected. -type Connection struct { - ThingID string `json:"thing_id,omitempty"` - ChannelID string `json:"channel_id,omitempty"` -} - -type UsersRelationRequest struct { - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -type UserGroupsRequest struct { - UserGroupIDs []string `json:"group_ids"` -} - -type UpdateUsernameReq struct { - id string - Username string `json:"username"` -} diff --git a/pkg/sdk/go/responses.go b/pkg/sdk/go/responses.go deleted file mode 100644 index c51f0426f..000000000 --- a/pkg/sdk/go/responses.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "time" - - "github.com/absmach/magistrala/pkg/transformers/senml" -) - -type createThingsRes struct { - Things []Thing `json:"things"` -} - -type PageRes struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` -} - -// ThingsPage contains list of things in a page with proper metadata. -type ThingsPage struct { - Things []Thing `json:"things"` - PageRes -} - -// ChannelsPage contains list of channels in a page with proper metadata. -type ChannelsPage struct { - Channels []Channel `json:"channels"` - PageRes -} - -// MessagesPage contains list of messages in a page with proper metadata. -type MessagesPage struct { - Messages []senml.Message `json:"messages,omitempty"` - PageRes -} - -type GroupsPage struct { - Groups []Group `json:"groups"` - PageRes -} - -type UsersPage struct { - Users []User `json:"users"` - PageRes -} - -type MembersPage struct { - Members []User `json:"members"` - PageRes -} - -// MembershipsPage contains page related metadata as well as list of memberships that -// belong to this page. -type MembershipsPage struct { - PageRes - Memberships []Group `json:"memberships"` -} - -type revokeCertsRes struct { - RevocationTime time.Time `json:"revocation_time"` -} - -// bootstrapsPage contains list of bootstrap configs in a page with proper metadata. -type BootstrapPage struct { - Configs []BootstrapConfig `json:"configs"` - PageRes -} - -type CertSerials struct { - Certs []Cert `json:"certs"` - PageRes -} - -type SubscriptionPage struct { - Subscriptions []Subscription `json:"subscriptions"` - PageRes -} - -type DomainsPage struct { - Domains []Domain `json:"domains"` - PageRes -} diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go deleted file mode 100644 index ab56dfe52..000000000 --- a/pkg/sdk/go/sdk.go +++ /dev/null @@ -1,1453 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/absmach/magistrala/pkg/errors" - "moul.io/http2curl" -) - -const ( - // CTJSON represents JSON content type. - CTJSON ContentType = "application/json" - - // CTJSONSenML represents JSON SenML content type. - CTJSONSenML ContentType = "application/senml+json" - - // CTBinary represents binary content type. - CTBinary ContentType = "application/octet-stream" - - // EnabledStatus represents enable status for a client. - EnabledStatus = "enabled" - - // DisabledStatus represents disabled status for a client. - DisabledStatus = "disabled" - - BearerPrefix = "Bearer " - - ThingPrefix = "Thing " -) - -// ContentType represents all possible content types. -type ContentType string - -var _ SDK = (*mgSDK)(nil) - -var ( - // ErrFailedCreation indicates that entity creation failed. - ErrFailedCreation = errors.New("failed to create entity in the db") - - // ErrFailedList indicates that entities list failed. - ErrFailedList = errors.New("failed to list entities") - - // ErrFailedUpdate indicates that entity update failed. - ErrFailedUpdate = errors.New("failed to update entity") - - // ErrFailedFetch indicates that fetching of entity data failed. - ErrFailedFetch = errors.New("failed to fetch entity") - - // ErrFailedRemoval indicates that entity removal failed. - ErrFailedRemoval = errors.New("failed to remove entity") - - // ErrFailedEnable indicates that client enable failed. - ErrFailedEnable = errors.New("failed to enable client") - - // ErrFailedDisable indicates that client disable failed. - ErrFailedDisable = errors.New("failed to disable client") - - ErrInvalidJWT = errors.New("invalid JWT") -) - -type MessagePageMetadata struct { - PageMetadata - Subtopic string `json:"subtopic,omitempty"` - Publisher string `json:"publisher,omitempty"` - Comparator string `json:"comparator,omitempty"` - BoolValue *bool `json:"vb,omitempty"` - StringValue string `json:"vs,omitempty"` - DataValue string `json:"vd,omitempty"` - From float64 `json:"from,omitempty"` - To float64 `json:"to,omitempty"` - Aggregation string `json:"aggregation,omitempty"` - Interval string `json:"interval,omitempty"` - Value float64 `json:"value,omitempty"` - Protocol string `json:"protocol,omitempty"` -} - -type PageMetadata struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Order string `json:"order,omitempty"` - Direction string `json:"direction,omitempty"` - Level uint64 `json:"level,omitempty"` - Identity string `json:"identity,omitempty"` - Email string `json:"email,omitempty"` - Username string `json:"username,omitempty"` - LastName string `json:"last_name,omitempty"` - FirstName string `json:"first_name,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Status string `json:"status,omitempty"` - Action string `json:"action,omitempty"` - Subject string `json:"subject,omitempty"` - Object string `json:"object,omitempty"` - Permission string `json:"permission,omitempty"` - Tag string `json:"tag,omitempty"` - Owner string `json:"owner,omitempty"` - SharedBy string `json:"shared_by,omitempty"` - Visibility string `json:"visibility,omitempty"` - OwnerID string `json:"owner_id,omitempty"` - Topic string `json:"topic,omitempty"` - Contact string `json:"contact,omitempty"` - State string `json:"state,omitempty"` - ListPermissions string `json:"list_perms,omitempty"` - InvitedBy string `json:"invited_by,omitempty"` - UserID string `json:"user_id,omitempty"` - DomainID string `json:"domain_id,omitempty"` - Relation string `json:"relation,omitempty"` - Operation string `json:"operation,omitempty"` - From int64 `json:"from,omitempty"` - To int64 `json:"to,omitempty"` - WithMetadata bool `json:"with_metadata,omitempty"` - WithAttributes bool `json:"with_attributes,omitempty"` - ID string `json:"id,omitempty"` -} - -// Credentials represent client credentials: it contains -// "username" which can be a username, generated name; -// and "secret" which can be a password or access token. -type Credentials struct { - Username string `json:"username,omitempty"` // username or generated login ID - Secret string `json:"secret,omitempty"` // password or token -} - -// SDK contains Magistrala API. -// -//go:generate mockery --name SDK --output=../mocks --filename sdk.go --quiet --note "Copyright (c) Abstract Machines" -type SDK interface { - // CreateUser registers magistrala user. - // - // example: - // user := sdk.User{ - // Name: "John Doe", - // Email: "john.doe@example", - // Credentials: sdk.Credentials{ - // Username: "john.doe", - // Secret: "12345678", - // }, - // } - // user, _ := sdk.CreateUser(user) - // fmt.Println(user) - CreateUser(user User, token string) (User, errors.SDKError) - - // User returns user object by id. - // - // example: - // user, _ := sdk.User("userID", "token") - // fmt.Println(user) - User(id, token string) (User, errors.SDKError) - - // Users returns list of users. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "John Doe", - // } - // users, _ := sdk.Users(pm, "token") - // fmt.Println(users) - Users(pm PageMetadata, token string) (UsersPage, errors.SDKError) - - // Members returns list of users that are members of a group. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // DomainID: "domainID" - // } - // members, _ := sdk.Members("groupID", pm, "token") - // fmt.Println(members) - Members(groupID string, meta PageMetadata, token string) (UsersPage, errors.SDKError) - - // UserProfile returns user logged in. - // - // example: - // user, _ := sdk.UserProfile("token") - // fmt.Println(user) - UserProfile(token string) (User, errors.SDKError) - - // UpdateUser updates existing user. - // - // example: - // user := sdk.User{ - // ID: "userID", - // Name: "John Doe", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // } - // user, _ := sdk.UpdateUser(user, "token") - // fmt.Println(user) - UpdateUser(user User, token string) (User, errors.SDKError) - - // UpdateUserEmail updates the user's email - // - // example: - // user := sdk.User{ - // ID: "userID", - // Credentials: sdk.Credentials{ - // Email: "john.doe@example", - // }, - // } - // user, _ := sdk.UpdateUserEmail(user, "token") - // fmt.Println(user) - UpdateUserEmail(user User, token string) (User, errors.SDKError) - - // UpdateUserTags updates the user's tags. - // - // example: - // user := sdk.User{ - // ID: "userID", - // Tags: []string{"tag1", "tag2"}, - // } - // user, _ := sdk.UpdateUserTags(user, "token") - // fmt.Println(user) - UpdateUserTags(user User, token string) (User, errors.SDKError) - - // UpdateUsername updates the user's Username. - // - // example: - // user := sdk.User{ - // ID: "userID", - // Credentials: sdk.Credentials{ - // Username: "john.doe", - // }, - // } - // user, _ := sdk.UpdateUsername(user, "token") - // fmt.Println(user) - UpdateUsername(user User, token string) (User, errors.SDKError) - - // UpdateProfilePicture updates the user's profile picture. - // - // example: - // user := sdk.User{ - // ID: "userID", - // ProfilePicture: "https://cloudstorage.example.com/bucket-name/user-images/profile-picture.jpg", - // } - // user, _ := sdk.UpdateProfilePicture(user, "token") - // fmt.Println(user) - UpdateProfilePicture(user User, token string) (User, errors.SDKError) - - // UpdateUserRole updates the user's role. - // - // example: - // user := sdk.User{ - // ID: "userID", - // Role: "role", - // } - // user, _ := sdk.UpdateUserRole(user, "token") - // fmt.Println(user) - UpdateUserRole(user User, token string) (User, errors.SDKError) - - // ResetPasswordRequest sends a password request email to a user. - // - // example: - // err := sdk.ResetPasswordRequest("example@email.com") - // fmt.Println(err) - ResetPasswordRequest(email string) errors.SDKError - - // ResetPassword changes a user's password to the one passed in the argument. - // - // example: - // err := sdk.ResetPassword("password","password","token") - // fmt.Println(err) - ResetPassword(password, confPass, token string) errors.SDKError - - // UpdatePassword updates user password. - // - // example: - // user, _ := sdk.UpdatePassword("oldPass", "newPass", "token") - // fmt.Println(user) - UpdatePassword(oldPass, newPass, token string) (User, errors.SDKError) - - // EnableUser changes the status of the user to enabled. - // - // example: - // user, _ := sdk.EnableUser("userID", "token") - // fmt.Println(user) - EnableUser(id, token string) (User, errors.SDKError) - - // DisableUser changes the status of the user to disabled. - // - // example: - // user, _ := sdk.DisableUser("userID", "token") - // fmt.Println(user) - DisableUser(id, token string) (User, errors.SDKError) - - // DeleteUser deletes a user with the given id. - // - // example: - // err := sdk.DeleteUser("userID", "token") - // fmt.Println(err) - DeleteUser(id, token string) errors.SDKError - - // CreateToken receives credentials and returns user token. - // - // example: - // lt := sdk.Login{ - // Identity: "email"/"username", - // Secret: "12345678", - // } - // token, _ := sdk.CreateToken(lt) - // fmt.Println(token) - CreateToken(lt Login) (Token, errors.SDKError) - - // RefreshToken receives credentials and returns user token. - // - // example: - // token, _ := sdk.RefreshToken("refresh_token") - // fmt.Println(token) - RefreshToken(token string) (Token, errors.SDKError) - - // ListUserChannels list all channels belongs a particular user id. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "viewer", "guest", "editor", "contributor", "create" - // } - // channels, _ := sdk.ListUserChannels("user_id_1", pm, "token") - // fmt.Println(channels) - ListUserChannels(userID string, pm PageMetadata, token string) (ChannelsPage, errors.SDKError) - - // ListUserGroups list all groups belongs a particular user id. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "contributor", "editor", "viewer", "guest", "create" - // } - // groups, _ := sdk.ListUserGroups("user_id_1", pm, "token") - // fmt.Println(channels) - ListUserGroups(userID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) - - // ListUserThings list all things belongs a particular user id. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // 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) - - // SeachUsers filters users and returns a page result. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "John Doe", - // } - // users, _ := sdk.SearchUsers(pm, "token") - // fmt.Println(users) - SearchUsers(pm PageMetadata, token string) (UsersPage, errors.SDKError) - - // CreateThing registers new thing and returns its id. - // - // example: - // thing := sdk.Thing{ - // Name: "My Thing", - // 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) - - // CreateThings registers new things and returns their ids. - // - // example: - // things := []sdk.Thing{ - // { - // Name: "My Thing 1", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // }, - // { - // Name: "My Thing 2", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // }, - // } - // things, _ := sdk.CreateThings(things, "domainID", "token") - // fmt.Println(things) - CreateThings(things []Thing, domainID, token string) ([]Thing, errors.SDKError) - - // Filters things and returns a page result. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Thing", - // } - // things, _ := sdk.Things(pm, "domainID", "token") - // fmt.Println(things) - Things(pm PageMetadata, domainID, token string) (ThingsPage, errors.SDKError) - - // ThingsByChannel returns page of things that are connected to specified channel. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Thing", - // } - // things, _ := sdk.ThingsByChannel("channelID", pm, "domainID", "token") - // fmt.Println(things) - ThingsByChannel(chanID string, pm PageMetadata, domainID, token string) (ThingsPage, errors.SDKError) - - // Thing returns thing object by id. - // - // example: - // thing, _ := sdk.Thing("thingID", "domainID", "token") - // fmt.Println(thing) - Thing(id, domainID, token string) (Thing, errors.SDKError) - - // ThingPermissions returns user permissions on the thing id. - // - // example: - // thing, _ := sdk.Thing("thingID", "domainID", "token") - // fmt.Println(thing) - ThingPermissions(id, domainID, token string) (Thing, errors.SDKError) - - // UpdateThing updates existing thing. - // - // example: - // thing := sdk.Thing{ - // ID: "thingID", - // Name: "My Thing", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // } - // thing, _ := sdk.UpdateThing(thing, "domainID", "token") - // fmt.Println(thing) - UpdateThing(thing Thing, domainID, token string) (Thing, errors.SDKError) - - // UpdateThingTags updates the client's tags. - // - // example: - // thing := sdk.Thing{ - // ID: "thingID", - // Tags: []string{"tag1", "tag2"}, - // } - // thing, _ := sdk.UpdateThingTags(thing, "domainID", "token") - // fmt.Println(thing) - UpdateThingTags(thing Thing, domainID, token string) (Thing, errors.SDKError) - - // UpdateThingSecret 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) - - // EnableThing changes client status to enabled. - // - // example: - // thing, _ := sdk.EnableThing("thingID", "domainID", "token") - // fmt.Println(thing) - EnableThing(id, domainID, token string) (Thing, errors.SDKError) - - // DisableThing 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) - - // ShareThing shares thing 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") - // fmt.Println(err) - ShareThing(thingID string, req UsersRelationRequest, domainID, token string) errors.SDKError - - // UnshareThing unshare a thing 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") - // fmt.Println(err) - UnshareThing(thingID string, req UsersRelationRequest, domainID, token string) errors.SDKError - - // ListThingUsers all users in a thing. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // 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") - // fmt.Println(users) - ListThingUsers(thingID string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) - - // DeleteThing deletes a thing with the given id. - // - // example: - // err := sdk.DeleteThing("thingID", "domainID", "token") - // fmt.Println(err) - DeleteThing(id, domainID, token string) errors.SDKError - - // CreateGroup creates new group and returns its id. - // - // example: - // group := sdk.Group{ - // Name: "My Group", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // } - // group, _ := sdk.CreateGroup(group, "domainID", "token") - // fmt.Println(group) - CreateGroup(group Group, domainID, token string) (Group, errors.SDKError) - - // Groups returns page of groups. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Group", - // } - // groups, _ := sdk.Groups(pm, "domainID", "token") - // fmt.Println(groups) - Groups(pm PageMetadata, domainID, token string) (GroupsPage, errors.SDKError) - - // Parents returns page of users groups. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Group", - // } - // groups, _ := sdk.Parents("groupID", pm, "domainID", "token") - // fmt.Println(groups) - Parents(id string, pm PageMetadata, domainID, token string) (GroupsPage, errors.SDKError) - - // Children returns page of users groups. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Group", - // } - // groups, _ := sdk.Children("groupID", pm, "domainID", "token") - // fmt.Println(groups) - Children(id string, pm PageMetadata, domainID, token string) (GroupsPage, errors.SDKError) - - // Group returns users group object by id. - // - // example: - // group, _ := sdk.Group("groupID", "domainID", "token") - // fmt.Println(group) - Group(id, domainID, token string) (Group, errors.SDKError) - - // GroupPermissions returns user permissions by group ID. - // - // example: - // group, _ := sdk.Group("groupID", "domainID" "token") - // fmt.Println(group) - GroupPermissions(id, domainID, token string) (Group, errors.SDKError) - - // UpdateGroup updates existing group. - // - // example: - // group := sdk.Group{ - // ID: "groupID", - // Name: "My Group", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // } - // group, _ := sdk.UpdateGroup(group, "domainID", "token") - // fmt.Println(group) - UpdateGroup(group Group, domainID, token string) (Group, errors.SDKError) - - // EnableGroup changes group status to enabled. - // - // example: - // group, _ := sdk.EnableGroup("groupID", "domainID", "token") - // fmt.Println(group) - EnableGroup(id, domainID, token string) (Group, errors.SDKError) - - // DisableGroup changes group status to disabled - soft delete. - // - // example: - // group, _ := sdk.DisableGroup("groupID", "domainID", "token") - // fmt.Println(group) - DisableGroup(id, domainID, token string) (Group, errors.SDKError) - - // AddUserToGroup add user to a group. - // - // 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.AddUserToGroup("groupID",req, "domainID", "token") - // fmt.Println(err) - AddUserToGroup(groupID string, req UsersRelationRequest, domainID, token string) errors.SDKError - - // RemoveUserFromGroup remove user from a group. - // - // 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.RemoveUserFromGroup("groupID",req, "domainID", "token") - // fmt.Println(err) - RemoveUserFromGroup(groupID string, req UsersRelationRequest, domainID, token string) errors.SDKError - - // ListGroupUsers list all users in the group id . - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "contributor", "editor", "viewer", "guest", "create" - // } - // groups, _ := sdk.ListGroupUsers("groupID", pm, "domainID", "token") - // fmt.Println(groups) - ListGroupUsers(groupID string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) - - // ListGroupChannels list all channels in the group id . - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "contributor", "editor", "viewer", "guest", "create" - // } - // groups, _ := sdk.ListGroupChannels("groupID", pm, "domainID", "token") - // fmt.Println(groups) - ListGroupChannels(groupID string, pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) - - // DeleteGroup delete given group id. - // - // example: - // err := sdk.DeleteGroup("groupID", "domainID", "token") - // fmt.Println(err) - DeleteGroup(id, domainID, token string) errors.SDKError - - // CreateChannel creates new channel and returns its id. - // - // example: - // channel := sdk.Channel{ - // Name: "My Channel", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // } - // channel, _ := sdk.CreateChannel(channel, "domainID", "token") - // fmt.Println(channel) - CreateChannel(channel Channel, domainID, token string) (Channel, errors.SDKError) - - // Channels returns page of channels. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Channel", - // } - // channels, _ := sdk.Channels(pm, "domainID", "token") - // fmt.Println(channels) - Channels(pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) - - // ChannelsByThing returns page of channels that are connected to specified thing. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Channel", - // } - // channels, _ := sdk.ChannelsByThing("thingID", pm, "domainID" "token") - // fmt.Println(channels) - ChannelsByThing(thingID string, pm PageMetadata, domainID, token string) (ChannelsPage, errors.SDKError) - - // Channel returns channel data by id. - // - // example: - // channel, _ := sdk.Channel("channelID", "domainID", "token") - // fmt.Println(channel) - Channel(id, domainID, token string) (Channel, errors.SDKError) - - // ChannelPermissions returns user permissions on the channel ID. - // - // example: - // channel, _ := sdk.Channel("channelID", "domainID", "token") - // fmt.Println(channel) - ChannelPermissions(id, domainID, token string) (Channel, errors.SDKError) - - // UpdateChannel updates existing channel. - // - // example: - // channel := sdk.Channel{ - // ID: "channelID", - // Name: "My Channel", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // } - // channel, _ := sdk.UpdateChannel(channel, "domainID", "token") - // fmt.Println(channel) - UpdateChannel(channel Channel, domainID, token string) (Channel, errors.SDKError) - - // EnableChannel changes channel status to enabled. - // - // example: - // channel, _ := sdk.EnableChannel("channelID", "domainID", "token") - // fmt.Println(channel) - EnableChannel(id, domainID, token string) (Channel, errors.SDKError) - - // DisableChannel changes channel status to disabled - soft delete. - // - // example: - // channel, _ := sdk.DisableChannel("channelID", "domainID", "token") - // fmt.Println(channel) - DisableChannel(id, domainID, token string) (Channel, errors.SDKError) - - // AddUserToChannel add user to a channel. - // - // 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.AddUserToChannel("channel_id", req, "domainID", "token") - // fmt.Println(err) - AddUserToChannel(channelID string, req UsersRelationRequest, domainID, token string) errors.SDKError - - // RemoveUserFromChannel remove user from a group. - // - // 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.RemoveUserFromChannel("channel_id", req, "domainID", "token") - // fmt.Println(err) - RemoveUserFromChannel(channelID string, req UsersRelationRequest, domainID, token string) errors.SDKError - - // ListChannelUsers list all users in a channel . - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission: "edit", // available Options: "administrator", "administrator", "delete", edit", "view", "share", "owner", "owner", "admin", "editor", "contributor", "editor", "viewer", "guest", "create" - // } - // users, _ := sdk.ListChannelUsers("channel_id", pm, "domainID", "token") - // fmt.Println(users) - ListChannelUsers(channelID string, pm PageMetadata, domainID, token string) (UsersPage, errors.SDKError) - - // AddUserGroupToChannel add user group to a channel. - // - // example: - // req := sdk.UserGroupsRequest{ - // GroupsIDs: ["group_id_1", "group_id_2", "group_id_3"] - // } - // err := sdk.AddUserGroupToChannel("channel_id",req, "domainID", "token") - // fmt.Println(err) - AddUserGroupToChannel(channelID string, req UserGroupsRequest, domainID, token string) errors.SDKError - - // RemoveUserGroupFromChannel remove user group from a channel. - // - // example: - // req := sdk.UserGroupsRequest{ - // GroupsIDs: ["group_id_1", "group_id_2", "group_id_3"] - // } - // err := sdk.RemoveUserGroupFromChannel("channel_id",req, "domainID", "token") - // fmt.Println(err) - RemoveUserGroupFromChannel(channelID string, req UserGroupsRequest, domainID, token string) errors.SDKError - - // ListChannelUserGroups list all user groups in a channel. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission: "view", - // } - // groups, _ := sdk.ListChannelUserGroups("channel_id_1", pm, "domainID", "token") - // fmt.Println(groups) - ListChannelUserGroups(channelID string, pm PageMetadata, domainID, token string) (GroupsPage, errors.SDKError) - - // DeleteChannel delete given group id. - // - // example: - // err := sdk.DeleteChannel("channelID", "domainID", "token") - // fmt.Println(err) - DeleteChannel(id, domainID, token string) errors.SDKError - - // Connect bulk connects things to channels specified by id. - // - // example: - // conns := sdk.Connection{ - // ChannelID: "channel_id_1", - // ThingID: "thing_id_1", - // } - // err := sdk.Connect(conns, "domainID", "token") - // fmt.Println(err) - Connect(conns Connection, domainID, token string) errors.SDKError - - // Disconnect - // - // example: - // conns := sdk.Connection{ - // ChannelID: "channel_id_1", - // ThingID: "thing_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. - // - // The `ConnectThing` method calls the `CreateThingPolicy` method under the hood. - // - // example: - // err := sdk.ConnectThing("thingID", "channelID", "token") - // fmt.Println(err) - ConnectThing(thingID, chanID, domainID, token string) errors.SDKError - - // DisconnectThing disconnect thing from specified channel by id. - // - // The `DisconnectThing` method calls the `DeleteThingPolicy` method under the hood. - // - // example: - // err := sdk.DisconnectThing("thingID", "channelID", "token") - // fmt.Println(err) - DisconnectThing(thingID, 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") - // fmt.Println(err) - SendMessage(chanID, msg, key string) errors.SDKError - - // ReadMessages read messages of specified channel. - // - // example: - // pm := sdk.MessagePageMetadata{ - // Offset: 0, - // Limit: 10, - // } - // msgs, _ := sdk.ReadMessages(pm,"channelID", "domainID", "token") - // fmt.Println(msgs) - ReadMessages(pm MessagePageMetadata, chanID, domainID, token string) (MessagesPage, errors.SDKError) - - // SetContentType sets message content type. - // - // example: - // err := sdk.SetContentType("application/json") - // fmt.Println(err) - SetContentType(ct ContentType) errors.SDKError - - // Health returns service health check. - // - // example: - // health, _ := sdk.Health("service") - // fmt.Println(health) - Health(service string) (HealthInfo, errors.SDKError) - - // AddBootstrap add bootstrap configuration - // - // example: - // cfg := sdk.BootstrapConfig{ - // ThingID: "thingID", - // Name: "bootstrap", - // ExternalID: "externalID", - // ExternalKey: "externalKey", - // Channels: []string{"channel1", "channel2"}, - // } - // id, _ := sdk.AddBootstrap(cfg, "domainID", "token") - // 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. - // - // example: - // bootstrap, _ := sdk.ViewBootstrap("id", "domainID", "token") - // fmt.Println(bootstrap) - ViewBootstrap(id, domainID, token string) (BootstrapConfig, errors.SDKError) - - // Update updates editable fields of the provided Config. - // - // example: - // cfg := sdk.BootstrapConfig{ - // ThingID: "thingID", - // Name: "bootstrap", - // ExternalID: "externalID", - // ExternalKey: "externalKey", - // Channels: []string{"channel1", "channel2"}, - // } - // err := sdk.UpdateBootstrap(cfg, "domainID", "token") - // fmt.Println(err) - UpdateBootstrap(cfg BootstrapConfig, domainID, token string) errors.SDKError - - // Update bootstrap config certificates. - // - // example: - // err := sdk.UpdateBootstrapCerts("id", "clientCert", "clientKey", "ca", "domainID", "token") - // 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. - // - // example: - // err := sdk.UpdateBootstrapConnection("id", []string{"channel1", "channel2"}, "domainID", "token") - // fmt.Println(err) - UpdateBootstrapConnection(id string, channels []string, domainID, token string) errors.SDKError - - // Remove removes Config with specified token that belongs to the user identified by the given token. - // - // example: - // err := sdk.RemoveBootstrap("id", "domainID", "token") - // fmt.Println(err) - RemoveBootstrap(id, domainID, token string) errors.SDKError - - // Bootstrap returns Config to the Thing with provided external ID using external key. - // - // example: - // bootstrap, _ := sdk.Bootstrap("externalID", "externalKey") - // fmt.Println(bootstrap) - Bootstrap(externalID, externalKey string) (BootstrapConfig, errors.SDKError) - - // BootstrapSecure retrieves a configuration with given external ID and encrypted external key. - // - // example: - // bootstrap, _ := sdk.BootstrapSecure("externalID", "externalKey", "cryptoKey") - // fmt.Println(bootstrap) - BootstrapSecure(externalID, externalKey, cryptoKey string) (BootstrapConfig, errors.SDKError) - - // Bootstraps retrieves a list of managed configs. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // } - // bootstraps, _ := sdk.Bootstraps(pm, "domainID", "token") - // 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. - // - // example: - // err := sdk.Whitelist("thingID", 1, "domainID", "token") - // fmt.Println(err) - Whitelist(thingID string, state int, domainID, token string) errors.SDKError - - // IssueCert issues a certificate for a thing required for mTLS. - // - // example: - // cert, _ := sdk.IssueCert("thingID", "24h", "domainID", "token") - // fmt.Println(cert) - IssueCert(thingID, validity, domainID, token string) (Cert, errors.SDKError) - - // ViewCert returns a certificate given certificate ID - // - // example: - // cert, _ := sdk.ViewCert("certID", "domainID", "token") - // fmt.Println(cert) - ViewCert(certID, domainID, token string) (Cert, errors.SDKError) - - // ViewCertByThing retrieves a list of certificates' serial IDs for a given thing ID. - // - // example: - // cserial, _ := sdk.ViewCertByThing("thingID", "domainID", "token") - // fmt.Println(cserial) - ViewCertByThing(thingID, domainID, token string) (CertSerials, errors.SDKError) - - // RevokeCert revokes certificate for thing with thingID - // - // example: - // tm, _ := sdk.RevokeCert("thingID", "domainID", "token") - // fmt.Println(tm) - RevokeCert(thingID, domainID, token string) (time.Time, errors.SDKError) - - // CreateSubscription creates a new subscription - // - // example: - // subscription, _ := sdk.CreateSubscription("topic", "contact", "token") - // fmt.Println(subscription) - CreateSubscription(topic, contact, token string) (string, errors.SDKError) - - // ListSubscriptions list subscriptions given list parameters. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // } - // subscriptions, _ := sdk.ListSubscriptions(pm, "token") - // fmt.Println(subscriptions) - ListSubscriptions(pm PageMetadata, token string) (SubscriptionPage, errors.SDKError) - - // ViewSubscription retrieves a subscription with the provided id. - // - // example: - // subscription, _ := sdk.ViewSubscription("id", "token") - // fmt.Println(subscription) - ViewSubscription(id, token string) (Subscription, errors.SDKError) - - // DeleteSubscription removes a subscription with the provided id. - // - // example: - // err := sdk.DeleteSubscription("id", "token") - // fmt.Println(err) - DeleteSubscription(id, token string) errors.SDKError - - // CreateDomain creates new domain and returns its details. - // - // example: - // domain := sdk.Domain{ - // Name: "My Domain", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // } - // domain, _ := sdk.CreateDomain(group, "token") - // fmt.Println(domain) - CreateDomain(d Domain, token string) (Domain, errors.SDKError) - - // Domain retrieve domain information of given domain ID . - // - // example: - // domain, _ := sdk.Domain("domainID", "token") - // fmt.Println(domain) - Domain(domainID, token string) (Domain, errors.SDKError) - - // DomainPermissions retrieve user permissions on the given domain ID . - // - // example: - // permissions, _ := sdk.DomainPermissions("domainID", "token") - // fmt.Println(permissions) - DomainPermissions(domainID, token string) (Domain, errors.SDKError) - - // UpdateDomain updates details of the given domain ID. - // - // example: - // domain := sdk.Domain{ - // ID : "domainID" - // Name: "New Domain Name", - // Metadata: sdk.Metadata{ - // "key": "value", - // }, - // } - // domain, _ := sdk.UpdateDomain(domain, "token") - // fmt.Println(domain) - UpdateDomain(d Domain, token string) (Domain, errors.SDKError) - - // Domains returns list of domain for the given filters. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Name: "My Domain", - // Permission : "view" - // } - // domains, _ := sdk.Domains(pm, "token") - // fmt.Println(domains) - Domains(pm PageMetadata, token string) (DomainsPage, errors.SDKError) - - // ListDomainUsers returns list of users for the given domain ID and filters. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission : "view" - // } - // users, _ := sdk.ListDomainUsers("domainID", pm, "token") - // fmt.Println(users) - ListDomainUsers(domainID string, pm PageMetadata, token string) (UsersPage, errors.SDKError) - - // ListUserDomains returns list of domains for the given user ID and filters. - // - // example: - // pm := sdk.PageMetadata{ - // Offset: 0, - // Limit: 10, - // Permission : "view" - // } - // domains, _ := sdk.ListUserDomains("userID", pm, "token") - // fmt.Println(domains) - ListUserDomains(userID string, pm PageMetadata, token string) (DomainsPage, errors.SDKError) - - // EnableDomain changes the status of the domain to enabled. - // - // example: - // err := sdk.EnableDomain("domainID", "token") - // fmt.Println(err) - EnableDomain(domainID, token string) errors.SDKError - - // DisableDomain changes the status of the domain to disabled. - // - // example: - // err := sdk.DisableDomain("domainID", "token") - // fmt.Println(err) - DisableDomain(domainID, token string) errors.SDKError - - // AddUserToDomain adds a user to a domain. - // - // example: - // req := sdk.UsersRelationRequest{ - // Relation: "contributor", // available options: "owner", "admin", "editor", "contributor", "member", "guest" - // UserIDs: ["user_id_1", "user_id_2", "user_id_3"] - // } - // err := sdk.AddUserToDomain("domainID", req, "token") - // fmt.Println(err) - AddUserToDomain(domainID string, req UsersRelationRequest, token string) errors.SDKError - - // RemoveUserFromDomain removes a user from a domain. - // - // example: - // err := sdk.RemoveUserFromDomain("domainID", "userID", "token") - // fmt.Println(err) - RemoveUserFromDomain(domainID, userID, token string) errors.SDKError - - // SendInvitation sends an invitation to the email address associated with the given user. - // - // For example: - // invitation := sdk.Invitation{ - // DomainID: "domainID", - // UserID: "userID", - // Relation: "contributor", // available options: "owner", "admin", "editor", "contributor", "guest" - // } - // err := sdk.SendInvitation(invitation, "token") - // fmt.Println(err) - SendInvitation(invitation Invitation, token string) (err error) - - // Invitation returns an invitation. - // - // For example: - // invitation, _ := sdk.Invitation("userID", "domainID", "token") - // fmt.Println(invitation) - Invitation(userID, domainID, token string) (invitation Invitation, err error) - - // Invitations returns a list of invitations. - // - // For example: - // invitations, _ := sdk.Invitations(PageMetadata{Offset: 0, Limit: 10}, "token") - // fmt.Println(invitations) - Invitations(pm PageMetadata, token string) (invitations InvitationPage, err error) - - // AcceptInvitation accepts an invitation by adding the user to the domain that they were invited to. - // - // For example: - // err := sdk.AcceptInvitation("domainID", "token") - // fmt.Println(err) - AcceptInvitation(domainID, token string) (err error) - - // RejectInvitation rejects an invitation. - // - // For example: - // err := sdk.RejectInvitation("domainID", "token") - // fmt.Println(err) - RejectInvitation(domainID, token string) (err error) - - // DeleteInvitation deletes an invitation. - // - // For example: - // err := sdk.DeleteInvitation("userID", "domainID", "token") - // fmt.Println(err) - DeleteInvitation(userID, domainID, token string) (err error) - - // Journal returns a list of journal logs. - // - // For example: - // journals, _ := sdk.Journal("thing", "thingID","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) -} - -type mgSDK struct { - bootstrapURL string - certsURL string - httpAdapterURL string - readerURL string - thingsURL string - usersURL string - domainsURL string - invitationsURL string - journalURL string - HostURL string - - msgContentType ContentType - client *http.Client - curlFlag bool -} - -// Config contains sdk configuration parameters. -type Config struct { - BootstrapURL string - CertsURL string - HTTPAdapterURL string - ReaderURL string - ThingsURL string - UsersURL string - DomainsURL string - InvitationsURL string - JournalURL string - HostURL string - - MsgContentType ContentType - TLSVerification bool - CurlFlag bool -} - -// NewSDK returns new magistrala SDK instance. -func NewSDK(conf Config) SDK { - return &mgSDK{ - bootstrapURL: conf.BootstrapURL, - certsURL: conf.CertsURL, - httpAdapterURL: conf.HTTPAdapterURL, - readerURL: conf.ReaderURL, - thingsURL: conf.ThingsURL, - usersURL: conf.UsersURL, - domainsURL: conf.DomainsURL, - invitationsURL: conf.InvitationsURL, - journalURL: conf.JournalURL, - HostURL: conf.HostURL, - - msgContentType: conf.MsgContentType, - client: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: !conf.TLSVerification, - }, - }, - }, - curlFlag: conf.CurlFlag, - } -} - -// processRequest creates and send a new HTTP request, and checks for errors in the HTTP response. -// It then returns the response headers, the response body, and the associated error(s) (if any). -func (sdk mgSDK) processRequest(method, reqUrl, token string, data []byte, headers map[string]string, expectedRespCodes ...int) (http.Header, []byte, errors.SDKError) { - req, err := http.NewRequest(method, reqUrl, bytes.NewReader(data)) - if err != nil { - return make(http.Header), []byte{}, errors.NewSDKError(err) - } - - // Sets a default value for the Content-Type. - // Overridden if Content-Type is passed in the headers arguments. - req.Header.Add("Content-Type", string(CTJSON)) - - for key, value := range headers { - req.Header.Add(key, value) - } - - if token != "" { - if !strings.Contains(token, ThingPrefix) { - token = BearerPrefix + token - } - req.Header.Set("Authorization", token) - } - - if sdk.curlFlag { - curlCommand, err := http2curl.GetCurlCommand(req) - if err != nil { - return nil, nil, errors.NewSDKError(err) - } - log.Println(curlCommand.String()) - } - - resp, err := sdk.client.Do(req) - if err != nil { - return make(http.Header), []byte{}, errors.NewSDKError(err) - } - defer resp.Body.Close() - - sdkerr := errors.CheckError(resp, expectedRespCodes...) - if sdkerr != nil { - return make(http.Header), []byte{}, sdkerr - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return make(http.Header), []byte{}, errors.NewSDKError(err) - } - - return resp.Header, body, nil -} - -func (sdk mgSDK) withQueryParams(baseURL, endpoint string, pm PageMetadata) (string, error) { - q, err := pm.query() - if err != nil { - return "", err - } - - return fmt.Sprintf("%s/%s?%s", baseURL, endpoint, q), nil -} - -func (pm PageMetadata) query() (string, error) { - q := url.Values{} - if pm.Offset != 0 { - q.Add("offset", strconv.FormatUint(pm.Offset, 10)) - } - if pm.Limit != 0 { - q.Add("limit", strconv.FormatUint(pm.Limit, 10)) - } - if pm.Total != 0 { - q.Add("total", strconv.FormatUint(pm.Total, 10)) - } - if pm.Order != "" { - q.Add("order", pm.Order) - } - if pm.Direction != "" { - q.Add("dir", pm.Direction) - } - if pm.Level != 0 { - q.Add("level", strconv.FormatUint(pm.Level, 10)) - } - if pm.Email != "" { - q.Add("email", pm.Email) - } - if pm.Identity != "" { - q.Add("identity", pm.Identity) - } - if pm.Username != "" { - q.Add("username", pm.Username) - } - if pm.FirstName != "" { - q.Add("first_name", pm.FirstName) - } - if pm.LastName != "" { - q.Add("last_name", pm.LastName) - } - if pm.Name != "" { - q.Add("name", pm.Name) - } - if pm.ID != "" { - q.Add("id", pm.ID) - } - if pm.Type != "" { - q.Add("type", pm.Type) - } - if pm.Visibility != "" { - q.Add("visibility", pm.Visibility) - } - if pm.Status != "" { - q.Add("status", pm.Status) - } - if pm.Metadata != nil { - md, err := json.Marshal(pm.Metadata) - if err != nil { - return "", errors.NewSDKError(err) - } - q.Add("metadata", string(md)) - } - if pm.Action != "" { - q.Add("action", pm.Action) - } - if pm.Subject != "" { - q.Add("subject", pm.Subject) - } - if pm.Object != "" { - q.Add("object", pm.Object) - } - if pm.Tag != "" { - q.Add("tag", pm.Tag) - } - if pm.Owner != "" { - q.Add("owner", pm.Owner) - } - if pm.SharedBy != "" { - q.Add("shared_by", pm.SharedBy) - } - if pm.Topic != "" { - q.Add("topic", pm.Topic) - } - if pm.Contact != "" { - q.Add("contact", pm.Contact) - } - if pm.State != "" { - q.Add("state", pm.State) - } - if pm.Permission != "" { - q.Add("permission", pm.Permission) - } - if pm.ListPermissions != "" { - q.Add("list_perms", pm.ListPermissions) - } - if pm.InvitedBy != "" { - q.Add("invited_by", pm.InvitedBy) - } - if pm.UserID != "" { - q.Add("user_id", pm.UserID) - } - if pm.DomainID != "" { - q.Add("domain_id", pm.DomainID) - } - if pm.Relation != "" { - q.Add("relation", pm.Relation) - } - if pm.Operation != "" { - q.Add("operation", pm.Operation) - } - if pm.From != 0 { - q.Add("from", strconv.FormatInt(pm.From, 10)) - } - if pm.To != 0 { - q.Add("to", strconv.FormatInt(pm.To, 10)) - } - q.Add("with_attributes", strconv.FormatBool(pm.WithAttributes)) - q.Add("with_metadata", strconv.FormatBool(pm.WithMetadata)) - - return q.Encode(), nil -} diff --git a/pkg/sdk/go/setup_test.go b/pkg/sdk/go/setup_test.go deleted file mode 100644 index be8b586cf..000000000 --- a/pkg/sdk/go/setup_test.go +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "os" - "regexp" - "testing" - "time" - - "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" -) - -const ( - invalidIdentity = "invalididentity" - Identity = "identity" - Email = "email" - InvalidEmail = "invalidemail" - secret = "strongsecret" - invalidToken = "invalid" - contentType = "application/senml+json" - invalid = "invalid" - wrongID = "wrongID" -) - -var ( - idProvider = uuid.New() - validMetadata = sdk.Metadata{"role": "client"} - user = generateTestUser(&testing.T{}) - description = "shortdescription" - gName = "groupname" - validToken = "valid" - limit uint64 = 5 - offset uint64 = 0 - total uint64 = 200 - passRegex = regexp.MustCompile("^.{8,}$") - validID = testsutil.GenerateUUID(&testing.T{}) -) - -func generateUUID(t *testing.T) string { - ulid, err := idProvider.ID() - assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - return ulid -} - -func convertUsers(cs []sdk.User) []users.User { - ccs := []users.User{} - - for _, c := range cs { - ccs = append(ccs, convertUser(c)) - } - - return ccs -} - -func convertThings(cs ...sdk.Thing) []things.Client { - ccs := []things.Client{} - - for _, c := range cs { - ccs = append(ccs, convertThing(c)) - } - - return ccs -} - -func convertGroups(cs []sdk.Group) []mggroups.Group { - cgs := []mggroups.Group{} - - for _, c := range cs { - cgs = append(cgs, convertGroup(c)) - } - - return cgs -} - -func convertChannels(cs []sdk.Channel) []mggroups.Group { - cgs := []mggroups.Group{} - - for _, c := range cs { - cgs = append(cgs, convertChannel(c)) - } - - return cgs -} - -func convertGroup(g sdk.Group) mggroups.Group { - if g.Status == "" { - g.Status = mggroups.EnabledStatus.String() - } - status, err := mggroups.ToStatus(g.Status) - if err != nil { - return mggroups.Group{} - } - - return mggroups.Group{ - ID: g.ID, - Domain: g.DomainID, - Parent: g.ParentID, - Name: g.Name, - Description: g.Description, - Metadata: mggroups.Metadata(g.Metadata), - Level: g.Level, - Path: g.Path, - Children: convertChildren(g.Children), - CreatedAt: g.CreatedAt, - UpdatedAt: g.UpdatedAt, - Status: status, - } -} - -func convertChildren(gs []*sdk.Group) []*mggroups.Group { - cg := []*mggroups.Group{} - - if len(gs) == 0 { - return cg - } - - for _, g := range gs { - insert := convertGroup(*g) - cg = append(cg, &insert) - } - - return cg -} - -func convertUser(c sdk.User) users.User { - if c.Status == "" { - c.Status = users.EnabledStatus.String() - } - status, err := users.ToStatus(c.Status) - if err != nil { - return users.User{} - } - role, err := users.ToRole(c.Role) - if err != nil { - return users.User{} - } - return users.User{ - ID: c.ID, - FirstName: c.FirstName, - LastName: c.LastName, - Tags: c.Tags, - Email: c.Email, - Credentials: users.Credentials(c.Credentials), - Metadata: users.Metadata(c.Metadata), - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - Status: status, - Role: role, - ProfilePicture: c.ProfilePicture, - } -} - -func convertThing(c sdk.Thing) things.Client { - if c.Status == "" { - c.Status = things.EnabledStatus.String() - } - status, err := things.ToStatus(c.Status) - if err != nil { - return things.Client{} - } - return things.Client{ - ID: c.ID, - Name: c.Name, - Tags: c.Tags, - Domain: c.DomainID, - Credentials: things.Credentials(c.Credentials), - Metadata: things.Metadata(c.Metadata), - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - Status: status, - } -} - -func convertChannel(g sdk.Channel) mggroups.Group { - if g.Status == "" { - g.Status = mggroups.EnabledStatus.String() - } - status, err := mggroups.ToStatus(g.Status) - if err != nil { - return mggroups.Group{} - } - return mggroups.Group{ - ID: g.ID, - Domain: g.DomainID, - Parent: g.ParentID, - Name: g.Name, - Description: g.Description, - Metadata: mggroups.Metadata(g.Metadata), - Level: g.Level, - Path: g.Path, - CreatedAt: g.CreatedAt, - UpdatedAt: g.UpdatedAt, - Status: status, - } -} - -func convertInvitation(i sdk.Invitation) invitations.Invitation { - return invitations.Invitation{ - InvitedBy: i.InvitedBy, - UserID: i.UserID, - DomainID: i.DomainID, - Token: i.Token, - Relation: i.Relation, - CreatedAt: i.CreatedAt, - UpdatedAt: i.UpdatedAt, - ConfirmedAt: i.ConfirmedAt, - Resend: i.Resend, - } -} - -func convertJournal(j sdk.Journal) journal.Journal { - return journal.Journal{ - ID: j.ID, - Operation: j.Operation, - OccurredAt: j.OccurredAt, - Attributes: j.Attributes, - Metadata: j.Metadata, - } -} - -func generateTestUser(t *testing.T) sdk.User { - createdAt, err := time.Parse(time.RFC3339, "2024-01-01T00:00:00Z") - assert.Nil(t, err, fmt.Sprintf("Unexpected error parsing time: %v", err)) - return sdk.User{ - ID: generateUUID(t), - FirstName: "userfirstname", - LastName: "userlastname", - Email: "useremail@example.com", - Credentials: sdk.Credentials{ - Username: "username", - Secret: secret, - }, - Tags: []string{"tag1", "tag2"}, - Metadata: validMetadata, - CreatedAt: createdAt, - UpdatedAt: createdAt, - Status: users.EnabledStatus.String(), - Role: users.UserRole.String(), - } -} - -func TestMain(m *testing.M) { - exitCode := m.Run() - os.Exit(exitCode) -} diff --git a/pkg/sdk/go/things.go b/pkg/sdk/go/things.go deleted file mode 100644 index a8cd234ff..000000000 --- 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/things_test.go b/pkg/sdk/go/things_test.go deleted file mode 100644 index 5a83b63fb..000000000 --- a/pkg/sdk/go/things_test.go +++ /dev/null @@ -1,2202 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/absmach/magistrala/internal/testsutil" - 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" - "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) { - 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, "") - - return httptest.NewServer(mux), tsvc, authn -} - -func TestCreateThing(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - createThingReq := sdk.Thing{ - Name: thing.Name, - Tags: thing.Tags, - Credentials: thing.Credentials, - Metadata: thing.Metadata, - Status: thing.Status, - } - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - createThingReq sdk.Thing - svcReq mgthings.Client - svcRes []mgthings.Client - svcErr error - authenticateErr error - response sdk.Thing - 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 thing with invalid token", - domainID: domainID, - token: invalidToken, - createThingReq: createThingReq, - svcReq: convertThing(createThingReq), - svcRes: []mgthings.Client{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.Thing{}, - 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 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 a thing with name too long", - domainID: domainID, - token: validToken, - createThingReq: sdk.Thing{ - Name: strings.Repeat("a", 1025), - Tags: thing.Tags, - Credentials: thing.Credentials, - Metadata: thing.Metadata, - Status: thing.Status, - }, - svcReq: mgthings.Client{}, - svcRes: []mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), - }, - { - desc: "create a thing with invalid id", - domainID: domainID, - token: validToken, - createThingReq: sdk.Thing{ - ID: "123456789", - Name: thing.Name, - Tags: thing.Tags, - Credentials: thing.Credentials, - Metadata: thing.Metadata, - Status: thing.Status, - }, - svcReq: mgthings.Client{}, - svcRes: []mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidIDFormat), http.StatusBadRequest), - }, - { - desc: "create a thing with a request that can't be marshalled", - domainID: domainID, - token: validToken, - createThingReq: sdk.Thing{ - Name: "test", - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: mgthings.Client{}, - svcRes: []mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - 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{ - "test": make(chan int), - }, - }}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("CreateClients", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.CreateThing(tc.createThingReq, 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, "CreateClients", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestCreateThings(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - things := []sdk.Thing{} - for i := 0; i < 3; i++ { - thing := generateTestThing(t) - things = append(things, thing) - } - - conf := sdk.Config{ - ThingsURL: 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: "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{ - "test": make(chan int), - }, - }}, - svcErr: nil, - response: []sdk.Thing{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("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) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "CreateClients", mock.Anything, tc.session, tc.svcReq[0], tc.svcReq[1], tc.svcReq[2]) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListThings(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - var things []sdk.Thing - for i := 10; i < 100; i++ { - thing := generateTestThing(t) - if i == 50 { - thing.Status = mgthings.DisabledStatus.String() - thing.Tags = []string{"tag1", "tag2"} - } - things = append(things, thing) - } - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - domainID string - session mgauthn.Session - pageMeta sdk.PageMetadata - svcReq mgthings.Page - svcRes mgthings.ClientsPage - svcErr error - authenticateErr error - response sdk.ThingsPage - err errors.SDKError - }{ - { - desc: "list all things successfully", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ - Offset: 0, - Limit: 100, - Total: uint64(len(things)), - }, - Clients: convertThings(things...), - }, - svcErr: nil, - response: sdk.ThingsPage{ - PageRes: sdk.PageRes{ - Limit: 100, - Total: uint64(len(things)), - }, - Things: things, - }, - }, - { - desc: "list all things with an invalid token", - domainID: domainID, - token: invalidToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.ClientsPage{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list all things with limit greater than max", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 1000, - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "list all things with name size greater than max", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Name: strings.Repeat("a", 1025), - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), - }, - { - desc: "list all things with status", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Status: mgthings.DisabledStatus.String(), - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - Status: mgthings.DisabledStatus, - }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ - Offset: 0, - Limit: 100, - Total: 1, - }, - Clients: convertThings(things[50]), - }, - svcErr: nil, - response: sdk.ThingsPage{ - PageRes: sdk.PageRes{ - Limit: 100, - Total: 1, - }, - Things: []sdk.Thing{things[50]}, - }, - err: nil, - }, - { - desc: "list all things with tags", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Tag: "tag1", - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - Tag: "tag1", - }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ - Offset: 0, - Limit: 100, - Total: 1, - }, - Clients: convertThings(things[50]), - }, - svcErr: nil, - response: sdk.ThingsPage{ - PageRes: sdk.PageRes{ - Limit: 100, - Total: 1, - }, - Things: []sdk.Thing{things[50]}, - }, - err: nil, - }, - { - desc: "list all things with invalid metadata", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list all things with response that can't be unmarshalled", - domainID: domainID, - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.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{ - "test": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("ListClients", mock.Anything, tc.session, mock.Anything, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Things(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, "ListClients", mock.Anything, tc.session, mock.Anything, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListThingsByChannel(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - var things []sdk.Thing - for i := 10; i < 100; i++ { - thing := generateTestThing(t) - if i == 50 { - thing.Status = mgthings.DisabledStatus.String() - } - things = append(things, thing) - } - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - domainID string - session mgauthn.Session - channelID string - pageMeta sdk.PageMetadata - svcReq mgthings.Page - svcRes mgthings.MembersPage - svcErr error - authenticateErr error - response sdk.ThingsPage - err errors.SDKError - }{ - { - desc: "list things successfully", - domainID: domainID, - token: validToken, - channelID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.MembersPage{ - Page: mgthings.Page{ - Offset: 0, - Limit: 100, - Total: uint64(len(things)), - }, - Members: convertThings(things...), - }, - svcErr: nil, - response: sdk.ThingsPage{ - PageRes: sdk.PageRes{ - Limit: 100, - Total: uint64(len(things)), - }, - Things: things, - }, - }, - { - desc: "list things with an invalid token", - domainID: domainID, - token: invalidToken, - channelID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.MembersPage{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list things with empty token", - domainID: domainID, - token: "", - channelID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.MembersPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list things with status", - domainID: domainID, - token: validToken, - channelID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Status: mgthings.DisabledStatus.String(), - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - Status: mgthings.DisabledStatus, - }, - svcRes: mgthings.MembersPage{ - Page: mgthings.Page{ - Offset: 0, - Limit: 100, - Total: 1, - }, - Members: convertThings(things[50]), - }, - svcErr: nil, - response: sdk.ThingsPage{ - PageRes: sdk.PageRes{ - Limit: 100, - Total: 1, - }, - Things: []sdk.Thing{things[50]}, - }, - err: nil, - }, - { - desc: "list things with empty channel id", - domainID: domainID, - token: validToken, - channelID: "", - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.MembersPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "list things with invalid metadata", - domainID: domainID, - token: validToken, - channelID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.MembersPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list things with response that can't be unmarshalled", - domainID: domainID, - token: validToken, - channelID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.MembersPage{ - Page: mgthings.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{ - "test": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("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) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListClientsByGroup", mock.Anything, tc.session, tc.channelID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewThing(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - svcRes mgthings.Client - svcErr error - authenticateErr error - response sdk.Thing - err errors.SDKError - }{ - { - desc: "view thing successfully", - domainID: domainID, - token: validToken, - thingID: thing.ID, - svcRes: convertThing(thing), - svcErr: nil, - response: thing, - err: nil, - }, - { - desc: "view thing with an invalid token", - domainID: domainID, - token: invalidToken, - thingID: thing.ID, - svcRes: mgthings.Client{}, - authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "view thing with empty token", - domainID: domainID, - token: "", - thingID: thing.ID, - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view thing with an invalid thing id", - domainID: domainID, - token: validToken, - thingID: wrongID, - svcRes: mgthings.Client{}, - svcErr: svcerr.ErrViewEntity, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "view thing with empty thing id", - domainID: domainID, - token: validToken, - thingID: "", - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "view thing 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{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewThingPermissions(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := sdk.Thing{ - Permissions: []string{policies.ViewPermission}, - } - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - svcRes []string - svcErr error - authenticateErr error - response sdk.Thing - err errors.SDKError - }{ - { - desc: "view thing permissions successfully", - domainID: domainID, - token: validToken, - thingID: validID, - svcRes: []string{policies.ViewPermission}, - svcErr: nil, - response: thing, - err: nil, - }, - { - desc: "view thing permissions with an invalid token", - domainID: domainID, - token: invalidToken, - thingID: validID, - svcRes: []string{}, - authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "view thing permissions with empty token", - domainID: domainID, - token: "", - thingID: thing.ID, - svcRes: []string{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view thing permissions with an invalid thing id", - domainID: domainID, - token: validToken, - thingID: wrongID, - svcRes: []string{}, - svcErr: svcerr.ErrViewEntity, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "view thing permissions with empty thing id", - domainID: domainID, - token: validToken, - thingID: "", - svcRes: []string{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateThing(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - updatedThing := thing - updatedThing.Name = "newName" - updatedThing.Metadata = map[string]interface{}{ - "newKey": "newValue", - } - updateThingReq := sdk.Thing{ - ID: thing.ID, - Name: updatedThing.Name, - Metadata: updatedThing.Metadata, - } - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - updateThingReq sdk.Thing - svcReq mgthings.Client - svcRes mgthings.Client - svcErr error - authenticateErr error - response sdk.Thing - 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 thing with an invalid token", - domainID: domainID, - token: invalidToken, - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: mgthings.Client{}, - authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, - 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 thing with an invalid thing id", - domainID: domainID, - token: validToken, - updateThingReq: sdk.Thing{ - ID: wrongID, - Name: updatedThing.Name, - }, - svcReq: convertThing(sdk.Thing{ - ID: wrongID, - Name: updatedThing.Name, - }), - svcRes: mgthings.Client{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update thing with empty thing id", - domainID: domainID, - token: validToken, - - updateThingReq: sdk.Thing{ - ID: "", - Name: updatedThing.Name, - }, - svcReq: convertThing(sdk.Thing{ - ID: "", - Name: updatedThing.Name, - }), - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "update thing with a request that can't be marshalled", - domainID: domainID, - token: validToken, - - updateThingReq: sdk.Thing{ - ID: "test", - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: mgthings.Client{}, - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - 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{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("Update", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateThing(tc.updateThingReq, 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, "Update", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateThingTags(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - updatedThing := thing - updatedThing.Tags = []string{"newTag1", "newTag2"} - updateThingReq := sdk.Thing{ - ID: thing.ID, - Tags: updatedThing.Tags, - } - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - updateThingReq sdk.Thing - svcReq mgthings.Client - svcRes mgthings.Client - svcErr error - authenticateErr error - response sdk.Thing - 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 thing tags with an invalid token", - domainID: domainID, - token: invalidToken, - updateThingReq: updateThingReq, - svcReq: convertThing(updateThingReq), - svcRes: mgthings.Client{}, - authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, - 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 thing tags with an invalid thing id", - domainID: domainID, - token: validToken, - updateThingReq: sdk.Thing{ - ID: wrongID, - Tags: updatedThing.Tags, - }, - svcReq: convertThing(sdk.Thing{ - ID: wrongID, - Tags: updatedThing.Tags, - }), - svcRes: mgthings.Client{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update thing tags with empty thing id", - domainID: domainID, - token: validToken, - updateThingReq: sdk.Thing{ - ID: "", - Tags: updatedThing.Tags, - }, - svcReq: convertThing(sdk.Thing{ - ID: "", - Tags: updatedThing.Tags, - }), - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "update thing tags with a request that can't be marshalled", - domainID: domainID, - token: validToken, - updateThingReq: sdk.Thing{ - ID: "test", - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: mgthings.Client{}, - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - 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{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("UpdateTags", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateThingTags(tc.updateThingReq, 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, "UpdateTags", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateThingSecret(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - newSecret := generateUUID(t) - updatedThing := thing - updatedThing.Credentials.Secret = newSecret - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - newSecret string - svcRes mgthings.Client - svcErr error - authenticateErr error - response sdk.Thing - err errors.SDKError - }{ - { - desc: "update thing secret successfully", - domainID: domainID, - token: validToken, - thingID: thing.ID, - newSecret: newSecret, - svcRes: convertThing(updatedThing), - svcErr: nil, - response: updatedThing, - err: nil, - }, - { - desc: "update thing secret with an invalid token", - domainID: domainID, - token: invalidToken, - thingID: thing.ID, - newSecret: newSecret, - svcRes: mgthings.Client{}, - authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "update thing secret with empty token", - domainID: domainID, - token: "", - thingID: thing.ID, - newSecret: newSecret, - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update thing secret with an invalid thing id", - domainID: domainID, - token: validToken, - thingID: wrongID, - newSecret: newSecret, - svcRes: mgthings.Client{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update thing secret with empty thing id", - domainID: domainID, - token: validToken, - thingID: "", - newSecret: newSecret, - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "update thing with empty new secret", - domainID: domainID, - token: validToken, - thingID: thing.ID, - newSecret: "", - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingSecret), http.StatusBadRequest), - }, - { - desc: "update thing secret with a response that can't be unmarshalled", - domainID: domainID, - token: validToken, - thingID: thing.ID, - newSecret: newSecret, - svcRes: mgthings.Client{ - Name: updatedThing.Name, - Tags: updatedThing.Tags, - Credentials: mgthings.Credentials(updatedThing.Credentials), - Metadata: mgthings.Metadata{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestEnableThing(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - enabledThing := thing - enabledThing.Status = mgthings.EnabledStatus.String() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - svcRes mgthings.Client - svcErr error - authenticateErr error - response sdk.Thing - err errors.SDKError - }{ - { - desc: "enable thing successfully", - domainID: domainID, - token: validToken, - thingID: thing.ID, - svcRes: convertThing(enabledThing), - svcErr: nil, - response: enabledThing, - err: nil, - }, - { - desc: "enable thing with an invalid token", - domainID: domainID, - token: invalidToken, - thingID: thing.ID, - svcRes: mgthings.Client{}, - authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "enable thing with an invalid thing id", - domainID: domainID, - token: validToken, - thingID: wrongID, - svcRes: mgthings.Client{}, - svcErr: svcerr.ErrEnableClient, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrEnableClient, http.StatusUnprocessableEntity), - }, - { - desc: "enable thing with empty thing id", - domainID: domainID, - token: validToken, - thingID: "", - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "enable thing 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{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDisableThing(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - disabledThing := thing - disabledThing.Status = mgthings.DisabledStatus.String() - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - svcRes mgthings.Client - svcErr error - authenticateErr error - response sdk.Thing - err errors.SDKError - }{ - { - desc: "disable thing successfully", - domainID: domainID, - token: validToken, - thingID: thing.ID, - svcRes: convertThing(disabledThing), - svcErr: nil, - response: disabledThing, - err: nil, - }, - { - desc: "disable thing with an invalid token", - domainID: domainID, - token: invalidToken, - thingID: thing.ID, - svcRes: mgthings.Client{}, - authenticateErr: svcerr.ErrAuthorization, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "disable thing with an invalid thing id", - domainID: domainID, - token: validToken, - thingID: wrongID, - svcRes: mgthings.Client{}, - svcErr: svcerr.ErrDisableClient, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrDisableClient, http.StatusInternalServerError), - }, - { - desc: "disable thing with empty thing id", - domainID: domainID, - token: validToken, - thingID: "", - svcRes: mgthings.Client{}, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "disable thing 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{ - "test": make(chan int), - }, - }, - svcErr: nil, - response: sdk.Thing{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestShareThing(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - shareReq sdk.UsersRelationRequest - authenticateErr error - svcErr error - err errors.SDKError - }{ - { - desc: "share thing successfully", - domainID: domainID, - token: validToken, - thingID: thing.ID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - svcErr: nil, - err: nil, - }, - { - desc: "share thing with an invalid token", - domainID: domainID, - token: invalidToken, - thingID: thing.ID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - authenticateErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "share thing with empty token", - domainID: domainID, - token: "", - thingID: thing.ID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - svcErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "share thing with an invalid thing id", - domainID: domainID, - token: validToken, - thingID: wrongID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - svcErr: svcerr.ErrUpdateEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "share thing with empty thing id", - domainID: domainID, - token: validToken, - thingID: "", - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "share thing with empty relation", - domainID: domainID, - token: validToken, - thingID: thing.ID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: "", - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMalformedPolicy), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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]) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUnshareThing(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - shareReq sdk.UsersRelationRequest - authenticateErr error - svcErr error - err errors.SDKError - }{ - { - desc: "unshare thing successfully", - domainID: domainID, - token: validToken, - thingID: thing.ID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - svcErr: nil, - err: nil, - }, - { - desc: "unshare thing with an invalid token", - domainID: domainID, - token: invalidToken, - thingID: thing.ID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - authenticateErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "unshare thing with empty token", - domainID: domainID, - token: "", - thingID: thing.ID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "unshare thing with an invalid thing id", - domainID: domainID, - token: validToken, - thingID: wrongID, - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - svcErr: svcerr.ErrUpdateEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "unshare thing with empty thing id", - domainID: domainID, - token: validToken, - thingID: "", - shareReq: sdk.UsersRelationRequest{ - UserIDs: []string{validID}, - Relation: policies.EditorRelation, - }, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - 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]) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDeleteThing(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - thing := generateTestThing(t) - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - domainID string - token string - session mgauthn.Session - thingID string - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "delete thing successfully", - domainID: domainID, - token: validToken, - thingID: thing.ID, - svcErr: nil, - err: nil, - }, - { - desc: "delete thing with an invalid token", - domainID: domainID, - token: invalidToken, - thingID: thing.ID, - authenticateErr: svcerr.ErrAuthorization, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), - }, - { - desc: "delete thing with empty token", - domainID: domainID, - token: "", - thingID: thing.ID, - svcErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "delete thing with an invalid thing id", - domainID: domainID, - token: validToken, - thingID: wrongID, - svcErr: svcerr.ErrRemoveEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrRemoveEntity, http.StatusUnprocessableEntity), - }, - { - desc: "delete thing with empty thing id", - domainID: domainID, - token: validToken, - thingID: "", - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Delete", mock.Anything, tc.session, tc.thingID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListUserThings(t *testing.T) { - ts, tsvc, auth := setupThings() - defer ts.Close() - - var things []sdk.Thing - for i := 10; i < 100; i++ { - thing := generateTestThing(t) - if i == 50 { - thing.Status = mgthings.DisabledStatus.String() - thing.Tags = []string{"tag1", "tag2"} - } - things = append(things, thing) - } - - conf := sdk.Config{ - ThingsURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - pageMeta sdk.PageMetadata - svcReq mgthings.Page - svcRes mgthings.ClientsPage - svcErr error - authenticateErr error - response sdk.ThingsPage - err errors.SDKError - }{ - { - desc: "list user things successfully", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - DomainID: domainID, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ - Offset: 0, - Limit: 100, - Total: uint64(len(things)), - }, - Clients: convertThings(things...), - }, - svcErr: nil, - response: sdk.ThingsPage{ - PageRes: sdk.PageRes{ - Limit: 100, - Total: uint64(len(things)), - }, - Things: things, - }, - }, - { - desc: "list user things with an invalid token", - token: invalidToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - DomainID: domainID, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.ClientsPage{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list user things with limit greater than max", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 1000, - DomainID: domainID, - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "list user things with name size greater than max", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Name: strings.Repeat("a", 1025), - DomainID: domainID, - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), - }, - { - desc: "list user things with status", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Status: mgthings.DisabledStatus.String(), - DomainID: domainID, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - Status: mgthings.DisabledStatus, - }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ - Offset: 0, - Limit: 100, - Total: 1, - }, - Clients: convertThings(things[50]), - }, - svcErr: nil, - response: sdk.ThingsPage{ - PageRes: sdk.PageRes{ - Limit: 100, - Total: 1, - }, - Things: []sdk.Thing{things[50]}, - }, - err: nil, - }, - { - desc: "list user things with tags", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Tag: "tag1", - DomainID: domainID, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - Tag: "tag1", - }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.Page{ - Offset: 0, - Limit: 100, - Total: 1, - }, - Clients: convertThings(things[50]), - }, - svcErr: nil, - response: sdk.ThingsPage{ - PageRes: sdk.PageRes{ - Limit: 100, - Total: 1, - }, - Things: []sdk.Thing{things[50]}, - }, - err: nil, - }, - { - desc: "list user things with invalid metadata", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - DomainID: domainID, - }, - svcReq: mgthings.Page{}, - svcRes: mgthings.ClientsPage{}, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list user things with response that can't be unmarshalled", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 100, - DomainID: domainID, - }, - svcReq: mgthings.Page{ - Offset: 0, - Limit: 100, - Permission: policies.ViewPermission, - }, - svcRes: mgthings.ClientsPage{ - Page: mgthings.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{ - "test": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.ThingsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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("ListClients", mock.Anything, tc.session, tc.userID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListUserThings(tc.userID, tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListClients", mock.Anything, tc.session, tc.userID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func generateTestThing(t *testing.T) sdk.Thing { - 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{ - ID: testsutil.GenerateUUID(t), - Name: "clientname", - Credentials: sdk.ClientCredentials{ - Identity: "thing@example.com", - Secret: generateUUID(t), - }, - Tags: []string{"tag1", "tag2"}, - Metadata: validMetadata, - Status: mgthings.EnabledStatus.String(), - CreatedAt: createdAt, - UpdatedAt: updatedAt, - } -} diff --git a/pkg/sdk/go/tokens.go b/pkg/sdk/go/tokens.go deleted file mode 100644 index 6f79aeec1..000000000 --- a/pkg/sdk/go/tokens.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/absmach/magistrala/pkg/errors" -) - -// Token is used for authentication purposes. -// It contains AccessToken, RefreshToken and AccessExpiry. -type Token struct { - AccessToken string `json:"access_token,omitempty"` - RefreshToken string `json:"refresh_token,omitempty"` - AccessType string `json:"access_type,omitempty"` -} - -type Login struct { - Identity string `json:"identity"` - Secret string `json:"secret"` -} - -func (sdk mgSDK) CreateToken(lt Login) (Token, errors.SDKError) { - data, err := json.Marshal(lt) - if err != nil { - return Token{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, issueTokenEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, "", data, nil, http.StatusCreated) - if sdkerr != nil { - return Token{}, sdkerr - } - var token Token - if err := json.Unmarshal(body, &token); err != nil { - return Token{}, errors.NewSDKError(err) - } - - return token, nil -} - -func (sdk mgSDK) RefreshToken(token string) (Token, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, refreshTokenEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusCreated) - if sdkerr != nil { - return Token{}, sdkerr - } - - t := Token{} - if err := json.Unmarshal(body, &t); err != nil { - return Token{}, errors.NewSDKError(err) - } - - return t, nil -} diff --git a/pkg/sdk/go/tokens_test.go b/pkg/sdk/go/tokens_test.go deleted file mode 100644 index 809d45367..000000000 --- a/pkg/sdk/go/tokens_test.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "net/http" - "testing" - - "github.com/absmach/magistrala" - mgauth "github.com/absmach/magistrala/auth" - "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" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestIssueToken(t *testing.T) { - ts, svc, _ := setupUsers() - defer ts.Close() - - client := generateTestUser(t) - token := generateTestToken() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - login sdk.Login - svcRes *magistrala.Token - svcErr error - response sdk.Token - err errors.SDKError - }{ - { - desc: "issue token successfully", - login: sdk.Login{ - Identity: client.Credentials.Username, - Secret: client.Credentials.Secret, - }, - svcRes: &magistrala.Token{ - AccessToken: token.AccessToken, - RefreshToken: &token.RefreshToken, - AccessType: mgauth.AccessKey.String(), - }, - svcErr: nil, - response: token, - err: nil, - }, - { - desc: "issue token with invalid identity", - login: sdk.Login{ - Identity: invalidIdentity, - Secret: client.Credentials.Secret, - }, - svcRes: &magistrala.Token{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.Token{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "issue token with invalid secret", - login: sdk.Login{ - Identity: client.Credentials.Username, - Secret: "invalid", - }, - svcRes: &magistrala.Token{}, - svcErr: svcerr.ErrLogin, - response: sdk.Token{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrLogin, http.StatusUnauthorized), - }, - { - desc: "issue token with empty identity", - login: sdk.Login{ - Identity: "", - Secret: client.Credentials.Secret, - }, - svcRes: &magistrala.Token{}, - svcErr: nil, - response: sdk.Token{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusBadRequest), - }, - { - desc: "issue token with empty secret", - login: sdk.Login{ - Identity: client.Credentials.Username, - Secret: "", - }, - svcRes: &magistrala.Token{}, - svcErr: nil, - response: sdk.Token{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPass), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("IssueToken", mock.Anything, tc.login.Identity, tc.login.Secret).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.CreateToken(tc.login) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "IssueToken", mock.Anything, tc.login.Identity, tc.login.Secret) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestRefreshToken(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - token := generateTestToken() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - svcRes *magistrala.Token - svcErr error - identifyErr error - response sdk.Token - err errors.SDKError - }{ - { - desc: "refresh token successfully", - token: token.RefreshToken, - svcRes: &magistrala.Token{ - AccessToken: token.AccessToken, - RefreshToken: &token.RefreshToken, - AccessType: token.AccessType, - }, - response: token, - err: nil, - }, - { - desc: "refresh token with invalid token", - token: invalidToken, - svcRes: nil, - identifyErr: svcerr.ErrAuthentication, - response: sdk.Token{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "refresh token with empty token", - token: "", - response: sdk.Token{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authCall := auth.On("Authenticate", mock.Anything, mock.Anything).Return(mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, tc.identifyErr) - svcCall := svc.On("RefreshToken", mock.Anything, mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, tc.token).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.RefreshToken(tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "RefreshToken", mock.Anything, mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, tc.token) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func generateTestToken() sdk.Token { - return sdk.Token{ - AccessToken: "access_token", - RefreshToken: "refresh_token", - AccessType: mgauth.AccessKey.String(), - } -} diff --git a/pkg/sdk/go/users.go b/pkg/sdk/go/users.go deleted file mode 100644 index 125b8c13d..000000000 --- a/pkg/sdk/go/users.go +++ /dev/null @@ -1,426 +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 ( - usersEndpoint = "users" - assignEndpoint = "assign" - unassignEndpoint = "unassign" - enableEndpoint = "enable" - disableEndpoint = "disable" - issueTokenEndpoint = "tokens/issue" - refreshTokenEndpoint = "tokens/refresh" - membersEndpoint = "members" - PasswordResetEndpoint = "password" -) - -// User represents magistrala user its credentials. -type User struct { - ID string `json:"id"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - Email string `json:"email,omitempty"` - Credentials Credentials `json:"credentials"` - Tags []string `json:"tags,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - Status string `json:"status,omitempty"` - Role string `json:"role,omitempty"` - ProfilePicture string `json:"profile_picture,omitempty"` -} - -func (sdk mgSDK) CreateUser(user User, token string) (User, errors.SDKError) { - data, err := json.Marshal(user) - if err != nil { - return User{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s", sdk.usersURL, usersEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, data, nil, http.StatusCreated) - if sdkerr != nil { - return User{}, sdkerr - } - - user = User{} - if err := json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) Users(pm PageMetadata, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, 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 - } - - var cp UsersPage - if err := json.Unmarshal(body, &cp); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return cp, nil -} - -func (sdk mgSDK) Members(groupID string, meta PageMetadata, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", meta.DomainID, groupsEndpoint, groupID, usersEndpoint), meta) - 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 - } - - var up UsersPage - if err := json.Unmarshal(body, &up); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return up, nil -} - -func (sdk mgSDK) User(id, token string) (User, errors.SDKError) { - if id == "" { - return User{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, id) - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - var user User - if err := json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) UserProfile(token string) (User, errors.SDKError) { - url := fmt.Sprintf("%s/%s/profile", sdk.usersURL, usersEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - var user User - if err := json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) UpdateUser(user User, token string) (User, errors.SDKError) { - if user.ID == "" { - return User{}, errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, user.ID) - - data, err := json.Marshal(user) - if err != nil { - return User{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - user = User{} - if err := json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) UpdateUserTags(user User, token string) (User, errors.SDKError) { - data, err := json.Marshal(user) - if err != nil { - return User{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/tags", sdk.usersURL, usersEndpoint, user.ID) - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - user = User{} - if err := json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) UpdateUserEmail(user User, token string) (User, errors.SDKError) { - ucir := updateUserEmailReq{token: token, id: user.ID, Email: user.Email} - - data, err := json.Marshal(ucir) - if err != nil { - return User{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/email", sdk.usersURL, usersEndpoint, user.ID) - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - user = User{} - if err := json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) ResetPasswordRequest(email string) errors.SDKError { - rpr := resetPasswordRequestreq{Email: email} - - data, err := json.Marshal(rpr) - if err != nil { - return errors.NewSDKError(err) - } - url := fmt.Sprintf("%s/%s/reset-request", sdk.usersURL, PasswordResetEndpoint) - - header := make(map[string]string) - header["Referer"] = sdk.HostURL - - _, _, sdkerr := sdk.processRequest(http.MethodPost, url, "", data, header, http.StatusCreated) - - return sdkerr -} - -func (sdk mgSDK) ResetPassword(password, confPass, token string) errors.SDKError { - rpr := resetPasswordReq{Token: token, Password: password, ConfPass: confPass} - - data, err := json.Marshal(rpr) - if err != nil { - return errors.NewSDKError(err) - } - url := fmt.Sprintf("%s/%s/reset", sdk.usersURL, PasswordResetEndpoint) - - _, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, data, nil, http.StatusCreated) - - return sdkerr -} - -func (sdk mgSDK) UpdatePassword(oldPass, newPass, token string) (User, errors.SDKError) { - ucsr := updateUserSecretReq{OldSecret: oldPass, NewSecret: newPass} - - data, err := json.Marshal(ucsr) - if err != nil { - return User{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/secret", sdk.usersURL, usersEndpoint) - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - var user User - if err = json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) UpdateUserRole(user User, token string) (User, errors.SDKError) { - data, err := json.Marshal(user) - if err != nil { - return User{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/role", sdk.usersURL, usersEndpoint, user.ID) - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - user = User{} - if err = json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) UpdateUsername(user User, token string) (User, errors.SDKError) { - uur := UpdateUsernameReq{id: user.ID, Username: user.Credentials.Username} - data, err := json.Marshal(uur) - if err != nil { - return User{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/username", sdk.usersURL, usersEndpoint, user.ID) - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - user = User{} - if err = json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) UpdateProfilePicture(user User, token string) (User, errors.SDKError) { - data, err := json.Marshal(user) - if err != nil { - return User{}, errors.NewSDKError(err) - } - - url := fmt.Sprintf("%s/%s/%s/picture", sdk.usersURL, usersEndpoint, user.ID) - - _, body, sdkerr := sdk.processRequest(http.MethodPatch, url, token, data, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - user = User{} - if err = json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -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) - if err != nil { - return ChannelsPage{}, errors.NewSDKError(err) - } - - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return ChannelsPage{}, sdkerr - } - cp := ChannelsPage{} - if err := json.Unmarshal(body, &cp); err != nil { - return ChannelsPage{}, errors.NewSDKError(err) - } - - return cp, nil -} - -func (sdk mgSDK) ListUserGroups(userID string, pm PageMetadata, token string) (GroupsPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/%s/%s/%s", pm.DomainID, usersEndpoint, userID, groupsEndpoint), pm) - if err != nil { - return GroupsPage{}, errors.NewSDKError(err) - } - _, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return GroupsPage{}, sdkerr - } - gp := GroupsPage{} - if err := json.Unmarshal(body, &gp); err != nil { - return GroupsPage{}, errors.NewSDKError(err) - } - - 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) - 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 - } - tp := ThingsPage{} - if err := json.Unmarshal(body, &tp); err != nil { - return ThingsPage{}, errors.NewSDKError(err) - } - - return tp, nil -} - -func (sdk mgSDK) SearchUsers(pm PageMetadata, token string) (UsersPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.usersURL, fmt.Sprintf("%s/search", 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 - } - - var cp UsersPage - if err := json.Unmarshal(body, &cp); err != nil { - return UsersPage{}, errors.NewSDKError(err) - } - - return cp, nil -} - -func (sdk mgSDK) EnableUser(id, token string) (User, errors.SDKError) { - return sdk.changeUserStatus(token, id, enableEndpoint) -} - -func (sdk mgSDK) DisableUser(id, token string) (User, errors.SDKError) { - return sdk.changeUserStatus(token, id, disableEndpoint) -} - -func (sdk mgSDK) changeUserStatus(token, id, status string) (User, errors.SDKError) { - url := fmt.Sprintf("%s/%s/%s/%s", sdk.usersURL, usersEndpoint, id, status) - - _, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, nil, nil, http.StatusOK) - if sdkerr != nil { - return User{}, sdkerr - } - - user := User{} - if err := json.Unmarshal(body, &user); err != nil { - return User{}, errors.NewSDKError(err) - } - - return user, nil -} - -func (sdk mgSDK) DeleteUser(id, token string) errors.SDKError { - if id == "" { - return errors.NewSDKError(apiutil.ErrMissingID) - } - url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, id) - _, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent) - return sdkerr -} diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go deleted file mode 100644 index 715000531..000000000 --- a/pkg/sdk/go/users_test.go +++ /dev/null @@ -1,2765 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package sdk_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/absmach/magistrala" - authmocks "github.com/absmach/magistrala/auth/mocks" - internalapi "github.com/absmach/magistrala/internal/api" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/authn" - mgauthn "github.com/absmach/magistrala/pkg/authn" - 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" - "github.com/absmach/magistrala/users" - "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" -) - -var ( - id = generateUUID(&testing.T{}) - domainID = "c717fa97-ffd9-40cb-8cf9-7c2859059395" -) - -func setupUsers() (*httptest.Server, *umocks.Service, *authnmocks.Authentication) { - usvc := new(umocks.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) - api.MakeHandler(usvc, authn, token, true, gsvc, mux, logger, "", passRegex, provider) - - return httptest.NewServer(mux), usvc, authn -} - -func TestCreateUser(t *testing.T) { - ts, svc, _ := setupUsers() - defer ts.Close() - - createSdkUserReq := sdk.User{ - FirstName: user.FirstName, - LastName: user.LastName, - Email: user.Email, - Tags: user.Tags, - Credentials: user.Credentials, - Metadata: user.Metadata, - Status: user.Status, - } - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - createSdkUserReq sdk.User - svcReq users.User - svcRes users.User - svcErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "register new user successfully", - token: validToken, - createSdkUserReq: createSdkUserReq, - svcReq: convertUser(createSdkUserReq), - svcRes: convertUser(user), - svcErr: nil, - response: user, - err: nil, - }, - { - desc: "register existing user", - token: validToken, - createSdkUserReq: createSdkUserReq, - svcReq: convertUser(createSdkUserReq), - svcRes: users.User{}, - svcErr: svcerr.ErrCreateEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "register user with invalid token", - token: invalidToken, - createSdkUserReq: createSdkUserReq, - svcReq: convertUser(createSdkUserReq), - svcRes: users.User{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "register user with empty token", - token: "", - createSdkUserReq: createSdkUserReq, - svcReq: convertUser(createSdkUserReq), - svcRes: users.User{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "register empty credentials user", - token: validToken, - createSdkUserReq: sdk.User{ - FirstName: createSdkUserReq.FirstName, - LastName: createSdkUserReq.LastName, - Email: createSdkUserReq.Email, - Credentials: sdk.Credentials{ - Username: "", - Secret: "", - }, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingUsername), http.StatusBadRequest), - }, - { - desc: "register user with first name too long", - token: validToken, - createSdkUserReq: sdk.User{ - FirstName: strings.Repeat("a", 1025), - Credentials: createSdkUserReq.Credentials, - Metadata: createSdkUserReq.Metadata, - Tags: createSdkUserReq.Tags, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize), http.StatusBadRequest), - }, - { - desc: "register user with empty userName", - token: validToken, - createSdkUserReq: sdk.User{ - FirstName: createSdkUserReq.FirstName, - LastName: createSdkUserReq.LastName, - Email: createSdkUserReq.Email, - Credentials: sdk.Credentials{ - Username: "", - Secret: createSdkUserReq.Credentials.Secret, - }, - Metadata: createSdkUserReq.Metadata, - Tags: createSdkUserReq.Tags, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingUsername), http.StatusBadRequest), - }, - { - desc: "register user with empty secret", - token: validToken, - createSdkUserReq: sdk.User{ - FirstName: createSdkUserReq.FirstName, - LastName: createSdkUserReq.LastName, - Email: createSdkUserReq.Email, - Credentials: sdk.Credentials{ - Username: createSdkUserReq.Credentials.Username, - Secret: "", - }, - Metadata: createSdkUserReq.Metadata, - Tags: createSdkUserReq.Tags, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPass), http.StatusBadRequest), - }, - { - desc: "register user with secret that is too short", - token: validToken, - createSdkUserReq: sdk.User{ - FirstName: createSdkUserReq.FirstName, - LastName: createSdkUserReq.LastName, - Email: createSdkUserReq.Email, - Credentials: sdk.Credentials{ - Username: createSdkUserReq.Credentials.Username, - Secret: "weak", - }, - Metadata: createSdkUserReq.Metadata, - Tags: createSdkUserReq.Tags, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrPasswordFormat), http.StatusBadRequest), - }, - { - desc: "register a user with request that can't be marshalled", - token: validToken, - createSdkUserReq: sdk.User{ - Credentials: sdk.Credentials{ - Username: "user", - Secret: "12345678", - }, - FirstName: createSdkUserReq.FirstName, - LastName: createSdkUserReq.LastName, - Email: createSdkUserReq.Email, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "register a user with response that can't be unmarshalled", - token: validToken, - createSdkUserReq: createSdkUserReq, - svcReq: convertUser(createSdkUserReq), - svcRes: users.User{ - ID: id, - FirstName: createSdkUserReq.FirstName, - LastName: createSdkUserReq.LastName, - Email: createSdkUserReq.Email, - Credentials: users.Credentials{ - Username: createSdkUserReq.Credentials.Username, - Secret: createSdkUserReq.Credentials.Secret, - }, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("Register", mock.Anything, mgauthn.Session{}, tc.svcReq, true).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.CreateUser(tc.createSdkUserReq, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Register", mock.Anything, authn.Session{}, tc.svcReq, true) - assert.True(t, ok) - } - svcCall.Unset() - }) - } -} - -func TestListUsers(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - var cls []sdk.User - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - for i := 10; i < 100; i++ { - cl := sdk.User{ - ID: generateUUID(t), - FirstName: fmt.Sprintf("user_%d", i), - Credentials: sdk.Credentials{ - Username: fmt.Sprintf("Username_%d", i), - Secret: fmt.Sprintf("password_%d", i), - }, - Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, - Status: users.EnabledStatus.String(), - Role: users.UserRole.String(), - } - if i == 50 { - cl.Status = users.DisabledStatus.String() - cl.Tags = []string{"tag1", "tag2"} - } - cls = append(cls, cl) - } - - cases := []struct { - desc string - token string - session mgauthn.Session - pageMeta sdk.PageMetadata - svcReq users.Page - svcRes users.UsersPage - svcErr error - authenticateErr error - response sdk.UsersPage - err errors.SDKError - }{ - { - desc: "list users successfully", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: users.Page{ - Offset: offset, - Limit: limit, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: users.UsersPage{ - Page: users.Page{ - Total: uint64(len(cls[offset:limit])), - }, - Users: convertUsers(cls[offset:limit]), - }, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(cls[offset:limit])), - }, - Users: cls[offset:limit], - }, - err: nil, - }, - { - desc: "list users with invalid token", - token: invalidToken, - session: mgauthn.Session{}, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: users.Page{ - Offset: offset, - Limit: limit, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: users.UsersPage{}, - svcErr: svcerr.ErrAuthentication, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list users with empty token", - token: "", - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: users.Page{}, - svcRes: users.UsersPage{}, - svcErr: nil, - authenticateErr: apiutil.ErrBearerToken, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list users with zero limit", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 0, - }, - svcReq: users.Page{ - Offset: offset, - Limit: 10, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: users.UsersPage{ - Page: users.Page{ - Total: uint64(len(cls[offset:10])), - }, - Users: convertUsers(cls[offset:10]), - }, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: uint64(len(cls[offset:10])), - }, - Users: cls[offset:10], - }, - err: nil, - }, - { - desc: "list users with limit greater than max", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: 101, - }, - svcReq: users.Page{}, - svcRes: users.UsersPage{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - { - desc: "list users with given metadata", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Metadata: sdk.Metadata{"name": "user_99"}, - }, - svcReq: users.Page{ - Offset: offset, - Limit: limit, - Metadata: users.Metadata{"name": "user_99"}, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{convertUser(cls[89])}, - }, - svcErr: nil, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Users: []sdk.User{cls[89]}, - }, - err: nil, - }, - { - desc: "list users with given status", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Status: users.DisabledStatus.String(), - }, - svcReq: users.Page{ - Offset: offset, - Limit: limit, - Status: users.DisabledStatus, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{convertUser(cls[50])}, - }, - svcErr: nil, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Users: []sdk.User{cls[50]}, - }, - err: nil, - }, - { - desc: "list users with given tag", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Tag: "tag1", - }, - svcReq: users.Page{ - Offset: offset, - Limit: limit, - Tag: "tag1", - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{convertUser(cls[50])}, - }, - svcErr: nil, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Users: []sdk.User{cls[50]}, - }, - err: nil, - }, - { - desc: "list users with request that can't be marshalled", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Metadata: sdk.Metadata{ - "test": make(chan int), - }, - }, - svcReq: users.Page{ - Offset: offset, - Limit: limit, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: users.UsersPage{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list users with response that can't be unmarshalled", - token: validToken, - pageMeta: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - }, - svcReq: users.Page{ - Offset: offset, - Limit: limit, - Order: internalapi.DefOrder, - Dir: internalapi.DefDir, - }, - svcRes: users.UsersPage{ - Page: users.Page{ - Total: uint64(len(cls[offset:limit])), - }, - Users: []users.User{ - { - ID: id, - FirstName: "user_99", - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - }, - }, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ListUsers", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Users(tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListUsers", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestSearchUsers(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - var cls []sdk.User - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - for i := 10; i < 100; i++ { - cl := sdk.User{ - ID: generateUUID(t), - FirstName: fmt.Sprintf("user_%d", i), - Email: fmt.Sprintf("email_%d", i), - Credentials: sdk.Credentials{ - Username: fmt.Sprintf("Username_%d", i), - Secret: fmt.Sprintf("password_%d", i), - }, - Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, - Status: users.EnabledStatus.String(), - Role: users.UserRole.String(), - } - if i == 50 { - cl.Status = users.DisabledStatus.String() - cl.Tags = []string{"tag1", "tag2"} - } - cls = append(cls, cl) - } - - cases := []struct { - desc string - token string - page sdk.PageMetadata - response []sdk.User - searchreturn users.UsersPage - err errors.SDKError - authenticateErr error - }{ - { - desc: "search for users", - token: validToken, - err: nil, - page: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Username: "user_20", - }, - response: []sdk.User{cls[10]}, - searchreturn: users.UsersPage{ - Users: []users.User{convertUser(cls[10])}, - Page: users.Page{ - Total: 1, - Offset: offset, - Limit: limit, - }, - }, - }, - { - desc: "search for users with invalid token", - token: invalidToken, - page: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Username: "user_10", - }, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - response: nil, - authenticateErr: svcerr.ErrAuthentication, - }, - { - desc: "search for users with empty token", - token: "", - page: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Username: "user_10", - }, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - response: nil, - authenticateErr: svcerr.ErrAuthentication, - }, - { - desc: "search for users with empty query", - token: validToken, - page: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - FirstName: "", - }, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptySearchQuery), http.StatusBadRequest), - }, - { - desc: "search for users with invalid length of query", - token: validToken, - page: sdk.PageMetadata{ - Offset: offset, - Limit: limit, - Username: "a", - }, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrLenSearchQuery, apiutil.ErrValidation), http.StatusBadRequest), - }, - { - desc: "search for users with invalid limit", - token: validToken, - page: sdk.PageMetadata{ - Offset: offset, - Limit: 0, - Username: "user_10", - }, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize), http.StatusBadRequest), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID}, tc.authenticateErr) - svcCall := svc.On("SearchUsers", mock.Anything, mock.Anything).Return(tc.searchreturn, tc.err) - page, err := mgsdk.SearchUsers(tc.page, tc.token) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %v, got %v", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, page.Users, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page.Users)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestViewUser(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "view user successfully", - token: validToken, - userID: user.ID, - svcRes: convertUser(user), - svcErr: nil, - response: user, - err: nil, - }, - { - desc: "view user with invalid token", - token: invalidToken, - userID: user.ID, - svcRes: users.User{}, - svcErr: svcerr.ErrAuthentication, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view user with empty token", - token: "", - userID: user.ID, - svcRes: users.User{}, - svcErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view user with invalid id", - token: validToken, - userID: wrongID, - svcRes: users.User{}, - svcErr: svcerr.ErrViewEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "view user with empty id", - token: validToken, - userID: "", - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "view user with response that can't be unmarshalled", - token: validToken, - userID: user.ID, - svcRes: users.User{ - ID: id, - FirstName: user.FirstName, - LastName: user.LastName, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("View", mock.Anything, tc.session, tc.userID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.User(tc.userID, 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.userID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUserProfile(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "view user profile successfully", - token: validToken, - svcRes: convertUser(user), - svcErr: nil, - response: user, - err: nil, - }, - { - desc: "view user profile with invalid token", - token: invalidToken, - svcRes: users.User{}, - svcErr: nil, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "view user profile with empty token", - token: "", - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "view user profile with response that can't be unmarshalled", - token: validToken, - svcRes: users.User{ - ID: id, - FirstName: user.FirstName, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ViewProfile", mock.Anything, tc.session).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UserProfile(tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ViewProfile", mock.Anything, tc.session) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateUser(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - updatedName := "updatedName" - updatedUser := user - updatedUser.FirstName = updatedName - - cases := []struct { - desc string - token string - session mgauthn.Session - updateUserReq sdk.User - svcReq users.User - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "update user name with valid token", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - FirstName: updatedName, - }, - svcReq: users.User{ - ID: user.ID, - FirstName: updatedName, - }, - svcRes: convertUser(updatedUser), - svcErr: nil, - response: updatedUser, - err: nil, - }, - { - desc: "update user name with invalid token", - token: invalidToken, - updateUserReq: sdk.User{ - ID: user.ID, - FirstName: updatedName, - }, - svcReq: users.User{ - ID: user.ID, - FirstName: updatedName, - }, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update user name with invalid id", - token: validToken, - updateUserReq: sdk.User{ - ID: wrongID, - FirstName: updatedName, - }, - svcReq: users.User{ - ID: wrongID, - FirstName: updatedName, - }, - svcRes: users.User{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update user name with empty token", - token: "", - updateUserReq: sdk.User{ - ID: user.ID, - FirstName: updatedName, - }, - svcReq: users.User{ - ID: user.ID, - FirstName: updatedName, - }, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update user name with empty id", - token: validToken, - updateUserReq: sdk.User{ - ID: "", - FirstName: updatedName, - }, - svcReq: users.User{ - ID: "", - FirstName: updatedName, - }, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - { - desc: "update user with request that can't be marshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: generateUUID(t), - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "update user with response that can't be unmarshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - FirstName: updatedName, - }, - svcReq: users.User{ - ID: user.ID, - FirstName: updatedName, - }, - svcRes: users.User{ - ID: id, - FirstName: updatedName, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("Update", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateUser(tc.updateUserReq, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Update", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateUserTags(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - updatedTags := []string{"updatedTag1", "updatedTag2"} - - updatedUser := user - updatedUser.Tags = updatedTags - - cases := []struct { - desc string - token string - session mgauthn.Session - updateUserReq sdk.User - svcReq users.User - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "update user tags with valid token", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - Tags: updatedTags, - }, - svcReq: users.User{ - ID: user.ID, - Tags: updatedTags, - }, - svcRes: convertUser(updatedUser), - svcErr: nil, - response: updatedUser, - err: nil, - }, - { - desc: "update user tags with invalid token", - token: invalidToken, - updateUserReq: sdk.User{ - ID: user.ID, - Tags: updatedTags, - }, - svcReq: users.User{ - ID: user.ID, - Tags: updatedTags, - }, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update user tags with empty token", - token: "", - updateUserReq: sdk.User{ - ID: user.ID, - Tags: updatedTags, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update user tags with invalid id", - token: validToken, - updateUserReq: sdk.User{ - ID: wrongID, - Tags: updatedTags, - }, - svcReq: users.User{ - ID: wrongID, - Tags: updatedTags, - }, - svcRes: users.User{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update user tags with empty id", - token: validToken, - updateUserReq: sdk.User{ - ID: "", - Tags: updatedTags, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "update user tags with request that can't be marshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: generateUUID(t), - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "update user tags with response that can't be unmarshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - Tags: updatedTags, - }, - svcReq: users.User{ - ID: user.ID, - Tags: updatedTags, - }, - svcRes: users.User{ - ID: id, - Tags: updatedTags, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("UpdateTags", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateUserTags(tc.updateUserReq, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UpdateTags", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateUserEmail(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - updatedEmail := "updatedEmail@email.com" - updatedUser := user - updatedUser.Email = updatedEmail - - cases := []struct { - desc string - token string - session mgauthn.Session - updateUserReq sdk.User - svcReq string - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "update email with valid token", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - Email: updatedEmail, - Credentials: sdk.Credentials{ - Secret: user.Credentials.Secret, - }, - }, - svcReq: updatedEmail, - svcRes: convertUser(updatedUser), - svcErr: nil, - response: updatedUser, - err: nil, - }, - { - desc: "update email with invalid token", - token: invalidToken, - updateUserReq: sdk.User{ - ID: user.ID, - Email: updatedEmail, - Credentials: sdk.Credentials{ - Secret: user.Credentials.Secret, - }, - }, - svcReq: updatedEmail, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update email with empty token", - token: "", - updateUserReq: sdk.User{ - ID: user.ID, - Email: updatedEmail, - Credentials: sdk.Credentials{ - Secret: user.Credentials.Secret, - }, - }, - svcReq: updatedEmail, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update email with invalid id", - token: validToken, - updateUserReq: sdk.User{ - ID: wrongID, - Email: updatedEmail, - Credentials: sdk.Credentials{ - Secret: user.Credentials.Secret, - }, - }, - svcReq: updatedEmail, - svcRes: users.User{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update email with empty id", - token: validToken, - updateUserReq: sdk.User{ - ID: "", - Email: updatedEmail, - Credentials: sdk.Credentials{ - Secret: user.Credentials.Secret, - }, - }, - svcReq: updatedEmail, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "update email with response that can't be unmarshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - Email: updatedEmail, - Credentials: sdk.Credentials{ - Secret: user.Credentials.Secret, - }, - }, - svcReq: updatedEmail, - svcRes: users.User{ - ID: id, - FirstName: updatedEmail, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("UpdateEmail", mock.Anything, tc.session, tc.updateUserReq.ID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateUserEmail(tc.updateUserReq, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UpdateEmail", mock.Anything, tc.session, tc.updateUserReq.ID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestResetPasswordRequest(t *testing.T) { - ts, svc, _ := setupUsers() - defer ts.Close() - - defHost := "http://localhost" - - conf := sdk.Config{ - UsersURL: ts.URL, - HostURL: defHost, - } - mgsdk := sdk.NewSDK(conf) - - validEmail := "test@email.com" - - cases := []struct { - desc string - email string - svcRes users.User - svcErr error - issueRes *magistrala.Token - issueErr error - err errors.SDKError - }{ - { - desc: "reset password request with valid email", - email: validEmail, - svcRes: convertUser(user), - svcErr: nil, - issueRes: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken}, - err: nil, - }, - { - desc: "reset password request with invalid email", - email: "invalidemail", - svcRes: users.User{}, - svcErr: svcerr.ErrViewEntity, - issueRes: &magistrala.Token{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "reset password request with empty email", - email: "", - svcRes: users.User{}, - svcErr: nil, - issueRes: &magistrala.Token{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingEmail), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("GenerateResetToken", mock.Anything, tc.email, defHost).Return(tc.svcErr) - svcCall1 := svc.On("SendPasswordReset", mock.Anything, mock.Anything, tc.email, user.Credentials.Username, tc.issueRes.AccessToken).Return(nil) - err := mgsdk.ResetPasswordRequest(tc.email) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "GenerateResetToken", mock.Anything, tc.email, defHost) - assert.True(t, ok) - } - svcCall.Unset() - svcCall1.Unset() - }) - } -} - -func TestResetPassword(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - newPassword := "newPassword" - - cases := []struct { - desc string - token string - session mgauthn.Session - newPassword string - confPassword string - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "reset password successfully", - token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - newPassword: newPassword, - confPassword: newPassword, - svcErr: nil, - err: nil, - }, - { - desc: "reset password with invalid token", - token: invalidToken, - newPassword: newPassword, - confPassword: newPassword, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "reset password with empty token", - token: "", - newPassword: newPassword, - confPassword: newPassword, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "reset password with empty new password", - token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - newPassword: "", - confPassword: newPassword, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPass), http.StatusBadRequest), - }, - { - desc: "reset password with empty confirm password", - token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - newPassword: newPassword, - confPassword: "", - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingConfPass), http.StatusBadRequest), - }, - { - desc: "reset password with new password not matching confirm password", - token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - newPassword: newPassword, - confPassword: "wrongPassword", - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidResetPass), http.StatusBadRequest), - }, - { - desc: "reset password with weak password", - token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID}, - newPassword: "weak", - confPassword: "weak", - svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrPasswordFormat), http.StatusBadRequest), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("ResetSecret", mock.Anything, tc.session, tc.newPassword).Return(tc.svcErr) - err := mgsdk.ResetPassword(tc.newPassword, tc.confPassword, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ResetSecret", mock.Anything, tc.session, tc.newPassword) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdatePassword(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - newPassword := "newPassword" - updatedUser := user - updatedUser.Credentials.Secret = newPassword - - cases := []struct { - desc string - token string - session mgauthn.Session - oldPassword string - newPassword string - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "update password successfully", - token: validToken, - oldPassword: secret, - newPassword: newPassword, - svcRes: convertUser(updatedUser), - svcErr: nil, - response: updatedUser, - err: nil, - }, - { - desc: "update password with invalid token", - token: invalidToken, - oldPassword: secret, - newPassword: newPassword, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update password with empty token", - token: "", - oldPassword: secret, - newPassword: newPassword, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update password with empty old password", - token: validToken, - oldPassword: "", - newPassword: newPassword, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPass), http.StatusBadRequest), - }, - { - desc: "update password with empty new password", - token: validToken, - oldPassword: secret, - newPassword: "", - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingPass), http.StatusBadRequest), - }, - { - desc: "update password with invalid new password", - token: validToken, - oldPassword: secret, - newPassword: "weak", - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrPasswordFormat), http.StatusBadRequest), - }, - { - desc: "update password with invalid old password", - token: validToken, - oldPassword: "wrongPassword", - newPassword: newPassword, - svcRes: users.User{}, - svcErr: svcerr.ErrLogin, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrLogin, http.StatusUnauthorized), - }, - { - desc: "update password with response that can't be unmarshalled", - token: validToken, - oldPassword: secret, - newPassword: newPassword, - svcRes: users.User{ - ID: id, - FirstName: user.FirstName, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("UpdateSecret", mock.Anything, tc.session, tc.oldPassword, tc.newPassword).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdatePassword(tc.oldPassword, tc.newPassword, 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.oldPassword, tc.newPassword) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateUserRole(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - updatedUser := user - updatedRole := users.AdminRole.String() - updatedUser.Role = updatedRole - - cases := []struct { - desc string - token string - session mgauthn.Session - updateUserReq sdk.User - svcReq users.User - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "update user role with valid token", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - Role: updatedRole, - Email: user.Email, - }, - svcReq: users.User{ - ID: user.ID, - Role: users.AdminRole, - }, - svcRes: convertUser(updatedUser), - svcErr: nil, - response: updatedUser, - err: nil, - }, - { - desc: "update user role with invalid token", - token: invalidToken, - updateUserReq: sdk.User{ - ID: user.ID, - Role: updatedRole, - }, - svcReq: users.User{ - ID: user.ID, - Role: users.AdminRole, - }, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update user role with empty token", - token: "", - updateUserReq: sdk.User{ - ID: user.ID, - Role: updatedRole, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update user role with invalid id", - token: validToken, - updateUserReq: sdk.User{ - ID: wrongID, - Role: updatedRole, - }, - svcReq: users.User{ - ID: wrongID, - Role: users.AdminRole, - }, - svcRes: users.User{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update user role with empty id", - token: validToken, - updateUserReq: sdk.User{ - ID: "", - Role: updatedRole, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "update user role with request that can't be marshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: generateUUID(t), - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "update user role with response that can't be unmarshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - Role: updatedRole, - }, - svcReq: users.User{ - ID: user.ID, - Role: users.AdminRole, - }, - svcRes: users.User{ - ID: id, - Role: users.AdminRole, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("UpdateRole", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateUserRole(tc.updateUserReq, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UpdateRole", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateUsername(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - updatedUser := user - updatedUsername := "updatedUsername" - updatedUser.Credentials.Username = updatedUsername - - cases := []struct { - desc string - token string - session mgauthn.Session - updateUserReq sdk.User - svcReq users.User - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "update username with valid token", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - Credentials: sdk.Credentials{ - Username: updatedUsername, - }, - }, - svcReq: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: updatedUsername, - }, - }, - svcRes: convertUser(updatedUser), - svcErr: nil, - response: updatedUser, - err: nil, - }, - { - desc: "update username with invalid token", - token: invalidToken, - updateUserReq: sdk.User{ - ID: user.ID, - Credentials: sdk.Credentials{ - Username: updatedUsername, - }, - }, - svcReq: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: updatedUsername, - }, - }, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update username with empty token", - token: "", - updateUserReq: sdk.User{ - ID: user.ID, - Credentials: sdk.Credentials{ - Username: updatedUsername, - }, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update username with invalid id", - token: validToken, - updateUserReq: sdk.User{ - ID: wrongID, - Credentials: sdk.Credentials{ - Username: updatedUsername, - }, - }, - svcReq: users.User{ - ID: wrongID, - Credentials: users.Credentials{ - Username: updatedUsername, - }, - }, - svcRes: users.User{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update username with empty id", - token: validToken, - updateUserReq: sdk.User{ - ID: "", - Credentials: sdk.Credentials{ - Username: updatedUsername, - }, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "update username with response that can't be unmarshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - Credentials: sdk.Credentials{ - Username: updatedUsername, - }, - }, - svcReq: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: updatedUsername, - }, - }, - svcRes: users.User{ - ID: id, - Credentials: users.Credentials{ - Username: updatedUsername, - }, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("UpdateUsername", mock.Anything, tc.session, tc.svcReq.ID, tc.svcReq.Credentials.Username).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateUsername(tc.updateUserReq, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UpdateUsername", mock.Anything, tc.session, tc.svcReq.ID, tc.svcReq.Credentials.Username) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdateProfilePicture(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - updatedProfilePicture := "http://updated.com/profile.jpg" - updatedUser := user - updatedUser.Email = updatedProfilePicture - - cases := []struct { - desc string - token string - session mgauthn.Session - updateUserReq sdk.User - svcReq users.User - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "update profile picture with valid token", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - ProfilePicture: updatedProfilePicture, - }, - svcReq: users.User{ - ID: user.ID, - ProfilePicture: updatedProfilePicture, - }, - svcRes: convertUser(updatedUser), - svcErr: nil, - response: updatedUser, - err: nil, - }, - { - desc: "update profile picture with invalid token", - token: invalidToken, - updateUserReq: sdk.User{ - ID: user.ID, - ProfilePicture: updatedProfilePicture, - }, - svcReq: users.User{ - ID: user.ID, - ProfilePicture: updatedProfilePicture, - }, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "update profile picture with empty token", - token: "", - updateUserReq: sdk.User{ - ID: user.ID, - ProfilePicture: updatedProfilePicture, - }, - svcReq: users.User{ - ID: user.ID, - ProfilePicture: updatedProfilePicture, - }, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "update profile picture with invalid id", - token: validToken, - updateUserReq: sdk.User{ - ID: wrongID, - ProfilePicture: updatedProfilePicture, - }, - svcReq: users.User{ - ID: wrongID, - ProfilePicture: updatedProfilePicture, - }, - svcRes: users.User{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "update profile picture with empty id", - token: validToken, - updateUserReq: sdk.User{ - ID: "", - ProfilePicture: updatedProfilePicture, - }, - svcReq: users.User{ - ID: "", - ProfilePicture: updatedProfilePicture, - }, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "update profile picture with request that can't be marshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: generateUUID(t), - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: users.User{}, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "update profile picture with response that can't be unmarshalled", - token: validToken, - updateUserReq: sdk.User{ - ID: user.ID, - ProfilePicture: updatedProfilePicture, - }, - svcReq: users.User{ - ID: user.ID, - ProfilePicture: updatedProfilePicture, - }, - svcRes: users.User{ - ID: id, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("UpdateProfilePicture", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.UpdateProfilePicture(tc.updateUserReq, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "UpdateProfilePicture", mock.Anything, tc.session, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestEnableUser(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - enabledUser := user - enabledUser.Status = users.EnabledStatus.String() - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "enable user with valid token", - token: validToken, - userID: user.ID, - svcRes: convertUser(enabledUser), - svcErr: nil, - response: enabledUser, - err: nil, - }, - { - desc: "enable user with invalid token", - token: invalidToken, - userID: user.ID, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "enable user with empty token", - token: "", - userID: user.ID, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("Enable", mock.Anything, tc.session, tc.userID).Return(tc.svcRes, tc.svcErr) - - resp, err := mgsdk.EnableUser(tc.userID, 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.userID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDisableUser(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - disabledUser := user - disabledUser.Status = users.DisabledStatus.String() - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - svcRes users.User - svcErr error - authenticateErr error - response sdk.User - err errors.SDKError - }{ - { - desc: "disable user with valid token", - token: validToken, - userID: user.ID, - svcRes: convertUser(disabledUser), - svcErr: nil, - - response: disabledUser, - err: nil, - }, - { - desc: "disable user with invalid token", - token: invalidToken, - userID: user.ID, - svcRes: users.User{}, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "disable user with empty token", - token: "", - userID: user.ID, - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "disable user with invalid id", - token: validToken, - userID: wrongID, - svcRes: users.User{}, - svcErr: svcerr.ErrUpdateEntity, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrUpdateEntity, http.StatusUnprocessableEntity), - }, - { - desc: "disable user with empty id", - token: validToken, - userID: "", - svcRes: users.User{}, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "disable user with response that can't be unmarshalled", - token: validToken, - userID: user.ID, - svcRes: users.User{ - ID: id, - Status: users.DisabledStatus, - Metadata: users.Metadata{ - "key": make(chan int), - }, - }, - svcErr: nil, - response: sdk.User{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("Disable", mock.Anything, tc.session, tc.userID).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.DisableUser(tc.userID, 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.userID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListMembers(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - member := generateTestUser(t) - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - groupID string - pageMeta sdk.PageMetadata - svcReq users.Page - svcRes users.MembersPage - svcErr error - authenticateErr error - response sdk.UsersPage - err errors.SDKError - }{ - { - desc: "list members successfully", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: policies.ViewPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{convertUser(member)}, - }, - svcErr: nil, - response: sdk.UsersPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Users: []sdk.User{member}, - }, - }, - { - desc: "list members with invalid token", - token: invalidToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: policies.ViewPermission, - }, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list members with empty token", - token: "", - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list members with invalid group id", - token: validToken, - groupID: wrongID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: policies.ViewPermission, - }, - svcErr: svcerr.ErrViewEntity, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "list members with empty group id", - token: validToken, - groupID: "", - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), - }, - { - desc: "list members with page metadata that can't be marshalled", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: users.Page{}, - svcRes: users.MembersPage{}, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list members with response that can't be unmarshalled", - token: validToken, - groupID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: users.Page{ - Offset: 0, - Limit: 10, - Permission: policies.ViewPermission, - }, - svcRes: users.MembersPage{ - Page: users.Page{ - Total: 1, - }, - Members: []users.User{{ - ID: member.ID, - FirstName: member.FirstName, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.UsersPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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 := svc.On("ListMembers", mock.Anything, tc.session, "groups", tc.groupID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Members(tc.groupID, tc.pageMeta, tc.token) - assert.Equal(t, tc.err, err) - assert.Equal(t, tc.response, resp) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "ListMembers", mock.Anything, tc.session, "groups", tc.groupID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestDeleteUser(t *testing.T) { - ts, svc, auth := setupUsers() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - svcErr error - authenticateErr error - err errors.SDKError - }{ - { - desc: "delete user successfully", - token: validToken, - userID: validID, - svcErr: nil, - err: nil, - }, - { - desc: "delete user with invalid token", - token: invalidToken, - userID: validID, - authenticateErr: svcerr.ErrAuthentication, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "delete user with empty token", - token: "", - userID: validID, - svcErr: nil, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "delete user with invalid id", - token: validToken, - userID: wrongID, - svcErr: svcerr.ErrRemoveEntity, - err: errors.NewSDKErrorWithStatus(svcerr.ErrRemoveEntity, http.StatusUnprocessableEntity), - }, - { - desc: "delete user with empty id", - token: validToken, - userID: "", - svcErr: nil, - err: errors.NewSDKError(apiutil.ErrMissingID), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - tc.session = mgauthn.Session{DomainUserID: validID, UserID: validID, DomainID: domainID} - } - authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr) - svcCall := svc.On("Delete", mock.Anything, tc.session, tc.userID).Return(tc.svcErr) - err := mgsdk.DeleteUser(tc.userID, tc.token) - assert.Equal(t, tc.err, err) - if tc.err == nil { - ok := svcCall.Parent.AssertCalled(t, "Delete", mock.Anything, tc.session, tc.userID) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestListUserGroups(t *testing.T) { - ts, svc, auth := setupGroups() - defer ts.Close() - - conf := sdk.Config{ - UsersURL: ts.URL, - } - mgsdk := sdk.NewSDK(conf) - - group := generateTestGroup(t) - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - pageMeta sdk.PageMetadata - svcReq groups.Page - svcRes groups.Page - svcErr error - authenticateErr error - response sdk.GroupsPage - err errors.SDKError - }{ - { - desc: "list user groups successfully", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{convertGroup(group)}, - }, - svcErr: nil, - response: sdk.GroupsPage{ - PageRes: sdk.PageRes{ - Total: 1, - }, - Groups: []sdk.Group{group}, - }, - err: nil, - }, - { - desc: "list user groups with invalid token", - token: invalidToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{convertGroup(group)}, - }, - authenticateErr: svcerr.ErrAuthentication, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), - }, - { - desc: "list user groups with empty token", - token: "", - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), - }, - { - desc: "list user groups with invalid user id", - token: validToken, - userID: wrongID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{}, - svcErr: svcerr.ErrViewEntity, - response: sdk.GroupsPage{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), - }, - { - desc: "list user groups with page metadata that can't be marshalled", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - svcReq: groups.Page{}, - svcRes: groups.Page{}, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("json: unsupported type: chan int")), - }, - { - desc: "list user groups with response that can't be unmarshalled", - token: validToken, - userID: validID, - pageMeta: sdk.PageMetadata{ - Offset: 0, - Limit: 10, - DomainID: domainID, - }, - svcReq: groups.Page{ - PageMeta: groups.PageMeta{ - Offset: 0, - Limit: 10, - }, - Permission: policies.ViewPermission, - Direction: -1, - }, - svcRes: groups.Page{ - PageMeta: groups.PageMeta{ - Total: 1, - }, - Groups: []groups.Group{{ - ID: group.ID, - Name: group.Name, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }}, - }, - svcErr: nil, - response: sdk.GroupsPage{}, - err: errors.NewSDKError(errors.New("unexpected end of JSON input")), - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - if tc.token == validToken { - 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 := svc.On("ListGroups", mock.Anything, tc.session, "users", tc.userID, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.ListUserGroups(tc.userID, tc.pageMeta, 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, "users", tc.userID, tc.svcReq) - assert.True(t, ok) - } - svcCall.Unset() - authCall.Unset() - }) - } -} diff --git a/pkg/sdk/mocks/sdk.go b/pkg/sdk/mocks/sdk.go deleted file mode 100644 index 6e3d21d02..000000000 --- a/pkg/sdk/mocks/sdk.go +++ /dev/null @@ -1,3021 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - errors "github.com/absmach/magistrala/pkg/errors" - mock "github.com/stretchr/testify/mock" - - sdk "github.com/absmach/magistrala/pkg/sdk/go" - - time "time" -) - -// SDK is an autogenerated mock type for the SDK type -type SDK struct { - mock.Mock -} - -// AcceptInvitation provides a mock function with given fields: domainID, token -func (_m *SDK) AcceptInvitation(domainID string, token string) error { - ret := _m.Called(domainID, token) - - if len(ret) == 0 { - panic("no return value specified for AcceptInvitation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(domainID, token) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// AddBootstrap provides a mock function with given fields: cfg, domainID, token -func (_m *SDK) AddBootstrap(cfg sdk.BootstrapConfig, domainID string, token string) (string, errors.SDKError) { - ret := _m.Called(cfg, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for AddBootstrap") - } - - var r0 string - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.BootstrapConfig, string, string) (string, errors.SDKError)); ok { - return rf(cfg, domainID, token) - } - if rf, ok := ret.Get(0).(func(sdk.BootstrapConfig, string, string) string); ok { - r0 = rf(cfg, domainID, token) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(sdk.BootstrapConfig, string, string) errors.SDKError); ok { - r1 = rf(cfg, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// AddUserGroupToChannel provides a mock function with given fields: channelID, req, domainID, token -func (_m *SDK) AddUserGroupToChannel(channelID string, req sdk.UserGroupsRequest, domainID string, token string) errors.SDKError { - ret := _m.Called(channelID, req, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for AddUserGroupToChannel") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.UserGroupsRequest, string, string) errors.SDKError); ok { - r0 = rf(channelID, req, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// AddUserToChannel provides a mock function with given fields: channelID, req, domainID, token -func (_m *SDK) AddUserToChannel(channelID string, req sdk.UsersRelationRequest, domainID string, token string) errors.SDKError { - ret := _m.Called(channelID, req, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for AddUserToChannel") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.UsersRelationRequest, string, string) errors.SDKError); ok { - r0 = rf(channelID, req, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// AddUserToDomain provides a mock function with given fields: domainID, req, token -func (_m *SDK) AddUserToDomain(domainID string, req sdk.UsersRelationRequest, token string) errors.SDKError { - ret := _m.Called(domainID, req, token) - - if len(ret) == 0 { - panic("no return value specified for AddUserToDomain") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.UsersRelationRequest, string) errors.SDKError); ok { - r0 = rf(domainID, req, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// AddUserToGroup provides a mock function with given fields: groupID, req, domainID, token -func (_m *SDK) AddUserToGroup(groupID string, req sdk.UsersRelationRequest, domainID string, token string) errors.SDKError { - ret := _m.Called(groupID, req, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for AddUserToGroup") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.UsersRelationRequest, string, string) errors.SDKError); ok { - r0 = rf(groupID, req, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// Bootstrap provides a mock function with given fields: externalID, externalKey -func (_m *SDK) Bootstrap(externalID string, externalKey string) (sdk.BootstrapConfig, errors.SDKError) { - ret := _m.Called(externalID, externalKey) - - if len(ret) == 0 { - panic("no return value specified for Bootstrap") - } - - var r0 sdk.BootstrapConfig - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.BootstrapConfig, errors.SDKError)); ok { - return rf(externalID, externalKey) - } - if rf, ok := ret.Get(0).(func(string, string) sdk.BootstrapConfig); ok { - r0 = rf(externalID, externalKey) - } else { - r0 = ret.Get(0).(sdk.BootstrapConfig) - } - - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(externalID, externalKey) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// BootstrapSecure provides a mock function with given fields: externalID, externalKey, cryptoKey -func (_m *SDK) BootstrapSecure(externalID string, externalKey string, cryptoKey string) (sdk.BootstrapConfig, errors.SDKError) { - ret := _m.Called(externalID, externalKey, cryptoKey) - - if len(ret) == 0 { - panic("no return value specified for BootstrapSecure") - } - - var r0 sdk.BootstrapConfig - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.BootstrapConfig, errors.SDKError)); ok { - return rf(externalID, externalKey, cryptoKey) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.BootstrapConfig); ok { - r0 = rf(externalID, externalKey, cryptoKey) - } else { - r0 = ret.Get(0).(sdk.BootstrapConfig) - } - - if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(externalID, externalKey, cryptoKey) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Bootstraps provides a mock function with given fields: pm, domainID, token -func (_m *SDK) Bootstraps(pm sdk.PageMetadata, domainID string, token string) (sdk.BootstrapPage, errors.SDKError) { - ret := _m.Called(pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Bootstraps") - } - - var r0 sdk.BootstrapPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) (sdk.BootstrapPage, errors.SDKError)); ok { - return rf(pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) sdk.BootstrapPage); ok { - r0 = rf(pm, domainID, token) - } else { - r0 = ret.Get(0).(sdk.BootstrapPage) - } - - 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 -} - -// Channel provides a mock function with given fields: id, domainID, token -func (_m *SDK) Channel(id string, domainID string, token string) (sdk.Channel, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Channel") - } - - var r0 sdk.Channel - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Channel, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Channel); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Channel) - } - - 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 -} - -// ChannelPermissions provides a mock function with given fields: id, domainID, token -func (_m *SDK) ChannelPermissions(id string, domainID string, token string) (sdk.Channel, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ChannelPermissions") - } - - var r0 sdk.Channel - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Channel, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Channel); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Channel) - } - - 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 -} - -// Channels provides a mock function with given fields: pm, domainID, token -func (_m *SDK) Channels(pm sdk.PageMetadata, domainID string, token string) (sdk.ChannelsPage, errors.SDKError) { - ret := _m.Called(pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Channels") - } - - var r0 sdk.ChannelsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) (sdk.ChannelsPage, errors.SDKError)); ok { - return rf(pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) sdk.ChannelsPage); ok { - r0 = rf(pm, domainID, token) - } else { - r0 = ret.Get(0).(sdk.ChannelsPage) - } - - 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 -} - -// 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) - - if len(ret) == 0 { - panic("no return value specified for ChannelsByThing") - } - - 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) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.ChannelsPage); ok { - r0 = rf(thingID, 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) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Children provides a mock function with given fields: id, pm, domainID, token -func (_m *SDK) Children(id string, pm sdk.PageMetadata, domainID string, token string) (sdk.GroupsPage, errors.SDKError) { - ret := _m.Called(id, pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Children") - } - - var r0 sdk.GroupsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.GroupsPage, errors.SDKError)); ok { - return rf(id, pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.GroupsPage); ok { - r0 = rf(id, pm, domainID, token) - } else { - r0 = ret.Get(0).(sdk.GroupsPage) - } - - if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string, string) errors.SDKError); ok { - r1 = rf(id, 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) - - if len(ret) == 0 { - panic("no return value specified for Connect") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Connection, string, string) errors.SDKError); ok { - r0 = rf(conns, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for ConnectThing") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string, string) errors.SDKError); ok { - r0 = rf(thingID, chanID, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// CreateChannel provides a mock function with given fields: channel, domainID, token -func (_m *SDK) CreateChannel(channel sdk.Channel, domainID string, token string) (sdk.Channel, errors.SDKError) { - ret := _m.Called(channel, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for CreateChannel") - } - - var r0 sdk.Channel - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Channel, string, string) (sdk.Channel, errors.SDKError)); ok { - return rf(channel, domainID, token) - } - if rf, ok := ret.Get(0).(func(sdk.Channel, string, string) sdk.Channel); ok { - r0 = rf(channel, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Channel) - } - - if rf, ok := ret.Get(1).(func(sdk.Channel, string, string) errors.SDKError); ok { - r1 = rf(channel, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for CreateDomain") - } - - var r0 sdk.Domain - 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.Domain, string) sdk.Domain); ok { - r0 = rf(d, token) - } else { - r0 = ret.Get(0).(sdk.Domain) - } - - 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) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for CreateGroup") - } - - var r0 sdk.Group - 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.Group, string, string) sdk.Group); ok { - r0 = rf(group, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Group) - } - - 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) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for CreateSubscription") - } - - var r0 string - 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(string, string, string) string); ok { - r0 = rf(topic, contact, token) - } else { - r0 = ret.Get(0).(string) - } - - 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) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for CreateThing") - } - - var r0 sdk.Thing - 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.Thing, string, string) sdk.Thing); ok { - r0 = rf(thing, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Thing) - } - - if rf, ok := ret.Get(1).(func(sdk.Thing, string, string) errors.SDKError); ok { - r1 = rf(thing, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for CreateThings") - } - - var r0 []sdk.Thing - 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([]sdk.Thing, string, string) []sdk.Thing); ok { - r0 = rf(things, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]sdk.Thing) - } - } - - if rf, ok := ret.Get(1).(func([]sdk.Thing, string, string) errors.SDKError); ok { - r1 = rf(things, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// CreateToken provides a mock function with given fields: lt -func (_m *SDK) CreateToken(lt sdk.Login) (sdk.Token, errors.SDKError) { - ret := _m.Called(lt) - - if len(ret) == 0 { - panic("no return value specified for CreateToken") - } - - var r0 sdk.Token - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Login) (sdk.Token, errors.SDKError)); ok { - return rf(lt) - } - if rf, ok := ret.Get(0).(func(sdk.Login) sdk.Token); ok { - r0 = rf(lt) - } else { - r0 = ret.Get(0).(sdk.Token) - } - - if rf, ok := ret.Get(1).(func(sdk.Login) errors.SDKError); ok { - r1 = rf(lt) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// CreateUser provides a mock function with given fields: user, token -func (_m *SDK) CreateUser(user sdk.User, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(user, token) - - if len(ret) == 0 { - panic("no return value specified for CreateUser") - } - - var r0 sdk.User - 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.User, string) sdk.User); ok { - r0 = rf(user, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - 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) - } - } - - return r0, r1 -} - -// DeleteChannel provides a mock function with given fields: id, domainID, token -func (_m *SDK) DeleteChannel(id string, domainID string, token string) errors.SDKError { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for DeleteChannel") - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for DeleteGroup") - } - - 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 -} - -// DeleteInvitation provides a mock function with given fields: userID, domainID, token -func (_m *SDK) DeleteInvitation(userID string, domainID string, token string) error { - ret := _m.Called(userID, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for DeleteInvitation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string, string, string) error); ok { - r0 = rf(userID, domainID, token) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// DeleteSubscription provides a mock function with given fields: id, token -func (_m *SDK) DeleteSubscription(id string, token string) errors.SDKError { - ret := _m.Called(id, token) - - if len(ret) == 0 { - panic("no return value specified for DeleteSubscription") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) errors.SDKError); ok { - r0 = rf(id, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(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) - - if len(ret) == 0 { - panic("no return value specified for DeleteUser") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) errors.SDKError); ok { - r0 = rf(id, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// DisableChannel provides a mock function with given fields: id, domainID, token -func (_m *SDK) DisableChannel(id string, domainID string, token string) (sdk.Channel, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for DisableChannel") - } - - var r0 sdk.Channel - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Channel, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Channel); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Channel) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for DisableDomain") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) errors.SDKError); ok { - r0 = rf(domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// DisableGroup provides a mock function with given fields: id, domainID, token -func (_m *SDK) DisableGroup(id string, domainID string, token string) (sdk.Group, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for DisableGroup") - } - - var r0 sdk.Group - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Group, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Group); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Group) - } - - 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 -} - -// 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) - - if len(ret) == 0 { - panic("no return value specified for DisableUser") - } - - var r0 sdk.User - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.User, errors.SDKError)); ok { - return rf(id, token) - } - if rf, ok := ret.Get(0).(func(string, string) sdk.User); ok { - r0 = rf(id, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(id, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Disconnect provides a mock function with given fields: connIDs, domainID, token -func (_m *SDK) Disconnect(connIDs sdk.Connection, domainID string, token string) errors.SDKError { - ret := _m.Called(connIDs, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Disconnect") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Connection, string, string) errors.SDKError); ok { - r0 = rf(connIDs, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for DisconnectThing") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string, string) errors.SDKError); ok { - r0 = rf(thingID, chanID, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// Domain provides a mock function with given fields: domainID, token -func (_m *SDK) Domain(domainID string, token string) (sdk.Domain, errors.SDKError) { - ret := _m.Called(domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Domain") - } - - var r0 sdk.Domain - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.Domain, errors.SDKError)); ok { - return rf(domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string) sdk.Domain); ok { - r0 = rf(domainID, token) - } else { - r0 = ret.Get(0).(sdk.Domain) - } - - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// DomainPermissions provides a mock function with given fields: domainID, token -func (_m *SDK) DomainPermissions(domainID string, token string) (sdk.Domain, errors.SDKError) { - ret := _m.Called(domainID, token) - - if len(ret) == 0 { - panic("no return value specified for DomainPermissions") - } - - var r0 sdk.Domain - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.Domain, errors.SDKError)); ok { - return rf(domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string) sdk.Domain); ok { - r0 = rf(domainID, token) - } else { - r0 = ret.Get(0).(sdk.Domain) - } - - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Domains provides a mock function with given fields: pm, token -func (_m *SDK) Domains(pm sdk.PageMetadata, token string) (sdk.DomainsPage, errors.SDKError) { - ret := _m.Called(pm, token) - - if len(ret) == 0 { - panic("no return value specified for Domains") - } - - var r0 sdk.DomainsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) (sdk.DomainsPage, errors.SDKError)); ok { - return rf(pm, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) sdk.DomainsPage); ok { - r0 = rf(pm, token) - } else { - r0 = ret.Get(0).(sdk.DomainsPage) - } - - if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string) errors.SDKError); ok { - r1 = rf(pm, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// EnableChannel provides a mock function with given fields: id, domainID, token -func (_m *SDK) EnableChannel(id string, domainID string, token string) (sdk.Channel, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for EnableChannel") - } - - var r0 sdk.Channel - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Channel, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Channel); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Channel) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for EnableDomain") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) errors.SDKError); ok { - r0 = rf(domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// EnableGroup provides a mock function with given fields: id, domainID, token -func (_m *SDK) EnableGroup(id string, domainID string, token string) (sdk.Group, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for EnableGroup") - } - - var r0 sdk.Group - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Group, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Group); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Group) - } - - 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 -} - -// 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) - - if len(ret) == 0 { - panic("no return value specified for EnableUser") - } - - var r0 sdk.User - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.User, errors.SDKError)); ok { - return rf(id, token) - } - if rf, ok := ret.Get(0).(func(string, string) sdk.User); ok { - r0 = rf(id, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(id, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Group provides a mock function with given fields: id, domainID, token -func (_m *SDK) Group(id string, domainID string, token string) (sdk.Group, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Group") - } - - var r0 sdk.Group - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Group, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Group); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Group) - } - - 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 -} - -// GroupPermissions provides a mock function with given fields: id, domainID, token -func (_m *SDK) GroupPermissions(id string, domainID string, token string) (sdk.Group, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for GroupPermissions") - } - - var r0 sdk.Group - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Group, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Group); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Group) - } - - 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 -} - -// Groups provides a mock function with given fields: pm, domainID, token -func (_m *SDK) Groups(pm sdk.PageMetadata, domainID string, token string) (sdk.GroupsPage, errors.SDKError) { - ret := _m.Called(pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Groups") - } - - var r0 sdk.GroupsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) (sdk.GroupsPage, errors.SDKError)); ok { - return rf(pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string, string) sdk.GroupsPage); ok { - r0 = rf(pm, domainID, token) - } else { - r0 = ret.Get(0).(sdk.GroupsPage) - } - - 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 -} - -// Health provides a mock function with given fields: service -func (_m *SDK) Health(service string) (sdk.HealthInfo, errors.SDKError) { - ret := _m.Called(service) - - if len(ret) == 0 { - panic("no return value specified for Health") - } - - var r0 sdk.HealthInfo - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string) (sdk.HealthInfo, errors.SDKError)); ok { - return rf(service) - } - if rf, ok := ret.Get(0).(func(string) sdk.HealthInfo); ok { - r0 = rf(service) - } else { - r0 = ret.Get(0).(sdk.HealthInfo) - } - - if rf, ok := ret.Get(1).(func(string) errors.SDKError); ok { - r1 = rf(service) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Invitation provides a mock function with given fields: userID, domainID, token -func (_m *SDK) Invitation(userID string, domainID string, token string) (sdk.Invitation, error) { - ret := _m.Called(userID, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Invitation") - } - - var r0 sdk.Invitation - var r1 error - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Invitation, error)); ok { - return rf(userID, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Invitation); ok { - r0 = rf(userID, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Invitation) - } - - if rf, ok := ret.Get(1).(func(string, string, string) error); ok { - r1 = rf(userID, domainID, token) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Invitations provides a mock function with given fields: pm, token -func (_m *SDK) Invitations(pm sdk.PageMetadata, token string) (sdk.InvitationPage, error) { - ret := _m.Called(pm, token) - - if len(ret) == 0 { - panic("no return value specified for Invitations") - } - - var r0 sdk.InvitationPage - var r1 error - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) (sdk.InvitationPage, error)); ok { - return rf(pm, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) sdk.InvitationPage); ok { - r0 = rf(pm, token) - } else { - r0 = ret.Get(0).(sdk.InvitationPage) - } - - if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string) error); ok { - r1 = rf(pm, token) - } else { - r1 = ret.Error(1) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for IssueCert") - } - - 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) - } - if rf, ok := ret.Get(0).(func(string, string, string, string) sdk.Cert); ok { - r0 = rf(thingID, 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) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Journal provides a mock function with given fields: entityType, entityID, domainID, pm, token -func (_m *SDK) Journal(entityType string, entityID string, domainID string, pm sdk.PageMetadata, token string) (sdk.JournalsPage, error) { - ret := _m.Called(entityType, entityID, domainID, pm, token) - - if len(ret) == 0 { - panic("no return value specified for Journal") - } - - var r0 sdk.JournalsPage - var r1 error - if rf, ok := ret.Get(0).(func(string, string, string, sdk.PageMetadata, string) (sdk.JournalsPage, error)); ok { - return rf(entityType, entityID, domainID, pm, token) - } - if rf, ok := ret.Get(0).(func(string, string, string, sdk.PageMetadata, string) sdk.JournalsPage); ok { - r0 = rf(entityType, entityID, domainID, pm, token) - } else { - r0 = ret.Get(0).(sdk.JournalsPage) - } - - if rf, ok := ret.Get(1).(func(string, string, string, sdk.PageMetadata, string) error); ok { - r1 = rf(entityType, entityID, domainID, pm, token) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListChannelUserGroups provides a mock function with given fields: channelID, pm, domainID, token -func (_m *SDK) ListChannelUserGroups(channelID string, pm sdk.PageMetadata, domainID string, token string) (sdk.GroupsPage, errors.SDKError) { - ret := _m.Called(channelID, pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ListChannelUserGroups") - } - - var r0 sdk.GroupsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.GroupsPage, errors.SDKError)); ok { - return rf(channelID, pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.GroupsPage); ok { - r0 = rf(channelID, pm, domainID, token) - } else { - r0 = ret.Get(0).(sdk.GroupsPage) - } - - 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 -} - -// 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 -} - -// ListDomainUsers provides a mock function with given fields: domainID, pm, token -func (_m *SDK) ListDomainUsers(domainID string, pm sdk.PageMetadata, token string) (sdk.UsersPage, errors.SDKError) { - ret := _m.Called(domainID, pm, token) - - if len(ret) == 0 { - panic("no return value specified for ListDomainUsers") - } - - var r0 sdk.UsersPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)); ok { - return rf(domainID, pm, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.UsersPage); ok { - r0 = rf(domainID, pm, token) - } else { - r0 = ret.Get(0).(sdk.UsersPage) - } - - if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string) errors.SDKError); ok { - r1 = rf(domainID, pm, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// ListGroupChannels provides a mock function with given fields: groupID, pm, domainID, token -func (_m *SDK) ListGroupChannels(groupID string, pm sdk.PageMetadata, domainID string, token string) (sdk.ChannelsPage, errors.SDKError) { - ret := _m.Called(groupID, pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ListGroupChannels") - } - - 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(groupID, pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.ChannelsPage); ok { - r0 = rf(groupID, 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(groupID, pm, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// ListGroupUsers provides a mock function with given fields: groupID, pm, domainID, token -func (_m *SDK) ListGroupUsers(groupID string, pm sdk.PageMetadata, domainID string, token string) (sdk.UsersPage, errors.SDKError) { - ret := _m.Called(groupID, pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ListGroupUsers") - } - - 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(groupID, pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.UsersPage); ok { - r0 = rf(groupID, 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(groupID, pm, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// ListSubscriptions provides a mock function with given fields: pm, token -func (_m *SDK) ListSubscriptions(pm sdk.PageMetadata, token string) (sdk.SubscriptionPage, errors.SDKError) { - ret := _m.Called(pm, token) - - if len(ret) == 0 { - panic("no return value specified for ListSubscriptions") - } - - var r0 sdk.SubscriptionPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) (sdk.SubscriptionPage, errors.SDKError)); ok { - return rf(pm, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) sdk.SubscriptionPage); ok { - r0 = rf(pm, token) - } else { - r0 = ret.Get(0).(sdk.SubscriptionPage) - } - - if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string) errors.SDKError); ok { - r1 = rf(pm, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for ListThingUsers") - } - - 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(thingID, pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.UsersPage); ok { - r0 = rf(thingID, 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(thingID, pm, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - 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) { - ret := _m.Called(userID, pm, token) - - if len(ret) == 0 { - panic("no return value specified for ListUserChannels") - } - - var r0 sdk.ChannelsPage - var r1 errors.SDKError - 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) sdk.ChannelsPage); ok { - r0 = rf(userID, pm, token) - } else { - r0 = ret.Get(0).(sdk.ChannelsPage) - } - - 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 -} - -// ListUserDomains provides a mock function with given fields: userID, pm, token -func (_m *SDK) ListUserDomains(userID string, pm sdk.PageMetadata, token string) (sdk.DomainsPage, errors.SDKError) { - ret := _m.Called(userID, pm, token) - - if len(ret) == 0 { - panic("no return value specified for ListUserDomains") - } - - var r0 sdk.DomainsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.DomainsPage, errors.SDKError)); ok { - return rf(userID, pm, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.DomainsPage); ok { - r0 = rf(userID, pm, token) - } else { - r0 = ret.Get(0).(sdk.DomainsPage) - } - - 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 -} - -// ListUserGroups provides a mock function with given fields: userID, pm, token -func (_m *SDK) ListUserGroups(userID string, pm sdk.PageMetadata, token string) (sdk.GroupsPage, errors.SDKError) { - ret := _m.Called(userID, pm, token) - - if len(ret) == 0 { - panic("no return value specified for ListUserGroups") - } - - var r0 sdk.GroupsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.GroupsPage, errors.SDKError)); ok { - return rf(userID, pm, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.GroupsPage); ok { - r0 = rf(userID, pm, token) - } else { - r0 = ret.Get(0).(sdk.GroupsPage) - } - - 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 -} - -// 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) - - if len(ret) == 0 { - panic("no return value specified for Members") - } - - var r0 sdk.UsersPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)); ok { - return rf(groupID, meta, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string) sdk.UsersPage); ok { - r0 = rf(groupID, meta, token) - } else { - r0 = ret.Get(0).(sdk.UsersPage) - } - - if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string) errors.SDKError); ok { - r1 = rf(groupID, meta, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Parents provides a mock function with given fields: id, pm, domainID, token -func (_m *SDK) Parents(id string, pm sdk.PageMetadata, domainID string, token string) (sdk.GroupsPage, errors.SDKError) { - ret := _m.Called(id, pm, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for Parents") - } - - var r0 sdk.GroupsPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) (sdk.GroupsPage, errors.SDKError)); ok { - return rf(id, pm, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, sdk.PageMetadata, string, string) sdk.GroupsPage); ok { - r0 = rf(id, pm, domainID, token) - } else { - r0 = ret.Get(0).(sdk.GroupsPage) - } - - if rf, ok := ret.Get(1).(func(string, sdk.PageMetadata, string, string) errors.SDKError); ok { - r1 = rf(id, pm, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// ReadMessages provides a mock function with given fields: pm, chanID, domainID, token -func (_m *SDK) ReadMessages(pm sdk.MessagePageMetadata, chanID string, domainID string, token string) (sdk.MessagesPage, errors.SDKError) { - ret := _m.Called(pm, chanID, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ReadMessages") - } - - var r0 sdk.MessagesPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.MessagePageMetadata, string, string, string) (sdk.MessagesPage, errors.SDKError)); ok { - return rf(pm, chanID, domainID, token) - } - if rf, ok := ret.Get(0).(func(sdk.MessagePageMetadata, string, string, string) sdk.MessagesPage); ok { - r0 = rf(pm, chanID, domainID, token) - } else { - r0 = ret.Get(0).(sdk.MessagesPage) - } - - if rf, ok := ret.Get(1).(func(sdk.MessagePageMetadata, string, string, string) errors.SDKError); ok { - r1 = rf(pm, chanID, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// RefreshToken provides a mock function with given fields: token -func (_m *SDK) RefreshToken(token string) (sdk.Token, errors.SDKError) { - ret := _m.Called(token) - - if len(ret) == 0 { - panic("no return value specified for RefreshToken") - } - - var r0 sdk.Token - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string) (sdk.Token, errors.SDKError)); ok { - return rf(token) - } - if rf, ok := ret.Get(0).(func(string) sdk.Token); ok { - r0 = rf(token) - } else { - r0 = ret.Get(0).(sdk.Token) - } - - if rf, ok := ret.Get(1).(func(string) errors.SDKError); ok { - r1 = rf(token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// RejectInvitation provides a mock function with given fields: domainID, token -func (_m *SDK) RejectInvitation(domainID string, token string) error { - ret := _m.Called(domainID, token) - - if len(ret) == 0 { - panic("no return value specified for RejectInvitation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(domainID, token) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// RemoveBootstrap provides a mock function with given fields: id, domainID, token -func (_m *SDK) RemoveBootstrap(id string, domainID string, token string) errors.SDKError { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for RemoveBootstrap") - } - - 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 -} - -// RemoveUserFromChannel provides a mock function with given fields: channelID, req, domainID, token -func (_m *SDK) RemoveUserFromChannel(channelID string, req sdk.UsersRelationRequest, domainID string, token string) errors.SDKError { - ret := _m.Called(channelID, req, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for RemoveUserFromChannel") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.UsersRelationRequest, string, string) errors.SDKError); ok { - r0 = rf(channelID, req, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// RemoveUserFromDomain provides a mock function with given fields: domainID, userID, token -func (_m *SDK) RemoveUserFromDomain(domainID string, userID string, token string) errors.SDKError { - ret := _m.Called(domainID, userID, token) - - if len(ret) == 0 { - panic("no return value specified for RemoveUserFromDomain") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) errors.SDKError); ok { - r0 = rf(domainID, userID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// RemoveUserFromGroup provides a mock function with given fields: groupID, req, domainID, token -func (_m *SDK) RemoveUserFromGroup(groupID string, req sdk.UsersRelationRequest, domainID string, token string) errors.SDKError { - ret := _m.Called(groupID, req, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for RemoveUserFromGroup") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.UsersRelationRequest, string, string) errors.SDKError); ok { - r0 = rf(groupID, req, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// RemoveUserGroupFromChannel provides a mock function with given fields: channelID, req, domainID, token -func (_m *SDK) RemoveUserGroupFromChannel(channelID string, req sdk.UserGroupsRequest, domainID string, token string) errors.SDKError { - ret := _m.Called(channelID, req, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for RemoveUserGroupFromChannel") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, sdk.UserGroupsRequest, string, string) errors.SDKError); ok { - r0 = rf(channelID, req, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// ResetPassword provides a mock function with given fields: password, confPass, token -func (_m *SDK) ResetPassword(password string, confPass string, token string) errors.SDKError { - ret := _m.Called(password, confPass, token) - - if len(ret) == 0 { - panic("no return value specified for ResetPassword") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) errors.SDKError); ok { - r0 = rf(password, confPass, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// ResetPasswordRequest provides a mock function with given fields: email -func (_m *SDK) ResetPasswordRequest(email string) errors.SDKError { - ret := _m.Called(email) - - if len(ret) == 0 { - panic("no return value specified for ResetPasswordRequest") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string) errors.SDKError); ok { - r0 = rf(email) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(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) - - if len(ret) == 0 { - panic("no return value specified for RevokeCert") - } - - 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) - } - if rf, ok := ret.Get(0).(func(string, string, string) time.Time); ok { - r0 = rf(thingID, 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) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// SearchUsers provides a mock function with given fields: pm, token -func (_m *SDK) SearchUsers(pm sdk.PageMetadata, token string) (sdk.UsersPage, errors.SDKError) { - ret := _m.Called(pm, token) - - if len(ret) == 0 { - panic("no return value specified for SearchUsers") - } - - var r0 sdk.UsersPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)); ok { - return rf(pm, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) sdk.UsersPage); ok { - r0 = rf(pm, token) - } else { - r0 = ret.Get(0).(sdk.UsersPage) - } - - if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string) errors.SDKError); ok { - r1 = rf(pm, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// SendInvitation provides a mock function with given fields: invitation, token -func (_m *SDK) SendInvitation(invitation sdk.Invitation, token string) error { - ret := _m.Called(invitation, token) - - if len(ret) == 0 { - panic("no return value specified for SendInvitation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(sdk.Invitation, string) error); ok { - r0 = rf(invitation, token) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SendMessage provides a mock function with given fields: chanID, msg, key -func (_m *SDK) SendMessage(chanID string, msg string, key string) errors.SDKError { - ret := _m.Called(chanID, msg, key) - - if len(ret) == 0 { - panic("no return value specified for SendMessage") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) errors.SDKError); ok { - r0 = rf(chanID, msg, key) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// SetContentType provides a mock function with given fields: ct -func (_m *SDK) SetContentType(ct sdk.ContentType) errors.SDKError { - ret := _m.Called(ct) - - if len(ret) == 0 { - panic("no return value specified for SetContentType") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.ContentType) errors.SDKError); ok { - r0 = rf(ct) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(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) - - if len(ret) == 0 { - panic("no return value specified for ShareThing") - } - - 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) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UnshareThing") - } - - 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) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// UpdateBootstrap provides a mock function with given fields: cfg, domainID, token -func (_m *SDK) UpdateBootstrap(cfg sdk.BootstrapConfig, domainID string, token string) errors.SDKError { - ret := _m.Called(cfg, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateBootstrap") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.BootstrapConfig, string, string) errors.SDKError); ok { - r0 = rf(cfg, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// UpdateBootstrapCerts provides a mock function with given fields: id, clientCert, clientKey, ca, domainID, token -func (_m *SDK) UpdateBootstrapCerts(id string, clientCert string, clientKey string, ca string, domainID string, token string) (sdk.BootstrapConfig, errors.SDKError) { - ret := _m.Called(id, clientCert, clientKey, ca, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateBootstrapCerts") - } - - var r0 sdk.BootstrapConfig - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string, string, string, string) (sdk.BootstrapConfig, errors.SDKError)); ok { - return rf(id, clientCert, clientKey, ca, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string, string, string, string) sdk.BootstrapConfig); ok { - r0 = rf(id, clientCert, clientKey, ca, domainID, token) - } else { - r0 = ret.Get(0).(sdk.BootstrapConfig) - } - - if rf, ok := ret.Get(1).(func(string, string, string, string, string, string) errors.SDKError); ok { - r1 = rf(id, clientCert, clientKey, ca, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// UpdateBootstrapConnection provides a mock function with given fields: id, channels, domainID, token -func (_m *SDK) UpdateBootstrapConnection(id string, channels []string, domainID string, token string) errors.SDKError { - ret := _m.Called(id, channels, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateBootstrapConnection") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, []string, string, string) errors.SDKError); ok { - r0 = rf(id, channels, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// UpdateChannel provides a mock function with given fields: channel, domainID, token -func (_m *SDK) UpdateChannel(channel sdk.Channel, domainID string, token string) (sdk.Channel, errors.SDKError) { - ret := _m.Called(channel, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateChannel") - } - - var r0 sdk.Channel - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.Channel, string, string) (sdk.Channel, errors.SDKError)); ok { - return rf(channel, domainID, token) - } - if rf, ok := ret.Get(0).(func(sdk.Channel, string, string) sdk.Channel); ok { - r0 = rf(channel, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Channel) - } - - if rf, ok := ret.Get(1).(func(sdk.Channel, string, string) errors.SDKError); ok { - r1 = rf(channel, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdateDomain") - } - - var r0 sdk.Domain - 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.Domain, string) sdk.Domain); ok { - r0 = rf(d, token) - } else { - r0 = ret.Get(0).(sdk.Domain) - } - - 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) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdateGroup") - } - - var r0 sdk.Group - 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.Group, string, string) sdk.Group); ok { - r0 = rf(group, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Group) - } - - 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) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdatePassword") - } - - var r0 sdk.User - 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(string, string, string) sdk.User); ok { - r0 = rf(oldPass, newPass, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - 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) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdateProfilePicture") - } - - var r0 sdk.User - 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.User, string) sdk.User); ok { - r0 = rf(user, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - 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) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdateThing") - } - - var r0 sdk.Thing - 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.Thing, string, string) sdk.Thing); ok { - r0 = rf(thing, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Thing) - } - - if rf, ok := ret.Get(1).(func(sdk.Thing, string, string) errors.SDKError); ok { - r1 = rf(thing, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdateThingSecret") - } - - var r0 sdk.Thing - 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, string) sdk.Thing); ok { - r0 = rf(id, secret, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Thing) - } - - 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) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdateThingTags") - } - - var r0 sdk.Thing - 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.Thing, string, string) sdk.Thing); ok { - r0 = rf(thing, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Thing) - } - - if rf, ok := ret.Get(1).(func(sdk.Thing, string, string) errors.SDKError); ok { - r1 = rf(thing, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// UpdateUser provides a mock function with given fields: user, token -func (_m *SDK) UpdateUser(user sdk.User, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(user, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateUser") - } - - var r0 sdk.User - 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.User, string) sdk.User); ok { - r0 = rf(user, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - 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) - } - } - - return r0, r1 -} - -// UpdateUserEmail provides a mock function with given fields: user, token -func (_m *SDK) UpdateUserEmail(user sdk.User, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(user, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateUserEmail") - } - - var r0 sdk.User - 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.User, string) sdk.User); ok { - r0 = rf(user, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - 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) - } - } - - return r0, r1 -} - -// UpdateUserRole provides a mock function with given fields: user, token -func (_m *SDK) UpdateUserRole(user sdk.User, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(user, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateUserRole") - } - - var r0 sdk.User - 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.User, string) sdk.User); ok { - r0 = rf(user, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - 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) - } - } - - return r0, r1 -} - -// UpdateUserTags provides a mock function with given fields: user, token -func (_m *SDK) UpdateUserTags(user sdk.User, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(user, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateUserTags") - } - - var r0 sdk.User - 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.User, string) sdk.User); ok { - r0 = rf(user, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - 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) - } - } - - return r0, r1 -} - -// UpdateUsername provides a mock function with given fields: user, token -func (_m *SDK) UpdateUsername(user sdk.User, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(user, token) - - if len(ret) == 0 { - panic("no return value specified for UpdateUsername") - } - - var r0 sdk.User - 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.User, string) sdk.User); ok { - r0 = rf(user, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - 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) - } - } - - return r0, r1 -} - -// User provides a mock function with given fields: id, token -func (_m *SDK) User(id string, token string) (sdk.User, errors.SDKError) { - ret := _m.Called(id, token) - - if len(ret) == 0 { - panic("no return value specified for User") - } - - var r0 sdk.User - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.User, errors.SDKError)); ok { - return rf(id, token) - } - if rf, ok := ret.Get(0).(func(string, string) sdk.User); ok { - r0 = rf(id, token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(id, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// UserProfile provides a mock function with given fields: token -func (_m *SDK) UserProfile(token string) (sdk.User, errors.SDKError) { - ret := _m.Called(token) - - if len(ret) == 0 { - panic("no return value specified for UserProfile") - } - - var r0 sdk.User - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string) (sdk.User, errors.SDKError)); ok { - return rf(token) - } - if rf, ok := ret.Get(0).(func(string) sdk.User); ok { - r0 = rf(token) - } else { - r0 = ret.Get(0).(sdk.User) - } - - if rf, ok := ret.Get(1).(func(string) errors.SDKError); ok { - r1 = rf(token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// Users provides a mock function with given fields: pm, token -func (_m *SDK) Users(pm sdk.PageMetadata, token string) (sdk.UsersPage, errors.SDKError) { - ret := _m.Called(pm, token) - - if len(ret) == 0 { - panic("no return value specified for Users") - } - - var r0 sdk.UsersPage - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) (sdk.UsersPage, errors.SDKError)); ok { - return rf(pm, token) - } - if rf, ok := ret.Get(0).(func(sdk.PageMetadata, string) sdk.UsersPage); ok { - r0 = rf(pm, token) - } else { - r0 = ret.Get(0).(sdk.UsersPage) - } - - if rf, ok := ret.Get(1).(func(sdk.PageMetadata, string) errors.SDKError); ok { - r1 = rf(pm, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// ViewBootstrap provides a mock function with given fields: id, domainID, token -func (_m *SDK) ViewBootstrap(id string, domainID string, token string) (sdk.BootstrapConfig, errors.SDKError) { - ret := _m.Called(id, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ViewBootstrap") - } - - var r0 sdk.BootstrapConfig - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.BootstrapConfig, errors.SDKError)); ok { - return rf(id, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.BootstrapConfig); ok { - r0 = rf(id, domainID, token) - } else { - r0 = ret.Get(0).(sdk.BootstrapConfig) - } - - 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 -} - -// ViewCert provides a mock function with given fields: certID, domainID, token -func (_m *SDK) ViewCert(certID string, domainID string, token string) (sdk.Cert, errors.SDKError) { - ret := _m.Called(certID, domainID, token) - - if len(ret) == 0 { - panic("no return value specified for ViewCert") - } - - var r0 sdk.Cert - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string, string) (sdk.Cert, errors.SDKError)); ok { - return rf(certID, domainID, token) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.Cert); ok { - r0 = rf(certID, domainID, token) - } else { - r0 = ret.Get(0).(sdk.Cert) - } - - if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { - r1 = rf(certID, domainID, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for ViewCertByThing") - } - - 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) - } - if rf, ok := ret.Get(0).(func(string, string, string) sdk.CertSerials); ok { - r0 = rf(thingID, 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) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - return r0, r1 -} - -// ViewSubscription provides a mock function with given fields: id, token -func (_m *SDK) ViewSubscription(id string, token string) (sdk.Subscription, errors.SDKError) { - ret := _m.Called(id, token) - - if len(ret) == 0 { - panic("no return value specified for ViewSubscription") - } - - var r0 sdk.Subscription - var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.Subscription, errors.SDKError)); ok { - return rf(id, token) - } - if rf, ok := ret.Get(0).(func(string, string) sdk.Subscription); ok { - r0 = rf(id, token) - } else { - r0 = ret.Get(0).(sdk.Subscription) - } - - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(id, token) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(errors.SDKError) - } - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for Whitelist") - } - - var r0 errors.SDKError - if rf, ok := ret.Get(0).(func(string, int, string, string) errors.SDKError); ok { - r0 = rf(thingID, state, domainID, token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(errors.SDKError) - } - } - - return r0 -} - -// NewSDK creates a new instance of SDK. 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 NewSDK(t interface { - mock.TestingT - Cleanup(func()) -}) *SDK { - mock := &SDK{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/server/coap/coap.go b/pkg/server/coap/coap.go deleted file mode 100644 index 62e7963e8..000000000 --- a/pkg/server/coap/coap.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package coap - -import ( - "context" - "fmt" - "log/slog" - "time" - - "github.com/absmach/magistrala/pkg/server" - gocoap "github.com/plgd-dev/go-coap/v3" - "github.com/plgd-dev/go-coap/v3/mux" -) - -type coapServer struct { - server.BaseServer - handler mux.HandlerFunc -} - -var _ server.Server = (*coapServer)(nil) - -func NewServer(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, handler mux.HandlerFunc, logger *slog.Logger) server.Server { - baseServer := server.NewBaseServer(ctx, cancel, name, config, logger) - - return &coapServer{ - BaseServer: baseServer, - handler: handler, - } -} - -func (s *coapServer) Start() error { - errCh := make(chan error) - s.Logger.Info(fmt.Sprintf("%s service started using http, exposed port %s", s.Name, s.Address)) - s.Logger.Info(fmt.Sprintf("%s service %s server listening at %s without TLS", s.Name, s.Protocol, s.Address)) - - go func() { - errCh <- gocoap.ListenAndServe("udp", s.Address, s.handler) - }() - - select { - case <-s.Ctx.Done(): - return s.Stop() - case err := <-errCh: - return err - } -} - -func (s *coapServer) Stop() error { - defer s.Cancel() - c := make(chan bool) - defer close(c) - select { - case <-c: - case <-time.After(server.StopWaitTime): - } - s.Logger.Info(fmt.Sprintf("%s service shutdown of http at %s", s.Name, s.Address)) - return nil -} diff --git a/pkg/server/coap/doc.go b/pkg/server/coap/doc.go deleted file mode 100644 index 5abb027ab..000000000 --- a/pkg/server/coap/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package coap contains the CoAP server implementation. -package coap diff --git a/pkg/server/doc.go b/pkg/server/doc.go deleted file mode 100644 index d5514a242..000000000 --- a/pkg/server/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package server contains the HTTP, gRPC and CoAP server implementation. -package server diff --git a/pkg/server/grpc/doc.go b/pkg/server/grpc/doc.go deleted file mode 100644 index 7e56327ff..000000000 --- a/pkg/server/grpc/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package grpc contains the gRPC server implementation. -package grpc diff --git a/pkg/server/grpc/grpc.go b/pkg/server/grpc/grpc.go deleted file mode 100644 index c57c9a673..000000000 --- a/pkg/server/grpc/grpc.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpc - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "log/slog" - "net" - "os" - "time" - - "github.com/absmach/magistrala/pkg/server" - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/health" - grpchealth "google.golang.org/grpc/health/grpc_health_v1" -) - -type serviceRegister func(srv *grpc.Server) - -type grpcServer struct { - server.BaseServer - server *grpc.Server - registerService serviceRegister - health *health.Server -} - -var _ server.Server = (*grpcServer)(nil) - -func NewServer(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, registerService serviceRegister, logger *slog.Logger) server.Server { - baseServer := server.NewBaseServer(ctx, cancel, name, config, logger) - - return &grpcServer{ - BaseServer: baseServer, - registerService: registerService, - } -} - -func (s *grpcServer) Start() error { - errCh := make(chan error) - grpcServerOptions := []grpc.ServerOption{ - grpc.StatsHandler(otelgrpc.NewServerHandler()), - } - - listener, err := net.Listen("tcp", s.Address) - if err != nil { - return fmt.Errorf("failed to listen on port %s: %w", s.Address, err) - } - creds := grpc.Creds(insecure.NewCredentials()) - - switch { - case s.Config.CertFile != "" || s.Config.KeyFile != "": - certificate, err := tls.LoadX509KeyPair(s.Config.CertFile, s.Config.KeyFile) - if err != nil { - return fmt.Errorf("failed to load auth gRPC client certificates: %w", err) - } - tlsConfig := &tls.Config{ - ClientAuth: tls.RequireAndVerifyClientCert, - Certificates: []tls.Certificate{certificate}, - } - - var mtlsCA string - // Loading Server CA file - rootCA, err := loadCertFile(s.Config.ServerCAFile) - if err != nil { - return fmt.Errorf("failed to load root ca file: %w", err) - } - if len(rootCA) > 0 { - if tlsConfig.RootCAs == nil { - tlsConfig.RootCAs = x509.NewCertPool() - } - if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCA) { - return fmt.Errorf("failed to append root ca to tls.Config") - } - mtlsCA = fmt.Sprintf("root ca %s", s.Config.ServerCAFile) - } - - // Loading Client CA File - clientCA, err := loadCertFile(s.Config.ClientCAFile) - if err != nil { - return fmt.Errorf("failed to load client ca file: %w", err) - } - if len(clientCA) > 0 { - if tlsConfig.ClientCAs == nil { - tlsConfig.ClientCAs = x509.NewCertPool() - } - if !tlsConfig.ClientCAs.AppendCertsFromPEM(clientCA) { - return fmt.Errorf("failed to append client ca to tls.Config") - } - mtlsCA = fmt.Sprintf("%s client ca %s", mtlsCA, s.Config.ClientCAFile) - } - creds = grpc.Creds(credentials.NewTLS(tlsConfig)) - switch { - case mtlsCA != "": - s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with TLS/mTLS cert %s , key %s and %s", s.Name, s.Address, s.Config.CertFile, s.Config.KeyFile, mtlsCA)) - default: - s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with TLS cert %s and key %s", s.Name, s.Address, s.Config.CertFile, s.Config.KeyFile)) - } - default: - s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s without TLS", s.Name, s.Address)) - } - - grpcServerOptions = append(grpcServerOptions, creds) - - s.server = grpc.NewServer(grpcServerOptions...) - s.health = health.NewServer() - grpchealth.RegisterHealthServer(s.server, s.health) - s.registerService(s.server) - s.health.SetServingStatus(s.Name, grpchealth.HealthCheckResponse_SERVING) - - go func() { - errCh <- s.server.Serve(listener) - }() - - select { - case <-s.Ctx.Done(): - return s.Stop() - case err := <-errCh: - s.Cancel() - return err - } -} - -func (s *grpcServer) Stop() error { - defer s.Cancel() - c := make(chan bool) - go func() { - defer close(c) - s.health.Shutdown() - s.server.GracefulStop() - }() - select { - case <-c: - case <-time.After(server.StopWaitTime): - } - s.Logger.Info(fmt.Sprintf("%s gRPC service shutdown at %s", s.Name, s.Address)) - - return nil -} - -func loadCertFile(certFile string) ([]byte, error) { - if certFile != "" { - return os.ReadFile(certFile) - } - return []byte{}, nil -} diff --git a/pkg/server/http/doc.go b/pkg/server/http/doc.go deleted file mode 100644 index 769fa7d4a..000000000 --- a/pkg/server/http/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package http contains the HTTP server implementation. -package http diff --git a/pkg/server/http/http.go b/pkg/server/http/http.go deleted file mode 100644 index d8a33332a..000000000 --- a/pkg/server/http/http.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "context" - "fmt" - "log/slog" - "net/http" - - "github.com/absmach/magistrala/pkg/server" -) - -const ( - httpProtocol = "http" - httpsProtocol = "https" -) - -type httpServer struct { - server.BaseServer - server *http.Server -} - -var _ server.Server = (*httpServer)(nil) - -func NewServer(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, handler http.Handler, logger *slog.Logger) server.Server { - baseServer := server.NewBaseServer(ctx, cancel, name, config, logger) - hserver := &http.Server{Addr: baseServer.Address, Handler: handler} - - return &httpServer{ - BaseServer: baseServer, - server: hserver, - } -} - -func (s *httpServer) Start() error { - errCh := make(chan error) - s.Protocol = httpProtocol - switch { - case s.Config.CertFile != "" || s.Config.KeyFile != "": - s.Protocol = httpsProtocol - s.Logger.Info(fmt.Sprintf("%s service %s server listening at %s with TLS cert %s and key %s", s.Name, s.Protocol, s.Address, s.Config.CertFile, s.Config.KeyFile)) - go func() { - errCh <- s.server.ListenAndServeTLS(s.Config.CertFile, s.Config.KeyFile) - }() - default: - s.Logger.Info(fmt.Sprintf("%s service %s server listening at %s without TLS", s.Name, s.Protocol, s.Address)) - go func() { - errCh <- s.server.ListenAndServe() - }() - } - select { - case <-s.Ctx.Done(): - return s.Stop() - case err := <-errCh: - return err - } -} - -func (s *httpServer) Stop() error { - defer s.Cancel() - ctx, cancel := context.WithTimeout(context.Background(), server.StopWaitTime) - defer cancel() - if err := s.server.Shutdown(ctx); err != nil { - s.Logger.Error(fmt.Sprintf("%s service %s server error occurred during shutdown at %s: %s", s.Name, s.Protocol, s.Address, err)) - return fmt.Errorf("%s service %s server error occurred during shutdown at %s: %w", s.Name, s.Protocol, s.Address, err) - } - s.Logger.Info(fmt.Sprintf("%s %s service shutdown of http at %s", s.Name, s.Protocol, s.Address)) - return nil -} diff --git a/pkg/server/server.go b/pkg/server/server.go deleted file mode 100644 index 1ae357e32..000000000 --- a/pkg/server/server.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package server - -import ( - "context" - "fmt" - "log/slog" - "os" - "os/signal" - "syscall" - "time" -) - -const StopWaitTime = 5 * time.Second - -// Server is an interface that defines the methods to start and stop a server. -type Server interface { - // Start starts the server. - Start() error - // Stop stops the server. - Stop() error -} - -// Config is a struct that contains the configuration for the server. -type Config struct { - Host string `env:"HOST" envDefault:"localhost"` - Port string `env:"PORT" envDefault:""` - CertFile string `env:"SERVER_CERT" envDefault:""` - KeyFile string `env:"SERVER_KEY" envDefault:""` - ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""` - ClientCAFile string `env:"CLIENT_CA_CERTS" envDefault:""` -} - -type BaseServer struct { - Ctx context.Context - Cancel context.CancelFunc - Name string - Address string - Config Config - Logger *slog.Logger - Protocol string -} - -func NewBaseServer(ctx context.Context, cancel context.CancelFunc, name string, config Config, logger *slog.Logger) BaseServer { - address := fmt.Sprintf("%s:%s", config.Host, config.Port) - - return BaseServer{ - Ctx: ctx, - Cancel: cancel, - Name: name, - Address: address, - Config: config, - Logger: logger, - } -} - -func stopAllServer(servers ...Server) error { - var err error - for _, server := range servers { - err1 := server.Stop() - if err1 != nil { - if err == nil { - err = fmt.Errorf("%w", err1) - } else { - err = fmt.Errorf("%v ; %w", err, err1) - } - } - } - return err -} - -// StopSignalHandler stops the server when a signal is received. -func StopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *slog.Logger, svcName string, servers ...Server) error { - var err error - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT, syscall.SIGABRT) - select { - case sig := <-c: - defer cancel() - err = stopAllServer(servers...) - if err != nil { - logger.Error(fmt.Sprintf("%s service error during shutdown: %v", svcName, err)) - } - logger.Info(fmt.Sprintf("%s service shutdown by signal: %s", svcName, sig)) - return err - case <-ctx.Done(): - return nil - } -} diff --git a/pkg/transformers/README.md b/pkg/transformers/README.md deleted file mode 100644 index 44a21202a..000000000 --- a/pkg/transformers/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Message Transformers - -A transformer service consumes events published by Magistrala adapters (such as MQTT and HTTP adapters) and transforms them to an arbitrary message format. A transformer can be imported as a standalone package and used for message transformation on the consumer side. - -Magistrala [SenML transformer](transformer) is an example of Transformer service for SenML messages. - -Magistrala [writers](writers) are using a standalone SenML transformer to preprocess messages before storing them. - -[transformers]: https://github.com/absmach/magistrala/tree/master/transformers/senml -[writers]: https://github.com/absmach/magistrala/tree/master/writers diff --git a/pkg/transformers/doc.go b/pkg/transformers/doc.go deleted file mode 100644 index 59ccb9a10..000000000 --- a/pkg/transformers/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package transformers contains the domain concept definitions needed to -// support Magistrala transformer services functionality. -package transformers diff --git a/pkg/transformers/json/README.md b/pkg/transformers/json/README.md deleted file mode 100644 index 4e34ed510..000000000 --- a/pkg/transformers/json/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# JSON Message Transformer - -JSON Transformer provides Message Transformer for JSON messages. -To transform Magistrala Message successfully, the payload must be a JSON object. - -For the messages that contain _JSON array as the root element_, JSON Transformer does normalization of the data: it creates a separate JSON message for each JSON object in the root. In order to be processed and stored properly, JSON messages need to contain message format information. For the sake of the simpler storing of the messages, nested JSON objects are flatten to a single JSON object, using composite keys with the default separator `/`. This implies that the separator character (`/`) _is not allowed in the JSON object key_. For example, the following JSON object: -```json -{ - "name": "name", - "id":8659456789564231564, - "in": 3.145, - "alarm": true, - "ts": 1571259850000, - "d": { - "tmp": 2.564, - "hmd": 87, - "loc": { - "x": 1, - "y": 2 - } - } -} -``` - -will be transformed to: - -```json - -{ - "name": "name", - "id":8659456789564231564, - "in": 3.145, - "alarm": true, - "ts": 1571259850000, - "d/tmp": 2.564, - "d/hmd": 87, - "d/loc/x": 1, - "d/loc/y": 2 -} -``` - -The message format is stored in *the subtopic*. It's the last part of the subtopic. In the example: - -``` -http://localhost:8008/channels//messages/home/temperature/myFormat -``` - -the message format is `myFormat`. It can be any valid subtopic name, JSON transformer is format-agnostic. The format is used by the JSON message consumers so that they can process the message properly. If the format is not present (i.e. message subtopic is empty), JSON Transformer will report an error. Since the Transformer is agnostic to the format, having format in the subtopic does not prevent the publisher to send the content of different formats to the same subtopic. It's up to the consumer to handle this kind of issue. Message writers, for example, will store the message(s) in the table/collection/measurement (depending on the underlying database) with the name of the format (which in the example is `myFormat`). Magistrala writers will try to save any format received (whether it will be successful depends on the writer implementation and the underlying database), but it's recommended that the publisher takes care not to send different formats to the same subtopic. - -Having a message format in the subtopic means that the subscriber has an option to subscribe to only one message format. This is a nice feature because message subscribers know what's the expected format of the message so that they can process it. If the message format is not important, wildcard subtopic can always be used to subscribe to any message format: - -``` -http://localhost:8185/channels//messages/home/temperature/* -``` diff --git a/pkg/transformers/json/doc.go b/pkg/transformers/json/doc.go deleted file mode 100644 index dc1b6c398..000000000 --- a/pkg/transformers/json/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package json contains JSON transformer. -package json diff --git a/pkg/transformers/json/example_test.go b/pkg/transformers/json/example_test.go deleted file mode 100644 index 27eaa276d..000000000 --- a/pkg/transformers/json/example_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package json_test - -import ( - "encoding/json" - "fmt" - - mgjson "github.com/absmach/magistrala/pkg/transformers/json" -) - -func ExampleParseFlat() { - in := map[string]interface{}{ - "key1": "value1", - "key2": "value2", - "key5/nested1/nested2": "value3", - "key5/nested1/nested3": "value4", - "key5/nested2/nested4": "value5", - } - - out := mgjson.ParseFlat(in) - b, err := json.MarshalIndent(out, "", " ") - if err != nil { - panic(err) - } - fmt.Println(string(b)) - // Output:{ - // "key1": "value1", - // "key2": "value2", - // "key5": { - // "nested1": { - // "nested2": "value3", - // "nested3": "value4" - // }, - // "nested2": { - // "nested4": "value5" - // } - // } - // } -} - -func ExampleFlatten() { - in := map[string]interface{}{ - "key1": "value1", - "key2": "value2", - "key5": map[string]interface{}{ - "nested1": map[string]interface{}{ - "nested2": "value3", - "nested3": "value4", - }, - "nested2": map[string]interface{}{ - "nested4": "value5", - }, - }, - } - out, err := mgjson.Flatten(in) - if err != nil { - panic(err) - } - b, err := json.MarshalIndent(out, "", " ") - if err != nil { - panic(err) - } - fmt.Println(string(b)) - // Output:{ - // "key1": "value1", - // "key2": "value2", - // "key5/nested1/nested2": "value3", - // "key5/nested1/nested3": "value4", - // "key5/nested2/nested4": "value5" - // } -} diff --git a/pkg/transformers/json/message.go b/pkg/transformers/json/message.go deleted file mode 100644 index ab5b1b6da..000000000 --- a/pkg/transformers/json/message.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package json - -// Payload represents JSON Message payload. -type Payload map[string]interface{} - -// Message represents a JSON messages. -type Message struct { - Channel string `json:"channel,omitempty" db:"channel" bson:"channel"` - Created int64 `json:"created,omitempty" db:"created" bson:"created"` - Subtopic string `json:"subtopic,omitempty" db:"subtopic" bson:"subtopic,omitempty"` - Publisher string `json:"publisher,omitempty" db:"publisher" bson:"publisher"` - Protocol string `json:"protocol,omitempty" db:"protocol" bson:"protocol"` - Payload Payload `json:"payload,omitempty" db:"payload" bson:"payload,omitempty"` -} - -// Messages represents a list of JSON messages. -type Messages struct { - Data []Message - Format string -} diff --git a/pkg/transformers/json/time.go b/pkg/transformers/json/time.go deleted file mode 100644 index 6495ea8f7..000000000 --- a/pkg/transformers/json/time.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package json - -import ( - "math" - "strconv" - "strings" - "time" - - "github.com/absmach/magistrala/pkg/errors" -) - -var errUnsupportedFormat = errors.New("unsupported time format") - -func parseTimestamp(format string, timestamp interface{}, location string) (time.Time, error) { - switch format { - case "unix", "unix_ms", "unix_us", "unix_ns": - return parseUnix(format, timestamp) - default: - if location == "" { - location = "UTC" - } - return parseTime(format, timestamp, location) - } -} - -func parseUnix(format string, timestamp interface{}) (time.Time, error) { - integer, fractional, err := parseComponents(timestamp) - if err != nil { - return time.Unix(0, 0), err - } - - switch strings.ToLower(format) { - case "unix": - return time.Unix(integer, fractional).UTC(), nil - case "unix_ms": - return time.Unix(0, integer*1e6).UTC(), nil - case "unix_us": - return time.Unix(0, integer*1e3).UTC(), nil - case "unix_ns": - return time.Unix(0, integer).UTC(), nil - default: - return time.Unix(0, 0), errUnsupportedFormat - } -} - -func parseComponents(timestamp interface{}) (int64, int64, error) { - switch ts := timestamp.(type) { - case string: - parts := strings.SplitN(ts, ".", 2) - if len(parts) == 2 { - return parseUnixTimeComponents(parts[0], parts[1]) - } - - parts = strings.SplitN(ts, ",", 2) - if len(parts) == 2 { - return parseUnixTimeComponents(parts[0], parts[1]) - } - - integer, err := strconv.ParseInt(ts, 10, 64) - if err != nil { - return 0, 0, err - } - return integer, 0, nil - case int8: - return int64(ts), 0, nil - case int16: - return int64(ts), 0, nil - case int32: - return int64(ts), 0, nil - case int64: - return ts, 0, nil - case uint8: - return int64(ts), 0, nil - case uint16: - return int64(ts), 0, nil - case uint32: - return int64(ts), 0, nil - case uint64: - return int64(ts), 0, nil - case float32: - integer, fractional := math.Modf(float64(ts)) - return int64(integer), int64(fractional * 1e9), nil - case float64: - integer, fractional := math.Modf(ts) - return int64(integer), int64(fractional * 1e9), nil - default: - return 0, 0, errUnsupportedFormat - } -} - -func parseUnixTimeComponents(first, second string) (int64, int64, error) { - integer, err := strconv.ParseInt(first, 10, 64) - if err != nil { - return 0, 0, err - } - - // Convert to nanoseconds, dropping any greater precision. - buf := []byte("000000000") - copy(buf, second) - - fractional, err := strconv.ParseInt(string(buf), 10, 64) - if err != nil { - return 0, 0, err - } - return integer, fractional, nil -} - -func parseTime(format string, timestamp interface{}, location string) (time.Time, error) { - switch ts := timestamp.(type) { - case string: - loc, err := time.LoadLocation(location) - if err != nil { - return time.Unix(0, 0), err - } - switch strings.ToLower(format) { - case "ansic": - format = time.ANSIC - case "unixdate": - format = time.UnixDate - case "rubydate": - format = time.RubyDate - case "rfc822": - format = time.RFC822 - case "rfc822z": - format = time.RFC822Z - case "rfc850": - format = time.RFC850 - case "rfc1123": - format = time.RFC1123 - case "rfc1123z": - format = time.RFC1123Z - case "rfc3339": - format = time.RFC3339 - case "rfc3339nano": - format = time.RFC3339Nano - case "stamp": - format = time.Stamp - case "stampmilli": - format = time.StampMilli - case "stampmicro": - format = time.StampMicro - case "stampnano": - format = time.StampNano - } - return time.ParseInLocation(format, ts, loc) - default: - return time.Unix(0, 0), errUnsupportedFormat - } -} diff --git a/pkg/transformers/json/transformer.go b/pkg/transformers/json/transformer.go deleted file mode 100644 index cf2666797..000000000 --- a/pkg/transformers/json/transformer.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package json - -import ( - "encoding/json" - "strings" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/transformers" -) - -const sep = "/" - -var ( - keys = [...]string{"publisher", "protocol", "channel", "subtopic"} - - // ErrTransform represents an error during parsing message. - ErrTransform = errors.New("unable to parse JSON object") - // ErrInvalidKey represents the use of a reserved message field. - ErrInvalidKey = errors.New("invalid object key") - // ErrInvalidTimeField represents the use an invalid time field. - ErrInvalidTimeField = errors.New("invalid time field") - - errUnknownFormat = errors.New("unknown format of JSON message") - errInvalidFormat = errors.New("invalid JSON object") - errInvalidNestedJSON = errors.New("invalid nested JSON object") -) - -// TimeField represents the message fields to use as timestamp. -type TimeField struct { - FieldName string `toml:"field_name"` - FieldFormat string `toml:"field_format"` - Location string `toml:"location"` -} - -type transformerService struct { - timeFields []TimeField -} - -// New returns a new JSON transformer. -func New(tfs []TimeField) transformers.Transformer { - return &transformerService{ - timeFields: tfs, - } -} - -// Transform transforms Magistrala message to a list of JSON messages. -func (ts *transformerService) Transform(msg *messaging.Message) (interface{}, error) { - ret := Message{ - Publisher: msg.GetPublisher(), - Created: msg.GetCreated(), - Protocol: msg.GetProtocol(), - Channel: msg.GetChannel(), - Subtopic: msg.GetSubtopic(), - } - - if ret.Subtopic == "" { - return nil, errors.Wrap(ErrTransform, errUnknownFormat) - } - - subs := strings.Split(ret.Subtopic, ".") - if len(subs) == 0 { - return nil, errors.Wrap(ErrTransform, errUnknownFormat) - } - - format := subs[len(subs)-1] - var payload interface{} - if err := json.Unmarshal(msg.GetPayload(), &payload); err != nil { - return nil, errors.Wrap(ErrTransform, err) - } - - switch p := payload.(type) { - case map[string]interface{}: - ret.Payload = p - - // Apply timestamp transformation rules depending on key/unit pairs - ts, err := ts.transformTimeField(p) - if err != nil { - return nil, errors.Wrap(ErrInvalidTimeField, err) - } - if ts != 0 { - ret.Created = ts - } - - return Messages{[]Message{ret}, format}, nil - case []interface{}: - res := []Message{} - // Make an array of messages from the root array. - for _, val := range p { - v, ok := val.(map[string]interface{}) - if !ok { - return nil, errors.Wrap(ErrTransform, errInvalidNestedJSON) - } - newMsg := ret - - // Apply timestamp transformation rules depending on key/unit pairs - ts, err := ts.transformTimeField(v) - if err != nil { - return nil, errors.Wrap(ErrInvalidTimeField, err) - } - if ts != 0 { - ret.Created = ts - } - - newMsg.Payload = v - res = append(res, newMsg) - } - return Messages{res, format}, nil - default: - return nil, errors.Wrap(ErrTransform, errInvalidFormat) - } -} - -// ParseFlat receives flat map that represents complex JSON objects and returns -// the corresponding complex JSON object with nested maps. It's the opposite -// of the Flatten function. -func ParseFlat(flat interface{}) interface{} { - msg := make(map[string]interface{}) - if v, ok := flat.(map[string]interface{}); ok { - for key, value := range v { - if value == nil { - continue - } - subKeys := strings.Split(key, sep) - n := len(subKeys) - if n == 1 { - msg[key] = value - continue - } - current := msg - for i, k := range subKeys { - if _, ok := current[k]; !ok { - current[k] = make(map[string]interface{}) - } - if i == n-1 { - current[k] = value - break - } - current = current[k].(map[string]interface{}) - } - } - } - return msg -} - -// Flatten makes nested maps flat using composite keys created by concatenation of the nested keys. -func Flatten(m map[string]interface{}) (map[string]interface{}, error) { - return flatten("", make(map[string]interface{}), m) -} - -func flatten(prefix string, m, m1 map[string]interface{}) (map[string]interface{}, error) { - for k, v := range m1 { - if strings.Contains(k, sep) { - return nil, ErrInvalidKey - } - for _, key := range keys { - if k == key { - return nil, ErrInvalidKey - } - } - switch val := v.(type) { - case map[string]interface{}: - var err error - m, err = flatten(prefix+k+sep, m, val) - if err != nil { - return nil, err - } - default: - m[prefix+k] = v - } - } - return m, nil -} - -func (ts *transformerService) transformTimeField(payload map[string]interface{}) (int64, error) { - if len(ts.timeFields) == 0 { - return 0, nil - } - - for _, tf := range ts.timeFields { - if val, ok := payload[tf.FieldName]; ok { - t, err := parseTimestamp(tf.FieldFormat, val, tf.Location) - if err != nil { - return 0, err - } - - return transformers.ToUnixNano(t.UnixNano()), nil - } - } - - return 0, nil -} diff --git a/pkg/transformers/json/transformer_test.go b/pkg/transformers/json/transformer_test.go deleted file mode 100644 index 6856a94e3..000000000 --- a/pkg/transformers/json/transformer_test.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package json_test - -import ( - "fmt" - "testing" - "time" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/stretchr/testify/assert" -) - -const ( - validPayload = `{"key1": "val1", "key2": 123, "key3": "val3", "key4": {"key5": "val5"}}` - tsPayload = `{"custom_ts_key": "1638310819", "key1": "val1", "key2": 123, "key3": "val3", "key4": {"key5": "val5"}}` - microsPayload = `{"custom_ts_micro_key": "1638310819000000", "key1": "val1", "key2": 123, "key3": "val3", "key4": {"key5": "val5"}}` - invalidTSPayload = `{"custom_ts_key": "abc", "key1": "val1", "key2": 123, "key3": "val3", "key4": {"key5": "val5"}}` - listPayload = `[{"key1": "val1", "key2": 123, "keylist3": "val3", "key4": {"key5": "val5"}}, {"key1": "val1", "key2": 123, "key3": "val3", "key4": {"key5": "val5"}}]` - invalidPayload = `{"key1": }` -) - -func TestTransformJSON(t *testing.T) { - now := time.Now().Unix() - ts := []json.TimeField{ - { - FieldName: "custom_ts_key", - FieldFormat: "unix", - }, { - FieldName: "custom_ts_micro_key", - FieldFormat: "unix_us", - }, - } - tr := json.New(ts) - msg := messaging.Message{ - Channel: "channel-1", - Subtopic: "subtopic-1", - Publisher: "publisher-1", - Protocol: "protocol", - Payload: []byte(validPayload), - Created: now, - } - invalid := messaging.Message{ - Channel: "channel-1", - Subtopic: "subtopic-1", - Publisher: "publisher-1", - Protocol: "protocol", - Payload: []byte(invalidPayload), - Created: now, - } - - listMsg := messaging.Message{ - Channel: "channel-1", - Subtopic: "subtopic-1", - Publisher: "publisher-1", - Protocol: "protocol", - Payload: []byte(listPayload), - Created: now, - } - - tsMsg := messaging.Message{ - Channel: "channel-1", - Subtopic: "subtopic-1", - Publisher: "publisher-1", - Protocol: "protocol", - Payload: []byte(tsPayload), - Created: now, - } - - microsMsg := messaging.Message{ - Channel: "channel-1", - Subtopic: "subtopic-1", - Publisher: "publisher-1", - Protocol: "protocol", - Payload: []byte(microsPayload), - Created: now, - } - - invalidFmt := messaging.Message{ - Channel: "channel-1", - Subtopic: "", - Publisher: "publisher-1", - Protocol: "protocol", - Payload: []byte(validPayload), - Created: now, - } - - invalidTimeField := messaging.Message{ - Channel: "channel-1", - Subtopic: "subtopic-1", - Publisher: "publisher-1", - Protocol: "protocol", - Payload: []byte(invalidTSPayload), - Created: now, - } - - jsonMsgs := json.Messages{ - Data: []json.Message{ - { - Channel: msg.Channel, - Subtopic: msg.Subtopic, - Publisher: msg.Publisher, - Protocol: msg.Protocol, - Created: msg.Created, - Payload: map[string]interface{}{ - "key1": "val1", - "key2": float64(123), - "key3": "val3", - "key4": map[string]interface{}{ - "key5": "val5", - }, - }, - }, - }, - Format: msg.Subtopic, - } - - jsonTSMsgs := json.Messages{ - Data: []json.Message{ - { - Channel: msg.Channel, - Subtopic: msg.Subtopic, - Publisher: msg.Publisher, - Protocol: msg.Protocol, - Created: int64(1638310819000000000), - Payload: map[string]interface{}{ - "custom_ts_key": "1638310819", - "key1": "val1", - "key2": float64(123), - "key3": "val3", - "key4": map[string]interface{}{ - "key5": "val5", - }, - }, - }, - }, - Format: msg.Subtopic, - } - - jsonMicrosMsgs := json.Messages{ - Data: []json.Message{ - { - Channel: msg.Channel, - Subtopic: msg.Subtopic, - Publisher: msg.Publisher, - Protocol: msg.Protocol, - Created: int64(1638310819000000000), - Payload: map[string]interface{}{ - "custom_ts_micro_key": "1638310819000000", - "key1": "val1", - "key2": float64(123), - "key3": "val3", - "key4": map[string]interface{}{ - "key5": "val5", - }, - }, - }, - }, - Format: msg.Subtopic, - } - - listJSON := json.Messages{ - Data: []json.Message{ - { - Channel: msg.Channel, - Subtopic: msg.Subtopic, - Publisher: msg.Publisher, - Protocol: msg.Protocol, - Created: msg.Created, - Payload: map[string]interface{}{ - "key1": "val1", - "key2": float64(123), - "keylist3": "val3", - "key4": map[string]interface{}{ - "key5": "val5", - }, - }, - }, - { - Channel: msg.Channel, - Subtopic: msg.Subtopic, - Publisher: msg.Publisher, - Protocol: msg.Protocol, - Created: msg.Created, - Payload: map[string]interface{}{ - "key1": "val1", - "key2": float64(123), - "key3": "val3", - "key4": map[string]interface{}{ - "key5": "val5", - }, - }, - }, - }, - Format: msg.Subtopic, - } - - cases := []struct { - desc string - msg *messaging.Message - json interface{} - err error - }{ - { - desc: "test transform JSON", - msg: &msg, - json: jsonMsgs, - err: nil, - }, - { - desc: "test transform JSON with an invalid subtopic", - msg: &invalidFmt, - json: nil, - err: json.ErrTransform, - }, - { - desc: "test transform JSON array", - msg: &listMsg, - json: listJSON, - err: nil, - }, - { - desc: "test transform JSON with invalid payload", - msg: &invalid, - json: nil, - err: json.ErrTransform, - }, - { - desc: "test transform JSON with timestamp transformation", - msg: &tsMsg, - json: jsonTSMsgs, - err: nil, - }, - { - desc: "test transform JSON with timestamp transformation in micros", - msg: µsMsg, - json: jsonMicrosMsgs, - err: nil, - }, - { - desc: "test transform JSON with invalid timestamp transformation in micros", - msg: &invalidTimeField, - json: nil, - err: json.ErrInvalidTimeField, - }, - } - - for _, tc := range cases { - m, err := tr.Transform(tc.msg) - assert.Equal(t, tc.json, m, fmt.Sprintf("%s got incorrect json response from Transform()", tc.desc)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err)) - } -} diff --git a/pkg/transformers/senml/README.md b/pkg/transformers/senml/README.md deleted file mode 100644 index d5dbd00e1..000000000 --- a/pkg/transformers/senml/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# SenML Message Transformer - -SenML Transformer provides Message Transformer for SenML messages. -It supports JSON and CBOR content types - To transform Magistrala Message successfully, the payload must be either JSON or CBOR encoded SenML message. diff --git a/pkg/transformers/senml/doc.go b/pkg/transformers/senml/doc.go deleted file mode 100644 index b7eceffee..000000000 --- a/pkg/transformers/senml/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package senml contains SenML transformer. -package senml diff --git a/pkg/transformers/senml/message.go b/pkg/transformers/senml/message.go deleted file mode 100644 index 7278abd00..000000000 --- a/pkg/transformers/senml/message.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package senml - -// Message represents a resolved (normalized) SenML record. -type Message struct { - Channel string `json:"channel,omitempty" db:"channel" bson:"channel"` - Subtopic string `json:"subtopic,omitempty" db:"subtopic" bson:"subtopic,omitempty"` - Publisher string `json:"publisher,omitempty" db:"publisher" bson:"publisher"` - Protocol string `json:"protocol,omitempty" db:"protocol" bson:"protocol"` - Name string `json:"name,omitempty" db:"name" bson:"name,omitempty"` - Unit string `json:"unit,omitempty" db:"unit" bson:"unit,omitempty"` - Time float64 `json:"time,omitempty" db:"time" bson:"time,omitempty"` - UpdateTime float64 `json:"update_time,omitempty" db:"update_time" bson:"update_time,omitempty"` - Value *float64 `json:"value,omitempty" db:"value" bson:"value,omitempty"` - StringValue *string `json:"string_value,omitempty" db:"string_value" bson:"string_value,omitempty"` - DataValue *string `json:"data_value,omitempty" db:"data_value" bson:"data_value,omitempty"` - BoolValue *bool `json:"bool_value,omitempty" db:"bool_value" bson:"bool_value,omitempty"` - Sum *float64 `json:"sum,omitempty" db:"sum" bson:"sum,omitempty"` -} diff --git a/pkg/transformers/senml/transformer.go b/pkg/transformers/senml/transformer.go deleted file mode 100644 index cce7f31f5..000000000 --- a/pkg/transformers/senml/transformer.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package senml - -import ( - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/transformers" - "github.com/absmach/senml" -) - -const ( - // JSON represents SenML in JSON format content type. - JSON = "application/senml+json" - // CBOR represents SenML in CBOR format content type. - CBOR = "application/senml+cbor" - - maxRelativeTime = 1 << 28 -) - -var ( - errDecode = errors.New("failed to decode senml") - errNormalize = errors.New("failed to normalize senml") -) - -var formats = map[string]senml.Format{ - JSON: senml.JSON, - CBOR: senml.CBOR, -} - -type transformer struct { - format senml.Format -} - -// New returns transformer service implementation for SenML messages. -func New(contentFormat string) transformers.Transformer { - format, ok := formats[contentFormat] - if !ok { - format = formats[JSON] - } - - return transformer{ - format: format, - } -} - -func (t transformer) Transform(msg *messaging.Message) (interface{}, error) { - raw, err := senml.Decode(msg.GetPayload(), t.format) - if err != nil { - return nil, errors.Wrap(errDecode, err) - } - - normalized, err := senml.Normalize(raw) - if err != nil { - return nil, errors.Wrap(errNormalize, err) - } - - msgs := make([]Message, len(normalized.Records)) - for i, v := range normalized.Records { - // Use reception timestamp if SenML messsage Time is missing - t := v.Time - if t == 0 { - t = float64(msg.GetCreated()) - } - - // If time is below 2**28 it is relative to the current time - // https://datatracker.ietf.org/doc/html/rfc8428#section-4.5.3 - if t >= maxRelativeTime { - t = transformers.ToUnixNano(t) - } - if v.UpdateTime >= maxRelativeTime { - v.UpdateTime = transformers.ToUnixNano(v.UpdateTime) - } - - msgs[i] = Message{ - Channel: msg.GetChannel(), - Subtopic: msg.GetSubtopic(), - Publisher: msg.GetPublisher(), - Protocol: msg.GetProtocol(), - Name: v.Name, - Unit: v.Unit, - Time: t, - UpdateTime: v.UpdateTime, - Value: v.Value, - BoolValue: v.BoolValue, - DataValue: v.DataValue, - StringValue: v.StringValue, - Sum: v.Sum, - } - } - - return msgs, nil -} diff --git a/pkg/transformers/senml/transformer_test.go b/pkg/transformers/senml/transformer_test.go deleted file mode 100644 index defed2736..000000000 --- a/pkg/transformers/senml/transformer_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package senml_test - -import ( - "encoding/hex" - "fmt" - "testing" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/messaging" - "github.com/absmach/magistrala/pkg/transformers/senml" - mgsenml "github.com/absmach/senml" - "github.com/stretchr/testify/assert" -) - -func TestTransformJSON(t *testing.T) { - // Following hex-encoded bytes correspond to the content of: - // [{"bn":"base-name","bt":100,"bu":"base-unit","bver":10,"bv":10,"bs":100,"n":"name","u":"unit","t":300,"ut":150,"v":42,"s":10}] - // For more details for mapping SenML labels to integers, please take a look here: https://tools.ietf.org/html/rfc8428#page-19. - jsonBytes, err := hex.DecodeString("5b7b22626e223a22626173652d6e616d65222c226274223a3130302c226275223a22626173652d756e6974222c2262766572223a31302c226276223a31302c226273223a3130302c226e223a226e616d65222c2275223a22756e6974222c2274223a3330302c227574223a3135302c2276223a34322c2273223a31307d5d") - assert.Nil(t, err, "Decoding JSON expected to succeed") - - tr := senml.New(senml.JSON) - msg := &messaging.Message{ - Channel: "channel", - Subtopic: "subtopic", - Publisher: "publisher", - Protocol: "protocol", - Payload: jsonBytes, - } - - jsonPld := msg - jsonPld.Payload = jsonBytes - - val := 52.0 - sum := 110.0 - msgs := []senml.Message{ - { - Channel: "channel", - Subtopic: "subtopic", - Publisher: "publisher", - Protocol: "protocol", - Name: "base-namename", - Unit: "unit", - Time: 400, - UpdateTime: 150, - Value: &val, - Sum: &sum, - }, - } - - cases := []struct { - desc string - msg *messaging.Message - msgs interface{} - err error - }{ - { - desc: "test normalize JSON", - msg: jsonPld, - msgs: msgs, - err: nil, - }, - { - desc: "test normalize defaults to JSON", - msg: msg, - msgs: msgs, - err: nil, - }, - } - - for _, tc := range cases { - msgs, err := tr.Transform(tc.msg) - assert.Equal(t, tc.msgs, msgs, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.msgs, msgs)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err)) - } -} - -func TestTransformCBOR(t *testing.T) { - // Following hex-encoded bytes correspond to the content of: - // [{-2: "base-name", -3: 100.0, -4: "base-unit", -1: 10, -5: 10.0, -6: 100.0, 0: "name", 1: "unit", 6: 300.0, 7: 150.0, 2: 42.0, 5: 10.0}] - // For more details for mapping SenML labels to integers, please take a look here: https://tools.ietf.org/html/rfc8428#page-19. - cborBytes, err := hex.DecodeString("81ac2169626173652d6e616d6522fb40590000000000002369626173652d756e6974200a24fb402400000000000025fb405900000000000000646e616d650164756e697406fb4072c0000000000007fb4062c0000000000002fb404500000000000005fb4024000000000000") - assert.Nil(t, err, "Decoding CBOR expected to succeed") - - tooManyBytes, err := hex.DecodeString("82AD2169626173652D6E616D6522F956402369626173652D756E6974200A24F9490025F9564000646E616D650164756E697406F95CB0036331323307F958B002F9514005F94900AA2169626173652D6E616D6522F956402369626173652D756E6974200A24F9490025F9564000646E616D6506F95CB007F958B005F94900") - assert.Nil(t, err, "Decoding CBOR expected to succeed") - - tr := senml.New(senml.CBOR) - - cborPld := &messaging.Message{ - Channel: "channel", - Subtopic: "subtopic", - Publisher: "publisher", - Protocol: "protocol", - Payload: cborBytes, - } - - tooManyMsg := &messaging.Message{ - Channel: "channel", - Subtopic: "subtopic", - Publisher: "publisher", - Protocol: "protocol", - Payload: tooManyBytes, - } - - val := 52.0 - sum := 110.0 - msgs := []senml.Message{ - { - Channel: "channel", - Subtopic: "subtopic", - Publisher: "publisher", - Protocol: "protocol", - Name: "base-namename", - Unit: "unit", - Time: 400, - UpdateTime: 150, - Value: &val, - Sum: &sum, - }, - } - - cases := []struct { - desc string - msg *messaging.Message - msgs interface{} - err error - }{ - { - desc: "test normalize CBOR", - msg: cborPld, - msgs: msgs, - err: nil, - }, - { - desc: "test invalid payload", - msg: tooManyMsg, - msgs: nil, - err: mgsenml.ErrTooManyValues, - }, - } - - for _, tc := range cases { - msgs, err := tr.Transform(tc.msg) - assert.Equal(t, tc.msgs, msgs, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.msgs, msgs)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err)) - } -} diff --git a/pkg/transformers/transformer.go b/pkg/transformers/transformer.go deleted file mode 100644 index aa5388767..000000000 --- a/pkg/transformers/transformer.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package transformers - -import "github.com/absmach/magistrala/pkg/messaging" - -// Transformer specifies API form Message transformer. -type Transformer interface { - // Transform Magistrala message to any other format. - Transform(msg *messaging.Message) (interface{}, error) -} - -type number interface { - uint64 | int64 | float64 -} - -// ToUnixNano converts time to UnixNano time format. -func ToUnixNano[N number](t N) N { - switch { - case t == 0: - return 0 - case t >= 1e18: // Check if the value is in nanoseconds - return t - case t >= 1e15 && t < 1e18: // Check if the value is in milliseconds - return t * 1e3 - case t >= 1e12 && t < 1e15: // Check if the value is in microseconds - return t * 1e6 - default: // Assume it's in seconds (Unix time) - return t * 1e9 - } -} diff --git a/pkg/transformers/transformer_test.go b/pkg/transformers/transformer_test.go deleted file mode 100644 index bcaa4125b..000000000 --- a/pkg/transformers/transformer_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package transformers_test - -import ( - "testing" - "time" - - "github.com/absmach/magistrala/pkg/transformers" -) - -var now = time.Now() - -func TestInt64ToUnixNano(t *testing.T) { - cases := []struct { - desc string - time int64 - want int64 - }{ - { - desc: "empty", - time: 0, - want: 0, - }, - { - desc: "unix", - time: now.Unix(), - want: now.Unix() * int64(time.Second), - }, - { - desc: "unix milli", - time: now.UnixMilli(), - want: now.UnixMilli() * int64(time.Millisecond), - }, - { - desc: "unix micro", - time: now.UnixMicro(), - want: now.UnixMicro() * int64(time.Microsecond), - }, - { - desc: "unix nano", - time: now.UnixNano(), - want: now.UnixNano(), - }, - { - desc: "1e9 nano", - time: time.Unix(1e9, 0).Unix(), - want: time.Unix(1e9, 0).UnixNano(), - }, - { - desc: "1e10 nano", - time: time.Unix(1e10, 0).Unix(), - want: time.Unix(1e10, 0).UnixNano(), - }, - { - desc: "1e12 nano", - time: time.UnixMilli(1e12).Unix(), - want: time.UnixMilli(1e12).UnixNano(), - }, - { - desc: "1e13 nano", - time: time.UnixMilli(1e13).Unix(), - want: time.UnixMilli(1e13).UnixNano(), - }, - { - desc: "1e15 nano", - time: time.UnixMicro(1e15).Unix(), - want: time.UnixMicro(1e15).UnixNano(), - }, - { - desc: "1e16 nano", - time: time.UnixMicro(1e16).Unix(), - want: time.UnixMicro(1e16).UnixNano(), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - got := transformers.ToUnixNano(c.time) - if got != c.want { - t.Errorf("ToUnixNano(%d) = %d; want %d", c.time, got, c.want) - } - t.Logf("ToUnixNano(%d) = %d; want %d", c.time, got, c.want) - }) - } -} - -func TestFloat64ToUnixNano(t *testing.T) { - cases := []struct { - desc string - time float64 - want float64 - }{ - { - desc: "empty", - time: 0, - want: 0, - }, - { - desc: "unix", - time: float64(now.Unix()), - want: float64(now.Unix() * int64(time.Second)), - }, - { - desc: "unix milli", - time: float64(now.UnixMilli()), - want: float64(now.UnixMilli() * int64(time.Millisecond)), - }, - { - desc: "unix micro", - time: float64(now.UnixMicro()), - want: float64(now.UnixMicro() * int64(time.Microsecond)), - }, - { - desc: "unix nano", - time: float64(now.UnixNano()), - want: float64(now.UnixNano()), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - got := transformers.ToUnixNano(c.time) - if got != c.want { - t.Errorf("ToUnixNano(%f) = %f; want %f", c.time, got, c.want) - } - t.Logf("ToUnixNano(%f) = %f; want %f", c.time, got, c.want) - }) - } -} - -func BenchmarkToUnixNano(b *testing.B) { - for i := 0; i < b.N; i++ { - transformers.ToUnixNano(now.Unix()) - transformers.ToUnixNano(now.UnixMilli()) - transformers.ToUnixNano(now.UnixMicro()) - transformers.ToUnixNano(now.UnixNano()) - } -} diff --git a/pkg/ulid/README.md b/pkg/ulid/README.md deleted file mode 100644 index 208b31119..000000000 --- a/pkg/ulid/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# ULID identity provider - -ULID identity provider generates a universally unique lexicographically sortable, string encoded identifier, a 128-bit number, unique for all practical purposes. diff --git a/pkg/ulid/doc.go b/pkg/ulid/doc.go deleted file mode 100644 index 622ced2e1..000000000 --- a/pkg/ulid/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package ulid contains ULID generator. -package ulid diff --git a/pkg/ulid/ulid.go b/pkg/ulid/ulid.go deleted file mode 100644 index a3c6fbc97..000000000 --- a/pkg/ulid/ulid.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package ulid provides a ULID identity provider. -package ulid - -import ( - "math/rand" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/errors" - "github.com/oklog/ulid/v2" -) - -// ErrGeneratingID indicates error in generating ULID. -var ErrGeneratingID = errors.New("generating id failed") - -var _ magistrala.IDProvider = (*ulidProvider)(nil) - -type ulidProvider struct { - entropy *rand.Rand -} - -// New instantiates a ULID provider. -func New() magistrala.IDProvider { - seed := time.Now().UnixNano() - source := rand.NewSource(seed) - return &ulidProvider{ - entropy: rand.New(source), - } -} - -func (up *ulidProvider) ID() (string, error) { - id, err := ulid.New(ulid.Timestamp(time.Now()), up.entropy) - if err != nil { - return "", err - } - - return id.String(), nil -} diff --git a/pkg/uuid/README.md b/pkg/uuid/README.md deleted file mode 100644 index e19a38f2c..000000000 --- a/pkg/uuid/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# UUID identity provider - -The UUID identity provider generates a random, universally unique identifier (UUID), unique for all practical purposes. diff --git a/pkg/uuid/doc.go b/pkg/uuid/doc.go deleted file mode 100644 index 7262babfe..000000000 --- a/pkg/uuid/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package uuid contains UUID generator. -package uuid diff --git a/pkg/uuid/mock.go b/pkg/uuid/mock.go deleted file mode 100644 index 040525122..000000000 --- a/pkg/uuid/mock.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package uuid - -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 = (*uuidProviderMock)(nil) - -type uuidProviderMock struct { - mu sync.Mutex - counter int -} - -func (up *uuidProviderMock) 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 &uuidProviderMock{} -} diff --git a/pkg/uuid/uuid.go b/pkg/uuid/uuid.go deleted file mode 100644 index 872cc2c62..000000000 --- a/pkg/uuid/uuid.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package uuid provides a UUID identity provider. -package uuid - -import ( - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/errors" - "github.com/gofrs/uuid/v5" -) - -// ErrGeneratingID indicates error in generating UUID. -var ErrGeneratingID = errors.New("failed to generate uuid") - -var _ magistrala.IDProvider = (*uuidProvider)(nil) - -type uuidProvider struct{} - -// New instantiates a UUID provider. -func New() magistrala.IDProvider { - return &uuidProvider{} -} - -func (up *uuidProvider) ID() (string, error) { - id, err := uuid.NewV4() - if err != nil { - return "", errors.Wrap(ErrGeneratingID, err) - } - - return id.String(), nil -} diff --git a/provision/README.md b/provision/README.md index 73f6c8639..39aa87a64 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 provides an HTTP API to interact with [SuperMQ][supermq]. +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 [SuperMQ][supermq] 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. [SuperMQ 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 [SuperMQ][supermq]. 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 | +| ------------------------------------ | -------------------------------------------------- | ----------------------- | +| SMQ_PROVISION_LOG_LEVEL | Service log level | debug | +| SMQ_PROVISION_USER | User (email) for accessing SuperMQ | | +| SMQ_PROVISION_PASS | SuperMQ password | user123 | +| SMQ_PROVISION_API_KEY | SuperMQ authentication token | | +| SMQ_PROVISION_CONFIG_FILE | Provision config file | config.toml | +| SMQ_PROVISION_HTTP_PORT | Provision service listening port | 9016 | +| SMQ_PROVISION_ENV_CLIENTS_TLS | SuperMQ SDK TLS verification | false | +| SMQ_PROVISION_SERVER_CERT | SuperMQ gRPC secure server cert | | +| SMQ_PROVISION_SERVER_KEY | SuperMQ gRPC secure server key | | +| SMQ_PROVISION_USERS_LOCATION | Users service URL | | +| SMQ_PROVISION_CLIENTS_LOCATION | Clients service URL | | +| SMQ_PROVISION_BS_SVC_URL | SuperMQ Bootstrap service URL | | +| SMQ_PROVISION_CERTS_SVC_URL | Certificates service URL | | +| SMQ_PROVISION_X509_PROVISIONING | Should X509 client cert be provisioned | false | +| SMQ_PROVISION_BS_CONFIG_PROVISIONING | Should client config be saved in Bootstrap service | true | +| SMQ_PROVISION_BS_AUTO_WHITELIST | Should client be auto whitelisted | true | +| SMQ_PROVISION_BS_CONTENT | Bootstrap service configs content, JSON format | {} | +| SMQ_PROVISION_CERTS_RSA_BITS | Certificate RSA bits parameter | 4096 | +| SMQ_PROVISION_CERTS_HOURS_VALID | Number of hours that certificate is valid | "2400h" | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq 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" @@ -83,12 +83,12 @@ Example of provision layout below ## Authentication -In order to create necessary entities provision service needs to authenticate against Magistrala. To provide authentication credentials to the provision service you can pass it in an environment variable or in a config file as Magistrala user and password or as API token that can be issued on `/users/tokens/issue`. +In order to create necessary entities provision service needs to authenticate against SuperMQ. To provide authentication credentials to the provision service you can pass it in an environment variable or in a config file as SuperMQ user and password or as API token that can be issued on `/users/tokens/issue`. Additionally users or API token can be passed in Authorization header, this authentication takes precedence over others. -- `username`, `password` - (`MG_PROVISION_USER`, `MG_PROVISION_PASSWORD` in [.env](../.env), `mg_user`, `mg_pass` in [config.toml](../docker/addons/provision/configs/config.toml)) -- API Key - (`MG_PROVISION_API_KEY` in [.env](../.env) or [config.toml](../docker/addons/provision/configs/config.toml)) +- `username`, `password` - (`SMQ_PROVISION_USER`, `SMQ_PROVISION_PASSWORD` in [.env](../.env), `mg_user`, `mg_pass` in [config.toml](../docker/addons/provision/configs/config.toml)) +- API Key - (`SMQ_PROVISION_API_KEY` in [.env](../.env) or [config.toml](../docker/addons/provision/configs/config.toml)) - `Authorization: Bearer Token` - request authorization header containing either users token. ## Running @@ -98,11 +98,11 @@ Provision service can be run as a standalone or in docker composition as addon t Standalone: ```bash -MG_PROVISION_BS_SVC_URL=http://localhost:9013 \ -MG_PROVISION_THINGS_LOCATION=http://localhost:9000 \ -MG_PROVISION_USERS_LOCATION=http://localhost:9002 \ -MG_PROVISION_CONFIG_FILE=docker/addons/provision/configs/config.toml \ -build/magistrala-provision +SMQ_PROVISION_BS_SVC_URL=http://localhost:9013 \ +SMQ_PROVISION_CLIENTS_LOCATION=http://localhost:9000 \ +SMQ_PROVISION_USERS_LOCATION=http://localhost:9002 \ +SMQ_PROVISION_CONFIG_FILE=docker/addons/provision/configs/config.toml \ +build/supermq-provision ``` Docker composition: @@ -114,16 +114,16 @@ docker compose -f docker/addons/provision/docker-compose.yml up For the case that credentials or API token is passed in configuration file or environment variables, call to `/mapping` endpoint doesn't require `Authentication` header: ```bash -curl -s -S -X POST http://localhost:/mapping -H 'Content-Type: application/json' -d '{"external_id": "33:52:77:99:43", "external_key": "223334fw2"}' +curl -s -S -X POST http://localhost:/mapping -H 'Content-Type: application/json' -d '{"external_id": "33:52:77:99:43", "external_key": "223334fw2"}' ``` In the case that provision service is not deployed with credentials or API key or you want to use user other than one being set in environment (or config file): ```bash -curl -s -S -X POST http://localhost:/mapping -H "Authorization: Bearer " -H 'Content-Type: application/json' -d '{"external_id": "", "external_key": ""}' +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,24 +171,24 @@ 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" } ``` -[magistrala]: https://github.com/absmach/magistrala -[bootstrap]: https://github.com/absmach/magistrala/tree/master/bootstrap +[supermq]: https://github.com/absmach/supermq +[bootstrap]: https://github.com/absmach/supermq/tree/main/bootstrap [export]: https://github.com/absmach/export [agent]: https://github.com/absmach/agent -[mgxui]: https://github.com/absmach/magistrala/ui +[mgxui]: https://github.com/absmach/supermq/ui diff --git a/provision/api/endpoint.go b/provision/api/endpoint.go index ec21527a5..2cdb55010 100644 --- a/provision/api/endpoint.go +++ b/provision/api/endpoint.go @@ -6,9 +6,9 @@ package api import ( "context" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/provision" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/pkg/errors" "github.com/go-kit/kit/endpoint" ) @@ -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/endpoint_test.go b/provision/api/endpoint_test.go index 369be0d9b..aa0ff84c3 100644 --- a/provision/api/endpoint_test.go +++ b/provision/api/endpoint_test.go @@ -12,12 +12,12 @@ import ( "testing" "github.com/absmach/magistrala/internal/testsutil" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/apiutil" - svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/provision" "github.com/absmach/magistrala/provision/api" "github.com/absmach/magistrala/provision/mocks" + apiutil "github.com/absmach/supermq/api/http/util" + smqlog "github.com/absmach/supermq/logger" + svcerr "github.com/absmach/supermq/pkg/errors/service" "github.com/stretchr/testify/assert" ) @@ -56,7 +56,7 @@ func (tr testRequest) make() (*http.Response, error) { func newProvisionServer() (*httptest.Server, *mocks.Service) { svc := new(mocks.Service) - logger := mglog.NewMock() + logger := smqlog.NewMock() mux := api.MakeHandler(svc, logger, "test") return httptest.NewServer(mux), svc } diff --git a/provision/api/logging.go b/provision/api/logging.go index 4d19af3c0..2156c722f 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/requests.go b/provision/api/requests.go index 847a235fc..60f9106a9 100644 --- a/provision/api/requests.go +++ b/provision/api/requests.go @@ -3,7 +3,7 @@ package api -import "github.com/absmach/magistrala/pkg/apiutil" +import apiutil "github.com/absmach/supermq/api/http/util" type provisionReq struct { token string diff --git a/provision/api/requests_test.go b/provision/api/requests_test.go index 5cc5428af..962dfe507 100644 --- a/provision/api/requests_test.go +++ b/provision/api/requests_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/pkg/errors" "github.com/stretchr/testify/assert" ) diff --git a/provision/api/responses.go b/provision/api/responses.go index 87c105225..8f85e8d07 100644 --- a/provision/api/responses.go +++ b/provision/api/responses.go @@ -7,14 +7,14 @@ import ( "encoding/json" "net/http" - "github.com/absmach/magistrala" - sdk "github.com/absmach/magistrala/pkg/sdk/go" + "github.com/absmach/supermq" + sdk "github.com/absmach/supermq/pkg/sdk" ) -var _ magistrala.Response = (*provisionRes)(nil) +var _ supermq.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/api/transport.go b/provision/api/transport.go index ae26a86b2..385f87430 100644 --- a/provision/api/transport.go +++ b/provision/api/transport.go @@ -9,11 +9,11 @@ import ( "log/slog" "net/http" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/provision" + "github.com/absmach/supermq" + api "github.com/absmach/supermq/api/http" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/pkg/errors" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -48,7 +48,7 @@ func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string) }) }) r.Handle("/metrics", promhttp.Handler()) - r.Get("/health", magistrala.Health("provision", instanceID)) + r.Get("/health", supermq.Health("provision", instanceID)) return r } diff --git a/provision/config.go b/provision/config.go index 7540e4401..41e6ccae9 100644 --- a/provision/config.go +++ b/provision/config.go @@ -7,9 +7,9 @@ import ( "fmt" "os" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/things" + "github.com/absmach/supermq/channels" + "github.com/absmach/supermq/clients" + "github.com/absmach/supermq/pkg/errors" "github.com/pelletier/go-toml" ) @@ -17,28 +17,28 @@ var errFailedToReadConfig = errors.New("failed to read config file") // ServiceConf represents service config. type ServiceConf struct { - Port string `toml:"port" env:"MG_PROVISION_HTTP_PORT" envDefault:"9016"` - LogLevel string `toml:"log_level" env:"MG_PROVISION_LOG_LEVEL" envDefault:"info"` - 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"` - 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"` - MgUsername string `toml:"mg_username" env:"MG_PROVISION_USERNAME" envDefault:"user"` - MgPass string `toml:"mg_pass" env:"MG_PROVISION_PASS" envDefault:"test"` - MgDomainID string `toml:"mg_domain_id" env:"MG_PROVISION_DOMAIN_ID" envDefault:""` - MgAPIKey string `toml:"mg_api_key" env:"MG_PROVISION_API_KEY" envDefault:""` - MgBSURL string `toml:"mg_bs_url" env:"MG_PROVISION_BS_SVC_URL" envDefault:"http://localhost:9000"` - MgCertsURL string `toml:"mg_certs_url" env:"MG_PROVISION_CERTS_SVC_URL" envDefault:"http://localhost:9019"` + Port string `toml:"port" env:"SMQ_PROVISION_HTTP_PORT" envDefault:"9016"` + LogLevel string `toml:"log_level" env:"SMQ_PROVISION_LOG_LEVEL" envDefault:"info"` + TLS bool `toml:"tls" env:"SMQ_PROVISION_ENV_CLIENTS_TLS" envDefault:"false"` + ServerCert string `toml:"server_cert" env:"SMQ_PROVISION_SERVER_CERT" envDefault:""` + ServerKey string `toml:"server_key" env:"SMQ_PROVISION_SERVER_KEY" envDefault:""` + ClientsURL string `toml:"clients_url" env:"SMQ_PROVISION_CLIENTS_LOCATION" envDefault:"http://localhost"` + UsersURL string `toml:"users_url" env:"SMQ_PROVISION_USERS_LOCATION" envDefault:"http://localhost"` + HTTPPort string `toml:"http_port" env:"SMQ_PROVISION_HTTP_PORT" envDefault:"9016"` + MgEmail string `toml:"smq_email" env:"SMQ_PROVISION_EMAIL" envDefault:"test@example.com"` + MgUsername string `toml:"smq_username" env:"SMQ_PROVISION_USERNAME" envDefault:"user"` + MgPass string `toml:"smq_pass" env:"SMQ_PROVISION_PASS" envDefault:"test"` + MgDomainID string `toml:"smq_domain_id" env:"SMQ_PROVISION_DOMAIN_ID" envDefault:""` + MgAPIKey string `toml:"smq_api_key" env:"SMQ_PROVISION_API_KEY" envDefault:""` + MgBSURL string `toml:"smq_bs_url" env:"SMQ_PROVISION_BS_SVC_URL" envDefault:"http://localhost:9000"` + MgCertsURL string `toml:"smq_certs_url" env:"SMQ_PROVISION_CERTS_SVC_URL" envDefault:"http://localhost:9019"` } // Bootstrap represetns the Bootstrap config. type Bootstrap struct { - X509Provision bool `toml:"x509_provision" env:"MG_PROVISION_X509_PROVISIONING" envDefault:"false"` - Provision bool `toml:"provision" env:"MG_PROVISION_BS_CONFIG_PROVISIONING" envDefault:"true"` - AutoWhiteList bool `toml:"autowhite_list" env:"MG_PROVISION_BS_AUTO_WHITELIST" envDefault:"true"` + X509Provision bool `toml:"x509_provision" env:"SMQ_PROVISION_X509_PROVISIONING" envDefault:"false"` + Provision bool `toml:"provision" env:"SMQ_PROVISION_BS_CONFIG_PROVISIONING" envDefault:"true"` + AutoWhiteList bool `toml:"autowhite_list" env:"SMQ_PROVISION_BS_AUTO_WHITELIST" envDefault:"true"` Content map[string]interface{} `toml:"content"` } @@ -55,20 +55,20 @@ type Gateway struct { // Cert represetns the certificate config. type Cert struct { - TTL string `json:"ttl" toml:"ttl" env:"MG_PROVISION_CERTS_HOURS_VALID" envDefault:"2400h"` + TTL string `json:"ttl" toml:"ttl" env:"SMQ_PROVISION_CERTS_HOURS_VALID" envDefault:"2400h"` } // 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:"SMQ_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:"SMQ_PROVISION_BS_CONTENT" envDefault:""` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"SMQ_MQTT_ADAPTER_INSTANCE_ID" envDefault:""` } // Save - store config in a file. diff --git a/provision/config_test.go b/provision/config_test.go index 6857b8265..7ba27c6dc 100644 --- a/provision/config_test.go +++ b/provision/config_test.go @@ -8,10 +8,10 @@ import ( "os" "testing" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/provision" - "github.com/absmach/magistrala/things" + "github.com/absmach/supermq/channels" + "github.com/absmach/supermq/clients" + "github.com/absmach/supermq/pkg/errors" "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,10 +42,11 @@ var ( Permissions: []string{"test"}, }, }, - Channels: []groups.Group{ + Channels: []channels.Channel{ { ID: "1234567890", Name: "test", + Tags: []string{"test"}, Metadata: map[string]interface{}{ "test": "test", }, diff --git a/provision/configs/config.toml b/provision/configs/config.toml index 38455eb23..650ed3518 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 ff45e5fac..1ee4a2006 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 228586aa7..b5000dd18 100644 --- a/provision/service.go +++ b/provision/service.go @@ -8,8 +8,8 @@ import ( "fmt" "log/slog" - "github.com/absmach/magistrala/pkg/errors" - sdk "github.com/absmach/magistrala/pkg/sdk/go" + "github.com/absmach/supermq/pkg/errors" + sdk "github.com/absmach/supermq/pkg/sdk" ) const ( @@ -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) } @@ -294,8 +294,8 @@ func (ps *provisionService) createTokenIfEmpty(token string) (string, error) { } u := sdk.Login{ - Identity: ps.conf.Server.MgUsername, - Secret: ps.conf.Server.MgPass, + Username: ps.conf.Server.MgUsername, + Password: ps.conf.Server.MgPass, } tkn, err := ps.sdk.CreateToken(u) if err != nil { @@ -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 4e3fd314b..e50bcde5e 100644 --- a/provision/service_test.go +++ b/provision/service_test.go @@ -8,13 +8,13 @@ import ( "testing" "github.com/absmach/magistrala/internal/testsutil" - mglog "github.com/absmach/magistrala/logger" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - 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/provision" + smqlog "github.com/absmach/supermq/logger" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" + svcerr "github.com/absmach/supermq/pkg/errors/service" + sdk "github.com/absmach/supermq/pkg/sdk" + sdkmocks "github.com/absmach/supermq/pkg/sdk/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -23,7 +23,7 @@ var validToken = "valid" func TestMapping(t *testing.T) { mgsdk := new(sdkmocks.SDK) - svc := provision.New(validConfig, mgsdk, mglog.NewMock()) + svc := provision.New(validConfig, mgsdk, smqlog.NewMock()) cases := []struct { desc string @@ -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,75 +155,75 @@ 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, }, } for _, c := range cases { t.Run(c.desc, func(t *testing.T) { mgsdk := new(sdkmocks.SDK) - svc := provision.New(c.config, mgsdk, mglog.NewMock()) + svc := provision.New(c.config, mgsdk, smqlog.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, + Username: c.config.Server.MgUsername, + Password: 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/re/api/endpoints.go b/re/api/endpoints.go index 6286ba852..2fa2a519a 100644 --- a/re/api/endpoints.go +++ b/re/api/endpoints.go @@ -6,10 +6,10 @@ package api import ( "context" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/authn" - svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/re" + api "github.com/absmach/supermq/api/http" + "github.com/absmach/supermq/pkg/authn" + svcerr "github.com/absmach/supermq/pkg/errors/service" "github.com/go-kit/kit/endpoint" ) @@ -21,6 +21,9 @@ func addRuleEndpoint(s re.Service) endpoint.Endpoint { } req := request.(addRuleReq) + if err := req.validate(); err != nil { + return addRuleRes{}, err + } rule, err := s.AddRule(ctx, session, req.Rule) if err != nil { return addRuleRes{}, err @@ -37,6 +40,9 @@ func viewRuleEndpoint(s re.Service) endpoint.Endpoint { } req := request.(viewRuleReq) + if err := req.validate(); err != nil { + return viewRuleRes{}, err + } rule, err := s.ViewRule(ctx, session, req.id) if err != nil { return viewRuleRes{}, err @@ -53,6 +59,9 @@ func updateRuleEndpoint(s re.Service) endpoint.Endpoint { } req := request.(updateRuleReq) + if err := req.validate(); err != nil { + return updateRuleRes{}, err + } rule, err := s.UpdateRule(ctx, session, req.Rule) if err != nil { return updateRuleRes{}, err @@ -69,6 +78,9 @@ func listRulesEndpoint(s re.Service) endpoint.Endpoint { } req := request.(listRulesReq) + if err := req.validate(); err != nil { + return pageRes{}, err + } page, err := s.ListRules(ctx, session, req.PageMeta) if err != nil { return rulesPageRes{}, nil @@ -88,10 +100,13 @@ func upadateRuleStatusEndpoint(s re.Service) endpoint.Endpoint { } req := request.(changeRuleStatusReq) + if err := req.validate(); err != nil { + return updateRoleStatusRes{}, err + } err := s.RemoveRule(ctx, session, req.id) if err != nil { - return changeRoleStatusRes{false}, err + return updateRoleStatusRes{false}, err } - return changeRoleStatusRes{true}, nil + return updateRoleStatusRes{true}, nil } } diff --git a/re/api/requests.go b/re/api/requests.go index da7952f23..4971f6855 100644 --- a/re/api/requests.go +++ b/re/api/requests.go @@ -4,9 +4,9 @@ package api import ( - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" "github.com/absmach/magistrala/re" + api "github.com/absmach/supermq/api/http" + apiutil "github.com/absmach/supermq/api/http/util" ) const maxLimitSize = 1000 diff --git a/re/api/responses.go b/re/api/responses.go index 16f30a721..0c230ef3d 100644 --- a/re/api/responses.go +++ b/re/api/responses.go @@ -7,17 +7,17 @@ import ( "fmt" "net/http" - "github.com/absmach/magistrala" "github.com/absmach/magistrala/re" + "github.com/absmach/supermq" ) var ( - _ magistrala.Response = (*viewRuleRes)(nil) - _ magistrala.Response = (*addRuleRes)(nil) - _ magistrala.Response = (*changeRuleStatusRes)(nil) - _ magistrala.Response = (*rulesPageRes)(nil) - _ magistrala.Response = (*updateRuleRes)(nil) - _ magistrala.Response = (*changeRoleStatusRes)(nil) + _ supermq.Response = (*viewRuleRes)(nil) + _ supermq.Response = (*addRuleRes)(nil) + _ supermq.Response = (*changeRuleStatusRes)(nil) + _ supermq.Response = (*rulesPageRes)(nil) + _ supermq.Response = (*updateRuleRes)(nil) + _ supermq.Response = (*updateRoleStatusRes)(nil) ) type pageRes struct { @@ -118,11 +118,11 @@ func (res changeRuleStatusRes) Empty() bool { return false } -type changeRoleStatusRes struct { +type updateRoleStatusRes struct { deleted bool } -func (res changeRoleStatusRes) Code() int { +func (res updateRoleStatusRes) Code() int { if res.deleted { return http.StatusNoContent } @@ -130,10 +130,10 @@ func (res changeRoleStatusRes) Code() int { return http.StatusOK } -func (res changeRoleStatusRes) Headers() map[string]string { +func (res updateRoleStatusRes) Headers() map[string]string { return map[string]string{} } -func (res changeRoleStatusRes) Empty() bool { +func (res updateRoleStatusRes) Empty() bool { return true } diff --git a/re/api/transport.go b/re/api/transport.go index 848bebe58..d98b8da12 100644 --- a/re/api/transport.go +++ b/re/api/transport.go @@ -10,13 +10,12 @@ import ( "net/http" "strings" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/invitations" - "github.com/absmach/magistrala/pkg/apiutil" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/re" + "github.com/absmach/supermq" + api "github.com/absmach/supermq/api/http" + apiutil "github.com/absmach/supermq/api/http/util" + mgauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" "github.com/go-chi/chi" kithttp "github.com/go-kit/kit/transport/http" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -76,7 +75,7 @@ func MakeHandler(svc re.Service, authn mgauthn.Authentication, logger *slog.Logg }) }) - mux.Get("/health", magistrala.Health("rule_engine", instanceID)) + mux.Get("/health", supermq.Health("rule_engine", instanceID)) mux.Handle("/metrics", promhttp.Handler()) return mux @@ -135,7 +134,7 @@ func decodeListRulesRequest(_ context.Context, r *http.Request) (interface{}, er func decodeUpdateRuleStatusRequest(_ context.Context, r *http.Request) (interface{}, error) { id := r.URL.Query().Get(idKey) - status, err := apiutil.ReadStringQuery(r, statusKey, invitations.All.String()) + status, err := apiutil.ReadStringQuery(r, statusKey, re.AllStatus.String()) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } diff --git a/re/postgres/repository.go b/re/postgres/repository.go index 755aa1701..1a37c2cd0 100644 --- a/re/postgres/repository.go +++ b/re/postgres/repository.go @@ -8,13 +8,13 @@ import ( "fmt" "strings" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/pkg/postgres" "github.com/absmach/magistrala/re" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" + "github.com/absmach/supermq/pkg/postgres" ) -// SQL Queries as Strings +// SQL Queries as Strings. const ( addRuleQuery = ` INSERT INTO rules (id, domain_id, input_channel, input_topic, logic_type, logic_value, diff --git a/re/service.go b/re/service.go index 02ba36044..ad3d23611 100644 --- a/re/service.go +++ b/re/service.go @@ -7,11 +7,11 @@ import ( "context" "time" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/consumers" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/messaging" - mgjson "github.com/absmach/magistrala/pkg/transformers/json" + "github.com/absmach/supermq" + "github.com/absmach/supermq/consumers" + "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/messaging" + mgjson "github.com/absmach/supermq/pkg/transformers/json" lua "github.com/yuin/gopher-lua" ) @@ -22,7 +22,7 @@ type Script struct { Value string `json:"value"` } -// daily, weekly or monthly +// Type can be daily, weekly or monthly. type ReccuringType uint const ( @@ -89,13 +89,13 @@ type Service interface { } type re struct { - idp magistrala.IDProvider + idp supermq.IDProvider repo Repository pubSub messaging.PubSub errors chan error } -func NewService(repo Repository, idp magistrala.IDProvider, pubSub messaging.PubSub) Service { +func NewService(repo Repository, idp supermq.IDProvider, pubSub messaging.PubSub) Service { return &re{ repo: repo, idp: idp, @@ -146,7 +146,9 @@ func (re *re) ConsumeAsync(ctx context.Context, msgs interface{}) { return } for _, r := range page.Rules { - go re.process(r, m) + go func(ctx context.Context) { + re.errors <- re.process(ctx, r, m) + }(ctx) } case mgjson.Message: default: @@ -157,7 +159,7 @@ func (re *re) Errors() <-chan error { return re.errors } -func (re *re) process(r Rule, msg *messaging.Message) error { +func (re *re) process(ctx context.Context, r Rule, msg *messaging.Message) error { l := lua.NewState() defer l.Close() @@ -195,7 +197,6 @@ func (re *re) process(r Rule, msg *messaging.Message) error { Created: time.Now().Unix(), Payload: []byte(result.String()), } - re.pubSub.Publish(context.Background(), m.Channel, m) + return re.pubSub.Publish(ctx, m.Channel, m) } - return nil } diff --git a/re/status.go b/re/status.go index 5042893bf..404c6927c 100644 --- a/re/status.go +++ b/re/status.go @@ -7,7 +7,7 @@ import ( "encoding/json" "strings" - svcerr "github.com/absmach/magistrala/pkg/errors/service" + svcerr "github.com/absmach/supermq/pkg/errors/service" ) // Status represents Rule status. diff --git a/readers/README.md b/readers/README.md index 4c7be5939..4f22131e4 100644 --- a/readers/README.md +++ b/readers/README.md @@ -1,7 +1,7 @@ # Readers -Readers provide implementations of various `message readers`. Message readers are services that consume normalized (in `SenML` format) Magistrala messages from data storage and expose HTTP API for message consumption. +Readers provide implementations of various `message readers`. Message readers are services that consume normalized (in `SenML` format) SuperMQ messages from data storage and expose HTTP API for message consumption. -For an in-depth explanation of the usage of `reader`, as well as thorough understanding of Magistrala, please check out the [official documentation][doc]. +For an in-depth explanation of the usage of `reader`, as well as thorough understanding of SuperMQ, please check out the [official documentation][doc]. -[doc]: https://docs.magistrala.abstractmachines.fr +[doc]: https://docs.supermq.abstractmachines.fr diff --git a/readers/api/endpoint.go b/readers/api/endpoint.go index 794063f79..61d1d6038 100644 --- a/readers/api/endpoint.go +++ b/readers/api/endpoint.go @@ -6,24 +6,24 @@ package api import ( "context" - "github.com/absmach/magistrala" - "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" + grpcChannelsV1 "github.com/absmach/supermq/api/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1" + apiutil "github.com/absmach/supermq/api/http/util" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/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 smqauthn.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 156e79ec7..ef8d9fdae 100644 --- a/readers/api/endpoint_test.go +++ b/readers/api/endpoint_test.go @@ -11,25 +11,26 @@ import ( "testing" "time" - "github.com/absmach/magistrala" "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" + grpcChannelsV1 "github.com/absmach/supermq/api/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1" + apiutil "github.com/absmach/supermq/api/http/util" + chmocks "github.com/absmach/supermq/channels/mocks" + climocks "github.com/absmach/supermq/clients/mocks" + smqauthn "github.com/absmach/supermq/pkg/authn" + authnmocks "github.com/absmach/supermq/pkg/authn/mocks" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/pkg/transformers/senml" + "github.com/absmach/supermq/readers" + "github.com/absmach/supermq/readers/api" + "github.com/absmach/supermq/readers/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" @@ -49,12 +50,11 @@ var ( vb = true vd = "dataValue" sum float64 = 42 - domainID = testsutil.GenerateUUID(&testing.T{}) - validSession = mgauthn.Session{UserID: testsutil.GenerateUUID(&testing.T{})} + validSession = smqauthn.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 +75,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 +132,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 { @@ -152,7 +152,7 @@ func TestReadAll(t *testing.T) { }{ { desc: "read page with valid offset and limit", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -164,81 +164,75 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with valid offset and limit as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusOK, res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { - desc: "read page as user without domain id", - url: fmt.Sprintf("%s/%s/channels/%s/messages", ts.URL, "", chanID), - token: userToken, - 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,45 +240,45 @@ 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{ - PageMetadata: readers.PageMetadata{Limit: 10}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { - 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{ - PageMetadata: readers.PageMetadata{}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { - 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{ - PageMetadata: readers.PageMetadata{Format: "messages"}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { - 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 +288,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 +300,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 +312,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 +324,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 +336,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 +348,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 +360,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 +372,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 +384,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 +396,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 +408,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 +434,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 +453,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 +465,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,35 +491,35 @@ 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{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Interval: "10h"}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { - 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,97 +529,97 @@ 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, }, { desc: "read page with valid offset and limit as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusOK, res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { desc: "read page with negative offset as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=-1&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=-1&limit=10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with negative limit as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=-10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=-10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with zero limit as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=0", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=0", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-integer offset as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=abc&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=abc&limit=10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-integer limit as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=abc", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=abc", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with invalid channel id as user", - url: fmt.Sprintf("%s/%s/channels//messages?offset=0&limit=10", ts.URL, domainID), + url: fmt.Sprintf("%s/channels//messages?offset=0&limit=10", ts.URL), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with invalid token as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), token: invalidToken, authResponse: false, status: http.StatusUnauthorized, @@ -633,21 +627,21 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with multiple offset as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with multiple limit as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with empty token as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), token: "", authResponse: false, status: http.StatusUnauthorized, @@ -655,55 +649,55 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with default offset as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?limit=10", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?limit=10", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusOK, res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { desc: "read page with default limit as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?offset=0", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusOK, res: pageRes{ - PageMetadata: readers.PageMetadata{}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { desc: "read page with senml format as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?format=messages", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?format=messages", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusOK, res: pageRes{ - PageMetadata: readers.PageMetadata{Format: "messages"}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { desc: "read page with subtopic as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, domainID, chanID, subtopic, httpProt), + url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt), token: userToken, authResponse: true, status: http.StatusOK, res: pageRes{ - PageMetadata: readers.PageMetadata{Subtopic: subtopic, Protocol: httpProt}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Subtopic: subtopic, Protocol: httpProt}, Total: uint64(len(queryMsgs)), Messages: queryMsgs[0:10], }, }, { desc: "read page with subtopic and protocol as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, domainID, chanID, subtopic, httpProt), + url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt), token: userToken, authResponse: true, status: http.StatusOK, @@ -715,7 +709,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with publisher as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?publisher=%s", ts.URL, domainID, chanID, pubID2), + url: fmt.Sprintf("%s/channels/%s/messages?publisher=%s", ts.URL, chanID, pubID2), token: userToken, authResponse: true, status: http.StatusOK, @@ -727,7 +721,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with protocol as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?protocol=http", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?protocol=http", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -739,7 +733,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with name as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?name=%s", ts.URL, domainID, chanID, msgName), + url: fmt.Sprintf("%s/channels/%s/messages?name=%s", ts.URL, chanID, msgName), token: userToken, authResponse: true, status: http.StatusOK, @@ -751,7 +745,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f", ts.URL, domainID, chanID, v), + url: fmt.Sprintf("%s/channels/%s/messages?v=%f", ts.URL, chanID, v), token: userToken, authResponse: true, status: http.StatusOK, @@ -763,7 +757,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and equal comparator as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v, readers.EqualKey), + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v, readers.EqualKey), token: userToken, authResponse: true, status: http.StatusOK, @@ -775,7 +769,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and lower-than comparator as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v+1, readers.LowerThanKey), + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanKey), token: userToken, authResponse: true, status: http.StatusOK, @@ -787,7 +781,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and lower-than-or-equal comparator as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v+1, readers.LowerThanEqualKey), + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanEqualKey), token: userToken, authResponse: true, status: http.StatusOK, @@ -799,7 +793,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and greater-than comparator as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v-1, readers.GreaterThanKey), + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanKey), token: userToken, status: http.StatusOK, authResponse: true, @@ -811,7 +805,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and greater-than-or-equal comparator as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v-1, readers.GreaterThanEqualKey), + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanEqualKey), token: userToken, authResponse: true, status: http.StatusOK, @@ -823,21 +817,21 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-float value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=ab01", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?v=ab01", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with value and wrong comparator as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, domainID, chanID, v-1), + url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, chanID, v-1), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with boolean value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=true", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?vb=true", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -849,14 +843,14 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-boolean value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=yes", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?vb=yes", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with string value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vs=%s", ts.URL, domainID, chanID, vs), + url: fmt.Sprintf("%s/channels/%s/messages?vs=%s", ts.URL, chanID, vs), token: userToken, authResponse: true, status: http.StatusOK, @@ -868,7 +862,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with data value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vd=%s", ts.URL, domainID, chanID, vd), + url: fmt.Sprintf("%s/channels/%s/messages?vd=%s", ts.URL, chanID, vd), token: userToken, authResponse: true, status: http.StatusOK, @@ -880,21 +874,21 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-float from as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?from=ABCD", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?from=ABCD", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-float to as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?to=ABCD", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?to=ABCD", ts.URL, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with from/to as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?from=%f&to=%f", ts.URL, domainID, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/channels/%s/messages?from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), token: userToken, authResponse: true, status: http.StatusOK, @@ -906,33 +900,33 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with aggregation as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX", ts.URL, chanID), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with interval as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?interval=10h", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?interval=10h", ts.URL, chanID), key: userToken, authResponse: true, status: http.StatusOK, res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Interval: "10h"}, + PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, Total: uint64(len(messages)), Messages: messages[0:10], }, }, { desc: "read page with aggregation and interval as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, domainID, chanID), + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, chanID), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval, to and from as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=%f", ts.URL, domainID, chanID, messages[19].Time, messages[4].Time), + 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: userToken, authResponse: true, status: http.StatusOK, @@ -944,35 +938,35 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with invalid aggregation and valid interval, to and from as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=invalid&interval=10h&from=%f&to=%f", ts.URL, domainID, chanID, messages[19].Time, messages[4].Time), + 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: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with invalid interval and valid aggregation, to and from as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10hrs&from=%f&to=%f", ts.URL, domainID, chanID, messages[19].Time, messages[4].Time), + 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: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with missing from as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&to=%f", ts.URL, domainID, chanID, messages[4].Time), + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&to=%f", ts.URL, chanID, messages[4].Time), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with invalid from as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&to=ABCD&from=%f", ts.URL, domainID, chanID, messages[4].Time), + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&to=ABCD&from=%f", ts.URL, chanID, messages[4].Time), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with invalid to as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=ABCD", ts.URL, domainID, chanID, messages[4].Time), + url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=ABCD", ts.URL, chanID, messages[4].Time), key: userToken, authResponse: true, status: http.StatusBadRequest, @@ -980,12 +974,14 @@ 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) + authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(validSession, tc.authnErr) if tc.key != "" { - authCall = things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.ThingsAuthzRes{Authorized: tc.authResponse}, tc.err) + authnCall = clients.On("Authenticate", mock.Anything, &grpcClientsV1.AuthnReq{ + ClientSecret: tc.key, + }).Return(&grpcClientsV1.AuthnRes{Id: testsutil.GenerateUUID(t), Authenticated: true}, tc.authnErr) } + authzCall := channels.On("Authorize", mock.Anything, mock.Anything).Return(&grpcChannelsV1.AuthzRes{Authorized: true}, tc.err) + repoCall := repo.On("ReadAll", chanID, tc.res.PageMetadata).Return(readers.MessagesPage{Total: tc.res.Total, Messages: fromSenml(tc.res.Messages)}, nil) req := testRequest{ client: ts.Client(), method: http.MethodGet, @@ -999,13 +995,13 @@ func TestReadAll(t *testing.T) { var page pageRes err = json.NewDecoder(res.Body).Decode(&page) assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) 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() + authzCall.Unset() + authnCall.Unset() + repoCall.Unset() } } diff --git a/readers/api/logging.go b/readers/api/logging.go index 49eedcbc0..30f013ec2 100644 --- a/readers/api/logging.go +++ b/readers/api/logging.go @@ -9,7 +9,7 @@ import ( "log/slog" "time" - "github.com/absmach/magistrala/readers" + "github.com/absmach/supermq/readers" ) var _ readers.MessageRepository = (*loggingMiddleware)(nil) diff --git a/readers/api/metrics.go b/readers/api/metrics.go index 026f3f435..717ab91bc 100644 --- a/readers/api/metrics.go +++ b/readers/api/metrics.go @@ -8,7 +8,7 @@ package api import ( "time" - "github.com/absmach/magistrala/readers" + "github.com/absmach/supermq/readers" "github.com/go-kit/kit/metrics" ) diff --git a/readers/api/requests.go b/readers/api/requests.go index df08f7963..a39dd5834 100644 --- a/readers/api/requests.go +++ b/readers/api/requests.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/readers" + apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/readers" ) const maxLimitSize = 1000 @@ -20,7 +20,6 @@ type listMessagesReq struct { chanID string token string key string - domainID string pageMeta readers.PageMetadata } @@ -28,9 +27,6 @@ func (req listMessagesReq) validate() error { if req.token == "" && req.key == "" { return apiutil.ErrBearerToken } - if req.token != "" && req.domainID == "" { - return apiutil.ErrMissingDomainID - } if req.chanID == "" { return apiutil.ErrMissingID diff --git a/readers/api/responses.go b/readers/api/responses.go index 980f23468..e1106c075 100644 --- a/readers/api/responses.go +++ b/readers/api/responses.go @@ -6,11 +6,11 @@ package api import ( "net/http" - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/readers" + "github.com/absmach/supermq" + "github.com/absmach/supermq/readers" ) -var _ magistrala.Response = (*pageRes)(nil) +var _ supermq.Response = (*pageRes)(nil) type pageRes struct { readers.PageMetadata diff --git a/readers/api/transport.go b/readers/api/transport.go index 194da47ff..50f8a1381 100644 --- a/readers/api/transport.go +++ b/readers/api/transport.go @@ -8,19 +8,19 @@ import ( "encoding/json" "net/http" - "github.com/absmach/magistrala" - "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/pkg/policies" - "github.com/absmach/magistrala/readers" + "github.com/absmach/supermq" + grpcChannelsV1 "github.com/absmach/supermq/api/grpc/channels/v1" + grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1" + apiutil "github.com/absmach/supermq/api/http/util" + smqauthn "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/connections" + "github.com/absmach/supermq/pkg/errors" + svcerr "github.com/absmach/supermq/pkg/errors/service" + "github.com/absmach/supermq/pkg/policies" + "github.com/absmach/supermq/readers" "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 ( @@ -47,23 +47,21 @@ const ( defFormat = "messages" ) -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 smqauthn.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..., ).ServeHTTP) - mux.Get("/health", magistrala.Health(svcName, instanceID)) + mux.Get("/health", supermq.Health(svcName, instanceID)) mux.Handle("/metrics", promhttp.Handler()) return mux @@ -154,10 +152,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, @@ -183,7 +180,7 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) { func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { w.Header().Set("Content-Type", contentType) - if ar, ok := response.(magistrala.Response); ok { + if ar, ok := response.(supermq.Response); ok { for k, v := range ar.Headers() { w.Header().Set(k, v) } @@ -239,43 +236,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 smqauthn.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 smqauthn.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/mocks/messages.go b/readers/mocks/messages.go index 3968840e8..ca6ceb40b 100644 --- a/readers/mocks/messages.go +++ b/readers/mocks/messages.go @@ -5,7 +5,7 @@ package mocks import ( - readers "github.com/absmach/magistrala/readers" + readers "github.com/absmach/supermq/readers" mock "github.com/stretchr/testify/mock" ) diff --git a/readers/postgres/README.md b/readers/postgres/README.md index 66e289d44..23b0105d5 100644 --- a/readers/postgres/README.md +++ b/readers/postgres/README.md @@ -8,46 +8,46 @@ 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 | "" | -| 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 | | +| Variable | Description | Default | +| ------------------------------------ | -------------------------------------------- | ---------------------------- | +| SMQ_POSTGRES_READER_LOG_LEVEL | Service log level | info | +| SMQ_POSTGRES_READER_HTTP_HOST | Service HTTP host | localhost | +| SMQ_POSTGRES_READER_HTTP_PORT | Service HTTP port | 9009 | +| SMQ_POSTGRES_READER_HTTP_SERVER_CERT | Service HTTP server cert | "" | +| SMQ_POSTGRES_READER_HTTP_SERVER_KEY | Service HTTP server key | "" | +| SMQ_POSTGRES_HOST | Postgres DB host | localhost | +| SMQ_POSTGRES_PORT | Postgres DB port | 5432 | +| SMQ_POSTGRES_USER | Postgres user | supermq | +| SMQ_POSTGRES_PASS | Postgres password | supermq | +| SMQ_POSTGRES_NAME | Postgres database name | messages | +| SMQ_POSTGRES_SSL_MODE | Postgres SSL mode | disabled | +| SMQ_POSTGRES_SSL_CERT | Postgres SSL certificate path | "" | +| SMQ_POSTGRES_SSL_KEY | Postgres SSL key | "" | +| SMQ_POSTGRES_SSL_ROOT_CERT | Postgres SSL root certificate path | "" | +| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | localhost:7000 | +| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC timeout in seconds | 1s | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_TLS | Clients service Auth gRPC TLS mode flag | false | +| SMQ_CLIENTS_AUTH_GRPC_CA_CERTS | Clients service Auth gRPC CA certificates | "" | +| SMQ_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 | +| SMQ_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | +| SMQ_AUTH_GRPC_CLIENT_TLS | Auth service gRPC TLS mode flag | false | +| SMQ_AUTH_GRPC_CA_CERTS | Auth service gRPC CA certificates | "" | +| SMQ_JAEGER_URL | Jaeger server URL | http://jaeger:4318/v1/traces | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | +| SMQ_POSTGRES_READER_INSTANCE_ID | Postgres reader instance ID | | ## Deployment -The service itself is distributed as Docker container. Check the [`postgres-reader`](https://github.com/absmach/magistrala/blob/main/docker/addons/postgres-reader/docker-compose.yml#L17-L41) service section in +The service itself is distributed as Docker container. Check the [`postgres-reader`](https://github.com/absmach/supermq/blob/main/docker/addons/postgres-reader/docker-compose.yml#L17-L41) service section in docker-compose file to see how service is deployed. To start the service, execute the following shell script: ```bash # download the latest version of the service -git clone https://github.com/absmach/magistrala +git clone https://github.com/absmach/supermq -cd magistrala +cd supermq # compile the postgres writer make postgres-writer @@ -56,32 +56,32 @@ make postgres-writer make install # Set the environment variables and run the service -MG_POSTGRES_READER_LOG_LEVEL=[Service log level] \ -MG_POSTGRES_READER_HTTP_HOST=[Service HTTP host] \ -MG_POSTGRES_READER_HTTP_PORT=[Service HTTP port] \ -MG_POSTGRES_READER_HTTP_SERVER_CERT=[Service HTTPS server certificate path] \ -MG_POSTGRES_READER_HTTP_SERVER_KEY=[Service HTTPS server key path] \ -MG_POSTGRES_HOST=[Postgres host] \ -MG_POSTGRES_PORT=[Postgres port] \ -MG_POSTGRES_USER=[Postgres user] \ -MG_POSTGRES_PASS=[Postgres password] \ -MG_POSTGRES_NAME=[Postgres database name] \ -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_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] \ -MG_AUTH_GRPC_CA_CERTS=[Auth service gRPC CA certificates] \ -MG_JAEGER_URL=[Jaeger server URL] \ -MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \ -MG_POSTGRES_READER_INSTANCE_ID=[Postgres reader instance ID] \ -$GOBIN/magistrala-postgres-reader +SMQ_POSTGRES_READER_LOG_LEVEL=[Service log level] \ +SMQ_POSTGRES_READER_HTTP_HOST=[Service HTTP host] \ +SMQ_POSTGRES_READER_HTTP_PORT=[Service HTTP port] \ +SMQ_POSTGRES_READER_HTTP_SERVER_CERT=[Service HTTPS server certificate path] \ +SMQ_POSTGRES_READER_HTTP_SERVER_KEY=[Service HTTPS server key path] \ +SMQ_POSTGRES_HOST=[Postgres host] \ +SMQ_POSTGRES_PORT=[Postgres port] \ +SMQ_POSTGRES_USER=[Postgres user] \ +SMQ_POSTGRES_PASS=[Postgres password] \ +SMQ_POSTGRES_NAME=[Postgres database name] \ +SMQ_POSTGRES_SSL_MODE=[Postgres SSL mode] \ +SMQ_POSTGRES_SSL_CERT=[Postgres SSL cert] \ +SMQ_POSTGRES_SSL_KEY=[Postgres SSL key] \ +SMQ_POSTGRES_SSL_ROOT_CERT=[Postgres SSL Root cert] \ +SMQ_CLIENTS_AUTH_GRPC_URL=[Clients service Auth GRPC URL] \ +SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=[Clients service Auth gRPC request timeout in seconds] \ +SMQ_CLIENTS_AUTH_GRPC_CLIENT_TLS=[Clients service Auth gRPC TLS mode flag] \ +SMQ_CLIENTS_AUTH_GRPC_CA_CERTS=[Clients service Auth gRPC CA certificates] \ +SMQ_AUTH_GRPC_URL=[Auth service gRPC URL] \ +SMQ_AUTH_GRPC_TIMEOUT=[Auth service gRPC request timeout in seconds] \ +SMQ_AUTH_GRPC_CLIENT_TLS=[Auth service gRPC TLS mode flag] \ +SMQ_AUTH_GRPC_CA_CERTS=[Auth service gRPC CA certificates] \ +SMQ_JAEGER_URL=[Jaeger server URL] \ +SMQ_SEND_TELEMETRY=[Send telemetry to supermq call home server] \ +SMQ_POSTGRES_READER_INSTANCE_ID=[Postgres reader instance ID] \ +$GOBIN/supermq-postgres-reader ``` ## Usage @@ -98,4 +98,4 @@ Comparator Usage Guide: | 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). +Official docs can be found [here](https://docs.supermq.abstractmachines.fr). diff --git a/readers/postgres/messages.go b/readers/postgres/messages.go index 4037b5b3d..2cab73e0a 100644 --- a/readers/postgres/messages.go +++ b/readers/postgres/messages.go @@ -7,9 +7,9 @@ import ( "encoding/json" "fmt" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/transformers/senml" - "github.com/absmach/magistrala/readers" + "github.com/absmach/supermq/pkg/errors" + "github.com/absmach/supermq/pkg/transformers/senml" + "github.com/absmach/supermq/readers" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" "github.com/jmoiron/sqlx" diff --git a/readers/postgres/messages_test.go b/readers/postgres/messages_test.go index 52b0e4028..e0a3831a0 100644 --- a/readers/postgres/messages_test.go +++ b/readers/postgres/messages_test.go @@ -9,12 +9,12 @@ import ( "testing" "time" - pwriter "github.com/absmach/magistrala/consumers/writers/postgres" "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/absmach/magistrala/pkg/transformers/senml" - "github.com/absmach/magistrala/readers" - preader "github.com/absmach/magistrala/readers/postgres" + pwriter "github.com/absmach/supermq/consumers/writers/postgres" + "github.com/absmach/supermq/pkg/transformers/json" + "github.com/absmach/supermq/pkg/transformers/senml" + "github.com/absmach/supermq/readers" + preader "github.com/absmach/supermq/readers/postgres" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/readers/postgres/setup_test.go b/readers/postgres/setup_test.go index 4e3bb0e41..4636f6a28 100644 --- a/readers/postgres/setup_test.go +++ b/readers/postgres/setup_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/absmach/magistrala/readers/postgres" + "github.com/absmach/supermq/readers/postgres" _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" diff --git a/readers/timescale/README.md b/readers/timescale/README.md index 7ce7db3b3..193d4ec15 100644 --- a/readers/timescale/README.md +++ b/readers/timescale/README.md @@ -8,45 +8,45 @@ 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 | "" | -| 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 | "" | +| Variable | Description | Default | +| ------------------------------------- | -------------------------------------------- | ---------------------------- | +| SMQ_TIMESCALE_READER_LOG_LEVEL | Service log level | info | +| SMQ_TIMESCALE_READER_HTTP_HOST | Service HTTP host | localhost | +| SMQ_TIMESCALE_READER_HTTP_PORT | Service HTTP port | 8180 | +| SMQ_TIMESCALE_READER_HTTP_SERVER_CERT | Service HTTP server certificate path | "" | +| SMQ_TIMESCALE_READER_HTTP_SERVER_KEY | Service HTTP server key path | "" | +| SMQ_TIMESCALE_HOST | Timescale DB host | localhost | +| SMQ_TIMESCALE_PORT | Timescale DB port | 5432 | +| SMQ_TIMESCALE_USER | Timescale user | supermq | +| SMQ_TIMESCALE_PASS | Timescale password | supermq | +| SMQ_TIMESCALE_NAME | Timescale database name | messages | +| SMQ_TIMESCALE_SSL_MODE | Timescale SSL mode | disabled | +| SMQ_TIMESCALE_SSL_CERT | Timescale SSL certificate path | "" | +| SMQ_TIMESCALE_SSL_KEY | Timescale SSL key | "" | +| SMQ_TIMESCALE_SSL_ROOT_CERT | Timescale SSL root certificate path | "" | +| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | localhost:7000 | +| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC timeout in seconds | 1s | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_TLS | Clients service Auth gRPC TLS enabled flag | false | +| SMQ_CLIENTS_AUTH_GRPC_CA_CERTS | Clients service Auth gRPC CA certificates | "" | +| SMQ_AUTH_GRPC_URL | Auth service gRPC URL | localhost:7001 | +| SMQ_AUTH_GRPC_TIMEOUT | Auth service gRPC timeout in seconds | 1s | +| SMQ_AUTH_GRPC_CLIENT_TLS | Auth service gRPC TLS enabled flag | false | +| SMQ_AUTH_GRPC_CA_CERT | Auth service gRPC CA certificate | "" | +| SMQ_JAEGER_URL | Jaeger server URL | http://jaeger:4318/v1/traces | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | +| SMQ_TIMESCALE_READER_INSTANCE_ID | Timescale reader instance ID | "" | ## Deployment -The service itself is distributed as Docker container. Check the [`timescale-reader`](https://github.com/absmach/magistrala/blob/main/docker/addons/timescale-reader/docker-compose.yml#L17-L41) service section in docker-compose file to see how service is deployed. +The service itself is distributed as Docker container. Check the [`timescale-reader`](https://github.com/absmach/supermq/blob/main/docker/addons/timescale-reader/docker-compose.yml#L17-L41) service section in docker-compose file to see how service is deployed. To start the service, execute the following shell script: ```bash # download the latest version of the service -git clone https://github.com/absmach/magistrala +git clone https://github.com/absmach/supermq -cd magistrala +cd supermq # compile the timescale writer make timescale-writer @@ -55,32 +55,32 @@ make timescale-writer make install # Set the environment variables and run the service -MG_TIMESCALE_READER_LOG_LEVEL=[Service log level] \ -MG_TIMESCALE_READER_HTTP_HOST=[Service HTTP host] \ -MG_TIMESCALE_READER_HTTP_PORT=[Service HTTP port] \ -MG_TIMESCALE_READER_HTTP_SERVER_CERT=[Service HTTP server cert] \ -MG_TIMESCALE_READER_HTTP_SERVER_KEY=[Service HTTP server key] \ -MG_TIMESCALE_HOST=[Timescale host] \ -MG_TIMESCALE_PORT=[Timescale port] \ -MG_TIMESCALE_USER=[Timescale user] \ -MG_TIMESCALE_PASS=[Timescale password] \ -MG_TIMESCALE_NAME=[Timescale database name] \ -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_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] \ -MG_AUTH_GRPC_CA_CERT=[Auth service Auth gRPC CA certificates] \ -MG_JAEGER_URL=[Jaeger server URL] \ -MG_SEND_TELEMETRY=[Send telemetry to magistrala call home server] \ -MG_TIMESCALE_READER_INSTANCE_ID=[Timescale reader instance ID] \ -$GOBIN/magistrala-timescale-reader +SMQ_TIMESCALE_READER_LOG_LEVEL=[Service log level] \ +SMQ_TIMESCALE_READER_HTTP_HOST=[Service HTTP host] \ +SMQ_TIMESCALE_READER_HTTP_PORT=[Service HTTP port] \ +SMQ_TIMESCALE_READER_HTTP_SERVER_CERT=[Service HTTP server cert] \ +SMQ_TIMESCALE_READER_HTTP_SERVER_KEY=[Service HTTP server key] \ +SMQ_TIMESCALE_HOST=[Timescale host] \ +SMQ_TIMESCALE_PORT=[Timescale port] \ +SMQ_TIMESCALE_USER=[Timescale user] \ +SMQ_TIMESCALE_PASS=[Timescale password] \ +SMQ_TIMESCALE_NAME=[Timescale database name] \ +SMQ_TIMESCALE_SSL_MODE=[Timescale SSL mode] \ +SMQ_TIMESCALE_SSL_CERT=[Timescale SSL cert] \ +SMQ_TIMESCALE_SSL_KEY=[Timescale SSL key] \ +SMQ_TIMESCALE_SSL_ROOT_CERT=[Timescale SSL Root cert] \ +SMQ_CLIENTS_AUTH_GRPC_URL=[Clients service Auth GRPC URL] \ +SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=[Clients service Auth gRPC request timeout in seconds] \ +SMQ_CLIENTS_AUTH_GRPC_CLIENT_TLS=[Clients service Auth gRPC TLS enabled flag] \ +SMQ_CLIENTS_AUTH_GRPC_CA_CERTS=[Clients service Auth gRPC CA certificates] \ +SMQ_AUTH_GRPC_URL=[Auth service Auth gRPC URL] \ +SMQ_AUTH_GRPC_TIMEOUT=[Auth service Auth gRPC request timeout in seconds] \ +SMQ_AUTH_GRPC_CLIENT_TLS=[Auth service Auth gRPC TLS enabled flag] \ +SMQ_AUTH_GRPC_CA_CERT=[Auth service Auth gRPC CA certificates] \ +SMQ_JAEGER_URL=[Jaeger server URL] \ +SMQ_SEND_TELEMETRY=[Send telemetry to supermq call home server] \ +SMQ_TIMESCALE_READER_INSTANCE_ID=[Timescale reader instance ID] \ +$GOBIN/supermq-timescale-reader ``` ## Usage @@ -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" | - -Official docs can be found [here](https://docs.magistrala.abstractmachines.fr). +| 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.supermq.abstractmachines.fr). diff --git a/readers/timescale/messages.go b/readers/timescale/messages.go index a6a844faf..94c96bdd5 100644 --- a/readers/timescale/messages.go +++ b/readers/timescale/messages.go @@ -7,9 +7,9 @@ import ( "encoding/json" "fmt" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/transformers/senml" - "github.com/absmach/magistrala/readers" + "github.com/absmach/supermq/pkg/errors" + "github.com/absmach/supermq/pkg/transformers/senml" + "github.com/absmach/supermq/readers" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" "github.com/jmoiron/sqlx" // required for DB access diff --git a/readers/timescale/messages_test.go b/readers/timescale/messages_test.go index 439a39429..23b2ba294 100644 --- a/readers/timescale/messages_test.go +++ b/readers/timescale/messages_test.go @@ -9,12 +9,12 @@ import ( "testing" "time" - twriter "github.com/absmach/magistrala/consumers/writers/timescale" "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/transformers/json" - "github.com/absmach/magistrala/pkg/transformers/senml" - "github.com/absmach/magistrala/readers" - treader "github.com/absmach/magistrala/readers/timescale" + twriter "github.com/absmach/supermq/consumers/writers/timescale" + "github.com/absmach/supermq/pkg/transformers/json" + "github.com/absmach/supermq/pkg/transformers/senml" + "github.com/absmach/supermq/readers" + treader "github.com/absmach/supermq/readers/timescale" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/readers/timescale/setup_test.go b/readers/timescale/setup_test.go index b4d14da50..519a6e717 100644 --- a/readers/timescale/setup_test.go +++ b/readers/timescale/setup_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/absmach/magistrala/readers/timescale" + "github.com/absmach/supermq/readers/timescale" _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access "github.com/jmoiron/sqlx" "github.com/ory/dockertest/v3" diff --git a/scripts/csv/channels.csv b/scripts/csv/channels.csv deleted file mode 100644 index 9b367f7c2..000000000 --- a/scripts/csv/channels.csv +++ /dev/null @@ -1,3 +0,0 @@ -channel_1 -channel_2 -channel_3 diff --git a/scripts/csv/things.csv b/scripts/csv/things.csv deleted file mode 100644 index 4636a4762..000000000 --- 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/things/README.md b/things/README.md deleted file mode 100644 index f570b0ff5..000000000 --- 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/doc.go b/things/api/doc.go deleted file mode 100644 index 2424852cc..000000000 --- a/things/api/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains API-related concerns: endpoint definitions, middlewares -// and all resource representations. -package api diff --git a/things/api/grpc/client.go b/things/api/grpc/client.go deleted file mode 100644 index f48ecd637..000000000 --- a/things/api/grpc/client.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpc - -import ( - "context" - "fmt" - "time" - - "github.com/absmach/magistrala" - "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" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -const svcName = "magistrala.ThingsService" - -var _ magistrala.ThingsServiceClient = (*grpcClient)(nil) - -type grpcClient struct { - timeout time.Duration - authorize endpoint.Endpoint -} - -// NewClient returns new gRPC client instance. -func NewClient(conn *grpc.ClientConn, timeout time.Duration) magistrala.ThingsServiceClient { - return &grpcClient{ - authorize: kitgrpc.NewClient( - conn, - svcName, - "Authorize", - encodeAuthorizeRequest, - decodeAuthorizeResponse, - magistrala.ThingsAuthzRes{}, - ).Endpoint(), - - timeout: timeout, - } -} - -func (client grpcClient) Authorize(ctx context.Context, req *magistrala.ThingsAuthzReq, _ ...grpc.CallOption) (r *magistrala.ThingsAuthzRes, 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(), - }) - if err != nil { - return &magistrala.ThingsAuthzRes{}, decodeError(err) - } - - ar := res.(authorizeRes) - return &magistrala.ThingsAuthzRes{Authorized: ar.authorized, Id: ar.id}, nil -} - -func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(*magistrala.ThingsAuthzRes) - return authorizeRes{authorized: res.Authorized, id: res.Id}, 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 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/things/api/grpc/doc.go deleted file mode 100644 index 20956ee50..000000000 --- a/things/api/grpc/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package grpc contains implementation of Auth service gRPC API. -package grpc diff --git a/things/api/grpc/endpoint.go b/things/api/grpc/endpoint.go deleted file mode 100644 index 0c00c38a7..000000000 --- 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 5feb89438..000000000 --- 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 890335ecf..000000000 --- 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/grpc/responses.go b/things/api/grpc/responses.go deleted file mode 100644 index 8e11f1273..000000000 --- a/things/api/grpc/responses.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpc - -type authorizeRes struct { - id string - authorized bool -} diff --git a/things/api/grpc/server.go b/things/api/grpc/server.go deleted file mode 100644 index 5dfe4584f..000000000 --- a/things/api/grpc/server.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package grpc - -import ( - "context" - - "github.com/absmach/magistrala" - mgauth "github.com/absmach/magistrala/auth" - "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) - -type grpcServer struct { - magistrala.UnimplementedThingsServiceServer - authorize kitgrpc.Handler -} - -// NewServer returns new AuthServiceServer instance. -func NewServer(svc things.Service) magistrala.ThingsServiceServer { - return &grpcServer{ - authorize: kitgrpc.NewServer( - (authorizeEndpoint(svc)), - decodeAuthorizeRequest, - encodeAuthorizeResponse, - ), - } -} - -func (s *grpcServer) Authorize(ctx context.Context, req *magistrala.ThingsAuthzReq) (*magistrala.ThingsAuthzRes, error) { - _, res, err := s.authorize.ServeGRPC(ctx, req) - if err != nil { - return nil, encodeError(err) - } - return res.(*magistrala.ThingsAuthzRes), 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(), - }, nil -} - -func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { - res := grpcRes.(authorizeRes) - return &magistrala.ThingsAuthzRes{Authorized: res.authorized, Id: res.id}, 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/things/api/http/channels.go b/things/api/http/channels.go deleted file mode 100644 index 7efd4685f..000000000 --- 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/clients.go b/things/api/http/clients.go deleted file mode 100644 index 285f5c439..000000000 --- a/things/api/http/clients.go +++ /dev/null @@ -1,380 +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" - "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 -} - -func decodeViewClient(_ context.Context, r *http.Request) (interface{}, error) { - req := viewClientReq{ - id: chi.URLParam(r, "thingID"), - } - - return req, nil -} - -func decodeViewClientPerms(_ context.Context, r *http.Request) (interface{}, error) { - req := viewClientPermsReq{ - id: chi.URLParam(r, "thingID"), - } - - return req, nil -} - -func decodeListClients(_ 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 := things.ToStatus(s) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listClientsReq{ - 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 decodeUpdateClient(_ 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 := updateClientReq{ - id: 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 decodeUpdateClientTags(_ 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 := updateClientTagsReq{ - id: 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 decodeUpdateClientCredentials(_ 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 := updateClientCredentialsReq{ - id: 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 decodeCreateClientReq(_ 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 c things.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, - } - - return req, nil -} - -func decodeCreateClientsReq(_ 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) - } - - c := createClientsReq{} - if err := json.NewDecoder(r.Body).Decode(&c.Things); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) - } - - return c, nil -} - -func decodeChangeClientStatus(_ context.Context, r *http.Request) (interface{}, error) { - req := changeClientStatusReq{ - id: chi.URLParam(r, "thingID"), - } - - return req, nil -} - -func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return nil, 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) - } - st, err := things.ToStatus(s) - 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) - } - req := listMembersReq{ - Page: things.Page{ - Status: st, - Offset: o, - Limit: l, - Permission: p, - Metadata: m, - ListPerms: lp, - }, - groupID: chi.URLParam(r, "groupID"), - } - 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"), - } - - return req, nil -} diff --git a/things/api/http/endpoints.go b/things/api/http/endpoints.go deleted file mode 100644 index 10b9abc69..000000000 --- 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/endpoints_test.go b/things/api/http/endpoints_test.go deleted file mode 100644 index 3c16c92e4..000000000 --- a/things/api/http/endpoints_test.go +++ /dev/null @@ -1,3356 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package http_test - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/0x6flab/namegenerator" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/internal/testsutil" - 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" - "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" -) - -var ( - secret = "strongsecret" - validCMetadata = things.Metadata{"role": "client"} - ID = testsutil.GenerateUUID(&testing.T{}) - client = things.Client{ - ID: ID, - Name: "clientname", - Tags: []string{"tag1", "tag2"}, - Credentials: things.Credentials{Identity: "clientidentity", Secret: secret}, - Metadata: validCMetadata, - Status: things.EnabledStatus, - } - validToken = "token" - inValidToken = "invalid" - inValid = "invalid" - validID = testsutil.GenerateUUID(&testing.T{}) - domainID = testsutil.GenerateUUID(&testing.T{}) - namesgen = namegenerator.NewGenerator() -) - -const contentType = "application/json" - -type testRequest struct { - client *http.Client - method string - url string - contentType string - token string - body io.Reader -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, tr.body) - if err != nil { - return nil, err - } - - if tr.token != "" { - req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) - } - - if tr.contentType != "" { - req.Header.Set("Content-Type", tr.contentType) - } - - req.Header.Set("Referer", "http://localhost") - - return tr.client.Do(req) -} - -func toJSON(data interface{}) string { - jsonData, err := json.Marshal(data) - if err != nil { - return "" - } - return string(jsonData) -} - -func newThingsServer() (*httptest.Server, *mocks.Service, *gmocks.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, "") - - return httptest.NewServer(mux), svc, gsvc, authn -} - -func TestCreateThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - client things.Client - domainID string - token string - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "register a new thing with a valid token", - client: client, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusCreated, - err: nil, - }, - { - desc: "register an existing thing", - client: client, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusConflict, - err: svcerr.ErrConflict, - }, - { - desc: "register a new thing with an empty token", - client: client, - domainID: domainID, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "register a thing with an invalid ID", - client: things.Client{ - ID: inValid, - Credentials: things.Credentials{ - Identity: "user@example.com", - Secret: "12345678", - }, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "register a thing that can't be marshalled", - client: things.Client{ - Credentials: things.Credentials{ - Identity: "user@example.com", - Secret: "12345678", - }, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: errors.ErrMalformedEntity, - }, - { - desc: "register thing with invalid status", - client: things.Client{ - ID: testsutil.GenerateUUID(t), - Credentials: things.Credentials{ - Identity: "newclientwithinvalidstatus@example.com", - Secret: secret, - }, - Status: things.AllStatus, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrInvalidStatus, - }, - { - desc: "create thing with invalid contentype", - client: things.Client{ - ID: testsutil.GenerateUUID(t), - Credentials: things.Credentials{ - Identity: "example@example.com", - Secret: secret, - }, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.client) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/", 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) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - authCall.Unset() - }) - } -} - -func TestCreateThings(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - num := 3 - var items []things.Client - for i := 0; i < num; i++ { - client := things.Client{ - ID: testsutil.GenerateUUID(t), - Name: namesgen.Generate(), - Credentials: things.Credentials{ - Identity: fmt.Sprintf("%s@example.com", namesgen.Generate()), - Secret: secret, - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - } - items = append(items, client) - } - - cases := []struct { - desc string - client []things.Client - domainID string - token string - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - len int - }{ - { - desc: "create things with valid token", - client: items, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusOK, - err: nil, - len: 3, - }, - { - desc: "create things with invalid token", - client: items, - token: inValidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - len: 0, - }, - { - desc: "create things with empty token", - client: items, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - len: 0, - }, - { - desc: "create things with empty request", - client: []things.Client{}, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - len: 0, - }, - { - desc: "create things with invalid IDs", - client: []things.Client{ - { - ID: inValid, - }, - { - ID: validID, - }, - { - ID: validID, - }, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "create things with invalid contentype", - client: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - }, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "create a thing that can't be marshalled", - client: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Credentials: things.Credentials{ - Identity: "user@example.com", - Secret: "12345678", - }, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - }, - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - err: errors.ErrMalformedEntity, - }, - { - desc: "create things with service error", - client: items, - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusUnprocessableEntity, - err: svcerr.ErrCreateEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.client) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/bulk", ts.URL, 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, mock.Anything, mock.Anything, mock.Anything).Return(tc.client, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - - var bodyRes respBody - err = json.NewDecoder(res.Body).Decode(&bodyRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if bodyRes.Err != "" || bodyRes.Message != "" { - err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message)) - } - 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.len, bodyRes.Total, fmt.Sprintf("%s: expected %d got %d", tc.desc, tc.len, bodyRes.Total)) - 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 TestListThings(t *testing.T) { - ts, svc, _, authn := newThingsServer() - 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: "list things 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{ - Total: 1, - }, - Clients: []things.Client{client}, - }, - err: nil, - }, - { - desc: "list things 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{ - Total: 1, - }, - Clients: []things.Client{client}, - }, - err: nil, - }, - { - desc: "list things with empty token", - domainID: domainID, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "list things with invalid token", - domainID: domainID, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list things with offset", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ - Offset: 1, - Total: 1, - }, - Clients: []things.Client{client}, - }, - query: "offset=1", - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with invalid offset", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "offset=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with limit", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ - Limit: 1, - Total: 1, - }, - Clients: []things.Client{client}, - }, - query: "limit=1", - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with invalid limit", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "limit=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with limit greater than max", - token: validToken, - domainID: domainID, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with name", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ - Total: 1, - }, - Clients: []things.Client{client}, - }, - query: "name=clientname", - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with invalid name", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "name=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with duplicate name", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "name=1&name=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list things with status", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ - Total: 1, - }, - Clients: []things.Client{client}, - }, - query: "status=enabled", - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with invalid status", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "status=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with duplicate status", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list things with tags", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ - Total: 1, - }, - Clients: []things.Client{client}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with invalid tags", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "tag=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with duplicate tags", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list things with metadata", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ - Total: 1, - }, - Clients: []things.Client{client}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with invalid metadata", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "metadata=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with duplicate metadata", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list things with permissions", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - listThingsResponse: things.ClientsPage{ - Page: things.Page{ - Total: 1, - }, - Clients: []things.Client{client}, - }, - query: "permission=view", - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with invalid permissions", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "permission=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with duplicate permissions", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list things 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{ - Total: 1, - }, - Clients: []things.Client{client}, - }, - query: "list_perms=true", - status: http.StatusOK, - err: nil, - }, - { - desc: "list things with invalid list perms", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "list_perms=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list things with duplicate list perms", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: domainID + "_" + validID, SuperAdmin: false}, - query: "list_perms=true&listPerms=true", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: ts.Client(), - method: http.MethodGet, - url: ts.URL + "/" + tc.domainID + "/things?" + 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) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - - var bodyRes respBody - err = json.NewDecoder(res.Body).Decode(&bodyRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if bodyRes.Err != "" || bodyRes.Message != "" { - err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message)) - } - 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() - authCall.Unset() - }) - } -} - -func TestViewThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - id string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "view client with valid token", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - id: client.ID, - status: http.StatusOK, - - err: nil, - }, - { - desc: "view client with invalid token", - domainID: domainID, - token: inValidToken, - id: client.ID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "view client with empty token", - domainID: domainID, - token: "", - id: client.ID, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "view client with invalid id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - id: inValid, - status: http.StatusForbidden, - - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: ts.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/things/%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) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - authCall.Unset() - }) - } -} - -func TestViewThingPerms(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - domainID string - token string - thingID string - response []string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "view thing permissions with valid token", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - thingID: client.ID, - response: []string{"view", "delete", "membership"}, - status: http.StatusOK, - - err: nil, - }, - { - desc: "view thing permissions with invalid token", - domainID: domainID, - token: inValidToken, - thingID: client.ID, - response: []string{}, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "view thing permissions with empty token", - domainID: domainID, - token: "", - thingID: client.ID, - response: []string{}, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "view thing permissions with invalid id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - thingID: inValid, - response: []string{}, - status: http.StatusForbidden, - - err: svcerr.ErrAuthorization, - }, - { - desc: "view thing permissions with empty id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - thingID: "", - response: []string{}, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(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), - 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) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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, len(tc.response), len(resBody.Permissions), fmt.Sprintf("%s: expected %d got %d", tc.desc, len(tc.response), len(resBody.Permissions))) - 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() - authCall.Unset() - }) - } -} - -func TestUpdateThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - newName := "newname" - newTag := "newtag" - newMetadata := things.Metadata{"newkey": "newvalue"} - - cases := []struct { - desc string - id string - data string - clientResponse things.Client - domainID string - token string - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "update thing 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{ - ID: client.ID, - Name: newName, - Tags: []string{newTag}, - Metadata: newMetadata, - }, - status: http.StatusOK, - - err: nil, - }, - { - desc: "update thing with invalid token", - id: client.ID, - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), - domainID: domainID, - token: inValidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update thing with empty token", - id: client.ID, - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), - domainID: domainID, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "update thing with invalid contentype", - id: client.ID, - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - - err: apiutil.ErrValidation, - }, - { - desc: "update thing with malformed data", - id: client.ID, - data: fmt.Sprintf(`{"name":%s}`, "invalid"), - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "update thing with empty id", - id: " ", - data: fmt.Sprintf(`{"name":"%s","tags":["%s"],"metadata":%s}`, newName, newTag, toJSON(newMetadata)), - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingID, - }, - { - desc: "update thing 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{}, - status: http.StatusBadRequest, - err: apiutil.ErrNameSize, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: ts.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/%s/things/%s", ts.URL, tc.domainID, tc.id), - 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("Update", mock.Anything, tc.authnRes, mock.Anything).Return(tc.clientResponse, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - - 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)) - } - - if err == nil { - assert.Equal(t, tc.clientResponse.ID, resBody.ID, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.clientResponse, resBody.ID)) - } - 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() - authCall.Unset() - }) - } -} - -func TestUpdateThingsTags(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - newTag := "newtag" - - cases := []struct { - desc string - id string - data string - contentType string - clientResponse things.Client - domainID string - token string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "update thing tags with valid token", - id: client.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - clientResponse: things.Client{ - ID: client.ID, - Tags: []string{newTag}, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusOK, - - err: nil, - }, - { - desc: "update thing tags with empty token", - id: client.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - domainID: domainID, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "update thing tags with invalid token", - id: client.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - domainID: domainID, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update thing tags with invalid id", - id: client.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusForbidden, - - err: svcerr.ErrAuthorization, - }, - { - desc: "update thing tags with invalid contentype", - id: client.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: "application/xml", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "update things tags with empty id", - id: "", - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "update things with malfomed data", - id: client.ID, - data: fmt.Sprintf(`{"tags":[%s]}`, newTag), - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(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), - 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("UpdateTags", mock.Anything, tc.authnRes, mock.Anything).Return(tc.clientResponse, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - 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 TestUpdateClientSecret(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - data string - client things.Client - contentType string - domainID string - token string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "update thing secret with valid token", - data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), - client: things.Client{ - ID: client.ID, - Credentials: things.Credentials{ - Identity: "clientname", - Secret: "strongersecret", - }, - }, - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "update thing secret with empty token", - data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), - client: things.Client{ - ID: client.ID, - Credentials: things.Credentials{ - Identity: "clientname", - Secret: "strongersecret", - }, - }, - contentType: contentType, - domainID: domainID, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "update thing secret with invalid token", - data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), - client: things.Client{ - ID: client.ID, - Credentials: things.Credentials{ - Identity: "clientname", - Secret: "strongersecret", - }, - }, - contentType: contentType, - domainID: domainID, - token: inValid, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update thing secret with empty id", - data: fmt.Sprintf(`{"secret": "%s"}`, "strongersecret"), - client: things.Client{ - ID: "", - Credentials: things.Credentials{ - Identity: "clientname", - Secret: "strongersecret", - }, - }, - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "update thing secret with empty secret", - data: fmt.Sprintf(`{"secret": "%s"}`, ""), - client: things.Client{ - ID: client.ID, - Credentials: things.Credentials{ - Identity: "clientname", - Secret: "", - }, - }, - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "update thing secret with invalid contentype", - data: fmt.Sprintf(`{"secret": "%s"}`, ""), - client: things.Client{ - ID: client.ID, - Credentials: things.Credentials{ - Identity: "clientname", - Secret: "", - }, - }, - contentType: "application/xml", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusUnsupportedMediaType, - - err: apiutil.ErrValidation, - }, - { - desc: "update thing secret with malformed data", - data: fmt.Sprintf(`{"secret": %s}`, "invalid"), - client: things.Client{ - ID: client.ID, - Credentials: things.Credentials{ - Identity: "clientname", - Secret: "", - }, - }, - contentType: contentType, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - 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.MethodPatch, - url: fmt.Sprintf("%s/%s/things/%s/secret", ts.URL, tc.domainID, tc.client.ID), - 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("UpdateSecret", mock.Anything, tc.authnRes, tc.client.ID, mock.Anything).Return(tc.client, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - 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 TestEnableThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - client things.Client - response things.Client - domainID string - token string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "enable thing with valid token", - client: client, - response: things.Client{ - ID: client.ID, - Status: things.EnabledStatus, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusOK, - - err: nil, - }, - { - desc: "enable thing with invalid token", - client: client, - domainID: domainID, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "enable thing with empty id", - client: things.Client{ - ID: "", - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.client) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/%s/enable", ts.URL, tc.domainID, tc.client.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("Enable", mock.Anything, tc.authnRes, tc.client.ID).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 - 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)) - } - if err == nil { - assert.Equal(t, tc.response.Status, resBody.Status, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.response.Status, resBody.Status)) - } - 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() - authCall.Unset() - }) - } -} - -func TestDisableThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - client things.Client - response things.Client - domainID string - token string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "disable thing with valid token", - client: client, - response: things.Client{ - ID: client.ID, - Status: things.DisabledStatus, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusOK, - - err: nil, - }, - { - desc: "disable thing with invalid token", - client: client, - domainID: domainID, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "disable thing with empty id", - client: things.Client{ - ID: "", - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.client) - req := testRequest{ - client: ts.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/%s/disable", ts.URL, tc.domainID, tc.client.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("Disable", mock.Anything, tc.authnRes, tc.client.ID).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 - 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)) - } - if err == nil { - assert.Equal(t, tc.response.Status, resBody.Status, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.response.Status, resBody.Status)) - } - 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() - authCall.Unset() - }) - } -} - -func TestShareThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - data string - thingID string - domainID string - token string - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "share thing with valid token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusCreated, - - err: nil, - }, - { - desc: "share thing with invalid token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, - domainID: domainID, - token: inValidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "share thing with empty token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, - domainID: domainID, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "share thing with empty id", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: " ", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingID, - }, - { - desc: "share thing with missing relation", - data: fmt.Sprintf(`{"relation": "%s", user_ids" : ["%s", "%s"]}`, " ", validID, validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingRelation, - }, - { - desc: "share thing with malformed data", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [%s, "%s"]}`, "editor", "invalid", validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "share thing with empty thing id", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: "", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "share thing with empty relation", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, " ", validID, validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingRelation, - }, - { - desc: "share thing with empty user ids", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [" ", " "]}`, "editor"), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "share thing with invalid content type", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - - 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/things/%s/share", ts.URL, tc.domainID, tc.thingID), - 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) - 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 TestUnShareThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - data string - thingID string - domainID string - token string - contentType string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "unshare thing with valid token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusNoContent, - - err: nil, - }, - { - desc: "unshare thing with invalid token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, - domainID: domainID, - token: inValidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "unshare thing with empty token", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, - domainID: domainID, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "unshare thing with empty id", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: " ", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingID, - }, - { - desc: "unshare thing with missing relation", - data: fmt.Sprintf(`{"relation": "%s", user_ids" : ["%s", "%s"]}`, " ", validID, validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingRelation, - }, - { - desc: "unshare thing with malformed data", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [%s, "%s"]}`, "editor", "invalid", validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unshare thing with empty thing id", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: "", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unshare thing with empty relation", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, " ", validID, validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingRelation, - }, - { - desc: "unshare thing with empty user ids", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : [" ", " "]}`, "editor"), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "unshare thing with invalid content type", - data: fmt.Sprintf(`{"relation": "%s", "user_ids" : ["%s", "%s"]}`, "editor", validID, validID), - thingID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - - 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/things/%s/unshare", ts.URL, tc.domainID, tc.thingID), - 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) - 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 TestDeleteThing(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - id string - domainID string - token string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "delete thing with valid token", - id: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusNoContent, - - err: nil, - }, - { - desc: "delete thing with invalid token", - id: client.ID, - domainID: domainID, - token: inValidToken, - authnRes: mgauthn.Session{}, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "delete thing with empty token", - id: client.ID, - domainID: domainID, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "delete thing with empty id", - id: " ", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - client: ts.Client(), - method: http.MethodDelete, - url: fmt.Sprintf("%s/%s/things/%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("Delete", mock.Anything, tc.authnRes, tc.id).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 TestListMembers(t *testing.T) { - ts, svc, _, authn := newThingsServer() - defer ts.Close() - - cases := []struct { - desc string - query string - groupID string - domainID string - token string - listMembersResponse things.MembersPage - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "list members with valid token", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Total: 1, - }, - Members: []things.Client{client}, - }, - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with empty token", - domainID: domainID, - token: "", - groupID: client.ID, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "list members with invalid token", - domainID: domainID, - token: inValidToken, - groupID: client.ID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list members with offset", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "offset=1", - groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Offset: 1, - Total: 1, - }, - Members: []things.Client{client}, - }, - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with invalid offset", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "offset=invalid", - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with limit", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "limit=1", - groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Limit: 1, - Total: 1, - }, - Members: []things.Client{client}, - }, - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with invalid limit", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "limit=invalid", - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with limit greater than 100", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: fmt.Sprintf("limit=%d", api.MaxLimitSize+1), - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with channel_id", - domainID: domainID, - token: validToken, - 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{ - Total: 1, - }, - Members: []things.Client{client}, - }, - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with invalid channel_id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "channel_id=invalid", - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with duplicate channel_id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: fmt.Sprintf("channel_id=%s&channel_id=%s", validID, validID), - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with connected set", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "connected=true", - groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Total: 1, - }, - Members: []things.Client{client}, - }, - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with invalid connected set", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "connected=invalid", - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with duplicate connected set", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "connected=true&connected=false", - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with empty group id", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - query: "", - groupID: "", - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with status", - query: fmt.Sprintf("status=%s", things.EnabledStatus), - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Total: 1, - }, - Members: []things.Client{client}, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with invalid status", - query: "status=invalid", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with duplicate status", - query: fmt.Sprintf("status=%s&status=%s", things.EnabledStatus, things.DisabledStatus), - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with metadata", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Total: 1, - }, - Members: []things.Client{client}, - }, - groupID: client.ID, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with invalid metadata", - query: "metadata=invalid", - groupID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with duplicate metadata", - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - groupID: client.ID, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list members with permission", - query: fmt.Sprintf("permission=%s", "view"), - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Total: 1, - }, - Members: []things.Client{client}, - }, - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with duplicate permission", - query: fmt.Sprintf("permission=%s&permission=%s", "view", "edit"), - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with list permission", - query: "list_perms=true", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Total: 1, - }, - Members: []things.Client{client}, - }, - groupID: client.ID, - status: http.StatusOK, - - err: nil, - }, - { - desc: "list members with invalid list permission", - query: "list_perms=invalid", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - desc: "list members with duplicate list permission", - query: "list_perms=true&list_perms=false", - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - status: http.StatusBadRequest, - - err: apiutil.ErrValidation, - }, - { - 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"), - domainID: domainID, - token: validToken, - authnRes: mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}, - groupID: client.ID, - listMembersResponse: things.MembersPage{ - Page: things.Page{ - Offset: 1, - Limit: 1, - Total: 1, - }, - Members: []things.Client{client}, - }, - status: http.StatusOK, - - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(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, - contentType: contentType, - token: tc.token, - } - - authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListClientsByGroup", mock.Anything, tc.authnRes, mock.Anything, mock.Anything).Return(tc.listMembersResponse, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - - var bodyRes respBody - err = json.NewDecoder(res.Body).Decode(&bodyRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if bodyRes.Err != "" || bodyRes.Message != "" { - err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message)) - } - 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() - authCall.Unset() - }) - } -} - -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"` -} - -type groupReqBody struct { - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` - GroupIDs []string `json:"group_ids"` - ChannelID string `json:"channel_id"` - ThingID string `json:"thing_id"` -} diff --git a/things/api/http/requests.go b/things/api/http/requests.go deleted file mode 100644 index 8c644cd95..000000000 --- a/things/api/http/requests.go +++ /dev/null @@ -1,255 +0,0 @@ -// 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" - "github.com/absmach/magistrala/things" -) - -type createClientReq struct { - thing things.Client -} - -func (req createClientReq) validate() error { - if len(req.thing.Name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - if req.thing.ID != "" { - return api.ValidateUUID(req.thing.ID) - } - - return nil -} - -type createClientsReq struct { - Things []things.Client -} - -func (req createClientsReq) validate() error { - if len(req.Things) == 0 { - return apiutil.ErrEmptyList - } - for _, thing := range req.Things { - if thing.ID != "" { - if err := api.ValidateUUID(thing.ID); err != nil { - return err - } - } - if len(thing.Name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - } - - return nil -} - -type viewClientReq struct { - id string -} - -func (req viewClientReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type viewClientPermsReq struct { - id string -} - -func (req viewClientPermsReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type listClientsReq struct { - status things.Status - offset uint64 - limit uint64 - name string - tag string - permission string - visibility string - userID string - listPerms bool - metadata things.Metadata - id string -} - -func (req listClientsReq) 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 listMembersReq struct { - things.Page - groupID string -} - -func (req listMembersReq) validate() error { - if req.groupID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateClientReq struct { - id string - Name string `json:"name,omitempty"` - Metadata map[string]interface{} `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` -} - -func (req updateClientReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - if len(req.Name) > api.MaxNameSize { - return apiutil.ErrNameSize - } - - return nil -} - -type updateClientTagsReq struct { - id string - Tags []string `json:"tags,omitempty"` -} - -func (req updateClientTagsReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateClientCredentialsReq struct { - id string - Secret string `json:"secret,omitempty"` -} - -func (req updateClientCredentialsReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - if req.Secret == "" { - return apiutil.ErrMissingSecret - } - - return nil -} - -type changeClientStatusReq struct { - id string -} - -func (req changeClientStatusReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type assignUsersRequest struct { - groupID string - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -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 == "" { - 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 - } - return nil -} - -type thingShareRequest struct { - thingID string - Relation string `json:"relation,omitempty"` - UserIDs []string `json:"user_ids,omitempty"` -} - -func (req *thingShareRequest) validate() error { - if req.thingID == "" { - return apiutil.ErrMissingID - } - if req.Relation == "" || len(req.UserIDs) == 0 { - return apiutil.ErrMalformedPolicy - } - return nil -} - -type deleteClientReq struct { - id string -} - -func (req deleteClientReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} diff --git a/things/api/http/requests_test.go b/things/api/http/requests_test.go deleted file mode 100644 index a4529a9b5..000000000 --- a/things/api/http/requests_test.go +++ /dev/null @@ -1,612 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package http - -import ( - "strings" - "testing" - - "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" -) - -const ( - valid = "valid" - invalid = "invalid" - name = "client" -) - -var validID = testsutil.GenerateUUID(&testing.T{}) - -func TestCreateThingReqValidate(t *testing.T) { - cases := []struct { - desc string - req createClientReq - err error - }{ - { - desc: "valid request", - req: createClientReq{ - thing: things.Client{ - ID: validID, - Name: valid, - }, - }, - err: nil, - }, - { - desc: "name too long", - req: createClientReq{ - thing: things.Client{ - ID: validID, - Name: strings.Repeat("a", api.MaxNameSize+1), - }, - }, - err: apiutil.ErrNameSize, - }, - { - desc: "invalid id", - req: createClientReq{ - thing: things.Client{ - ID: invalid, - Name: valid, - }, - }, - err: apiutil.ErrInvalidIDFormat, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - err := tc.req.validate() - assert.Equal(t, tc.err, err) - }) - } -} - -func TestCreateThingsReqValidate(t *testing.T) { - cases := []struct { - desc string - req createClientsReq - err error - }{ - { - desc: "valid request", - req: createClientsReq{ - Things: []things.Client{ - { - ID: validID, - Name: valid, - }, - }, - }, - err: nil, - }, - { - desc: "empty list", - req: createClientsReq{ - Things: []things.Client{}, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "name too long", - req: createClientsReq{ - Things: []things.Client{ - { - ID: validID, - Name: strings.Repeat("a", api.MaxNameSize+1), - }, - }, - }, - err: apiutil.ErrNameSize, - }, - { - desc: "invalid id", - req: createClientsReq{ - Things: []things.Client{ - { - ID: invalid, - Name: valid, - }, - }, - }, - err: apiutil.ErrInvalidIDFormat, - }, - } - 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 TestViewClientReqValidate(t *testing.T) { - cases := []struct { - desc string - req viewClientReq - err error - }{ - { - desc: "valid request", - req: viewClientReq{ - id: validID, - }, - err: nil, - }, - { - desc: "empty id", - req: viewClientReq{ - id: "", - }, - 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 TestViewClientPermsReq(t *testing.T) { - cases := []struct { - desc string - req viewClientPermsReq - err error - }{ - { - desc: "valid request", - req: viewClientPermsReq{ - id: validID, - }, - err: nil, - }, - { - desc: "empty id", - req: viewClientPermsReq{ - id: "", - }, - 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 TestListClientsReqValidate(t *testing.T) { - cases := []struct { - desc string - req listClientsReq - err error - }{ - { - desc: "valid request", - req: listClientsReq{ - limit: 10, - }, - err: nil, - }, - { - desc: "limit too big", - req: listClientsReq{ - limit: api.MaxLimitSize + 1, - }, - err: apiutil.ErrLimitSize, - }, - { - desc: "limit too small", - req: listClientsReq{ - limit: 0, - }, - err: apiutil.ErrLimitSize, - }, - { - desc: "invalid visibility", - req: listClientsReq{ - limit: 10, - visibility: "invalid", - }, - err: apiutil.ErrInvalidVisibilityType, - }, - { - desc: "name too long", - req: listClientsReq{ - limit: 10, - name: strings.Repeat("a", api.MaxNameSize+1), - }, - err: apiutil.ErrNameSize, - }, - } - 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 TestListMembersReqValidate(t *testing.T) { - cases := []struct { - desc string - req listMembersReq - err error - }{ - { - desc: "valid request", - req: listMembersReq{ - groupID: validID, - }, - err: nil, - }, - { - desc: "empty id", - req: listMembersReq{ - groupID: "", - }, - 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 TestUpdateClientReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateClientReq - err error - }{ - { - desc: "valid request", - req: updateClientReq{ - id: validID, - Name: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: updateClientReq{ - id: "", - Name: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "name too long", - req: updateClientReq{ - id: validID, - Name: strings.Repeat("a", api.MaxNameSize+1), - }, - err: apiutil.ErrNameSize, - }, - } - 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 TestUpdateClientTagsReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateClientTagsReq - err error - }{ - { - desc: "valid request", - req: updateClientTagsReq{ - id: validID, - Tags: []string{"tag1", "tag2"}, - }, - err: nil, - }, - { - desc: "empty id", - req: updateClientTagsReq{ - id: "", - Tags: []string{"tag1", "tag2"}, - }, - 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 TestUpdateClientCredentialsReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateClientCredentialsReq - err error - }{ - { - desc: "valid request", - req: updateClientCredentialsReq{ - id: validID, - Secret: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: updateClientCredentialsReq{ - id: "", - Secret: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty secret", - req: updateClientCredentialsReq{ - id: validID, - Secret: "", - }, - err: apiutil.ErrMissingSecret, - }, - } - 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 TestChangeClientStatusReqValidate(t *testing.T) { - cases := []struct { - desc string - req changeClientStatusReq - err error - }{ - { - desc: "valid request", - req: changeClientStatusReq{ - id: validID, - }, - err: nil, - }, - { - desc: "empty id", - req: changeClientStatusReq{ - id: "", - }, - 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 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 - req deleteClientReq - err error - }{ - { - desc: "valid request", - req: deleteClientReq{ - id: validID, - }, - err: nil, - }, - { - desc: "empty id", - req: deleteClientReq{ - id: "", - }, - 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) - }) - } -} diff --git a/things/api/http/responses.go b/things/api/http/responses.go deleted file mode 100644 index c998bb058..000000000 --- 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 415e463d0..000000000 --- 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/doc.go b/things/cache/doc.go deleted file mode 100644 index c73f0c041..000000000 --- a/things/cache/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package cache contains the domain concept definitions needed to -// support Magistrala things cache service functionality. -package cache diff --git a/things/cache/setup_test.go b/things/cache/setup_test.go deleted file mode 100644 index 716f0672c..000000000 --- a/things/cache/setup_test.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cache_test - -import ( - "context" - "fmt" - "log" - "os" - "testing" - - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "github.com/redis/go-redis/v9" -) - -var ( - redisClient *redis.Client - redisURL string -) - -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: "redis", - Tag: "7.2.4-alpine", - }, func(config *docker.HostConfig) { - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{Name: "no"} - }) - if err != nil { - log.Fatalf("Could not start container: %s", err) - } - - redisURL = fmt.Sprintf("redis://localhost:%s/0", container.GetPort("6379/tcp")) - opts, err := redis.ParseURL(redisURL) - if err != nil { - log.Fatalf("Could not parse redis URL: %s", err) - } - - if err := pool.Retry(func() error { - redisClient = redis.NewClient(opts) - - return redisClient.Ping(context.Background()).Err() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - code := m.Run() - - if err := pool.Purge(container); err != nil { - log.Fatalf("Could not purge container: %s", err) - } - - os.Exit(code) -} diff --git a/things/cache/things.go b/things/cache/things.go deleted file mode 100644 index b09aa6efc..000000000 --- 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/cache/things_test.go b/things/cache/things_test.go deleted file mode 100644 index 8fa34e222..000000000 --- a/things/cache/things_test.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package cache_test - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "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" -) - -const ( - testKey = "testKey" - testID = "testID" - testKey2 = "testKey2" - testID2 = "testID2" -) - -func TestSave(t *testing.T) { - redisClient.FlushAll(context.Background()) - tscache := cache.NewCache(redisClient, 1*time.Minute) - ctx := context.Background() - - cases := []struct { - desc string - key string - id string - err error - }{ - { - desc: "Save thing to cache", - key: testKey, - id: testID, - err: nil, - }, - { - desc: "Save already cached thing to cache", - key: testKey, - id: testID, - err: nil, - }, - { - desc: "Save another thing to cache", - key: testKey2, - id: testID2, - err: nil, - }, - { - desc: "Save thing with long key ", - key: strings.Repeat("a", 513*1024*1024), - id: testID, - err: repoerr.ErrCreateEntity, - }, - { - desc: "Save thing with long id ", - key: testKey, - id: strings.Repeat("a", 513*1024*1024), - err: repoerr.ErrCreateEntity, - }, - { - desc: "Save thing with empty key", - key: "", - id: testID, - err: repoerr.ErrCreateEntity, - }, - { - desc: "Save thing with empty id", - key: testKey, - id: "", - err: repoerr.ErrCreateEntity, - }, - { - desc: "Save thing with empty key and id", - key: "", - id: "", - err: repoerr.ErrCreateEntity, - }, - } - - for _, tc := range cases { - err := tscache.Save(ctx, tc.key, tc.id) - if err == nil { - id, _ := tscache.ID(ctx, tc.key) - assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.id, id)) - } - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) - } -} - -func TestID(t *testing.T) { - redisClient.FlushAll(context.Background()) - tscache := cache.NewCache(redisClient, 1*time.Minute) - ctx := context.Background() - - err := tscache.Save(ctx, testKey, testID) - assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err)) - - cases := []struct { - desc string - key string - id string - err error - }{ - { - desc: "Get thing ID from cache", - key: testKey, - id: testID, - err: nil, - }, - { - desc: "Get thing ID from cache for non existing thing", - key: "nonExistingKey", - id: "", - err: repoerr.ErrNotFound, - }, - { - desc: "Get thing ID from cache for empty key", - key: "", - id: "", - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - id, err := tscache.ID(ctx, tc.key) - if err == nil { - assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.id, id)) - } - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestRemove(t *testing.T) { - redisClient.FlushAll(context.Background()) - tscache := cache.NewCache(redisClient, 1*time.Minute) - ctx := context.Background() - - err := tscache.Save(ctx, testKey, testID) - assert.Nil(t, err, fmt.Sprintf("Unexpected error while trying to save: %s", err)) - - cases := []struct { - desc string - key string - err error - }{ - { - desc: "Remove existing thing from cache", - key: testID, - err: nil, - }, - { - desc: "Remove non existing thing from cache", - key: testID2, - err: nil, - }, - { - desc: "Remove thing with empty ID from cache", - key: "", - err: nil, - }, - { - desc: "Remove thing with long id from cache", - key: strings.Repeat("a", 513*1024*1024), - err: repoerr.ErrRemoveEntity, - }, - } - - for _, tc := range cases { - err := tscache.Remove(ctx, tc.key) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} diff --git a/things/clients.go b/things/clients.go deleted file mode 100644 index 8894c171e..000000000 --- a/things/clients.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package things - -import ( - "context" - "time" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/postgres" -) - -type AuthzReq struct { - ChannelID string - ClientID string - ClientKey string - Permission string -} - -type ClientRepository struct { - DB postgres.Database -} - -// Repository is the interface that wraps the basic methods for -// a client repository. -// -//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" -type Repository interface { - // RetrieveByID retrieves client by its unique ID. - RetrieveByID(ctx context.Context, id string) (Client, error) - - // RetrieveAll retrieves all clients. - RetrieveAll(ctx context.Context, pm Page) (ClientsPage, error) - - // SearchClients retrieves clients based on search criteria. - SearchClients(ctx context.Context, pm Page) (ClientsPage, error) - - // RetrieveAllByIDs retrieves for given client IDs . - RetrieveAllByIDs(ctx context.Context, pm Page) (ClientsPage, error) - - // Update updates the client name and metadata. - Update(ctx context.Context, client Client) (Client, error) - - // UpdateTags updates the client tags. - UpdateTags(ctx context.Context, client Client) (Client, error) - - // UpdateIdentity updates identity for client with given id. - UpdateIdentity(ctx context.Context, client Client) (Client, error) - - // UpdateSecret updates secret for client with given identity. - UpdateSecret(ctx context.Context, client Client) (Client, error) - - // ChangeStatus changes client status to enabled or disabled - ChangeStatus(ctx context.Context, client Client) (Client, error) - - // Delete deletes client with given id - Delete(ctx context.Context, id string) error - - // Save persists the client account. A non-nil error is returned to indicate - // operation failure. - Save(ctx context.Context, client ...Client) ([]Client, error) - - // RetrieveBySecret retrieves a client based on the secret (key). - RetrieveBySecret(ctx context.Context, key string) (Client, error) -} - -// 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" -type Service interface { - // CreateClients creates new client. In case of the failed registration, a - // non-nil error value is returned. - CreateClients(ctx context.Context, session authn.Session, client ...Client) ([]Client, error) - - // 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) - - // UpdateTags updates the client's tags. - UpdateTags(ctx context.Context, session authn.Session, client Client) (Client, error) - - // UpdateSecret updates the client's secret - UpdateSecret(ctx context.Context, session authn.Session, id, key string) (Client, error) - - // Enable logically enableds the client identified with the provided ID - Enable(ctx context.Context, session authn.Session, id string) (Client, error) - - // 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 - - // Identify returns client ID for given client key. - Identify(ctx context.Context, key string) (string, error) - - // Authorize used for Clients authorization. - Authorize(ctx context.Context, req AuthzReq) (string, error) - - // Delete deletes client with given ID. - Delete(ctx context.Context, session authn.Session, id string) error -} - -// Cache contains client caching interface. -// -//go:generate mockery --name Cache --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 - - // ID returns client ID for given client secret. - ID(ctx context.Context, clientSecret string) (string, error) - - // Removes client from cache. - Remove(ctx context.Context, clientID string) error -} - -// Client Struct represents a client. - -type Client struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Tags []string `json:"tags,omitempty"` - Domain string `json:"domain_id,omitempty"` - Credentials Credentials `json:"credentials,omitempty"` - Metadata 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 Status `json:"status,omitempty"` // 1 for enabled, 0 for disabled - Permissions []string `json:"permissions,omitempty"` - Identity string `json:"identity,omitempty"` -} - -// ClientsPage contains page related metadata as well as list. -type ClientsPage struct { - Page - Clients []Client -} - -// MembersPage contains page related metadata as well as list of members that -// belong to this page. - -type MembersPage struct { - Page - Members []Client -} - -// Page contains the page metadata that helps navigation. - -type Page 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 Metadata `json:"metadata,omitempty"` - Domain string `json:"domain,omitempty"` - Tag string `json:"tag,omitempty"` - Permission string `json:"permission,omitempty"` - Status Status `json:"status,omitempty"` - IDs []string `json:"ids,omitempty"` - Identity string `json:"identity,omitempty"` - ListPerms bool `json:"-"` -} - -// Metadata represents arbitrary JSON. -type Metadata map[string]interface{} - -// Credentials represent client credentials: its -// "identity" which can be a username, email, generated name; -// and "secret" which can be a password or access token. -type Credentials struct { - Identity string `json:"identity,omitempty"` // username or generated login ID - Secret string `json:"secret,omitempty"` // password or token -} diff --git a/things/doc.go b/things/doc.go deleted file mode 100644 index c22b93031..000000000 --- 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 901dcfa7d..000000000 --- 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/doc.go b/things/events/doc.go deleted file mode 100644 index cb8cccbf3..000000000 --- a/things/events/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package events provides the domain concept definitions needed to support -// things clients events functionality. -package events diff --git a/things/events/events.go b/things/events/events.go deleted file mode 100644 index 486ebba09..000000000 --- a/things/events/events.go +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "time" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/things" -) - -const ( - clientPrefix = "thing." - clientCreate = clientPrefix + "create" - clientUpdate = clientPrefix + "update" - clientChangeStatus = clientPrefix + "change_status" - clientRemove = clientPrefix + "remove" - clientView = clientPrefix + "view" - clientViewPerms = clientPrefix + "view_perms" - clientList = clientPrefix + "list" - clientListByGroup = clientPrefix + "list_by_channel" - clientIdentify = clientPrefix + "identify" - clientAuthorize = clientPrefix + "authorize" -) - -var ( - _ events.Event = (*createClientEvent)(nil) - _ events.Event = (*updateClientEvent)(nil) - _ events.Event = (*changeStatusClientEvent)(nil) - _ events.Event = (*viewClientEvent)(nil) - _ events.Event = (*viewClientPermsEvent)(nil) - _ events.Event = (*listClientEvent)(nil) - _ events.Event = (*listClientByGroupEvent)(nil) - _ events.Event = (*identifyClientEvent)(nil) - _ events.Event = (*authorizeClientEvent)(nil) - _ events.Event = (*shareClientEvent)(nil) - _ events.Event = (*removeClientEvent)(nil) -) - -type createClientEvent struct { - things.Client -} - -func (cce createClientEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": clientCreate, - "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 - } - if cce.Credentials.Identity != "" { - val["identity"] = cce.Credentials.Identity - } - - return val, nil -} - -type updateClientEvent struct { - things.Client - operation string -} - -func (uce updateClientEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": clientUpdate, - "updated_at": uce.UpdatedAt, - "updated_by": uce.UpdatedBy, - } - if uce.operation != "" { - val["operation"] = clientUpdate + "_" + 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.Credentials.Identity != "" { - val["identity"] = uce.Credentials.Identity - } - 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 changeStatusClientEvent struct { - id string - status string - updatedAt time.Time - updatedBy string -} - -func (rce changeStatusClientEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": clientChangeStatus, - "id": rce.id, - "status": rce.status, - "updated_at": rce.updatedAt, - "updated_by": rce.updatedBy, - }, nil -} - -type viewClientEvent struct { - things.Client -} - -func (vce viewClientEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": clientView, - "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.Credentials.Identity != "" { - val["identity"] = vce.Credentials.Identity - } - 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 viewClientPermsEvent struct { - permissions []string -} - -func (vcpe viewClientPermsEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": clientViewPerms, - "permissions": vcpe.permissions, - } - return val, nil -} - -type listClientEvent struct { - reqUserID string - things.Page -} - -func (lce listClientEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": clientList, - "reqUserID": lce.reqUserID, - "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 - } - if lce.Identity != "" { - val["identity"] = lce.Identity - } - - return val, nil -} - -type listClientByGroupEvent struct { - things.Page - channelID string -} - -func (lcge listClientByGroupEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": clientListByGroup, - "total": lcge.Total, - "offset": lcge.Offset, - "limit": lcge.Limit, - "channel_id": lcge.channelID, - } - - if lcge.Name != "" { - val["name"] = lcge.Name - } - if lcge.Order != "" { - val["order"] = lcge.Order - } - if lcge.Dir != "" { - val["dir"] = lcge.Dir - } - if lcge.Metadata != nil { - val["metadata"] = lcge.Metadata - } - if lcge.Domain != "" { - val["domain"] = lcge.Domain - } - if lcge.Tag != "" { - val["tag"] = lcge.Tag - } - if lcge.Permission != "" { - val["permission"] = lcge.Permission - } - if lcge.Status.String() != "" { - val["status"] = lcge.Status.String() - } - if lcge.Identity != "" { - val["identity"] = lcge.Identity - } - - return val, nil -} - -type identifyClientEvent struct { - thingID string -} - -func (ice identifyClientEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": clientIdentify, - "id": ice.thingID, - }, nil -} - -type authorizeClientEvent struct { - thingID string - channelID string - permission string -} - -func (ice authorizeClientEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": clientAuthorize, - "id": ice.thingID, - } - - if ice.permission != "" { - val["permission"] = ice.permission - } - if ice.channelID != "" { - val["channelID"] = ice.channelID - } - - return val, nil -} - -type shareClientEvent struct { - action string - id string - relation string - userIDs []string -} - -func (sce shareClientEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": clientPrefix + sce.action, - "id": sce.id, - "relation": sce.relation, - "user_ids": sce.userIDs, - }, nil -} - -type removeClientEvent struct { - id string -} - -func (dce removeClientEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": clientRemove, - "id": dce.id, - }, nil -} diff --git a/things/events/streams.go b/things/events/streams.go deleted file mode 100644 index 295fb37bc..000000000 --- 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 85a3af5d2..000000000 --- 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/doc.go b/things/middleware/doc.go deleted file mode 100644 index 253c83585..000000000 --- a/things/middleware/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package middleware provides middleware for Magistrala Things service. -package middleware diff --git a/things/middleware/logging.go b/things/middleware/logging.go deleted file mode 100644 index a176159c8..000000000 --- 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/middleware/metrics.go b/things/middleware/metrics.go deleted file mode 100644 index 6b6ecd2d6..000000000 --- a/things/middleware/metrics.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "time" - - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/things" - "github.com/go-kit/kit/metrics" -) - -var _ things.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc things.Service -} - -// MetricsMiddleware returns a new metrics middleware wrapper. -func MetricsMiddleware(svc things.Service, counter metrics.Counter, latency metrics.Histogram) things.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -func (ms *metricsMiddleware) CreateClients(ctx context.Context, session authn.Session, things ...things.Client) ([]things.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...) -} - -func (ms *metricsMiddleware) View(ctx context.Context, session authn.Session, id string) (things.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_client").Add(1) - ms.latency.With("method", "view_client").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.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) { - defer func(begin time.Time) { - ms.counter.With("method", "list_clients").Add(1) - ms.latency.With("method", "list_clients").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListClients(ctx, session, reqUserID, pm) -} - -func (ms *metricsMiddleware) Update(ctx context.Context, session authn.Session, thing things.Client) (things.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) -} - -func (ms *metricsMiddleware) UpdateTags(ctx context.Context, session authn.Session, thing things.Client) (things.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) -} - -func (ms *metricsMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (things.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_client_secret").Add(1) - ms.latency.With("method", "update_client_secret").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateSecret(ctx, session, oldSecret, newSecret) -} - -func (ms *metricsMiddleware) Enable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "enable_client").Add(1) - ms.latency.With("method", "enable_client").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Enable(ctx, session, id) -} - -func (ms *metricsMiddleware) Disable(ctx context.Context, session authn.Session, id string) (things.Client, error) { - defer func(begin time.Time) { - ms.counter.With("method", "disable_client").Add(1) - ms.latency.With("method", "disable_client").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.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 { - defer func(begin time.Time) { - ms.counter.With("method", "share").Add(1) - ms.latency.With("method", "share").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Share(ctx, session, id, relation, userids...) -} - -func (ms *metricsMiddleware) Unshare(ctx context.Context, session authn.Session, id, relation string, userids ...string) error { - defer func(begin time.Time) { - ms.counter.With("method", "unshare").Add(1) - ms.latency.With("method", "unshare").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Unshare(ctx, session, id, relation, userids...) -} - -func (ms *metricsMiddleware) Delete(ctx context.Context, session authn.Session, id string) 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()) - }(time.Now()) - return ms.svc.Delete(ctx, session, id) -} diff --git a/things/mocks/cache.go b/things/mocks/cache.go deleted file mode 100644 index 9e729c2ce..000000000 --- a/things/mocks/cache.go +++ /dev/null @@ -1,94 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// Cache is an autogenerated mock type for the Cache type -type Cache struct { - mock.Mock -} - -// ID provides a mock function with given fields: ctx, clientSecret -func (_m *Cache) ID(ctx context.Context, clientSecret string) (string, error) { - ret := _m.Called(ctx, clientSecret) - - if len(ret) == 0 { - panic("no return value specified for ID") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { - return rf(ctx, clientSecret) - } - if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { - r0 = rf(ctx, clientSecret) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, clientSecret) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Remove provides a mock function with given fields: ctx, clientID -func (_m *Cache) Remove(ctx context.Context, clientID string) error { - ret := _m.Called(ctx, clientID) - - 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, clientID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Save provides a mock function with given fields: ctx, clientSecret, clientID -func (_m *Cache) Save(ctx context.Context, clientSecret string, clientID string) error { - ret := _m.Called(ctx, clientSecret, clientID) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, clientSecret, clientID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewCache creates a new instance of Cache. 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 NewCache(t interface { - mock.TestingT - Cleanup(func()) -}) *Cache { - mock := &Cache{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/things/mocks/doc.go b/things/mocks/doc.go deleted file mode 100644 index 16ed198af..000000000 --- a/things/mocks/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mocks contains mocks for testing purposes. -package mocks diff --git a/things/mocks/repository.go b/things/mocks/repository.go deleted file mode 100644 index 2917461ba..000000000 --- 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 9719334dc..000000000 --- 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 136280a86..000000000 --- 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 bc9fe3f7a..000000000 --- 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/clients_test.go b/things/postgres/clients_test.go deleted file mode 100644 index b03b7d4f6..000000000 --- a/things/postgres/clients_test.go +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "strings" - "testing" - - "github.com/0x6flab/namegenerator" - "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" -) - -const maxNameSize = 1024 - -var ( - invalidName = strings.Repeat("m", maxNameSize+10) - thingIdentity = "thing-identity@example.com" - thingName = "thing name" - invalidDomainID = strings.Repeat("m", maxNameSize+10) - namegen = namegenerator.NewGenerator() -) - -func TestClientsSave(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) - - uid := testsutil.GenerateUUID(t) - domainID := testsutil.GenerateUUID(t) - secret := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - things []things.Client - err error - }{ - { - desc: "add new thing successfully", - things: []things.Client{ - { - ID: uid, - Domain: domainID, - Name: thingName, - Credentials: things.Credentials{ - Identity: thingIdentity, - Secret: secret, - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: nil, - }, - { - desc: "add multiple things successfully", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Credentials: things.Credentials{ - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - { - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Credentials: things.Credentials{ - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - { - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Credentials: things.Credentials{ - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: nil, - }, - { - desc: "add new thing with duplicate secret", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Domain: domainID, - Name: namegen.Generate(), - Credentials: things.Credentials{ - Identity: thingIdentity, - Secret: secret, - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "add multiple things with one thing having duplicate secret", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Credentials: things.Credentials{ - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - { - ID: testsutil.GenerateUUID(t), - Domain: domainID, - Name: namegen.Generate(), - Credentials: things.Credentials{ - Identity: thingIdentity, - Secret: secret, - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "add new thing without domain id", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Name: thingName, - Credentials: things.Credentials{ - Identity: "withoutdomain-thing@example.com", - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: nil, - }, - { - desc: "add thing with invalid thing id", - things: []things.Client{ - { - ID: invalidName, - Domain: domainID, - Name: thingName, - Credentials: things.Credentials{ - Identity: "invalidid-thing@example.com", - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "add multiple things with one thing having invalid thing id", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Credentials: things.Credentials{ - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - { - ID: invalidName, - Domain: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Credentials: things.Credentials{ - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "add thing with invalid thing name", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Name: invalidName, - Domain: domainID, - Credentials: things.Credentials{ - Identity: "invalidname-thing@example.com", - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "add thing with invalid thing domain id", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Domain: invalidDomainID, - Credentials: things.Credentials{ - Identity: "invaliddomainid-thing@example.com", - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "add thing with invalid thing identity", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Name: thingName, - Credentials: things.Credentials{ - Identity: invalidName, - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - }, - }, - err: repoerr.ErrCreateEntity, - }, - { - desc: "add thing with a missing thing identity", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Name: "missing-thing-identity", - Credentials: things.Credentials{ - Identity: "", - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - }, - }, - err: nil, - }, - { - desc: "add thing with a missing thing secret", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Domain: testsutil.GenerateUUID(t), - Credentials: things.Credentials{ - Identity: "missing-thing-secret@example.com", - Secret: "", - }, - Metadata: things.Metadata{}, - }, - }, - err: nil, - }, - { - desc: "add a thing with invalid metadata", - things: []things.Client{ - { - ID: testsutil.GenerateUUID(t), - Name: namegen.Generate(), - Credentials: things.Credentials{ - Identity: fmt.Sprintf("%s@example.com", namegen.Generate()), - Secret: testsutil.GenerateUUID(t), - }, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }, - }, - err: errors.ErrMalformedEntity, - }, - } - for _, tc := range cases { - rThings, err := repo.Save(context.Background(), tc.things...) - 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 - } - assert.Equal(t, tc.things, rThings, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.things, rThings)) - } - } -} - -func TestThingsRetrieveBySecret(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{ - ID: testsutil.GenerateUUID(t), - Name: thingName, - Credentials: things.Credentials{ - Identity: thingIdentity, - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - } - - _, err := repo.Save(context.Background(), thing) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - cases := []struct { - desc string - secret string - response things.Client - err error - }{ - { - desc: "retrieve thing by secret successfully", - secret: thing.Credentials.Secret, - response: thing, - err: nil, - }, - { - desc: "retrieve thing by invalid secret", - secret: "non-existent-secret", - response: things.Client{}, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve thing by empty secret", - secret: "", - response: things.Client{}, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - res, err := repo.RetrieveBySecret(context.Background(), tc.secret) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, res, tc.response, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, res)) - } -} - -func TestRetrieveByID(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{ - ID: testsutil.GenerateUUID(t), - Name: thingName, - Credentials: things.Credentials{ - Identity: thingIdentity, - Secret: testsutil.GenerateUUID(t), - }, - Metadata: things.Metadata{}, - Status: things.EnabledStatus, - } - - _, err := repo.Save(context.Background(), thing) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - - cases := []struct { - desc string - id string - response things.Client - err error - }{ - { - desc: "successfully", - id: thing.ID, - response: thing, - err: nil, - }, - { - desc: "with invalid id", - id: testsutil.GenerateUUID(t), - response: things.Client{}, - err: repoerr.ErrNotFound, - }, - { - desc: "with empty id", - id: "", - response: things.Client{}, - err: repoerr.ErrNotFound, - }, - } - for _, c := range cases { - t.Run(c.desc, func(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) - } - }) - } -} diff --git a/things/postgres/doc.go b/things/postgres/doc.go deleted file mode 100644 index 6e8346350..000000000 --- a/things/postgres/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres contains the database implementation of clients repository layer. -package postgres diff --git a/things/postgres/init.go b/things/postgres/init.go deleted file mode 100644 index 28e07a2cc..000000000 --- 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/postgres/setup_test.go b/things/postgres/setup_test.go deleted file mode 100644 index a167f6434..000000000 --- a/things/postgres/setup_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "database/sql" - "fmt" - "log" - "os" - "testing" - "time" - - 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" - "go.opentelemetry.io/otel" -) - -var ( - db *sqlx.DB - database pgclient.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") - - // exponential backoff-retry, because the application in the container might not be ready to accept connections yet - pool.MaxWait = 120 * time.Second - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err := sql.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := pgclient.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = pgclient.Setup(dbConfig, *cpostgres.Migration()); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - if db, err = pgclient.Connect(dbConfig); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - database = pgclient.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/things/roles.go b/things/roles.go deleted file mode 100644 index 390ebbc9b..000000000 --- a/things/roles.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package things - -import ( - "encoding/json" - "strings" - - "github.com/absmach/magistrala/pkg/apiutil" -) - -// Role represents Client role. -type Role uint8 - -// Possible Client role values. -const ( - UserRole Role = iota - AdminRole - - // AllRole is used for querying purposes to list clients irrespective - // of their role - both admin and user. It is never stored in the - // database as the actual Client role and should always be the largest - // value in this enumeration. - AllRole -) - -// String representation of the possible role values. -const ( - Admin = "admin" - User = "user" -) - -// String converts client role to string literal. -func (cs Role) String() string { - switch cs { - case AdminRole: - return Admin - case UserRole: - return User - case AllRole: - return All - default: - return Unknown - } -} - -// ToRole converts string value to a valid Client role. -func ToRole(status string) (Role, error) { - switch status { - case "", User: - return UserRole, nil - case Admin: - return AdminRole, nil - case All: - return AllRole, nil - default: - return Role(0), apiutil.ErrInvalidRole - } -} - -func (r Role) MarshalJSON() ([]byte, error) { - return json.Marshal(r.String()) -} - -func (r *Role) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToRole(str) - *r = val - return err -} diff --git a/things/roles_test.go b/things/roles_test.go deleted file mode 100644 index 2d50aeaa8..000000000 --- a/things/roles_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package things_test - -import ( - "testing" - - "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 - expected string - }{ - { - desc: "User", - role: things.UserRole, - expected: "user", - }, - { - desc: "Admin", - role: things.AdminRole, - expected: "admin", - }, - { - desc: "All", - role: things.AllRole, - expected: "all", - }, - { - desc: "Unknown", - role: things.Role(100), - expected: "unknown", - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - got := c.role.String() - assert.Equal(t, c.expected, got, "String() = %v, expected %v", got, c.expected) - }) - } -} - -func TestToRole(t *testing.T) { - cases := []struct { - desc string - role string - expected things.Role - err error - }{ - { - desc: "User", - role: "user", - expected: things.UserRole, - err: nil, - }, - { - desc: "Admin", - role: "admin", - expected: things.AdminRole, - err: nil, - }, - { - desc: "All", - role: "all", - expected: things.AllRole, - err: nil, - }, - { - desc: "Unknown", - role: "unknown", - expected: things.Role(0), - err: apiutil.ErrInvalidRole, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - got, err := things.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) - }) - } -} - -func TestRoleMarshalJSON(t *testing.T) { - cases := []struct { - desc string - expected []byte - role things.Role - err error - }{ - { - desc: "User", - expected: []byte(`"user"`), - role: things.UserRole, - err: nil, - }, - { - desc: "Admin", - expected: []byte(`"admin"`), - role: things.AdminRole, - err: nil, - }, - { - desc: "All", - expected: []byte(`"all"`), - role: things.AllRole, - err: nil, - }, - { - desc: "Unknown", - expected: []byte(`"unknown"`), - role: things.Role(100), - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - got, err := tc.role.MarshalJSON() - assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err) - assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected) - }) - } -} - -func TestRoleUnmarshalJSON(t *testing.T) { - cases := []struct { - desc string - expected things.Role - role []byte - err error - }{ - { - desc: "User", - expected: things.UserRole, - role: []byte(`"user"`), - err: nil, - }, - { - desc: "Admin", - expected: things.AdminRole, - role: []byte(`"admin"`), - err: nil, - }, - { - desc: "All", - expected: things.AllRole, - role: []byte(`"all"`), - err: nil, - }, - { - desc: "Unknown", - expected: things.Role(0), - role: []byte(`"unknown"`), - err: apiutil.ErrInvalidRole, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var r things.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/things/service.go deleted file mode 100644 index 475902084..000000000 --- a/things/service.go +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 -package things - -import ( - "context" - "time" - - "github.com/absmach/magistrala" - mgauth "github.com/absmach/magistrala/auth" - "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" - "golang.org/x/sync/errgroup" -) - -type service struct { - evaluator policies.Evaluator - policysvc policies.Service - clients Repository - clientCache Cache - idProvider magistrala.IDProvider -} - -// 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, - } -} - -func (svc service) Authorize(ctx context.Context, req AuthzReq) (string, error) { - clientID, err := svc.Identify(ctx, req.ClientKey) - if err != nil { - return "", 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 -} - -func (svc service) CreateClients(ctx context.Context, session authn.Session, cli ...Client) ([]Client, error) { - var clients []Client - for _, c := range cli { - if c.ID == "" { - clientID, err := svc.idProvider.ID() - if err != nil { - return []Client{}, err - } - c.ID = clientID - } - if c.Credentials.Secret == "" { - key, err := svc.idProvider.ID() - if err != nil { - return []Client{}, err - } - c.Credentials.Secret = key - } - if c.Status != DisabledStatus && c.Status != EnabledStatus { - return []Client{}, svcerr.ErrInvalidStatus - } - c.Domain = session.DomainID - c.CreatedAt = time.Now() - clients = append(clients, c) - } - - err := svc.addClientPolicies(ctx, session.DomainUserID, session.DomainID, clients) - if err != nil { - return []Client{}, err - } - 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) - } - } - }() - - saved, err := svc.clients.Save(ctx, clients...) - if err != nil { - return nil, errors.Wrap(svcerr.ErrCreateEntity, 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) - 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 - switch { - case (reqUserID != "" && reqUserID != session.UserID): - rtids, err := svc.listClientIDs(ctx, mgauth.EncodeDomainUserID(session.DomainID, reqUserID), pm.Permission) - if err != nil { - return ClientsPage{}, errors.Wrap(svcerr.ErrNotFound, err) - } - ids, err = svc.filterAllowedClientIDs(ctx, session.DomainUserID, pm.Permission, rtids) - if err != nil { - return ClientsPage{}, errors.Wrap(svcerr.ErrNotFound, err) - } - default: - switch session.SuperAdmin { - case true: - pm.Domain = session.DomainID - default: - ids, err = svc.listClientIDs(ctx, session.DomainUserID, pm.Permission) - if err != nil { - return ClientsPage{}, errors.Wrap(svcerr.ErrNotFound, err) - } - } - } - - if len(ids) == 0 && pm.Domain == "" { - return ClientsPage{}, nil - } - pm.IDs = ids - tp, err := svc.clients.SearchClients(ctx, pm) - if err != nil { - return ClientsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - if pm.ListPerms && len(tp.Clients) > 0 { - g, ctx := errgroup.WithContext(ctx) - - for i := range tp.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, &tp.Clients[iter]) - }) - } - - if err := g.Wait(); err != nil { - return ClientsPage{}, err - } - } - return tp, 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, client *Client) error { - permissions, err := svc.listUserClientPermission(ctx, userID, client.ID) - if err != nil { - return err - } - client.Permissions = permissions - return nil -} - -func (svc service) listUserClientPermission(ctx context.Context, userID, clientID string) ([]string, error) { - permissions, err := svc.policysvc.ListPermissions(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Object: clientID, - ObjectType: policies.ThingType, - }, []string{}) - if err != nil { - return []string{}, errors.Wrap(svcerr.ErrAuthorization, err) - } - return permissions, nil -} - -func (svc service) listClientIDs(ctx context.Context, userID, permission string) ([]string, error) { - tids, err := svc.policysvc.ListAllObjects(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Permission: permission, - ObjectType: policies.ThingType, - }) - if err != nil { - return nil, errors.Wrap(svcerr.ErrNotFound, err) - } - return tids.Policies, nil -} - -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{ - SubjectType: policies.UserType, - Subject: userID, - Permission: permission, - ObjectType: policies.ThingType, - }) - if err != nil { - return nil, errors.Wrap(svcerr.ErrNotFound, err) - } - for _, clientID := range clientIDs { - for _, tid := range tids.Policies { - if clientID == tid { - ids = append(ids, clientID) - } - } - } - return ids, nil -} - -func (svc service) Update(ctx context.Context, session authn.Session, thi Client) (Client, error) { - client := Client{ - ID: thi.ID, - Name: thi.Name, - Metadata: thi.Metadata, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - client, err := svc.clients.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) { - client := Client{ - ID: thi.ID, - Tags: thi.Tags, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - client, err := svc.clients.UpdateTags(ctx, client) - if err != nil { - return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return client, nil -} - -func (svc service) UpdateSecret(ctx context.Context, session authn.Session, id, key string) (Client, error) { - client := Client{ - ID: id, - Credentials: Credentials{ - Secret: key, - }, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - Status: EnabledStatus, - } - client, err := svc.clients.UpdateSecret(ctx, client) - if err != nil { - return Client{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return client, nil -} - -func (svc service) Enable(ctx context.Context, session authn.Session, id string) (Client, error) { - client := Client{ - ID: id, - Status: EnabledStatus, - UpdatedAt: time.Now(), - } - client, err := svc.changeClientStatus(ctx, session, client) - if err != nil { - return Client{}, errors.Wrap(ErrEnableClient, err) - } - - return client, nil -} - -func (svc service) Disable(ctx context.Context, session authn.Session, id string) (Client, error) { - client := Client{ - ID: id, - Status: DisabledStatus, - UpdatedAt: time.Now(), - } - client, err := svc.changeClientStatus(ctx, session, client) - if err != nil { - return Client{}, errors.Wrap(ErrDisableClient, err) - } - - if err := svc.clientCache.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, - }) - } - if err := svc.policysvc.AddPolicies(ctx, policyList); 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, - Object: id, - }) - } - if err := svc.policysvc.DeletePolicies(ctx, policyList); err != nil { - return errors.Wrap(svcerr.ErrUpdateEntity, 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 { - return errors.Wrap(svcerr.ErrRemoveEntity, err) - } - - req := policies.Policy{ - Object: id, - ObjectType: policies.ThingType, - } - - if err := svc.policysvc.DeletePolicyFilter(ctx, req); err != nil { - return errors.Wrap(svcerr.ErrRemoveEntity, err) - } - - if err := svc.clients.Delete(ctx, id); err != nil { - return errors.Wrap(svcerr.ErrRemoveEntity, err) - } - - return nil -} - -func (svc service) changeClientStatus(ctx context.Context, session authn.Session, client Client) (Client, error) { - dbClient, err := svc.clients.RetrieveByID(ctx, client.ID) - if err != nil { - return Client{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - if dbClient.Status == client.Status { - return Client{}, errors.ErrStatusAlreadyAssigned - } - - client.UpdatedBy = session.UserID - - client, err = svc.clients.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/things/service_test.go b/things/service_test.go deleted file mode 100644 index 79aa727ed..000000000 --- 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/standalone/doc.go b/things/standalone/doc.go deleted file mode 100644 index 68ca6a78d..000000000 --- a/things/standalone/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// 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 -// require connection to Auth service. -package standalone diff --git a/things/standalone/standalone.go b/things/standalone/standalone.go deleted file mode 100644 index 5d14ffba7..000000000 --- a/things/standalone/standalone.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package standalone diff --git a/things/status.go b/things/status.go deleted file mode 100644 index f34ed99be..000000000 --- a/things/status.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package things - -import ( - "encoding/json" - "strings" - - svcerr "github.com/absmach/magistrala/pkg/errors/service" -) - -// Status represents Client status. -type Status uint8 - -// Possible Client status values. -const ( - // EnabledStatus represents enabled Client. - EnabledStatus Status = iota - // DisabledStatus represents disabled Client. - DisabledStatus - // DeletedStatus represents a client that will be deleted. - DeletedStatus - - // AllStatus is used for querying purposes to list clients irrespective - // of their status - both enabled and disabled. It is never stored in the - // database as the actual Client status and should always be the largest - // value in this enumeration. - AllStatus -) - -// String representation of the possible status values. -const ( - Disabled = "disabled" - Enabled = "enabled" - Deleted = "deleted" - All = "all" - Unknown = "unknown" -) - -// String converts client/group status to string literal. -func (s Status) String() string { - switch s { - case DisabledStatus: - return Disabled - case EnabledStatus: - return Enabled - case DeletedStatus: - return Deleted - case AllStatus: - return All - default: - return Unknown - } -} - -// ToStatus converts string value to a valid Client status. -func ToStatus(status string) (Status, error) { - switch status { - case "", Enabled: - return EnabledStatus, nil - case Disabled: - return DisabledStatus, nil - case Deleted: - return DeletedStatus, nil - case All: - return AllStatus, nil - } - return Status(0), svcerr.ErrInvalidStatus -} - -// Custom Marshaller for Client. -func (s Status) MarshalJSON() ([]byte, error) { - return json.Marshal(s.String()) -} - -func (client Client) MarshalJSON() ([]byte, error) { - type Alias Client - return json.Marshal(&struct { - Alias - Status string `json:"status,omitempty"` - }{ - Alias: (Alias)(client), - Status: client.Status.String(), - }) -} - -// Custom Unmarshaler for Client. -func (s *Status) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToStatus(str) - *s = val - return err -} diff --git a/things/status_test.go b/things/status_test.go deleted file mode 100644 index 9df845bf5..000000000 --- a/things/status_test.go +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package things_test - -import ( - "testing" - - 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 - expected string - }{ - { - desc: "Enabled", - status: things.EnabledStatus, - expected: "enabled", - }, - { - desc: "Disabled", - status: things.DisabledStatus, - expected: "disabled", - }, - { - desc: "Deleted", - status: things.DeletedStatus, - expected: "deleted", - }, - { - desc: "All", - status: things.AllStatus, - expected: "all", - }, - { - desc: "Unknown", - status: things.Status(100), - expected: "unknown", - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - got := tc.status.String() - assert.Equal(t, tc.expected, got, "String() = %v, expected %v", got, tc.expected) - }) - } -} - -func TestToStatus(t *testing.T) { - cases := []struct { - desc string - status string - expetcted things.Status - err error - }{ - { - desc: "Enabled", - status: "enabled", - expetcted: things.EnabledStatus, - err: nil, - }, - { - desc: "Disabled", - status: "disabled", - expetcted: things.DisabledStatus, - err: nil, - }, - { - desc: "Deleted", - status: "deleted", - expetcted: things.DeletedStatus, - err: nil, - }, - { - desc: "All", - status: "all", - expetcted: things.AllStatus, - err: nil, - }, - { - desc: "Unknown", - status: "unknown", - expetcted: things.Status(0), - err: svcerr.ErrInvalidStatus, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - got, err := things.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) - }) - } -} - -func TestStatusMarshalJSON(t *testing.T) { - cases := []struct { - desc string - expected []byte - status things.Status - err error - }{ - { - desc: "Enabled", - expected: []byte(`"enabled"`), - status: things.EnabledStatus, - err: nil, - }, - { - desc: "Disabled", - expected: []byte(`"disabled"`), - status: things.DisabledStatus, - err: nil, - }, - { - desc: "Deleted", - expected: []byte(`"deleted"`), - status: things.DeletedStatus, - err: nil, - }, - { - desc: "All", - expected: []byte(`"all"`), - status: things.AllStatus, - err: nil, - }, - { - desc: "Unknown", - expected: []byte(`"unknown"`), - status: things.Status(100), - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - got, err := tc.status.MarshalJSON() - assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err) - assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected) - }) - } -} - -func TestStatusUnmarshalJSON(t *testing.T) { - cases := []struct { - desc string - expected things.Status - status []byte - err error - }{ - { - desc: "Enabled", - expected: things.EnabledStatus, - status: []byte(`"enabled"`), - err: nil, - }, - { - desc: "Disabled", - expected: things.DisabledStatus, - status: []byte(`"disabled"`), - err: nil, - }, - { - desc: "Deleted", - expected: things.DeletedStatus, - status: []byte(`"deleted"`), - err: nil, - }, - { - desc: "All", - expected: things.AllStatus, - status: []byte(`"all"`), - err: nil, - }, - { - desc: "Unknown", - expected: things.Status(0), - status: []byte(`"unknown"`), - err: svcerr.ErrInvalidStatus, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - var s things.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) - }) - } -} - -func TestUserMarshalJSON(t *testing.T) { - cases := []struct { - desc string - expected []byte - user things.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}, - 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}, - 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}, - 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}, - 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)}, - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - got, err := tc.user.MarshalJSON() - assert.Equal(t, tc.err, err, "MarshalJSON() error = %v, expected %v", err, tc.err) - assert.Equal(t, tc.expected, got, "MarshalJSON() = %v, expected %v", got, tc.expected) - }) - } -} diff --git a/things/tracing/doc.go b/things/tracing/doc.go deleted file mode 100644 index 1d803beca..000000000 --- a/things/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala things clients service. -// -// This package provides tracing middleware for Magistrala things clients service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala things 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/things/tracing/tracing.go b/things/tracing/tracing.go deleted file mode 100644 index 20fe07b5f..000000000 --- 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/golangci.yml b/tools/config/golangci.yml index d38b122e6..8c1b5f017 100644 --- a/tools/config/golangci.yml +++ b/tools/config/golangci.yml @@ -24,15 +24,18 @@ linters-settings: alias: - pkg: github.com/absmach/callhome/pkg/client alias: chclient - - pkg: github.com/absmach/magistrala/logger - alias: mglog - - pkg: github.com/absmach/magistrala/pkg/errors/service + - pkg: github.com/absmach/supermq/logger + alias: smqlog + - pkg: github.com/absmach/supermq/pkg/errors/service alias: svcerr - - pkg: github.com/absmach/magistrala/pkg/errors/repository + - pkg: github.com/absmach/supermq/pkg/errors/repository alias: repoerr - - pkg: github.com/absmach/magistrala/pkg/sdk/mocks + - pkg: github.com/absmach/supermq/pkg/sdk/mocks alias: sdkmocks - + - pkg: github.com/absmach/supermq/api/http/util + alias: apiutil + - pkg: github.com/absmach/supermq/api/http + alias: api gocritic: enabled-checks: - importShadow diff --git a/tools/config/mockery.yaml b/tools/config/mockery.yaml index 69e231658..677e52f6d 100644 --- a/tools/config/mockery.yaml +++ b/tools/config/mockery.yaml @@ -6,25 +6,50 @@ filename: "{{.InterfaceName}}.go" outpkg: "mocks" boilerplate-file: "./tools/config/boilerplate.txt" packages: - github.com/absmach/magistrala: + github.com/absmach/supermq/api/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/supermq/api/grpc/domains/v1: + interfaces: DomainsServiceClient: config: - dir: "./auth/mocks" + dir: "./domains/mocks" mockname: "DomainsServiceClient" filename: "domains_client.go" + github.com/absmach/supermq/api/grpc/token/v1: + interfaces: TokenServiceClient: config: dir: "./auth/mocks" mockname: "TokenServiceClient" filename: "token_client.go" + github.com/absmach/supermq/api/grpc/channels/v1: + interfaces: + ChannelsServiceClient: + config: + dir: "./channels/mocks" + mockname: "ChannelsServiceClient" + filename: "channels_client.go" + github.com/absmach/supermq/api/grpc/groups/v1: + interfaces: + GroupsServiceClient: + config: + dir: "./groups/mocks" + mockname: "GroupsServiceClient" + filename: "groups_client.go" + github.com/absmach/supermq/pkg/sdk: + interfaces: + SDK: + config: + dir: "./pkg/sdk/mocks" + mockname: "SDK" + filename: "sdk.go" - github.com/absmach/magistrala/certs/pki/amcerts: + github.com/absmach/supermq/certs/pki/amcerts: interfaces: Agent: config: diff --git a/tools/doc.go b/tools/doc.go index 296a4b2b4..14c89c6fb 100644 --- a/tools/doc.go +++ b/tools/doc.go @@ -1,5 +1,5 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package tools contains tools for Magistrala. +// Package tools contains tools for SuperMQ. package tools diff --git a/tools/e2e/README.md b/tools/e2e/README.md index 6e3584514..c1412c8d4 100644 --- a/tools/e2e/README.md +++ b/tools/e2e/README.md @@ -1,6 +1,6 @@ -# Magistrala Users Groups Things and Channels E2E Testing Tool +# SuperMQ 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 @@ -13,11 +13,11 @@ 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). -Complete documentation is available at https://docs.magistrala.abstractmachines.fr +Tool for testing end-to-end flow of SuperMQ by doing a couple of operations namely: +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.supermq.abstractmachines.fr Usage: @@ -28,7 +28,7 @@ Usage: Examples: Here is a simple example of using e2e tool. -Use the following commands from the root Magistrala directory: +Use the following commands from the root SuperMQ directory: go run tools/e2e/cmd/main.go go run tools/e2e/cmd/main.go --host 142.93.118.47 @@ -38,19 +38,19 @@ go run tools/e2e/cmd/main.go --host localhost --num 10 --num_of_messages 100 --p 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) + -H, --host string address for a running SuperMQ instance (default "localhost") + -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: +To use `-H` option, you can specify the address for the SuperMQ instance as an argument when running the program. For example, if the SuperMQ instance is running on another computer with the IP address 192.168.0.1, you could use the following command: ```bash go run tools/e2e/cmd/main.go --host 142.93.118.47 ``` -This will tell the program to connect to the Magistrala instance running on the specified IP address. +This will tell the program to connect to the SuperMQ instance running on the specified IP address. If you want to create a list of channels with certificates: @@ -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 5574382a6..44c42e379 100644 --- a/tools/e2e/cmd/main.go +++ b/tools/e2e/cmd/main.go @@ -1,13 +1,13 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -// Package main contains e2e tool for testing Magistrala. +// Package main contains e2e tool for testing SuperMQ. package main import ( "log" - "github.com/absmach/magistrala/tools/e2e" + "github.com/absmach/supermq/tools/e2e" cc "github.com/ivanpirog/coloredcobra" "github.com/spf13/cobra" ) @@ -19,14 +19,14 @@ func main() { rootCmd := &cobra.Command{ 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" + - "Complete documentation is available at https://docs.magistrala.abstractmachines.fr", + Short: "e2e is end-to-end testing tool for SuperMQ", + Long: "Tool for testing end-to-end flow of supermq by doing a couple of operations namely:\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.supermq.abstractmachines.fr", Example: "Here is a simple example of using e2e tool.\n" + - "Use the following commands from the root magistrala directory:\n\n" + + "Use the following commands from the root supermq directory:\n\n" + "go run tools/e2e/cmd/main.go\n" + "go run tools/e2e/cmd/main.go --host 142.93.118.47\n" + "go run tools/e2e/cmd/main.go --host localhost --num 10 --num_of_messages 100 --prefix e2e", @@ -47,9 +47,9 @@ 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.Host, "host", "H", "localhost", "address for a running supermq instance") + 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 e7bf35403..48f2f074f 100644 --- a/tools/e2e/e2e.go +++ b/tools/e2e/e2e.go @@ -14,7 +14,7 @@ import ( "time" "github.com/0x6flab/namegenerator" - sdk "github.com/absmach/magistrala/pkg/sdk/go" + sdk "github.com/absmach/supermq/pkg/sdk" "github.com/gookit/color" "github.com/gorilla/websocket" "golang.org/x/sync/errgroup" @@ -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") @@ -149,8 +149,8 @@ func createUser(s sdk.SDK, conf Config) (string, string, error) { } login := sdk.Login{ - Identity: user.Credentials.Username, - Secret: user.Credentials.Secret, + Username: user.Credentials.Username, + Password: user.Credentials.Secret, } token, err := s.CreateToken(login) if err != nil { @@ -170,8 +170,8 @@ func createUser(s sdk.SDK, conf Config) (string, string, error) { } login = sdk.Login{ - Identity: user.Credentials.Username, - Secret: user.Credentials.Secret, + Username: user.Credentials.Username, + Password: user.Credentials.Secret, } token, err = s.CreateToken(login) if err != nil { @@ -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,16 @@ 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, - ChannelID: channel.ID, + ClientIDs: []string{c.ID}, + ChannelIDs: []string{channel.ID}, + Types: []string{"publish", "subscribe"}, } 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 +554,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 +582,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", "supermq", "-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 f94eb4d21..9765f078a 100644 --- a/tools/mqtt-bench/README.md +++ b/tools/mqtt-bench/README.md @@ -1,11 +1,11 @@ # MQTT Benchmarking Tool -A simple MQTT benchmarking tool for Magistrala platform. +A simple MQTT benchmarking tool for SuperMQ 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 SuperMQ clients as subscribers over a number of channels and +uses other SuperMQ 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. +SuperMQ clients used must be pre-provisioned first, and SuperMQ `provision` tool can be used for this purpose. ## Installation @@ -20,8 +20,8 @@ The tool supports multiple concurrent clients, publishers and subscribers config ``` ./mqtt-bench --help -Tool for extensive load and benchmarking of MQTT brokers used within Magistrala platform. -Complete documentation is available at https://docs.magistrala.abstractmachines.fr +Tool for extensive load and benchmarking of MQTT brokers used within SuperMQ platform. +Complete documentation is available at https://docs.supermq.abstractmachines.fr Usage: mqtt-bench [flags] @@ -33,7 +33,7 @@ Flags: -n, --count int Number of messages sent per publisher (default 100) -f, --format string Output format: text|json (default "text") -h, --help help for mqtt-bench - -m, --magistrala string config file for Magistrala connections (default "connections.toml") + -m, --supermq string config file for SuperMQ connections (default "connections.toml") --mtls Use mtls for connection -p, --pubs int Number of publishers (default 10) -q, --qos int QoS for published messages, values 0 1 2 @@ -46,11 +46,11 @@ 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 SuperMQ connection data (channels, clientIDs, clientKeys, certs). You can use `provision` tool (in tools/provision) to create this TOML config file. ```bash -go run tools/mqtt-bench/cmd/main.go -u test@magistrala.com -p test1234 --host http://127.0.0.1 --num 100 > tools/mqtt-bench/mgconn.toml +go run tools/mqtt-bench/cmd/main.go -u test@supermq.com -p test1234 --host http://127.0.0.1 --num 100 > tools/mqtt-bench/mgconn.toml ``` Example use and output @@ -58,11 +58,11 @@ Example use and output Without mtls: ``` -go run tools/mqtt-bench/cmd/main.go --broker tcp://localhost:1883 --count 100 --size 100 --qos 0 --format text --pubs 10 --magistrala tools/mqtt-bench/mgconn.toml +go run tools/mqtt-bench/cmd/main.go --broker tcp://localhost:1883 --count 100 --size 100 --qos 0 --format text --pubs 10 --supermq tools/mqtt-bench/mgconn.toml ``` With mtls -go run tools/mqtt-bench/cmd/main.go --broker tcps://localhost:8883 --count 100 --size 100 --qos 0 --format text --pubs 10 --magistrala tools/mqtt-bench/mgconn.toml --mtls -ca docker/ssl/certs/ca.crt +go run tools/mqtt-bench/cmd/main.go --broker tcps://localhost:8883 --count 100 --size 100 --qos 0 --format text --pubs 10 --supermq tools/mqtt-bench/mgconn.toml --mtls -ca docker/ssl/certs/ca.crt ``` @@ -100,8 +100,8 @@ count = 100 [log] quiet = false -[magistrala] -connections_file = "mgconn.toml" +[supermq] +connections_file = "smqconn.toml" ``` diff --git a/tools/mqtt-bench/bench.go b/tools/mqtt-bench/bench.go index b79f7a3d0..0e10dabc2 100644 --- a/tools/mqtt-bench/bench.go +++ b/tools/mqtt-bench/bench.go @@ -14,7 +14,7 @@ import ( "sync" "time" - mglog "github.com/absmach/magistrala/logger" + smqlog "github.com/absmach/supermq/logger" "github.com/pelletier/go-toml" ) @@ -23,7 +23,7 @@ func Benchmark(cfg Config) error { if err := checkConnection(cfg.MQTT.Broker.URL, 1); err != nil { return err } - logger, err := mglog.New(os.Stdout, "debug") + logger, err := smqlog.New(os.Stdout, "debug") if err != nil { return err } @@ -44,14 +44,14 @@ func Benchmark(cfg Config) error { caByte, _ = io.ReadAll(caFile) } - data, err := os.ReadFile(cfg.Mg.ConnFile) + data, err := os.ReadFile(cfg.Smq.ConnFile) if err != nil { return fmt.Errorf("error loading connections file: %s", err) } - mg := magistrala{} + mg := superMQ{} if err := toml.Unmarshal(data, &mg); err != nil { - return fmt.Errorf("cannot load Magistrala connections config %s \nUse tools/provision to create file", cfg.Mg.ConnFile) + return fmt.Errorf("cannot load SuperMQ connections config %s \nUse tools/provision to create file", cfg.Smq.ConnFile) } resCh := make(chan *runResults) @@ -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 channel, cli client, 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/cmd/main.go b/tools/mqtt-bench/cmd/main.go index f3edf7d36..a2cf5fc0e 100644 --- a/tools/mqtt-bench/cmd/main.go +++ b/tools/mqtt-bench/cmd/main.go @@ -7,7 +7,7 @@ package main import ( "log" - bench "github.com/absmach/magistrala/tools/mqtt-bench" + bench "github.com/absmach/supermq/tools/mqtt-bench" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -19,9 +19,9 @@ func main() { // Command rootCmd := &cobra.Command{ Use: "mqtt-bench", - Short: "mqtt-bench is MQTT benchmark tool for Magistrala", - Long: `Tool for exctensive load and benchmarking of MQTT brokers used within the Magistrala platform. -Complete documentation is available at https://docs.magistrala.abstractmachines.fr`, + Short: "mqtt-bench is MQTT benchmark tool for SuperMQ", + Long: `Tool for exctensive load and benchmarking of MQTT brokers used within the SuperMQ platform. +Complete documentation is available at https://docs.supermq.abstractmachines.fr`, Run: func(cmd *cobra.Command, args []string) { if confFile != "" { viper.SetConfigFile(confFile) @@ -69,7 +69,7 @@ Complete documentation is available at https://docs.magistrala.abstractmachines. // Config file rootCmd.PersistentFlags().StringVarP(&confFile, "config", "c", "config.toml", "config file for mqtt-bench") - rootCmd.PersistentFlags().StringVarP(&bconf.Mg.ConnFile, "magistrala", "m", "connections.toml", "config file for Magistrala connections") + rootCmd.PersistentFlags().StringVarP(&bconf.Smq.ConnFile, "supermq", "m", "connections.toml", "config file for SuperMQ connections") if err := rootCmd.Execute(); err != nil { log.Fatal(err) diff --git a/tools/mqtt-bench/config.go b/tools/mqtt-bench/config.go index a67a12c37..0a59a29b4 100644 --- a/tools/mqtt-bench/config.go +++ b/tools/mqtt-bench/config.go @@ -39,30 +39,30 @@ type logConfig struct { Quiet bool `toml:"quiet" mapstructure:"quiet"` } -type magistralaFile struct { +type smqFile 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 client 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 { +type channel struct { ChannelID string `toml:"channel_id" mapstructure:"channel_id"` } -type magistrala struct { - Things []mgThing `toml:"things" mapstructure:"things"` - Channels []mgChannel `toml:"channels" mapstructure:"channels"` +type superMQ struct { + Clients []client `toml:"clients" mapstructure:"clients"` + Channels []channel `toml:"channels" mapstructure:"channels"` } // Config struct holds benchmark configuration. type Config struct { - MQTT mqttConfig `toml:"mqtt" mapstructure:"mqtt"` - Test testConfig `toml:"test" mapstructure:"test"` - Log logConfig `toml:"log" mapstructure:"log"` - Mg magistralaFile `toml:"magistrala" mapstructure:"magistrala"` + MQTT mqttConfig `toml:"mqtt" mapstructure:"mqtt"` + Test testConfig `toml:"test" mapstructure:"test"` + Log logConfig `toml:"log" mapstructure:"log"` + Smq smqFile `toml:"supermq" mapstructure:"supermq"` } diff --git a/tools/mqtt-bench/templates/reference.toml b/tools/mqtt-bench/templates/reference.toml index 5a60e8a69..c03235826 100644 --- a/tools/mqtt-bench/templates/reference.toml +++ b/tools/mqtt-bench/templates/reference.toml @@ -25,5 +25,5 @@ count = 70 [log] quiet = true -[magistrala] +[supermq] connections_file = "../provision/mgconn.toml" diff --git a/tools/provision/README.md b/tools/provision/README.md index 77d706837..d3a95e5eb 100644 --- a/tools/provision/README.md +++ b/tools/provision/README.md @@ -1,61 +1,63 @@ -# Magistrala Things and Channels Provisioning Tool +# SuperMQ 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`). +that can be used by SuperMQ 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. -Complete documentation is available at https://docs.magistrala.abstractmachines.fr +Tool for provisioning series of SuperMQ channels and clients and connecting them together. +Complete documentation is available at https://docs.supermq.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) - -p, --password string magistrala users password + --host string address for supermq instance (default "https://localhost") + --num int number of channels and clients to create and connect (default 10) + -p, --password string supermq users password --ssl create certificates for mTLS access - -u, --username string magistrala user - --prefix string name prefix for things and channels + -u, --username string supermq user + --prefix string name prefix for clients and channels ``` Example: -``` -go run tools/provision/cmd/main.go -u test@magistrala.com -p test1234 --host https://142.93.118.47 + +```bash +go run tools/provision/cmd/main.go -u test@supermq.com -p test1234 --host https://142.93.118.47 ``` If you want to create a list of channels with certificates: -``` -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 +```bash +go run tools/provision/cmd/main.go --host http://localhost --num 10 -u test@supermq.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 1b7461e14..418c543df 100644 --- a/tools/provision/cmd/main.go +++ b/tools/provision/cmd/main.go @@ -7,7 +7,7 @@ package main import ( "log" - "github.com/absmach/magistrala/tools/provision" + "github.com/absmach/supermq/tools/provision" "github.com/spf13/cobra" ) @@ -16,9 +16,9 @@ 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. -Complete documentation is available at https://docs.magistrala.abstractmachines.fr`, + Short: "provision is provisioning tool for SuperMQ", + Long: `Tool for provisioning series of SuperMQ channels and clients and connecting them together. +Complete documentation is available at https://docs.supermq.abstractmachines.fr`, Run: func(_ *cobra.Command, _ []string) { if err := provision.Provision(pconf); err != nil { log.Fatal(err) @@ -27,14 +27,14 @@ 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.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().StringVarP(&pconf.Host, "host", "", "https://localhost", "address for supermq instance") + rootCmd.PersistentFlags().StringVarP(&pconf.Prefix, "prefix", "", "", "name prefix for clients and channels") + rootCmd.PersistentFlags().StringVarP(&pconf.Username, "username", "u", "", "supermq user") + rootCmd.PersistentFlags().StringVarP(&pconf.Password, "password", "p", "", "supermq users password") + 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 342b0abe9..da596dc21 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 d0316a077..35258fe57 100644 --- a/tools/provision/provision.go +++ b/tools/provision/provision.go @@ -21,7 +21,7 @@ import ( "time" "github.com/0x6flab/namegenerator" - sdk "github.com/absmach/magistrala/pkg/sdk/go" + sdk "github.com/absmach/supermq/pkg/sdk" ) const ( @@ -31,13 +31,13 @@ const ( var namesgenerator = namegenerator.NewGenerator() -// MgConn - structure describing Magistrala connection set. +// MgConn - structure describing SuperMQ 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), @@ -95,7 +95,7 @@ func Provision(conf Config) error { var err error // Login user - token, err := s.CreateToken(sdk.Login{Identity: user.Credentials.Username, Secret: user.Credentials.Secret}) + token, err := s.CreateToken(sdk.Login{Username: user.Credentials.Username, Password: user.Credentials.Secret}) if err != nil { return fmt.Errorf("unable to login user: %s", err.Error()) } @@ -114,8 +114,8 @@ func Provision(conf Config) error { } // Login to domain token, err = s.CreateToken(sdk.Login{ - Identity: user.Credentials.Username, - Secret: user.Credentials.Secret, + Username: user.Credentials.Username, + Password: user.Credentials.Secret, }) if err != nil { return fmt.Errorf("unable to login user: %w", err) @@ -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) } @@ -206,9 +206,9 @@ func Provision(conf Config) error { tmpl := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ - Organization: []string{"Magistrala"}, - CommonName: things[i].Credentials.Secret, - OrganizationalUnit: []string{"magistrala"}, + Organization: []string{"SuperMQ"}, + CommonName: clients[i].Credentials.Secret, + OrganizationalUnit: []string{"supermq"}, }, NotBefore: notBefore, NotAfter: notAfter, @@ -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,12 @@ func Provision(conf Config) error { for _, cID := range cIDs { for _, tID := range tIDs { conIDs := sdk.Connection{ - ThingID: tID, - ChannelID: cID, + ClientIDs: []string{tID}, + ChannelIDs: []string{cID}, + Types: []string{"publish", "subscribe"}, } 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/README.md b/users/README.md deleted file mode 100644 index cdcfce87f..000000000 --- a/users/README.md +++ /dev/null @@ -1,132 +0,0 @@ -# Users - -Users service provides an HTTP API for managing users. Through this API clients are able to do the following actions: - -- register new accounts -- login -- manage account(s) (list, update, delete) - -For 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_USERS_LOG_LEVEL | Log level for users service (debug, info, warn, error) | info | -| MG_USERS_ADMIN_EMAIL | Default user, created on startup | | -| MG_USERS_ADMIN_PASSWORD | Default user password, created on startup | 12345678 | -| MG_USERS_PASS_REGEX | Password regex | ^.{8,}$ | -| MG_TOKEN_RESET_ENDPOINT | Password request reset endpoint, for constructing link | /reset-request | -| MG_USERS_HTTP_HOST | Users service HTTP host | localhost | -| MG_USERS_HTTP_PORT | Users service HTTP port | 9002 | -| MG_USERS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| MG_USERS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | -| MG_USERS_HTTP_SERVER_CA_CERTS | Path to the PEM encoded server CA certificate file | "" | -| MG_USERS_HTTP_CLIENT_CA_CERTS | Path to the PEM encoded client CA certificate file | "" | -| MG_AUTH_GRPC_URL | Auth service GRPC URL | localhost:8181 | -| MG_AUTH_GRPC_TIMEOUT | Auth service GRPC timeout | 1s | -| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded client certificate file | "" | -| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded client key file | "" | -| MG_AUTH_GRPC_SERVER_CA_CERTS | Path to the PEM encoded server CA certificate file | "" | -| MG_USERS_DB_HOST | Database host address | localhost | -| MG_USERS_DB_PORT | Database host port | 5432 | -| MG_USERS_DB_USER | Database user | magistrala | -| MG_USERS_DB_PASS | Database password | magistrala | -| MG_USERS_DB_NAME | Name of the database used by the service | users | -| MG_USERS_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | -| MG_USERS_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | -| MG_USERS_DB_SSL_KEY | Path to the PEM encoded key file | "" | -| MG_USERS_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | -| MG_EMAIL_HOST | Mail server host | localhost | -| MG_EMAIL_PORT | Mail server port | 25 | -| MG_EMAIL_USERNAME | Mail server username | "" | -| MG_EMAIL_PASSWORD | Mail server password | "" | -| MG_EMAIL_FROM_ADDRESS | Email "from" address | "" | -| MG_EMAIL_FROM_NAME | Email "from" name | "" | -| MG_EMAIL_TEMPLATE | Email template for sending emails with password reset link | email.tmpl | -| MG_USERS_ES_URL | Event store URL | | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_OAUTH_UI_REDIRECT_URL | OAuth UI redirect URL | | -| MG_OAUTH_UI_ERROR_URL | OAuth UI error URL | | -| MG_USERS_DELETE_INTERVAL | Interval for deleting users | 24h | -| MG_USERS_DELETE_AFTER | Time after which users are deleted | 720h | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server. | true | -| MG_USERS_INSTANCE_ID | Magistrala instance ID | "" | - -## Deployment - -The service itself is distributed as Docker container. Check the [`users`](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yml) 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 service -make users - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_USERS_LOG_LEVEL=info \ -MG_USERS_ADMIN_EMAIL=admin@example.com \ -MG_USERS_ADMIN_PASSWORD=12345678 \ -MG_USERS_PASS_REGEX="^.{8,}$" \ -MG_TOKEN_RESET_ENDPOINT="/reset-request" \ -MG_USERS_HTTP_HOST=localhost \ -MG_USERS_HTTP_PORT=9002 \ -MG_USERS_HTTP_SERVER_CERT="" \ -MG_USERS_HTTP_SERVER_KEY="" \ -MG_USERS_HTTP_SERVER_CA_CERTS="" \ -MG_USERS_HTTP_CLIENT_CA_CERTS="" \ -MG_AUTH_GRPC_URL=localhost:8181 \ -MG_AUTH_GRPC_TIMEOUT=1s \ -MG_AUTH_GRPC_CLIENT_CERT="" \ -MG_AUTH_GRPC_CLIENT_KEY="" \ -MG_AUTH_GRPC_SERVER_CA_CERTS="" \ -MG_USERS_DB_HOST=localhost \ -MG_USERS_DB_PORT=5432 \ -MG_USERS_DB_USER=magistrala \ -MG_USERS_DB_PASS=magistrala \ -MG_USERS_DB_NAME=users \ -MG_USERS_DB_SSL_MODE=disable \ -MG_USERS_DB_SSL_CERT="" \ -MG_USERS_DB_SSL_KEY="" \ -MG_USERS_DB_SSL_ROOT_CERT="" \ -MG_EMAIL_HOST=smtp.mailtrap.io \ -MG_EMAIL_PORT=2525 \ -MG_EMAIL_USERNAME="18bf7f7070513" \ -MG_EMAIL_PASSWORD="2b0d302e775b1e" \ -MG_EMAIL_FROM_ADDRESS=from@example.com \ -MG_EMAIL_FROM_NAME=Example \ -MG_EMAIL_TEMPLATE="docker/templates/users.tmpl" \ -MG_USERS_ES_URL=nats://localhost:4222 \ -MG_JAEGER_URL=http://localhost:14268/api/traces \ -MG_JAEGER_TRACE_RATIO=1.0 \ -MG_SEND_TELEMETRY=true \ -MG_OAUTH_UI_REDIRECT_URL=http://localhost:9095/domains \ -MG_OAUTH_UI_ERROR_URL=http://localhost:9095/error \ -MG_USERS_DELETE_INTERVAL=24h \ -MG_USERS_DELETE_AFTER=720h \ -MG_USERS_INSTANCE_ID="" \ -$GOBIN/magistrala-users -``` - -If `MG_EMAIL_TEMPLATE` doesn't point to any file service will function but password reset functionality will not work. The email environment variables are used to send emails with password reset link. The service expects a file in Go template format. The template should be something like [this](https://github.com/absmach/magistrala/blob/main/docker/templates/users.tmpl). - -Setting `MG_USERS_HTTP_SERVER_CERT` and `MG_USERS_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_USERS_HTTP_SERVER_CA_CERTS` will enable TLS against the service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. Setting `MG_USERS_HTTP_CLIENT_CA_CERTS` will enable TLS against the service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. - -Setting `MG_AUTH_GRPC_CLIENT_CERT` and `MG_AUTH_GRPC_CLIENT_KEY` will enable TLS against the auth service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_AUTH_GRPC_SERVER_CA_CERTS` will enable TLS against the auth service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. - -## Usage - -For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=users-openapi.yml). - -[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/users/api/doc.go b/users/api/doc.go deleted file mode 100644 index 2424852cc..000000000 --- a/users/api/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains API-related concerns: endpoint definitions, middlewares -// and all resource representations. -package api diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go deleted file mode 100644 index 32d219cb1..000000000 --- a/users/api/endpoint_test.go +++ /dev/null @@ -1,4352 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "regexp" - "strings" - "testing" - - "github.com/absmach/magistrala" - authmocks "github.com/absmach/magistrala/auth/mocks" - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/internal/testsutil" - 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" - "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" - "github.com/absmach/magistrala/users/mocks" - "github.com/go-chi/chi/v5" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - secret = "strongsecret" - validCMetadata = users.Metadata{"role": "user"} - user = users.User{ - ID: testsutil.GenerateUUID(&testing.T{}), - LastName: "doe", - FirstName: "jane", - Tags: []string{"foo", "bar"}, - Email: "useremail@example.com", - Credentials: users.Credentials{Username: "username", Secret: secret}, - Metadata: validCMetadata, - Status: users.EnabledStatus, - } - validToken = "valid" - inValidToken = "invalid" - inValid = "invalid" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - passRegex = regexp.MustCompile("^.{8,}$") - testReferer = "http://localhost" - domainID = testsutil.GenerateUUID(&testing.T{}) -) - -const contentType = "application/json" - -type testRequest struct { - user *http.Client - method string - url string - contentType string - referer string - token string - body io.Reader -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, tr.body) - if err != nil { - return nil, err - } - - if tr.token != "" { - req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) - } - - if tr.contentType != "" { - req.Header.Set("Content-Type", tr.contentType) - } - - req.Header.Set("Referer", tr.referer) - - return tr.user.Do(req) -} - -func newUsersServer() (*httptest.Server, *mocks.Service, *gmocks.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) - - return httptest.NewServer(mux), svc, gsvc, authn -} - -func toJSON(data interface{}) string { - jsonData, err := json.Marshal(data) - if err != nil { - return "" - } - return string(jsonData) -} - -func TestRegister(t *testing.T) { - us, svc, _, _ := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - user users.User - token string - contentType string - status int - err error - }{ - { - desc: "register a new user with a valid token", - user: user, - token: validToken, - contentType: contentType, - status: http.StatusCreated, - err: nil, - }, - { - desc: "register an existing user", - user: user, - token: validToken, - contentType: contentType, - status: http.StatusConflict, - err: svcerr.ErrConflict, - }, - { - desc: "register a new user with an empty token", - user: user, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "register a user with an invalid ID", - user: users.User{ - ID: inValid, - Email: "user@example.com", - Credentials: users.Credentials{ - Secret: "12345678", - }, - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "register a user that can't be marshalled", - user: users.User{ - Email: "user@example.com", - Credentials: users.Credentials{ - Secret: "12345678", - }, - Metadata: map[string]interface{}{ - "test": make(chan int), - }, - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "register user with invalid status", - user: users.User{ - Email: "newclientwithinvalidstatus@example.com", - FirstName: "newclientwithinvalidstatus", - LastName: "newclientwithinvalidstatus", - Credentials: users.Credentials{ - Username: "username", - Secret: secret, - }, - Status: users.AllStatus, - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrInvalidStatus, - }, - { - desc: "register a user with name too long", - user: users.User{ - FirstName: strings.Repeat("a", 1025), - LastName: "newuserwithnametoolong", - Email: "newuserwithinvalidname@example.com", - Credentials: users.Credentials{ - Secret: secret, - }, - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "register user with invalid content type", - user: user, - token: validToken, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "register user with empty request body", - user: users.User{}, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.user) - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/users/", us.URL), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(data), - } - - svcCall := svc.On("Register", mock.Anything, mgauthn.Session{}, tc.user, true).Return(tc.user, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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() - }) - } -} - -func TestView(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - id string - status int - authnRes mgauthn.Session - authnErr error - svcErr error - err error - }{ - { - desc: "view user as admin with valid token", - token: validToken, - id: user.ID, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "view user with invalid token", - token: inValidToken, - id: user.ID, - status: http.StatusUnauthorized, - authnRes: mgauthn.Session{}, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "view user with empty token", - token: "", - id: user.ID, - status: http.StatusUnauthorized, - authnRes: mgauthn.Session{}, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "view user as normal user successfully", - token: validToken, - id: user.ID, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "view user with invalid ID", - token: validToken, - id: inValid, - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - svcErr: svcerr.ErrViewEntity, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/users/%s", us.URL, tc.id), - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("View", mock.Anything, tc.authnRes, tc.id).Return(users.User{}, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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 TestViewProfile(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - id string - status int - authnRes mgauthn.Session - authnErr error - svcErr error - err error - }{ - { - desc: "view profile with valid token", - token: validToken, - id: user.ID, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "view profile with invalid token", - token: inValidToken, - id: user.ID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - authnRes: mgauthn.Session{}, - err: svcerr.ErrAuthentication, - }, - { - desc: "view profile with empty token", - token: "", - id: user.ID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - authnRes: mgauthn.Session{}, - err: apiutil.ErrBearerToken, - }, - { - desc: "view profile with service error", - token: validToken, - id: user.ID, - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - svcErr: svcerr.ErrViewEntity, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/users/profile", us.URL), - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ViewProfile", mock.Anything, tc.authnRes).Return(users.User{}, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var errRes respBody - err = json.NewDecoder(res.Body).Decode(&errRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if errRes.Err != "" || errRes.Message != "" { - err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message)) - } - 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 TestListUsers(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - query string - token string - listUsersResponse users.UsersPage - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "list users as admin with valid token", - token: validToken, - status: http.StatusOK, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty token", - token: "", - status: http.StatusUnauthorized, - authnRes: mgauthn.Session{}, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - status: http.StatusUnauthorized, - authnRes: mgauthn.Session{}, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - query: "offset=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit", - token: validToken, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - 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, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "name=username", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate name", - token: validToken, - query: "name=1&name=2", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with status", - token: validToken, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - query: "status=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate tags", - token: validToken, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - query: "metadata=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - 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, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=view", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with list perms", - token: validToken, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "list_perms=true", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate list perms", - token: validToken, - query: "list_perms=true&list_perms=true", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate email", - token: validToken, - query: "email=1&email=2", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with duplicate list perms", - token: validToken, - query: "list_perms=true&list_perms=true", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: validID}, - err: nil, - }, - { - desc: "list users with duplicate email", - token: validToken, - query: "email=1&email=2", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: validID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with order", - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - token: validToken, - query: "order=name", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate order", - token: validToken, - query: "order=name&order=name", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with invalid order direction", - token: validToken, - query: "dir=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate order direction", - token: validToken, - query: "dir=asc&dir=asc", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: us.URL + "/users?" + tc.query, - contentType: contentType, - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListUsers", mock.Anything, tc.authnRes, mock.Anything).Return(tc.listUsersResponse, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - var bodyRes respBody - err = json.NewDecoder(res.Body).Decode(&bodyRes) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err)) - if bodyRes.Err != "" || bodyRes.Message != "" { - err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message)) - } - 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 TestSearchUsers(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnErr error - svcErr error - err error - }{ - { - desc: "search users with valid token", - token: validToken, - status: http.StatusOK, - query: "username=username", - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - err: nil, - }, - { - desc: "search users with empty token", - token: "", - query: "username=username", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "search users with invalid token", - token: inValidToken, - query: "username=username", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "search users with offset", - token: validToken, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "username=username&offset=1", - status: http.StatusOK, - err: nil, - }, - { - desc: "search users with invalid offset", - token: validToken, - query: "username=username&offset=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "search users with limit", - token: validToken, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "username=username&limit=1", - status: http.StatusOK, - err: nil, - }, - { - desc: "search users with invalid limit", - token: validToken, - query: "username=username&limit=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "search users with empty query", - token: validToken, - query: "", - status: http.StatusBadRequest, - err: apiutil.ErrEmptySearchQuery, - }, - { - desc: "search users with invalid length of query", - token: validToken, - query: "username=a", - status: http.StatusBadRequest, - err: apiutil.ErrLenSearchQuery, - }, - { - desc: "serach users with service error", - token: validToken, - query: "username=username", - status: http.StatusBadRequest, - svcErr: svcerr.ErrViewEntity, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/users/search?", us.URL) + tc.query, - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(mgauthn.Session{UserID: validID, DomainID: domainID}, tc.authnErr) - svcCall := svc.On("SearchUsers", mock.Anything, mock.Anything).Return( - users.UsersPage{ - Page: tc.listUsersResponse.Page, - Users: tc.listUsersResponse.Users, - }, - tc.svcErr) - 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 TestUpdate(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - newName := "newname" - newMetadata := users.Metadata{"newkey": "newvalue"} - - cases := []struct { - desc string - id string - data string - userResponse users.User - token string - authnRes mgauthn.Session - authnErr error - contentType string - status int - err error - }{ - { - desc: "update as admin user with valid token", - id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - userResponse: users.User{ - ID: user.ID, - FirstName: newName, - Metadata: newMetadata, - }, - status: http.StatusOK, - err: nil, - }, - { - desc: "update as normal user with valid token", - id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - userResponse: users.User{ - ID: user.ID, - FirstName: newName, - Metadata: newMetadata, - }, - status: http.StatusOK, - err: nil, - }, - { - desc: "update user with invalid token", - id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), - token: inValidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update user with empty token", - id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "update user with invalid id", - id: inValid, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusForbidden, - err: svcerr.ErrAuthorization, - }, - { - desc: "update user with invalid contentype", - id: user.ID, - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "update user with malformed data", - id: user.ID, - data: fmt.Sprintf(`{"name":%s}`, "invalid"), - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "update user with empty id", - id: " ", - data: fmt.Sprintf(`{"name":"%s","metadata":%s}`, newName, toJSON(newMetadata)), - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/users/%s", us.URL, tc.id), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("Update", mock.Anything, tc.authnRes, mock.Anything).Return(tc.userResponse, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - 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 TestUpdateTags(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - defer us.Close() - newTag := "newtag" - - cases := []struct { - desc string - id string - data string - contentType string - userResponse users.User - token string - authnRes mgauthn.Session - authnErr error - status int - err error - }{ - { - desc: "updateuser tags as admin with valid token", - id: user.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - userResponse: users.User{ - ID: user.ID, - Tags: []string{newTag}, - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "updateuser tags as normal user with valid token", - id: user.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - userResponse: users.User{ - ID: user.ID, - Tags: []string{newTag}, - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "update user tags with empty token", - id: user.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - token: "", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "update user tags with invalid token", - id: user.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update user tags with invalid id", - id: user.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusForbidden, - err: svcerr.ErrAuthorization, - }, - { - desc: "update user tags with invalid contentype", - id: user.ID, - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: "application/xml", - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "update user tags with empty id", - id: "", - data: fmt.Sprintf(`{"tags":["%s"]}`, newTag), - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "update user with malfomed data", - id: user.ID, - data: fmt.Sprintf(`{"tags":%s}`, newTag), - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/users/%s/tags", us.URL, tc.id), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("UpdateTags", mock.Anything, tc.authnRes, mock.Anything).Return(tc.userResponse, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - } - if err == nil { - assert.Equal(t, tc.userResponse.Tags, resBody.Tags, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.userResponse.Tags, resBody.Tags)) - } - 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 TestUpdateEmail(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - newuseremail := "newuseremail@example.com" - - cases := []struct { - desc string - data string - user users.User - contentType string - token string - authnRes mgauthn.Session - authnErr error - status int - svcErr error - err error - }{ - { - desc: "update user email as admin with valid token", - data: fmt.Sprintf(`{"email": "%s"}`, newuseremail), - user: users.User{ - ID: user.ID, - Email: newuseremail, - Credentials: users.Credentials{ - Secret: "secret", - }, - }, - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "update user email as normal user with valid token", - data: fmt.Sprintf(`{"email": "%s"}`, newuseremail), - user: users.User{ - ID: user.ID, - Email: newuseremail, - Credentials: users.Credentials{ - Secret: "secret", - }, - }, - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: validID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "update user email with empty token", - data: fmt.Sprintf(`{"email": "%s"}`, newuseremail), - user: users.User{ - ID: user.ID, - Email: newuseremail, - Credentials: users.Credentials{ - Secret: "secret", - }, - }, - contentType: contentType, - token: "", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "update user email with invalid token", - data: fmt.Sprintf(`{"email": "%s"}`, newuseremail), - user: users.User{ - ID: user.ID, - Email: newuseremail, - Credentials: users.Credentials{ - Secret: "secret", - }, - }, - contentType: contentType, - token: inValid, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update user email with empty id", - data: fmt.Sprintf(`{"email": "%s"}`, newuseremail), - user: users.User{ - ID: "", - Email: newuseremail, - Credentials: users.Credentials{ - Secret: "secret", - }, - }, - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: validID}, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "update user email with invalid contentype", - data: fmt.Sprintf(`{"email": "%s"}`, ""), - user: users.User{ - ID: user.ID, - Email: newuseremail, - Credentials: users.Credentials{ - Secret: "secret", - }, - }, - contentType: "application/xml", - token: validToken, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "update user email with malformed data", - data: fmt.Sprintf(`{"email": %s}`, "invalid"), - user: users.User{ - ID: user.ID, - Email: "", - Credentials: users.Credentials{ - Secret: "secret", - }, - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "update user email with service error", - data: fmt.Sprintf(`{"email": "%s"}`, newuseremail), - user: users.User{ - ID: user.ID, - Email: newuseremail, - }, - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - req := testRequest{ - user: us.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/users/%s/email", us.URL, tc.user.ID), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("UpdateEmail", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.user, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - 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 TestUpdateUsername(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - newusername := "newusername" - - cases := []struct { - desc string - data string - user users.User - contentType string - token string - authnRes mgauthn.Session - authnErr error - status int - err error - }{ - { - desc: "update username as admin with valid token", - data: fmt.Sprintf(`{"username": "%s"}`, newusername), - user: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: newusername, - }, - }, - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "update username with empty token", - data: fmt.Sprintf(`{"username": "%s"}`, newusername), - user: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: newusername, - }, - }, - contentType: contentType, - token: "", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "update username with invalid token", - data: fmt.Sprintf(`{"username": "%s"}`, newusername), - user: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: newusername, - }, - }, - contentType: contentType, - token: inValid, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update username with empty id", - data: fmt.Sprintf(`{"username": "%s"}`, newusername), - user: users.User{ - ID: "", - Credentials: users.Credentials{ - Username: newusername, - }, - }, - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: validID}, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "update username with invalid contentype", - data: fmt.Sprintf(`{"username": "%s"}`, ""), - user: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: newusername, - }, - }, - contentType: "application/xml", - token: validToken, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "update user email with malformed data", - data: fmt.Sprintf(`{"email": %s}`, "invalid"), - user: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: newusername, - }, - }, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "update username with invalid username", - data: fmt.Sprintf(`{"username": "%s"}`, "invalid"), - user: users.User{ - ID: user.ID, - Credentials: users.Credentials{ - Username: newusername, - }, - }, - contentType: contentType, - token: validToken, - status: http.StatusUnprocessableEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - req := testRequest{ - user: us.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/users/%s/username", us.URL, tc.user.ID), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("UpdateUsername", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.user, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - 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 TestUpdateProfilePicture(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - newprofilepicture := "https://example.com/newprofilepicture" - - cases := []struct { - desc string - data string - user users.User - contentType string - token string - authnRes mgauthn.Session - authnErr error - status int - svcErr error - err error - }{ - { - desc: "update profile picture as admin with valid token", - data: fmt.Sprintf(`{"profile_picture": "%s"}`, newprofilepicture), - user: users.User{ - ID: user.ID, - ProfilePicture: newprofilepicture, - }, - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "update profile picture with empty token", - data: fmt.Sprintf(`{"profile_picture": "%s"}`, newprofilepicture), - user: users.User{}, - contentType: contentType, - token: "", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "update profile_picture with invalid token", - data: fmt.Sprintf(`{"profile_picture": "%s"}`, newprofilepicture), - user: users.User{}, - contentType: contentType, - token: inValid, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update profile_picture with empty id", - data: fmt.Sprintf(`{"profile_picture": "%s"}`, newprofilepicture), - user: users.User{ - ID: "", - ProfilePicture: newprofilepicture, - }, - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: validID}, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "update profile_picture with invalid contentype", - data: fmt.Sprintf(`{"profile_picture": "%s"}`, ""), - user: users.User{ - ID: user.ID, - ProfilePicture: newprofilepicture, - }, - contentType: "application/xml", - token: validToken, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "update profile picture with malformed data", - data: fmt.Sprintf(`{"profile_picture": %s}`, "invalid"), - user: users.User{}, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "update profile picture with failed to update", - data: fmt.Sprintf(`{"profile_picture": "%s"}`, "invalid"), - user: users.User{ - ID: user.ID, - }, - contentType: contentType, - token: validToken, - status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - req := testRequest{ - user: us.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/users/%s/picture", us.URL, tc.user.ID), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("UpdateProfilePicture", mock.Anything, tc.authnRes, mock.Anything).Return(tc.user, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - 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 TestPasswordResetRequest(t *testing.T) { - us, svc, _, _ := newUsersServer() - defer us.Close() - - testemail := "test@example.com" - testhost := "example.com" - - cases := []struct { - desc string - data string - contentType string - referer string - status int - generateErr error - sendErr error - err error - }{ - { - desc: "password reset request with valid email", - data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, testemail, testhost), - contentType: contentType, - referer: testReferer, - status: http.StatusCreated, - err: nil, - }, - { - desc: "password reset request with empty email", - data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, "", testhost), - contentType: contentType, - referer: testReferer, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "password reset request with empty host", - data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, testemail, ""), - contentType: contentType, - referer: "", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "password reset request with invalid email", - data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, "invalid", testhost), - contentType: contentType, - referer: testReferer, - status: http.StatusNotFound, - generateErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "password reset with malformed data", - data: fmt.Sprintf(`{"email": %s, "host": %s}`, testemail, testhost), - contentType: contentType, - referer: testReferer, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "password reset with invalid contentype", - data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, testemail, testhost), - contentType: "application/xml", - referer: testReferer, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "password reset with failed to issue token", - data: fmt.Sprintf(`{"email": "%s", "host": "%s"}`, testemail, testhost), - contentType: contentType, - referer: testReferer, - status: http.StatusUnauthorized, - generateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/password/reset-request", us.URL), - contentType: tc.contentType, - referer: tc.referer, - body: strings.NewReader(tc.data), - } - svcCall := svc.On("GenerateResetToken", mock.Anything, mock.Anything, mock.Anything).Return(tc.generateErr) - svcCall1 := svc.On("SendPasswordReset", mock.Anything, mock.Anything, mock.Anything, mock.Anything, validToken).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() - svcCall1.Unset() - }) - } -} - -func TestPasswordReset(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - strongPass := "StrongPassword" - - cases := []struct { - desc string - data string - token string - contentType string - status int - authnRes mgauthn.Session - authnErr error - svcErr error - err error - }{ - { - desc: "password reset with valid token", - data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, strongPass, strongPass), - token: validToken, - contentType: contentType, - status: http.StatusCreated, - err: nil, - }, - { - desc: "password reset with invalid token", - data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, inValidToken, strongPass, strongPass), - token: inValidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "password reset to weak password", - data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, "weak", "weak"), - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrPasswordFormat, - }, - { - desc: "password reset with empty token", - data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, "", strongPass, strongPass), - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "password reset with empty password", - data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, "", ""), - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "password reset with malformed data", - data: fmt.Sprintf(`{"token": "%s", "password": %s, "confirm_password": %s}`, validToken, strongPass, strongPass), - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "password reset with invalid contentype", - data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, strongPass, strongPass), - token: validToken, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "password reset with service error", - data: fmt.Sprintf(`{"token": "%s", "password": "%s", "confirm_password": "%s"}`, validToken, strongPass, strongPass), - token: validToken, - contentType: contentType, - status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodPut, - url: fmt.Sprintf("%s/password/reset", us.URL), - contentType: tc.contentType, - referer: testReferer, - token: tc.token, - body: strings.NewReader(tc.data), - } - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ResetSecret", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcErr) - 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 TestUpdateRole(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - data string - userID string - token string - contentType string - authnRes mgauthn.Session - authnErr error - status int - svcErr error - err error - }{ - { - desc: "update user role as admin with valid token", - data: fmt.Sprintf(`{"role": "%s"}`, "admin"), - userID: user.ID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusOK, - err: nil, - }, - { - desc: "update user role as normal user with valid token", - data: fmt.Sprintf(`{"role": "%s"}`, "admin"), - userID: user.ID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusOK, - err: nil, - }, - { - desc: "update user role with invalid token", - data: fmt.Sprintf(`{"role": "%s"}`, "admin"), - userID: user.ID, - token: inValidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update user role with empty token", - data: fmt.Sprintf(`{"role": "%s"}`, "admin"), - userID: user.ID, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "update user with invalid role", - data: fmt.Sprintf(`{"role": "%s"}`, "invalid"), - userID: user.ID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrInvalidRole, - }, - { - desc: "update user with invalid contentype", - data: fmt.Sprintf(`{"role": "%s"}`, "admin"), - userID: user.ID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "update user with malformed data", - data: fmt.Sprintf(`{"role": %s}`, "admin"), - userID: user.ID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "update user with service error", - data: fmt.Sprintf(`{"role": "%s"}`, "admin"), - userID: user.ID, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - contentType: contentType, - status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/users/%s/role", us.URL, tc.userID), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("UpdateRole", mock.Anything, tc.authnRes, mock.Anything).Return(users.User{}, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - 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 TestUpdateSecret(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - data string - user users.User - contentType string - token string - status int - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "update user secret with valid token", - data: `{"old_secret": "strongersecret", "new_secret": "strongersecret"}`, - user: users.User{ - ID: user.ID, - Email: "username", - Credentials: users.Credentials{ - Secret: "strongersecret", - }, - }, - contentType: contentType, - token: validToken, - status: http.StatusOK, - err: nil, - }, - { - desc: "update user secret with empty token", - data: `{"old_secret": "strongersecret", "new_secret": "strongersecret"}`, - user: users.User{ - ID: user.ID, - Email: "username", - Credentials: users.Credentials{ - Secret: "strongersecret", - }, - }, - contentType: contentType, - token: "", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "update user secret with invalid token", - data: `{"old_secret": "strongersecret", "new_secret": "strongersecret"}`, - user: users.User{ - ID: user.ID, - Email: "username", - Credentials: users.Credentials{ - Secret: "strongersecret", - }, - }, - contentType: contentType, - token: inValid, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - - { - desc: "update user secret with empty secret", - data: `{"old_secret": "", "new_secret": "strongersecret"}`, - user: users.User{ - ID: user.ID, - Email: "username", - Credentials: users.Credentials{ - Secret: "", - }, - }, - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingPass, - }, - { - desc: "update user secret with invalid contentype", - data: `{"old_secret": "strongersecret", "new_secret": "strongersecret"}`, - user: users.User{ - ID: user.ID, - Email: "username", - Credentials: users.Credentials{ - Secret: "", - }, - }, - contentType: "application/xml", - token: validToken, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - { - desc: "update user secret with malformed data", - data: fmt.Sprintf(`{"secret": %s}`, "invalid"), - user: users.User{ - ID: user.ID, - Email: "username", - Credentials: users.Credentials{ - Secret: "", - }, - }, - contentType: contentType, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodPatch, - url: fmt.Sprintf("%s/users/secret", us.URL), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("UpdateSecret", mock.Anything, tc.authnRes, mock.Anything, mock.Anything).Return(tc.user, tc.err) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - 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)) - 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 TestIssueToken(t *testing.T) { - us, svc, _, _ := newUsersServer() - defer us.Close() - - validUsername := "valid" - - cases := []struct { - desc string - data string - contentType string - status int - err error - }{ - { - desc: "issue token with valid identity and secret", - data: fmt.Sprintf(`{"identity": "%s", "secret": "%s"}`, validUsername, secret), - contentType: contentType, - status: http.StatusCreated, - err: nil, - }, - { - desc: "issue token with empty identity", - data: fmt.Sprintf(`{"identity": "%s", "secret": "%s"}`, "", secret), - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "issue token with empty secret", - data: fmt.Sprintf(`{"identity": "%s", "secret": "%s"}`, validUsername, ""), - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "issue token with invalid email", - data: fmt.Sprintf(`{"identity": "%s", "secret": "%s"}`, "invalid", secret), - contentType: contentType, - status: http.StatusUnauthorized, - err: svcerr.ErrAuthentication, - }, - { - desc: "issues token with malformed data", - data: fmt.Sprintf(`{"identity": %s, "secret": %s}`, validUsername, secret), - contentType: contentType, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "issue token with invalid contentype", - data: fmt.Sprintf(`{"identity": "%s", "secret": "%s"}`, "invalid", secret), - contentType: "application/xml", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - } - - 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/issue", us.URL), - contentType: tc.contentType, - body: strings.NewReader(tc.data), - } - - svcCall := svc.On("IssueToken", mock.Anything, mock.Anything, mock.Anything, 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)) - } - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - }) - } -} - -func TestRefreshToken(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - data string - contentType string - token string - authnRes mgauthn.Session - authnErr error - status int - refreshErr error - err error - }{ - { - desc: "refresh token with valid token", - data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, validToken, validID), - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusCreated, - err: nil, - }, - { - desc: "refresh token with invalid token", - data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, inValidToken, validID), - contentType: contentType, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "refresh token with empty token", - data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, "", validID), - contentType: contentType, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "refresh token with invalid domain", - data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, validToken, "invalid"), - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusUnauthorized, - err: svcerr.ErrAuthentication, - }, - { - desc: "refresh token with malformed data", - data: fmt.Sprintf(`{"refresh_token": %s, "domain_id": %s}`, validToken, validID), - contentType: contentType, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "refresh token with invalid contentype", - data: fmt.Sprintf(`{"refresh_token": "%s", "domain_id": "%s"}`, validToken, validID), - contentType: "application/xml", - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrValidation, - }, - } - - 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)) - } - 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() - defer us.Close() - cases := []struct { - desc string - user users.User - response users.User - token string - authnRes mgauthn.Session - authnErr error - status int - svcErr error - err error - }{ - { - desc: "enable user as admin with valid token", - user: user, - response: users.User{ - ID: user.ID, - Status: users.EnabledStatus, - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "enable user as normal user with valid token", - user: user, - response: users.User{ - ID: user.ID, - Status: users.EnabledStatus, - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "enable user with invalid token", - user: user, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "enable user with empty id", - user: users.User{ - ID: "", - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "enable user with service error", - user: user, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.user) - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/users/%s/enable", us.URL, tc.user.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("Enable", mock.Anything, tc.authnRes, mock.Anything).Return(tc.user, tc.svcErr) - 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)) - } - 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 TestDisable(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - user users.User - response users.User - token string - authnRes mgauthn.Session - authnErr error - status int - svcErr error - err error - }{ - { - desc: "disable user as admin with valid token", - user: user, - response: users.User{ - ID: user.ID, - Status: users.DisabledStatus, - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID, SuperAdmin: true}, - status: http.StatusOK, - err: nil, - }, - { - desc: "disable user as normal user with valid token", - user: user, - response: users.User{ - ID: user.ID, - Status: users.DisabledStatus, - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusOK, - err: nil, - }, - { - desc: "disable user with invalid token", - user: user, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "disable user with empty id", - user: users.User{ - ID: "", - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, - }, - { - desc: "disable user with service error", - user: user, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.user) - req := testRequest{ - user: us.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/users/%s/disable", us.URL, tc.user.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("Disable", mock.Anything, mock.Anything, mock.Anything).Return(tc.user, tc.svcErr) - 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 TestDelete(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - user users.User - response users.User - token string - authnRes mgauthn.Session - authnErr error - status int - svcErr error - err error - }{ - { - desc: "delete user as admin with valid token", - user: user, - response: users.User{ - ID: user.ID, - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusNoContent, - err: nil, - }, - { - desc: "delete user with invalid token", - user: user, - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "delete user with empty id", - user: users.User{ - ID: "", - }, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusMethodNotAllowed, - err: apiutil.ErrMissingID, - }, - { - desc: "delete user with service error", - user: user, - token: validToken, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - status: http.StatusUnprocessableEntity, - svcErr: svcerr.ErrRemoveEntity, - err: svcerr.ErrRemoveEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - data := toJSON(tc.user) - req := testRequest{ - user: us.Client(), - method: http.MethodDelete, - url: fmt.Sprintf("%s/users/%s", us.URL, tc.user.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - repoCall := svc.On("Delete", mock.Anything, tc.authnRes, tc.user.ID).Return(tc.svcErr) - 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)) - repoCall.Unset() - authnCall.Unset() - }) - } -} - -func TestListUsersByUserGroupId(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - groupID string - domainID string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "list users with valid token", - token: validToken, - groupID: validID, - domainID: validID, - status: http.StatusOK, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty id", - token: validToken, - groupID: "", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrMissingID, - }, - { - desc: "list users with empty token", - token: "", - groupID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - groupID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - groupID: validID, - query: "offset=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - groupID: validID, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - groupID: 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 user name", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "username=username", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid user name", - token: validToken, - groupID: 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, - groupID: validID, - query: "username=1&username=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with status", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - groupID: validID, - query: "status=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - groupID: validID, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid tags", - token: validToken, - groupID: validID, - query: "tag=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate tags", - token: validToken, - groupID: validID, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - groupID: validID, - query: "metadata=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - groupID: 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, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with permissions", - token: validToken, - groupID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=view", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - groupID: validID, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - listUsersResponse: users.UsersPage{}, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - groupID: validID, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid email", - token: validToken, - groupID: validID, - query: "email=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate email", - token: validToken, - groupID: validID, - query: "email=1&email=2", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/groups/%s/users?", us.URL, validID, tc.groupID) + tc.query, - token: tc.token, - } - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListMembers", mock.Anything, mgauthn.Session{UserID: validID, DomainID: validID, DomainUserID: validID + "_" + validID}, mock.Anything, mock.Anything, mock.Anything).Return( - users.MembersPage{ - Page: tc.listUsersResponse.Page, - Members: tc.listUsersResponse.Users, - }, - 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 TestListUsersByChannelID(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - channelID string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "list users with valid token", - token: validToken, - status: http.StatusOK, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty token", - token: "", - channelID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - channelID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - channelID: validID, - query: "offset=invalid", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - channelID: validID, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - channelID: 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 user name", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "username=username", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid user name", - token: validToken, - channelID: 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, - query: "username=1&username=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with status", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - channelID: validID, - query: "status=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid tags", - token: validToken, - channelID: validID, - query: "tag=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate tags", - token: validToken, - channelID: validID, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - channelID: validID, - query: "metadata=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with permissions", - token: validToken, - channelID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=view", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - channelID: validID, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - channelID: validID, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid email", - token: validToken, - channelID: validID, - query: "email=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate email", - token: validToken, - channelID: validID, - query: "email=1&email=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with list_perms", - token: validToken, - channelID: validID, - query: "list_perms=true", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid list_perms", - token: validToken, - channelID: validID, - query: "list_perms=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate list_perms", - token: validToken, - query: "list_perms=true&list_perms=false", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/channels/%s/users?", us.URL, validID, validID) + tc.query, - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListMembers", mock.Anything, mgauthn.Session{UserID: validID, DomainID: validID, DomainUserID: validID + "_" + validID}, mock.Anything, mock.Anything, mock.Anything).Return( - users.MembersPage{ - Page: tc.listUsersResponse.Page, - Members: tc.listUsersResponse.Users, - }, - 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 TestListUsersByDomainID(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - domainID string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "list users with valid token", - token: validToken, - domainID: validID, - status: http.StatusOK, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty token", - token: "", - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - domainID: validID, - query: "offset=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - domainID: validID, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - domainID: 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 user name", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "username=username", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid user name", - token: validToken, - domainID: 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, - domainID: validID, - query: "username=1&username=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with status", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - domainID: validID, - query: "status=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid tags", - token: validToken, - domainID: validID, - query: "tag=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate tags", - token: validToken, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - domainID: validID, - query: "metadata=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&metadata=%7B%22domain%22%3A%20%22example.com%22%7D", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with permissions", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=membership", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - domainID: validID, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - domainID: validID, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid email", - token: validToken, - domainID: validID, - query: "email=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate email", - token: validToken, - query: "email=1&email=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users wiith list permissions", - token: validToken, - domainID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - query: "list_perms=true", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid list_perms", - token: validToken, - domainID: validID, - query: "list_perms=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate list_perms", - token: validToken, - query: "list_perms=true&list_perms=false", - status: http.StatusBadRequest, - err: apiutil.ErrValidation, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - req := testRequest{ - user: us.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/%s/users?", us.URL, validID) + tc.query, - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListMembers", mock.Anything, mgauthn.Session{UserID: validID, DomainID: validID, DomainUserID: validID + "_" + validID}, mock.Anything, mock.Anything, mock.Anything).Return( - users.MembersPage{ - Page: tc.listUsersResponse.Page, - Members: tc.listUsersResponse.Users, - }, - 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 TestListUsersByThingID(t *testing.T) { - us, svc, _, authn := newUsersServer() - defer us.Close() - - cases := []struct { - desc string - token string - thingID string - page users.Page - status int - query string - listUsersResponse users.UsersPage - authnRes mgauthn.Session - authnErr error - err error - }{ - { - desc: "list users with valid token", - token: validToken, - thingID: validID, - status: http.StatusOK, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with empty token", - token: "", - thingID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: apiutil.ErrBearerToken, - }, - { - desc: "list users with invalid token", - token: inValidToken, - thingID: validID, - status: http.StatusUnauthorized, - authnErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users with offset", - token: validToken, - thingID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Offset: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "offset=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid offset", - token: validToken, - thingID: 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, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Limit: 1, - Total: 1, - }, - Users: []users.User{user}, - }, - query: "limit=1", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid limit", - token: validToken, - thingID: validID, - query: "limit=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with limit greater than max", - token: validToken, - thingID: 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, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "name=username", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid user name", - token: validToken, - thingID: 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 status", - token: validToken, - thingID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "status=enabled", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid status", - token: validToken, - thingID: validID, - query: "status=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate status", - token: validToken, - query: "status=enabled&status=disabled", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with tags", - token: validToken, - thingID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "tag=tag1,tag2", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid tags", - token: validToken, - thingID: validID, - query: "tag=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate tags", - token: validToken, - query: "tag=tag1&tag=tag2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with metadata", - token: validToken, - thingID: validID, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "metadata=%7B%22domain%22%3A%20%22example.com%22%7D&", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid metadata", - token: validToken, - thingID: validID, - query: "metadata=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate metadata", - token: validToken, - thingID: 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, - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - query: "permission=view", - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with duplicate permissions", - token: validToken, - query: "permission=view&permission=view", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "list users with email", - token: validToken, - thingID: validID, - query: fmt.Sprintf("email=%s", user.Email), - listUsersResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{ - user, - }, - }, - status: http.StatusOK, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: nil, - }, - { - desc: "list users with invalid email", - token: validToken, - thingID: validID, - query: "email=invalid", - status: http.StatusBadRequest, - authnRes: mgauthn.Session{UserID: validID, DomainID: domainID}, - err: apiutil.ErrValidation, - }, - { - desc: "list users with duplicate email", - token: validToken, - query: "email=1&email=2", - status: http.StatusBadRequest, - err: apiutil.ErrInvalidQueryParams, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(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, - token: tc.token, - } - - authnCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr) - svcCall := svc.On("ListMembers", mock.Anything, mgauthn.Session{UserID: validID, DomainID: validID, DomainUserID: validID + "_" + validID}, mock.Anything, mock.Anything, mock.Anything).Return( - users.MembersPage{ - Page: tc.listUsersResponse.Page, - Members: tc.listUsersResponse.Users, - }, - 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 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"` - Total int `json:"total"` - ID string `json:"id"` - Tags []string `json:"tags"` - Role users.Role `json:"role"` - Status users.Status `json:"status"` -} - -type groupReqBody struct { - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` - GroupIDs []string `json:"group_ids"` -} diff --git a/users/api/endpoints.go b/users/api/endpoints.go deleted file mode 100644 index dcb8986f4..000000000 --- a/users/api/endpoints.go +++ /dev/null @@ -1,593 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -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/users" - "github.com/go-kit/kit/endpoint" -) - -func registrationEndpoint(svc users.Service, selfRegister bool) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createUserReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - session := authn.Session{} - - var ok bool - if !selfRegister { - session, ok = ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - } - - user, err := svc.Register(ctx, session, req.User, selfRegister) - if err != nil { - return nil, err - } - - return createUserRes{ - User: user, - created: true, - }, nil - } -} - -func viewEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(viewUserReq) - 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 - } - user, err := svc.View(ctx, session, req.id) - if err != nil { - return nil, err - } - - return viewUserRes{User: user}, nil - } -} - -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 - } - client, err := svc.ViewProfile(ctx, session) - if err != nil { - return nil, err - } - - return viewUserRes{User: client}, nil - } -} - -func listUsersEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listUsersReq) - 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 := users.Page{ - Status: req.status, - Offset: req.offset, - Limit: req.limit, - Username: req.userName, - Tag: req.tag, - Metadata: req.metadata, - FirstName: req.firstName, - LastName: req.lastName, - Email: req.email, - Order: req.order, - Dir: req.dir, - Id: req.id, - } - - page, err := svc.ListUsers(ctx, session, pm) - if err != nil { - return nil, err - } - - res := usersPageRes{ - pageRes: pageRes{ - Total: page.Total, - Offset: page.Offset, - Limit: page.Limit, - }, - Users: []viewUserRes{}, - } - for _, user := range page.Users { - res.Users = append(res.Users, viewUserRes{User: user}) - } - - return res, nil - } -} - -func searchUsersEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(searchUsersReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - pm := users.Page{ - Offset: req.Offset, - Limit: req.Limit, - Username: req.Username, - FirstName: req.FirstName, - LastName: req.LastName, - Id: req.Id, - Order: req.Order, - Dir: req.Dir, - } - page, err := svc.SearchUsers(ctx, pm) - if err != nil { - return nil, err - } - - res := usersPageRes{ - pageRes: pageRes{ - Total: page.Total, - Offset: page.Offset, - Limit: page.Limit, - }, - Users: []viewUserRes{}, - } - for _, user := range page.Users { - res.Users = append(res.Users, viewUserRes{User: user}) - } - - return res, nil - } -} - -func listMembersByGroupEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersByObjectReq) - req.objectKind = "groups" - 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.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) - if err != nil { - return nil, err - } - - return buildUsersResponse(page), nil - } -} - -func listMembersByChannelEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersByObjectReq) - // In spiceDB schema, using the same 'group' type for both channels and groups, rather than having a separate type for channels. - req.objectKind = "groups" - 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.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) - if err != nil { - return nil, err - } - - return buildUsersResponse(page), nil - } -} - -func listMembersByThingEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersByObjectReq) - req.objectKind = "things" - 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.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) - if err != nil { - return nil, err - } - - return buildUsersResponse(page), nil - } -} - -func listMembersByDomainEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listMembersByObjectReq) - req.objectKind = "domains" - 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.ListMembers(ctx, session, req.objectKind, req.objectID, req.Page) - if err != nil { - return nil, err - } - - return buildUsersResponse(page), nil - } -} - -func updateEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateUserReq) - 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 - } - - user := users.User{ - ID: req.id, - FirstName: req.FirstName, - LastName: req.LastName, - Metadata: req.Metadata, - } - - user, err := svc.Update(ctx, session, user) - if err != nil { - return nil, err - } - - return updateUserRes{User: user}, nil - } -} - -func updateTagsEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateUserTagsReq) - 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 - } - - user := users.User{ - ID: req.id, - Tags: req.Tags, - } - - user, err := svc.UpdateTags(ctx, session, user) - if err != nil { - return nil, err - } - - return updateUserRes{User: user}, nil - } -} - -func updateEmailEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateEmailReq) - 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 - } - - user, err := svc.UpdateEmail(ctx, session, req.id, req.Email) - if err != nil { - return nil, err - } - - return updateUserRes{User: user}, nil - } -} - -// Password reset request endpoint. -// When successful password reset link is generated. -// Link is generated using MG_TOKEN_RESET_ENDPOINT env. -// and value from Referer header for host. -// {Referer}+{MG_TOKEN_RESET_ENDPOINT}+{token=TOKEN} -// http://magistrala.com/reset-request?token=xxxxxxxxxxx. -// Email with a link is being sent to the user. -// When user clicks on a link it should get the ui with form to -// enter new password, when form is submitted token and new password -// must be sent as PUT request to 'password/reset' passwordResetEndpoint. -func passwordResetRequestEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(passwResetReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - if err := svc.GenerateResetToken(ctx, req.Email, req.Host); err != nil { - return nil, err - } - - return passwResetReqRes{Msg: MailSent}, nil - } -} - -// This is endpoint that actually sets new password in password reset flow. -// When user clicks on a link in email finally ends on this endpoint as explained in -// the comment above. -func passwordResetEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(resetTokenReq) - 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.ResetSecret(ctx, session, req.Password); err != nil { - return nil, err - } - - return passwChangeRes{}, nil - } -} - -func updateSecretEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateUserSecretReq) - 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 - } - user, err := svc.UpdateSecret(ctx, session, req.OldSecret, req.NewSecret) - if err != nil { - return nil, err - } - - return updateUserRes{User: user}, nil - } -} - -func updateUsernameEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateUsernameReq) - 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 - } - - user, err := svc.UpdateUsername(ctx, session, req.id, req.Username) - if err != nil { - return nil, err - } - - return updateUserRes{User: user}, nil - } -} - -func updateProfilePictureEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateProfilePictureReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - user := users.User{ - ID: req.id, - ProfilePicture: req.ProfilePicture, - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - user, err := svc.UpdateProfilePicture(ctx, session, user) - if err != nil { - return nil, err - } - - return updateUserRes{User: user}, nil - } -} - -func updateRoleEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateUserRoleReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - user := users.User{ - ID: req.id, - Role: req.role, - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - user, err := svc.UpdateRole(ctx, session, user) - if err != nil { - return nil, err - } - - return updateUserRes{User: user}, nil - } -} - -func issueTokenEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(loginUserReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - token, err := svc.IssueToken(ctx, req.Identity, req.Secret) - if err != nil { - return nil, err - } - - return tokenRes{ - AccessToken: token.GetAccessToken(), - RefreshToken: token.GetRefreshToken(), - AccessType: token.GetAccessType(), - }, nil - } -} - -func refreshTokenEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(tokenReq) - 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 - } - - token, err := svc.RefreshToken(ctx, session, req.RefreshToken) - if err != nil { - return nil, err - } - - return tokenRes{ - AccessToken: token.GetAccessToken(), - RefreshToken: token.GetRefreshToken(), - AccessType: token.GetAccessType(), - }, nil - } -} - -func enableEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeUserStatusReq) - 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 - } - - user, err := svc.Enable(ctx, session, req.id) - if err != nil { - return nil, err - } - - return changeUserStatusRes{User: user}, nil - } -} - -func disableEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeUserStatusReq) - 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 - } - - user, err := svc.Disable(ctx, session, req.id) - if err != nil { - return nil, err - } - - return changeUserStatusRes{User: user}, nil - } -} - -func deleteEndpoint(svc users.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeUserStatusReq) - 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 deleteUserRes{true}, nil - } -} - -func buildUsersResponse(cp users.MembersPage) usersPageRes { - res := usersPageRes{ - pageRes: pageRes{ - Total: cp.Total, - Offset: cp.Offset, - Limit: cp.Limit, - }, - Users: []viewUserRes{}, - } - - for _, user := range cp.Members { - res.Users = append(res.Users, viewUserRes{User: user}) - } - - return res -} diff --git a/users/api/groups.go b/users/api/groups.go deleted file mode 100644 index 72cb478c6..000000000 --- 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/requests.go b/users/api/requests.go deleted file mode 100644 index 5fb97978d..000000000 --- a/users/api/requests.go +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "net/mail" - "net/url" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/users" -) - -const maxLimitSize = 100 - -type createUserReq struct { - users.User -} - -func (req createUserReq) validate() error { - if len(req.User.FirstName) > api.MaxNameSize { - return apiutil.ErrNameSize - } - if len(req.User.LastName) > api.MaxNameSize { - return apiutil.ErrNameSize - } - if req.User.FirstName == "" { - return apiutil.ErrMissingFirstName - } - if req.User.LastName == "" { - return apiutil.ErrMissingLastName - } - if req.User.Credentials.Username == "" { - return apiutil.ErrMissingUsername - } - // Username must not be a valid email format due to username/email login. - if _, err := mail.ParseAddress(req.User.Credentials.Username); err == nil { - return apiutil.ErrInvalidUsername - } - if req.User.Email == "" { - return apiutil.ErrMissingEmail - } - // Email must be in a valid format. - if _, err := mail.ParseAddress(req.User.Email); err != nil { - return apiutil.ErrInvalidEmail - } - if req.User.Credentials.Secret == "" { - return apiutil.ErrMissingPass - } - if !passRegex.MatchString(req.User.Credentials.Secret) { - return apiutil.ErrPasswordFormat - } - if req.User.Status == users.AllStatus { - return svcerr.ErrInvalidStatus - } - if req.User.ProfilePicture != "" { - if _, err := url.Parse(req.User.ProfilePicture); err != nil { - return apiutil.ErrInvalidProfilePictureURL - } - } - - return req.User.Validate() -} - -type viewUserReq struct { - id string -} - -func (req viewUserReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type listUsersReq struct { - status users.Status - offset uint64 - limit uint64 - userName string - tag string - firstName string - lastName string - email string - metadata users.Metadata - order string - dir string - id string -} - -func (req listUsersReq) validate() error { - if req.limit > maxLimitSize || req.limit < 1 { - return apiutil.ErrLimitSize - } - if req.dir != "" && (req.dir != api.AscDir && req.dir != api.DescDir) { - return apiutil.ErrInvalidDirection - } - - return nil -} - -type searchUsersReq struct { - Offset uint64 - Limit uint64 - Username string - FirstName string - LastName string - Id string - Order string - Dir string -} - -func (req searchUsersReq) validate() error { - if req.Username == "" && req.Id == "" && req.FirstName == "" && req.LastName == "" { - return apiutil.ErrEmptySearchQuery - } - - return nil -} - -type listMembersByObjectReq struct { - users.Page - objectKind string - objectID string -} - -func (req listMembersByObjectReq) validate() error { - if req.objectID == "" { - return apiutil.ErrMissingID - } - if req.objectKind == "" { - return apiutil.ErrMissingMemberKind - } - - return nil -} - -type updateUserReq struct { - id string - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - Metadata users.Metadata `json:"metadata,omitempty"` -} - -func (req updateUserReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateUserTagsReq struct { - id string - Tags []string `json:"tags,omitempty"` -} - -func (req updateUserTagsReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateUserRoleReq struct { - id string - role users.Role - Role string `json:"role,omitempty"` -} - -func (req updateUserRoleReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateEmailReq struct { - id string - Email string `json:"email,omitempty"` -} - -func (req updateEmailReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - if _, err := mail.ParseAddress(req.Email); err != nil { - return apiutil.ErrInvalidEmail - } - - return nil -} - -type updateUserSecretReq struct { - OldSecret string `json:"old_secret,omitempty"` - NewSecret string `json:"new_secret,omitempty"` -} - -func (req updateUserSecretReq) validate() error { - if req.OldSecret == "" || req.NewSecret == "" { - return apiutil.ErrMissingPass - } - if !passRegex.MatchString(req.NewSecret) { - return apiutil.ErrPasswordFormat - } - - return nil -} - -type updateUsernameReq struct { - id string - Username string `json:"username,omitempty"` -} - -func (req updateUsernameReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - if len(req.Username) > api.MaxNameSize { - return apiutil.ErrNameSize - } - if req.Username == "" { - return apiutil.ErrMissingUsername - } - - return nil -} - -type updateProfilePictureReq struct { - id string - ProfilePicture string `json:"profile_picture,omitempty"` -} - -func (req updateProfilePictureReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - if _, err := url.Parse(req.ProfilePicture); err != nil { - return apiutil.ErrInvalidProfilePictureURL - } - return nil -} - -type changeUserStatusReq struct { - id string -} - -func (req changeUserStatusReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type loginUserReq struct { - Identity string `json:"identity,omitempty"` - Secret string `json:"secret,omitempty"` -} - -func (req loginUserReq) validate() error { - if req.Identity == "" { - return apiutil.ErrMissingIdentity - } - if req.Secret == "" { - return apiutil.ErrMissingPass - } - - return nil -} - -type tokenReq struct { - RefreshToken string `json:"refresh_token,omitempty"` -} - -func (req tokenReq) validate() error { - if req.RefreshToken == "" { - return apiutil.ErrBearerToken - } - - return nil -} - -type passwResetReq struct { - Email string `json:"email"` - Host string `json:"host"` -} - -func (req passwResetReq) validate() error { - if req.Email == "" { - return apiutil.ErrMissingEmail - } - if req.Host == "" { - return apiutil.ErrMissingHost - } - - return nil -} - -type resetTokenReq struct { - Token string `json:"token"` - Password string `json:"password"` - ConfPass string `json:"confirm_password"` -} - -func (req resetTokenReq) validate() error { - if req.Password == "" { - return apiutil.ErrMissingPass - } - if req.ConfPass == "" { - return apiutil.ErrMissingConfPass - } - if req.Token == "" { - return apiutil.ErrBearerToken - } - if req.Password != req.ConfPass { - return apiutil.ErrInvalidResetPass - } - if !passRegex.MatchString(req.ConfPass) { - return apiutil.ErrPasswordFormat - } - - return nil -} - -type assignUsersReq struct { - groupID string - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -func (req assignUsersReq) 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 unassignUsersReq struct { - groupID string - Relation string `json:"relation"` - UserIDs []string `json:"user_ids"` -} - -func (req unassignUsersReq) validate() error { - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.UserIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type assignGroupsReq struct { - groupID string - domainID string - GroupIDs []string `json:"group_ids"` -} - -func (req assignGroupsReq) validate() error { - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.GroupIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} - -type unassignGroupsReq struct { - groupID string - domainID string - GroupIDs []string `json:"group_ids"` -} - -func (req unassignGroupsReq) validate() error { - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - - if req.groupID == "" { - return apiutil.ErrMissingID - } - - if len(req.GroupIDs) == 0 { - return apiutil.ErrEmptyList - } - - return nil -} diff --git a/users/api/requests_test.go b/users/api/requests_test.go deleted file mode 100644 index 462ecebe4..000000000 --- a/users/api/requests_test.go +++ /dev/null @@ -1,858 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "net/url" - "strings" - "testing" - - "github.com/absmach/magistrala/internal/api" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/users" - "github.com/stretchr/testify/assert" -) - -const ( - valid = "valid" - invalid = "invalid" - secret = "QJg58*aMan7j" - name = "user" -) - -var ( - validID = testsutil.GenerateUUID(&testing.T{}) - domain = testsutil.GenerateUUID(&testing.T{}) -) - -func TestCreateUserReqValidate(t *testing.T) { - cases := []struct { - desc string - req createUserReq - err error - }{ - { - desc: "valid request", - req: createUserReq{ - User: users.User{ - ID: validID, - FirstName: valid, - LastName: valid, - Email: "example@domain.com", - Credentials: users.Credentials{ - Username: "example", - Secret: secret, - }, - }, - }, - err: nil, - }, - { - desc: "name too long", - req: createUserReq{ - User: users.User{ - ID: validID, - FirstName: strings.Repeat("a", api.MaxNameSize+1), - LastName: valid, - }, - }, - err: apiutil.ErrNameSize, - }, - { - desc: "missing email in request", - req: createUserReq{ - User: users.User{ - ID: validID, - FirstName: valid, - LastName: valid, - Credentials: users.Credentials{ - Username: "example", - Secret: secret, - }, - }, - }, - err: apiutil.ErrMissingEmail, - }, - { - desc: "missing secret in request", - req: createUserReq{ - User: users.User{ - ID: validID, - FirstName: valid, - LastName: valid, - Email: "example@domain.com", - Credentials: users.Credentials{ - Username: "example", - }, - }, - }, - err: apiutil.ErrMissingPass, - }, - { - desc: "invalid secret in request", - req: createUserReq{ - User: users.User{ - ID: validID, - FirstName: valid, - LastName: valid, - Email: "example@domain.com", - Credentials: users.Credentials{ - Username: "example", - Secret: "invalid", - }, - }, - }, - err: apiutil.ErrPasswordFormat, - }, - } - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err) - } -} - -func TestViewUserReqValidate(t *testing.T) { - cases := []struct { - desc string - req viewUserReq - err error - }{ - { - desc: "valid request", - req: viewUserReq{ - id: validID, - }, - err: nil, - }, - { - desc: "empty id", - req: viewUserReq{ - id: "", - }, - err: apiutil.ErrMissingID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestListUsersReqValidate(t *testing.T) { - cases := []struct { - desc string - req listUsersReq - err error - }{ - { - desc: "valid request", - req: listUsersReq{ - limit: 10, - }, - err: nil, - }, - { - desc: "limit too big", - req: listUsersReq{ - limit: api.MaxLimitSize + 1, - }, - err: apiutil.ErrLimitSize, - }, - { - desc: "limit too small", - req: listUsersReq{ - limit: 0, - }, - err: apiutil.ErrLimitSize, - }, - { - desc: "invalid direction", - req: listUsersReq{ - limit: 10, - dir: "invalid", - }, - err: apiutil.ErrInvalidDirection, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestSearchUsersReqValidate(t *testing.T) { - cases := []struct { - desc string - req searchUsersReq - err error - }{ - { - desc: "valid request", - req: searchUsersReq{ - Username: name, - }, - err: nil, - }, - { - desc: "empty query", - req: searchUsersReq{}, - err: apiutil.ErrEmptySearchQuery, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err) - } -} - -func TestListMembersByObjectReqValidate(t *testing.T) { - cases := []struct { - desc string - req listMembersByObjectReq - err error - }{ - { - desc: "valid request", - req: listMembersByObjectReq{ - objectKind: "group", - objectID: validID, - }, - err: nil, - }, - { - desc: "empty object kind", - req: listMembersByObjectReq{ - objectKind: "", - objectID: validID, - }, - err: apiutil.ErrMissingMemberKind, - }, - { - desc: "empty object id", - req: listMembersByObjectReq{ - objectKind: "group", - objectID: "", - }, - err: apiutil.ErrMissingID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err) - } -} - -func TestUpdateUserReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateUserReq - err error - }{ - { - desc: "valid request", - req: updateUserReq{ - id: validID, - }, - err: nil, - }, - { - desc: "empty id", - req: updateUserReq{ - id: "", - }, - err: apiutil.ErrMissingID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestUpdateUserTagsReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateUserTagsReq - err error - }{ - { - desc: "valid request", - req: updateUserTagsReq{ - id: validID, - Tags: []string{"tag1", "tag2"}, - }, - err: nil, - }, - { - desc: "empty id", - req: updateUserTagsReq{ - id: "", - Tags: []string{"tag1", "tag2"}, - }, - err: apiutil.ErrMissingID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestUpdateUsernameReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateUsernameReq - err error - }{ - { - desc: "valid request", - req: updateUsernameReq{ - id: validID, - Username: "validUsername", - }, - err: nil, - }, - { - desc: "missing user ID", - req: updateUsernameReq{ - id: "", - Username: "validUsername", - }, - err: apiutil.ErrMissingID, - }, - { - desc: "name too long", - req: updateUsernameReq{ - id: validID, - Username: strings.Repeat("a", api.MaxNameSize+1), - }, - err: apiutil.ErrNameSize, - }, - } - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err) - } -} - -func TestUpdateProfilePictureReqValidate(t *testing.T) { - base64EncodedString := "https://example.com/profile.jpg" - - parsedURL, err := url.Parse(base64EncodedString) - if err != nil { - t.Fatalf("Error parsing URL: %v", err) - } - cases := []struct { - desc string - req updateProfilePictureReq - err error - }{ - { - desc: "valid request", - req: updateProfilePictureReq{ - id: validID, - ProfilePicture: parsedURL.String(), - }, - err: nil, - }, - { - desc: "empty ID", - req: updateProfilePictureReq{ - id: "", - ProfilePicture: parsedURL.String(), - }, - err: apiutil.ErrMissingID, - }, - } - for _, tc := range cases { - err := tc.req.validate() - assert.Equal(t, tc.err, err, "%s: expected %s got %s\n", tc.desc, tc.err, err) - } -} - -func TestUpdateUserRoleReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateUserRoleReq - err error - }{ - { - desc: "valid request", - req: updateUserRoleReq{ - id: validID, - Role: "admin", - }, - err: nil, - }, - { - desc: "empty id", - req: updateUserRoleReq{ - id: "", - Role: "admin", - }, - err: apiutil.ErrMissingID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestUpdateUserEmailReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateEmailReq - err error - }{ - { - desc: "valid request", - req: updateEmailReq{ - id: validID, - Email: "example@example.com", - }, - err: nil, - }, - { - desc: "empty id", - req: updateEmailReq{ - id: "", - Email: "example@example.com", - }, - err: apiutil.ErrMissingID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestUpdateUserSecretReqValidate(t *testing.T) { - cases := []struct { - desc string - req updateUserSecretReq - err error - }{ - { - desc: "valid request", - req: updateUserSecretReq{ - OldSecret: secret, - NewSecret: secret, - }, - err: nil, - }, - { - desc: "missing old secret", - req: updateUserSecretReq{ - OldSecret: "", - NewSecret: secret, - }, - err: apiutil.ErrMissingPass, - }, - { - desc: "missing new secret", - req: updateUserSecretReq{ - OldSecret: secret, - NewSecret: "", - }, - err: apiutil.ErrMissingPass, - }, - { - desc: "invalid new secret", - req: updateUserSecretReq{ - OldSecret: secret, - NewSecret: "invalid", - }, - err: apiutil.ErrPasswordFormat, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err) - } -} - -func TestChangeUserStatusReqValidate(t *testing.T) { - cases := []struct { - desc string - req changeUserStatusReq - err error - }{ - { - desc: "valid request", - req: changeUserStatusReq{ - id: validID, - }, - err: nil, - }, - { - desc: "empty id", - req: changeUserStatusReq{ - id: "", - }, - err: apiutil.ErrMissingID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestLoginUserReqValidate(t *testing.T) { - cases := []struct { - desc string - req loginUserReq - err error - }{ - { - desc: "valid request with identity", - req: loginUserReq{ - Identity: "example", - Secret: secret, - }, - err: nil, - }, - { - desc: "empty identity", - req: loginUserReq{ - Identity: "", - Secret: secret, - }, - err: apiutil.ErrMissingIdentity, - }, - { - desc: "empty secret", - req: loginUserReq{ - Secret: "", - Identity: "example", - }, - err: apiutil.ErrMissingPass, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestTokenReqValidate(t *testing.T) { - cases := []struct { - desc string - req tokenReq - err error - }{ - { - desc: "valid request", - req: tokenReq{ - RefreshToken: valid, - }, - err: nil, - }, - { - desc: "empty token", - req: tokenReq{ - RefreshToken: "", - }, - err: apiutil.ErrBearerToken, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestPasswResetReqValidate(t *testing.T) { - cases := []struct { - desc string - req passwResetReq - err error - }{ - { - desc: "valid request", - req: passwResetReq{ - Email: "example@example.com", - Host: "example.com", - }, - err: nil, - }, - { - desc: "empty email", - req: passwResetReq{ - Email: "", - Host: "example.com", - }, - err: apiutil.ErrMissingEmail, - }, - { - desc: "empty host", - req: passwResetReq{ - Email: "example@example.com", - Host: "", - }, - err: apiutil.ErrMissingHost, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestResetTokenReqValidate(t *testing.T) { - cases := []struct { - desc string - req resetTokenReq - err error - }{ - { - desc: "valid request", - req: resetTokenReq{ - Token: valid, - Password: secret, - ConfPass: secret, - }, - err: nil, - }, - { - desc: "empty token", - req: resetTokenReq{ - Token: "", - Password: secret, - ConfPass: secret, - }, - err: apiutil.ErrBearerToken, - }, - { - desc: "empty password", - req: resetTokenReq{ - Token: valid, - Password: "", - ConfPass: secret, - }, - err: apiutil.ErrMissingPass, - }, - { - desc: "empty confpass", - req: resetTokenReq{ - Token: valid, - Password: secret, - ConfPass: "", - }, - err: apiutil.ErrMissingConfPass, - }, - { - desc: "mismatching password and confpass", - req: resetTokenReq{ - Token: valid, - Password: "secret", - ConfPass: secret, - }, - err: apiutil.ErrInvalidResetPass, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err) - } -} - -func TestAssignUsersRequestValidate(t *testing.T) { - cases := []struct { - desc string - req assignUsersReq - err error - }{ - { - desc: "valid request", - req: assignUsersReq{ - groupID: validID, - UserIDs: []string{validID}, - Relation: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: assignUsersReq{ - groupID: "", - UserIDs: []string{validID}, - Relation: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty users", - req: assignUsersReq{ - groupID: validID, - UserIDs: []string{}, - Relation: valid, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty relation", - req: assignUsersReq{ - groupID: validID, - UserIDs: []string{validID}, - Relation: "", - }, - err: apiutil.ErrMissingRelation, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestUnassignUsersRequestValidate(t *testing.T) { - cases := []struct { - desc string - req unassignUsersReq - err error - }{ - { - desc: "valid request", - req: unassignUsersReq{ - groupID: validID, - UserIDs: []string{validID}, - Relation: valid, - }, - err: nil, - }, - { - desc: "empty id", - req: unassignUsersReq{ - groupID: "", - UserIDs: []string{validID}, - Relation: valid, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty users", - req: unassignUsersReq{ - groupID: validID, - UserIDs: []string{}, - Relation: valid, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty relation", - req: unassignUsersReq{ - groupID: validID, - UserIDs: []string{validID}, - Relation: "", - }, - err: nil, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestAssignGroupsRequestValidate(t *testing.T) { - cases := []struct { - desc string - req assignGroupsReq - err error - }{ - { - desc: "valid request", - req: assignGroupsReq{ - domainID: domain, - groupID: validID, - GroupIDs: []string{validID}, - }, - err: nil, - }, - { - desc: "empty group id", - req: assignGroupsReq{ - domainID: domain, - groupID: "", - GroupIDs: []string{validID}, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty user group ids", - req: assignGroupsReq{ - domainID: domain, - groupID: validID, - GroupIDs: []string{}, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty domain id", - req: assignGroupsReq{ - domainID: "", - groupID: validID, - GroupIDs: []string{validID}, - }, - err: apiutil.ErrMissingDomainID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} - -func TestUnassignGroupsRequestValidate(t *testing.T) { - cases := []struct { - desc string - req unassignGroupsReq - err error - }{ - { - desc: "valid request", - req: unassignGroupsReq{ - domainID: domain, - groupID: validID, - GroupIDs: []string{validID}, - }, - err: nil, - }, - { - desc: "empty group id", - req: unassignGroupsReq{ - domainID: domain, - groupID: "", - GroupIDs: []string{validID}, - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty user group ids", - req: unassignGroupsReq{ - domainID: domain, - groupID: validID, - GroupIDs: []string{}, - }, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty domain id", - req: unassignGroupsReq{ - domainID: "", - groupID: validID, - GroupIDs: []string{valid}, - }, - err: apiutil.ErrMissingDomainID, - }, - } - for _, c := range cases { - err := c.req.validate() - assert.Equal(t, c.err, err, "%s: expected %s got %s\n", c.desc, c.err, err) - } -} diff --git a/users/api/responses.go b/users/api/responses.go deleted file mode 100644 index 21df78d36..000000000 --- a/users/api/responses.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/users" -) - -// MailSent message response when link is sent. -const MailSent = "Email with reset link is sent" - -var ( - _ magistrala.Response = (*tokenRes)(nil) - _ magistrala.Response = (*viewUserRes)(nil) - _ magistrala.Response = (*createUserRes)(nil) - _ magistrala.Response = (*changeUserStatusRes)(nil) - _ magistrala.Response = (*usersPageRes)(nil) - _ magistrala.Response = (*viewMembersRes)(nil) - _ magistrala.Response = (*passwResetReqRes)(nil) - _ magistrala.Response = (*passwChangeRes)(nil) - _ magistrala.Response = (*assignUsersRes)(nil) - _ magistrala.Response = (*unassignUsersRes)(nil) - _ magistrala.Response = (*updateUserRes)(nil) - _ magistrala.Response = (*tokenRes)(nil) - _ magistrala.Response = (*deleteUserRes)(nil) -) - -type pageRes struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset"` - Total uint64 `json:"total"` -} - -type createUserRes struct { - users.User - created bool -} - -func (res createUserRes) Code() int { - if res.created { - return http.StatusCreated - } - - return http.StatusOK -} - -func (res createUserRes) Headers() map[string]string { - if res.created { - return map[string]string{ - "Location": fmt.Sprintf("/users/%s", res.ID), - } - } - - return map[string]string{} -} - -func (res createUserRes) Empty() bool { - return false -} - -type tokenRes struct { - AccessToken string `json:"access_token,omitempty"` - RefreshToken string `json:"refresh_token,omitempty"` - AccessType string `json:"access_type,omitempty"` -} - -func (res tokenRes) Code() int { - return http.StatusCreated -} - -func (res tokenRes) Headers() map[string]string { - return map[string]string{} -} - -func (res tokenRes) Empty() bool { - return res.AccessToken == "" || res.RefreshToken == "" -} - -type updateUserRes struct { - users.User `json:",inline"` -} - -func (res updateUserRes) Code() int { - return http.StatusOK -} - -func (res updateUserRes) Headers() map[string]string { - return map[string]string{} -} - -func (res updateUserRes) Empty() bool { - return false -} - -type viewUserRes struct { - users.User `json:",inline"` -} - -func (res viewUserRes) Code() int { - return http.StatusOK -} - -func (res viewUserRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewUserRes) Empty() bool { - return false -} - -type usersPageRes struct { - pageRes - Users []viewUserRes `json:"users"` -} - -func (res usersPageRes) Code() int { - return http.StatusOK -} - -func (res usersPageRes) Headers() map[string]string { - return map[string]string{} -} - -func (res usersPageRes) Empty() bool { - return false -} - -type viewMembersRes struct { - users.User `json:",inline"` -} - -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 changeUserStatusRes struct { - users.User `json:",inline"` -} - -func (res changeUserStatusRes) Code() int { - return http.StatusOK -} - -func (res changeUserStatusRes) Headers() map[string]string { - return map[string]string{} -} - -func (res changeUserStatusRes) Empty() bool { - return false -} - -type passwResetReqRes struct { - Msg string `json:"msg"` -} - -func (res passwResetReqRes) Code() int { - return http.StatusCreated -} - -func (res passwResetReqRes) Headers() map[string]string { - return map[string]string{} -} - -func (res passwResetReqRes) Empty() bool { - return false -} - -type passwChangeRes struct{} - -func (res passwChangeRes) Code() int { - return http.StatusCreated -} - -func (res passwChangeRes) Headers() map[string]string { - return map[string]string{} -} - -func (res passwChangeRes) Empty() bool { - return false -} - -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 deleteUserRes struct { - deleted bool -} - -func (res deleteUserRes) Code() int { - if res.deleted { - return http.StatusNoContent - } - - return http.StatusOK -} - -func (res deleteUserRes) Headers() map[string]string { - return map[string]string{} -} - -func (res deleteUserRes) Empty() bool { - return true -} diff --git a/users/api/transport.go b/users/api/transport.go deleted file mode 100644 index e3334b2ab..000000000 --- a/users/api/transport.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "log/slog" - "net/http" - "regexp" - - "github.com/absmach/magistrala" - 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" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// 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) - - mux.Get("/health", magistrala.Health("users", instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} diff --git a/users/api/users.go b/users/api/users.go deleted file mode 100644 index c712034d7..000000000 --- a/users/api/users.go +++ /dev/null @@ -1,736 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "encoding/json" - "log/slog" - "net/http" - "regexp" - "strings" - - "github.com/absmach/magistrala" - mgauth "github.com/absmach/magistrala/auth" - "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/pkg/oauth2" - "github.com/absmach/magistrala/pkg/policies" - "github.com/absmach/magistrala/users" - "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -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 { - passRegex = pr - - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - r.Route("/users", func(r chi.Router) { - switch selfRegister { - case true: - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - registrationEndpoint(svc, selfRegister), - decodeCreateUserReq, - api.EncodeResponse, - opts..., - ), "register_user").ServeHTTP) - default: - r.With(api.AuthenticateMiddleware(authn, false)).Post("/", otelhttp.NewHandler(kithttp.NewServer( - registrationEndpoint(svc, selfRegister), - decodeCreateUserReq, - api.EncodeResponse, - opts..., - ), "register_user").ServeHTTP) - } - - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, false)) - - r.Get("/profile", otelhttp.NewHandler(kithttp.NewServer( - viewProfileEndpoint(svc), - decodeViewProfile, - api.EncodeResponse, - opts..., - ), "view_profile").ServeHTTP) - - r.Get("/{id}", otelhttp.NewHandler(kithttp.NewServer( - viewEndpoint(svc), - decodeViewUser, - api.EncodeResponse, - opts..., - ), "view_user").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - listUsersEndpoint(svc), - decodeListUsers, - api.EncodeResponse, - opts..., - ), "list_users").ServeHTTP) - - r.Get("/search", otelhttp.NewHandler(kithttp.NewServer( - searchUsersEndpoint(svc), - decodeSearchUsers, - api.EncodeResponse, - opts..., - ), "search_users").ServeHTTP) - - r.Patch("/secret", otelhttp.NewHandler(kithttp.NewServer( - updateSecretEndpoint(svc), - decodeUpdateUserSecret, - api.EncodeResponse, - opts..., - ), "update_user_secret").ServeHTTP) - - r.Patch("/{id}", otelhttp.NewHandler(kithttp.NewServer( - updateEndpoint(svc), - decodeUpdateUser, - api.EncodeResponse, - opts..., - ), "update_user").ServeHTTP) - - r.Patch("/{id}/username", otelhttp.NewHandler(kithttp.NewServer( - updateUsernameEndpoint(svc), - decodeUpdateUsername, - api.EncodeResponse, - opts..., - ), "update_username").ServeHTTP) - - r.Patch("/{id}/picture", otelhttp.NewHandler(kithttp.NewServer( - updateProfilePictureEndpoint(svc), - decodeUpdateUserProfilePicture, - api.EncodeResponse, - opts..., - ), "update_profile_picture").ServeHTTP) - - r.Patch("/{id}/tags", otelhttp.NewHandler(kithttp.NewServer( - updateTagsEndpoint(svc), - decodeUpdateUserTags, - api.EncodeResponse, - opts..., - ), "update_user_tags").ServeHTTP) - - r.Patch("/{id}/email", otelhttp.NewHandler(kithttp.NewServer( - updateEmailEndpoint(svc), - decodeUpdateUserEmail, - api.EncodeResponse, - opts..., - ), "update_user_email").ServeHTTP) - - r.Patch("/{id}/role", otelhttp.NewHandler(kithttp.NewServer( - updateRoleEndpoint(svc), - decodeUpdateUserRole, - api.EncodeResponse, - opts..., - ), "update_user_role").ServeHTTP) - - r.Post("/{id}/enable", otelhttp.NewHandler(kithttp.NewServer( - enableEndpoint(svc), - decodeChangeUserStatus, - api.EncodeResponse, - opts..., - ), "enable_user").ServeHTTP) - - r.Post("/{id}/disable", otelhttp.NewHandler(kithttp.NewServer( - disableEndpoint(svc), - decodeChangeUserStatus, - api.EncodeResponse, - opts..., - ), "disable_user").ServeHTTP) - - r.Delete("/{id}", otelhttp.NewHandler(kithttp.NewServer( - deleteEndpoint(svc), - decodeChangeUserStatus, - api.EncodeResponse, - opts..., - ), "delete_user").ServeHTTP) - - r.Post("/tokens/refresh", otelhttp.NewHandler(kithttp.NewServer( - refreshTokenEndpoint(svc), - decodeRefreshToken, - api.EncodeResponse, - opts..., - ), "refresh_token").ServeHTTP) - }) - }) - - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, false)) - r.Put("/password/reset", otelhttp.NewHandler(kithttp.NewServer( - passwordResetEndpoint(svc), - decodePasswordReset, - api.EncodeResponse, - opts..., - ), "password_reset").ServeHTTP) - }) - - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, true)) - - // Ideal location: users service, groups endpoint. - // Reason for placing here : - // SpiceDB provides list of user ids in given user_group_id - // and users service can access spiceDB and get the user list with user_group_id. - // Request to get list of users present in the user_group_id {groupID} - r.Get("/{domainID}/groups/{groupID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByGroupEndpoint(svc), - decodeListMembersByGroup, - api.EncodeResponse, - opts..., - ), "list_users_by_user_group_id").ServeHTTP) - - // Ideal location: things 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. - // Request to get list of users present in the user_group_id {channelID} - r.Get("/{domainID}/channels/{channelID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByChannelEndpoint(svc), - decodeListMembersByChannel, - api.EncodeResponse, - opts..., - ), "list_users_by_channel_id").ServeHTTP) - - r.Get("/{domainID}/things/{thingID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByThingEndpoint(svc), - decodeListMembersByThing, - api.EncodeResponse, - opts..., - ), "list_users_by_thing_id").ServeHTTP) - - r.Get("/{domainID}/users", otelhttp.NewHandler(kithttp.NewServer( - listMembersByDomainEndpoint(svc), - decodeListMembersByDomain, - api.EncodeResponse, - opts..., - ), "list_users_by_domain_id").ServeHTTP) - }) - - r.Post("/users/tokens/issue", otelhttp.NewHandler(kithttp.NewServer( - issueTokenEndpoint(svc), - decodeCredentials, - api.EncodeResponse, - opts..., - ), "issue_token").ServeHTTP) - - r.Post("/password/reset-request", otelhttp.NewHandler(kithttp.NewServer( - passwordResetRequestEndpoint(svc), - decodePasswordResetRequest, - api.EncodeResponse, - opts..., - ), "password_reset_req").ServeHTTP) - - for _, provider := range providers { - r.HandleFunc("/oauth/callback/"+provider.Name(), oauth2CallbackHandler(provider, svc, tokenClient)) - } - - return r -} - -func decodeViewUser(_ context.Context, r *http.Request) (interface{}, error) { - req := viewUserReq{ - id: chi.URLParam(r, "id"), - } - - return req, nil -} - -func decodeViewProfile(_ context.Context, r *http.Request) (interface{}, error) { - return nil, nil -} - -func decodeListUsers(_ context.Context, r *http.Request) (interface{}, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefUserStatus) - 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.UsernameKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - d, err := apiutil.ReadStringQuery(r, api.EmailKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - i, err := apiutil.ReadStringQuery(r, api.FirstNameKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - f, err := apiutil.ReadStringQuery(r, api.LastNameKey, "") - 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) - } - order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DefDir) - 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) - } - - st, err := users.ToStatus(s) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - req := listUsersReq{ - status: st, - offset: o, - limit: l, - metadata: m, - userName: n, - firstName: i, - lastName: f, - tag: t, - order: order, - dir: dir, - id: id, - email: d, - } - - return req, nil -} - -func decodeSearchUsers(_ 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) - } - n, err := apiutil.ReadStringQuery(r, api.UsernameKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - f, err := apiutil.ReadStringQuery(r, api.FirstNameKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - e, err := apiutil.ReadStringQuery(r, api.LastNameKey, "") - 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) - } - order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DefDir) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - req := searchUsersReq{ - Offset: o, - Limit: l, - Username: n, - FirstName: f, - LastName: e, - Id: id, - Order: order, - Dir: dir, - } - - for _, field := range []string{req.Username, req.Id} { - if field != "" && len(field) < 3 { - req = searchUsersReq{} - return req, errors.Wrap(apiutil.ErrLenSearchQuery, apiutil.ErrValidation) - } - } - - return req, nil -} - -func decodeUpdateUser(_ 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 := updateUserReq{ - id: chi.URLParam(r, "id"), - } - 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 decodeUpdateUserTags(_ 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 := updateUserTagsReq{ - id: chi.URLParam(r, "id"), - } - 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 decodeUpdateUserEmail(_ 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 := updateEmailReq{ - id: chi.URLParam(r, "id"), - } - 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 decodeUpdateUserSecret(_ 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 := updateUserSecretReq{} - 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 decodeUpdateUsername(_ 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 := updateUsernameReq{ - id: chi.URLParam(r, "id"), - } - 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 decodeUpdateUserProfilePicture(_ 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 := updateProfilePictureReq{ - id: chi.URLParam(r, "id"), - } - - 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 decodePasswordResetRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, apiutil.ErrUnsupportedContentType - } - - var req passwResetReq - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - - req.Host = r.Header.Get("Referer") - return req, nil -} - -func decodePasswordReset(_ 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 req resetTokenReq - 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 decodeUpdateUserRole(_ 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 := updateUserRoleReq{ - id: chi.URLParam(r, "id"), - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) - } - var err error - req.role, err = users.ToRole(req.Role) - return req, err -} - -func decodeCredentials(_ 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 := loginUserReq{} - 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 decodeRefreshToken(_ 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 := tokenReq{RefreshToken: apiutil.ExtractBearerToken(r)} - - return req, nil -} - -func decodeCreateUserReq(_ 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 req createUserReq - 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 decodeChangeUserStatus(_ context.Context, r *http.Request) (interface{}, error) { - req := changeUserStatusReq{ - id: chi.URLParam(r, "id"), - } - - return req, nil -} - -func decodeListMembersByGroup(_ 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, "groupID"), - } - - return req, nil -} - -func decodeListMembersByChannel(_ 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, "channelID"), - } - - return req, nil -} - -func decodeListMembersByThing(_ 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"), - } - - return req, nil -} - -func decodeListMembersByDomain(_ context.Context, r *http.Request) (interface{}, error) { - page, err := queryPageParams(r, policies.MembershipPermission) - if err != nil { - return nil, err - } - - req := listMembersByObjectReq{ - Page: page, - objectID: chi.URLParam(r, "domainID"), - } - - return req, nil -} - -func queryPageParams(r *http.Request, defPermission string) (users.Page, error) { - s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefClientStatus) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - o, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - m, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - n, err := apiutil.ReadStringQuery(r, api.UsernameKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - f, err := apiutil.ReadStringQuery(r, api.FirstNameKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - a, err := apiutil.ReadStringQuery(r, api.LastNameKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - i, err := apiutil.ReadStringQuery(r, api.EmailKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - t, err := apiutil.ReadStringQuery(r, api.TagKey, "") - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - st, err := users.ToStatus(s) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - p, err := apiutil.ReadStringQuery(r, api.PermissionKey, defPermission) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - lp, err := apiutil.ReadBoolQuery(r, api.ListPerms, api.DefListPerms) - if err != nil { - return users.Page{}, errors.Wrap(apiutil.ErrValidation, err) - } - return users.Page{ - Status: st, - Offset: o, - Limit: l, - Metadata: m, - FirstName: f, - Username: n, - LastName: a, - Email: i, - Tag: t, - Permission: p, - ListPerms: lp, - }, nil -} - -// oauth2CallbackHandler is a http.HandlerFunc that handles OAuth2 callbacks. -func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service, tokenClient magistrala.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) - return - } - state := r.FormValue("state") - if state != oauth.State() { - http.Redirect(w, r, oauth.ErrorURL()+"?error=invalid%20state", http.StatusSeeOther) - return - } - - if code := r.FormValue("code"); code != "" { - token, err := oauth.Exchange(r.Context(), code) - if err != nil { - http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) - return - } - - user, err := oauth.UserInfo(token.AccessToken) - if err != nil { - http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) - return - } - - user, err = svc.OAuthCallback(r.Context(), user) - if err != nil { - http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) - return - } - if err := svc.OAuthAddUserPolicy(r.Context(), user); err != nil { - http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) - return - } - - jwt, err := tokenClient.Issue(r.Context(), &magistrala.IssueReq{ - UserId: user.ID, - Type: uint32(mgauth.AccessKey), - }) - if err != nil { - http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) - return - } - - http.SetCookie(w, &http.Cookie{ - Name: "access_token", - Value: jwt.GetAccessToken(), - Path: "/", - HttpOnly: true, - Secure: true, - }) - http.SetCookie(w, &http.Cookie{ - Name: "refresh_token", - Value: jwt.GetRefreshToken(), - Path: "/", - HttpOnly: true, - Secure: true, - }) - - http.Redirect(w, r, oauth.RedirectURL(), http.StatusFound) - return - } - - http.Redirect(w, r, oauth.ErrorURL()+"?error=empty%20code", http.StatusSeeOther) - } -} diff --git a/users/delete_handler.go b/users/delete_handler.go deleted file mode 100644 index cbe623b68..000000000 --- a/users/delete_handler.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// The DeleteHandler is a cron job that runs periodically to delete users that have been marked as deleted -// for a certain period of time together with the user's policies from the auth service. -// The handler runs in a separate goroutine and checks for users that have been marked as deleted for a certain period of time. -// If the user has been marked as deleted for more than the specified period, -// the handler deletes the user's policies from the auth service and deletes the user from the database. - -package users - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/policies" -) - -const defLimit = uint64(100) - -type handler struct { - users Repository - domains magistrala.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) { - handler := &handler{ - users: users, - domains: domainsClient, - policies: policyService, - checkInterval: defCheckInterval, - deleteAfter: deleteAfter, - logger: logger, - } - - go func() { - ticker := time.NewTicker(handler.checkInterval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - handler.handle(ctx) - } - } - }() -} - -func (h *handler) handle(ctx context.Context) { - pm := Page{Limit: defLimit, Offset: 0, Status: DeletedStatus} - - for { - dbUsers, err := h.users.RetrieveAll(ctx, pm) - if err != nil { - h.logger.Error("failed to retrieve users", slog.Any("error", err)) - break - } - if dbUsers.Total == 0 { - break - } - - for _, u := range dbUsers.Users { - if time.Since(u.UpdatedAt) < h.deleteAfter { - continue - } - - deletedRes, err := h.domains.DeleteUserFromDomains(ctx, &magistrala.DeleteUserReq{ - Id: u.ID, - }) - if err != nil { - h.logger.Error("failed to delete user from domains", slog.Any("error", err)) - continue - } - if !deletedRes.Deleted { - h.logger.Error("failed to delete user from domains", slog.Any("error", svcerr.ErrAuthorization)) - continue - } - - req := policies.Policy{ - Subject: u.ID, - SubjectType: policies.UserType, - } - if err := h.policies.DeletePolicyFilter(ctx, req); err != nil { - h.logger.Error("failed to delete user policies", slog.Any("error", err)) - continue - } - - if err := h.users.Delete(ctx, u.ID); err != nil { - h.logger.Error("failed to delete user", slog.Any("error", err)) - continue - } - - h.logger.Info("user deleted", slog.Group("user", - slog.String("id", u.ID), - slog.String("first_name", u.FirstName), - slog.String("last_name", u.LastName), - )) - } - } -} diff --git a/users/doc.go b/users/doc.go deleted file mode 100644 index 242071154..000000000 --- a/users/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package users contains the domain concept definitions needed to -// support Magistrala users service functionality. -// -// This package defines the core domain concepts and types necessary to -// handle users in the context of a Magistrala users service. It abstracts -// the underlying complexities of user management and provides a structured -// approach to working with users. -package users diff --git a/users/emailer.go b/users/emailer.go deleted file mode 100644 index 9f0c5396c..000000000 --- a/users/emailer.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package users - -// Emailer wrapper around the email. -// -//go:generate mockery --name Emailer --output=./mocks --filename emailer.go --quiet --note "Copyright (c) Abstract Machines" -type Emailer interface { - // SendPasswordReset sends an email to the user with a link to reset the password. - SendPasswordReset(To []string, host, user, token string) error -} diff --git a/users/emailer/doc.go b/users/emailer/doc.go deleted file mode 100644 index 4db3fb1c8..000000000 --- a/users/emailer/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package emailer contains the domain concept definitions needed to support -// Magistrala users email service functionality. -package emailer diff --git a/users/emailer/emailer.go b/users/emailer/emailer.go deleted file mode 100644 index 030a74ab9..000000000 --- a/users/emailer/emailer.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package emailer - -import ( - "fmt" - - "github.com/absmach/magistrala/internal/email" - "github.com/absmach/magistrala/users" -) - -var _ users.Emailer = (*emailer)(nil) - -type emailer struct { - resetURL string - agent *email.Agent -} - -// New creates new emailer utility. -func New(url string, c *email.Config) (users.Emailer, error) { - e, err := email.New(c) - return &emailer{resetURL: url, agent: e}, err -} - -func (e *emailer) SendPasswordReset(to []string, host, user, token string) error { - url := fmt.Sprintf("%s%s?token=%s", host, e.resetURL, token) - return e.agent.Send(to, "", "Password Reset Request", "", user, url, "") -} diff --git a/users/errors.go b/users/errors.go deleted file mode 100644 index 7dc6b0a9a..000000000 --- a/users/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package users - -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/users/events/doc.go b/users/events/doc.go deleted file mode 100644 index 86f9918a2..000000000 --- a/users/events/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package events provides the domain concept definitions needed to -// support Magistrala users service functionality. -package events diff --git a/users/events/events.go b/users/events/events.go deleted file mode 100644 index 844fe77b5..000000000 --- a/users/events/events.go +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "time" - - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/users" -) - -const ( - userPrefix = "user." - userCreate = userPrefix + "create" - userUpdate = userPrefix + "update" - userRemove = userPrefix + "remove" - userView = userPrefix + "view" - profileView = userPrefix + "view_profile" - userList = userPrefix + "list" - userSearch = userPrefix + "search" - userListByGroup = userPrefix + "list_by_group" - userIdentify = userPrefix + "identify" - generateResetToken = userPrefix + "generate_reset_token" - issueToken = userPrefix + "issue_token" - refreshToken = userPrefix + "refresh_token" - resetSecret = userPrefix + "reset_secret" - sendPasswordReset = userPrefix + "send_password_reset" - oauthCallback = userPrefix + "oauth_callback" - addClientPolicy = userPrefix + "add_policy" - deleteUser = userPrefix + "delete" - userUpdateUsername = userPrefix + "update_username" - userUpdateProfilePicture = userPrefix + "update_profile_picture" -) - -var ( - _ events.Event = (*createUserEvent)(nil) - _ events.Event = (*updateUserEvent)(nil) - _ events.Event = (*updateProfilePictureEvent)(nil) - _ events.Event = (*updateUsernameEvent)(nil) - _ events.Event = (*removeUserEvent)(nil) - _ events.Event = (*viewUserEvent)(nil) - _ events.Event = (*viewProfileEvent)(nil) - _ events.Event = (*listUserEvent)(nil) - _ events.Event = (*listUserByGroupEvent)(nil) - _ events.Event = (*searchUserEvent)(nil) - _ events.Event = (*identifyUserEvent)(nil) - _ events.Event = (*generateResetTokenEvent)(nil) - _ events.Event = (*issueTokenEvent)(nil) - _ events.Event = (*refreshTokenEvent)(nil) - _ events.Event = (*resetSecretEvent)(nil) - _ events.Event = (*sendPasswordResetEvent)(nil) - _ events.Event = (*oauthCallbackEvent)(nil) - _ events.Event = (*deleteUserEvent)(nil) - _ events.Event = (*addUserPolicyEvent)(nil) -) - -type createUserEvent struct { - users.User -} - -func (uce createUserEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userCreate, - "id": uce.ID, - "status": uce.Status.String(), - "created_at": uce.CreatedAt, - } - - if uce.FirstName != "" { - val["first_name"] = uce.FirstName - } - if uce.LastName != "" { - val["last_name"] = uce.LastName - } - if len(uce.Tags) > 0 { - val["tags"] = uce.Tags - } - if uce.Metadata != nil { - val["metadata"] = uce.Metadata - } - if uce.Credentials.Username != "" { - val["username"] = uce.Credentials.Username - } - if uce.Email != "" { - val["email"] = uce.Email - } - - return val, nil -} - -type updateUserEvent struct { - users.User - operation string -} - -func (uce updateUserEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userUpdate, - "updated_at": uce.UpdatedAt, - "updated_by": uce.UpdatedBy, - } - if uce.operation != "" { - val["operation"] = userUpdate + "_" + uce.operation - } - - if uce.ID != "" { - val["id"] = uce.ID - } - if uce.FirstName != "" { - val["first_name"] = uce.FirstName - } - if uce.LastName != "" { - val["last_name"] = uce.LastName - } - if len(uce.Tags) > 0 { - val["tags"] = uce.Tags - } - if uce.Credentials.Username != "" { - val["username"] = uce.Credentials.Username - } - if uce.Email != "" { - val["email"] = uce.Email - } - 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 updateUsernameEvent struct { - users.User -} - -func (une updateUsernameEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userUpdateUsername, - "updated_at": une.UpdatedAt, - "updated_by": une.UpdatedBy, - } - - if une.ID != "" { - val["id"] = une.ID - } - if une.FirstName != "" { - val["first_name"] = une.FirstName - } - if une.LastName != "" { - val["last_name"] = une.LastName - } - if une.Credentials.Username != "" { - val["username"] = une.Credentials.Username - } - - return val, nil -} - -type updateProfilePictureEvent struct { - users.User -} - -func (uppe updateProfilePictureEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userUpdateProfilePicture, - "updated_at": uppe.UpdatedAt, - "updated_by": uppe.UpdatedBy, - } - - if uppe.ID != "" { - val["id"] = uppe.ID - } - if uppe.ProfilePicture != "" { - val["profile_picture"] = uppe.ProfilePicture - } - - return val, nil -} - -type removeUserEvent struct { - id string - status string - updatedAt time.Time - updatedBy string -} - -func (rce removeUserEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": userRemove, - "id": rce.id, - "status": rce.status, - "updated_at": rce.updatedAt, - "updated_by": rce.updatedBy, - }, nil -} - -type viewUserEvent struct { - users.User -} - -func (vue viewUserEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userView, - "id": vue.ID, - } - - if vue.LastName != "" { - val["last_name"] = vue.LastName - } - if vue.FirstName != "" { - val["first_name"] = vue.FirstName - } - if len(vue.Tags) > 0 { - val["tags"] = vue.Tags - } - if vue.Email != "" { - val["email"] = vue.Email - } - if vue.Credentials.Username != "" { - val["email"] = vue.Credentials.Username - } - if vue.Metadata != nil { - val["metadata"] = vue.Metadata - } - if !vue.CreatedAt.IsZero() { - val["created_at"] = vue.CreatedAt - } - if !vue.UpdatedAt.IsZero() { - val["updated_at"] = vue.UpdatedAt - } - if vue.UpdatedBy != "" { - val["updated_by"] = vue.UpdatedBy - } - if vue.Status.String() != "" { - val["status"] = vue.Status.String() - } - - return val, nil -} - -type viewProfileEvent struct { - users.User -} - -func (vpe viewProfileEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": profileView, - "id": vpe.ID, - } - - if vpe.FirstName != "" { - val["first_name"] = vpe.FirstName - } - if len(vpe.Tags) > 0 { - val["tags"] = vpe.Tags - } - if vpe.Credentials.Username != "" { - val["username"] = vpe.Credentials.Username - } - if vpe.Metadata != nil { - val["metadata"] = vpe.Metadata - } - if !vpe.CreatedAt.IsZero() { - val["created_at"] = vpe.CreatedAt - } - if !vpe.UpdatedAt.IsZero() { - val["updated_at"] = vpe.UpdatedAt - } - if vpe.UpdatedBy != "" { - val["updated_by"] = vpe.UpdatedBy - } - if vpe.Status.String() != "" { - val["status"] = vpe.Status.String() - } - if vpe.Email != "" { - val["email"] = vpe.Email - } - - return val, nil -} - -type listUserEvent struct { - users.Page -} - -func (lue listUserEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userList, - "total": lue.Total, - "offset": lue.Offset, - "limit": lue.Limit, - } - - if lue.FirstName != "" { - val["first_name"] = lue.FirstName - } - if lue.LastName != "" { - val["last_name"] = lue.LastName - } - if lue.Order != "" { - val["order"] = lue.Order - } - if lue.Dir != "" { - val["dir"] = lue.Dir - } - if lue.Metadata != nil { - val["metadata"] = lue.Metadata - } - if lue.Domain != "" { - val["domain"] = lue.Domain - } - if lue.Tag != "" { - val["tag"] = lue.Tag - } - if lue.Permission != "" { - val["permission"] = lue.Permission - } - if lue.Status.String() != "" { - val["status"] = lue.Status.String() - } - if lue.Username != "" { - val["username"] = lue.Username - } - if lue.Email != "" { - val["email"] = lue.Email - } - - return val, nil -} - -type listUserByGroupEvent struct { - users.Page - objectKind string - objectID string -} - -func (lcge listUserByGroupEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userListByGroup, - "total": lcge.Total, - "offset": lcge.Offset, - "limit": lcge.Limit, - "object_kind": lcge.objectKind, - "object_id": lcge.objectID, - } - - if lcge.Username != "" { - val["username"] = lcge.Username - } - if lcge.Order != "" { - val["order"] = lcge.Order - } - if lcge.Dir != "" { - val["dir"] = lcge.Dir - } - if lcge.Metadata != nil { - val["metadata"] = lcge.Metadata - } - if lcge.Domain != "" { - val["domain"] = lcge.Domain - } - if lcge.Tag != "" { - val["tag"] = lcge.Tag - } - if lcge.Permission != "" { - val["permission"] = lcge.Permission - } - if lcge.Status.String() != "" { - val["status"] = lcge.Status.String() - } - if lcge.FirstName != "" { - val["first_name"] = lcge.FirstName - } - if lcge.LastName != "" { - val["last_name"] = lcge.LastName - } - if lcge.Email != "" { - val["email"] = lcge.Email - } - - return val, nil -} - -type searchUserEvent struct { - users.Page -} - -func (sce searchUserEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": userSearch, - "total": sce.Total, - "offset": sce.Offset, - "limit": sce.Limit, - } - if sce.Username != "" { - val["username"] = sce.Username - } - if sce.FirstName != "" { - val["first_name"] = sce.FirstName - } - if sce.LastName != "" { - val["last_name"] = sce.LastName - } - if sce.Email != "" { - val["email"] = sce.Email - } - if sce.Id != "" { - val["id"] = sce.Id - } - - return val, nil -} - -type identifyUserEvent struct { - userID string -} - -func (ise identifyUserEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": userIdentify, - "id": ise.userID, - }, nil -} - -type generateResetTokenEvent struct { - email string - host string -} - -func (grte generateResetTokenEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": generateResetToken, - "email": grte.email, - "host": grte.host, - }, nil -} - -type issueTokenEvent struct { - username string -} - -func (ite issueTokenEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": issueToken, - "username": ite.username, - }, nil -} - -type refreshTokenEvent struct{} - -func (rte refreshTokenEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": refreshToken, - }, nil -} - -type resetSecretEvent struct{} - -func (rse resetSecretEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": resetSecret, - }, nil -} - -type sendPasswordResetEvent struct { - host string - email string - user string -} - -func (spre sendPasswordResetEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": sendPasswordReset, - "host": spre.host, - "email": spre.email, - "user": spre.user, - }, nil -} - -type oauthCallbackEvent struct { - userID string -} - -func (oce oauthCallbackEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": oauthCallback, - "user_id": oce.userID, - }, nil -} - -type deleteUserEvent struct { - id string -} - -func (dce deleteUserEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": deleteUser, - "id": dce.id, - }, nil -} - -type addUserPolicyEvent struct { - id string - role string -} - -func (acpe addUserPolicyEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "operation": addClientPolicy, - "id": acpe.id, - "role": acpe.role, - }, nil -} diff --git a/users/events/streams.go b/users/events/streams.go deleted file mode 100644 index 0820a0e27..000000000 --- a/users/events/streams.go +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package events - -import ( - "context" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/events" - "github.com/absmach/magistrala/pkg/events/store" - "github.com/absmach/magistrala/users" -) - -const streamID = "magistrala.users" - -var _ users.Service = (*eventStore)(nil) - -type eventStore struct { - events.Publisher - svc users.Service -} - -// NewEventStoreMiddleware returns wrapper around users service that sends -// events to event store. -func NewEventStoreMiddleware(ctx context.Context, svc users.Service, url string) (users.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) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) { - user, err := es.svc.Register(ctx, session, user, selfRegister) - if err != nil { - return user, err - } - - event := createUserEvent{ - user, - } - - if err := es.Publish(ctx, event); err != nil { - return user, err - } - - return user, nil -} - -func (es *eventStore) Update(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - user, err := es.svc.Update(ctx, session, user) - if err != nil { - return user, err - } - - return es.update(ctx, "", user) -} - -func (es *eventStore) UpdateRole(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - user, err := es.svc.UpdateRole(ctx, session, user) - if err != nil { - return user, err - } - - return es.update(ctx, "role", user) -} - -func (es *eventStore) UpdateTags(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - user, err := es.svc.UpdateTags(ctx, session, user) - if err != nil { - return user, err - } - - return es.update(ctx, "tags", user) -} - -func (es *eventStore) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (users.User, error) { - user, err := es.svc.UpdateSecret(ctx, session, oldSecret, newSecret) - if err != nil { - return user, err - } - - return es.update(ctx, "secret", user) -} - -func (es *eventStore) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (users.User, error) { - user, err := es.svc.UpdateUsername(ctx, session, id, username) - if err != nil { - return user, err - } - - event := updateUsernameEvent{ - user, - } - - if err := es.Publish(ctx, event); err != nil { - return user, err - } - - return user, nil -} - -func (es *eventStore) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - user, err := es.svc.UpdateProfilePicture(ctx, session, user) - if err != nil { - return user, err - } - - event := updateProfilePictureEvent{ - user, - } - - if err := es.Publish(ctx, event); err != nil { - return user, err - } - - return user, nil -} - -func (es *eventStore) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (users.User, error) { - user, err := es.svc.UpdateEmail(ctx, session, id, email) - if err != nil { - return user, err - } - - return es.update(ctx, "email", user) -} - -func (es *eventStore) update(ctx context.Context, operation string, user users.User) (users.User, error) { - event := updateUserEvent{ - user, operation, - } - - if err := es.Publish(ctx, event); err != nil { - return user, err - } - - return user, nil -} - -func (es *eventStore) View(ctx context.Context, session authn.Session, id string) (users.User, error) { - user, err := es.svc.View(ctx, session, id) - if err != nil { - return user, err - } - - event := viewUserEvent{ - user, - } - - if err := es.Publish(ctx, event); err != nil { - return user, err - } - - return user, nil -} - -func (es *eventStore) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) { - user, err := es.svc.ViewProfile(ctx, session) - if err != nil { - return user, err - } - - event := viewProfileEvent{ - user, - } - - if err := es.Publish(ctx, event); err != nil { - return user, err - } - - return user, nil -} - -func (es *eventStore) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) { - cp, err := es.svc.ListUsers(ctx, session, pm) - if err != nil { - return cp, err - } - event := listUserEvent{ - pm, - } - - if err := es.Publish(ctx, event); err != nil { - return cp, err - } - - return cp, nil -} - -func (es *eventStore) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) { - cp, err := es.svc.SearchUsers(ctx, pm) - if err != nil { - return cp, err - } - event := searchUserEvent{ - pm, - } - - if err := es.Publish(ctx, event); err != nil { - return cp, err - } - - return cp, nil -} - -func (es *eventStore) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.MembersPage, error) { - mp, err := es.svc.ListMembers(ctx, session, objectKind, objectID, pm) - if err != nil { - return mp, err - } - event := listUserByGroupEvent{ - pm, objectKind, objectID, - } - - 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) (users.User, error) { - user, err := es.svc.Enable(ctx, session, id) - if err != nil { - return user, err - } - - return es.delete(ctx, user) -} - -func (es *eventStore) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) { - user, err := es.svc.Disable(ctx, session, id) - if err != nil { - return user, err - } - - return es.delete(ctx, user) -} - -func (es *eventStore) delete(ctx context.Context, user users.User) (users.User, error) { - event := removeUserEvent{ - id: user.ID, - updatedAt: user.UpdatedAt, - updatedBy: user.UpdatedBy, - status: user.Status.String(), - } - - if err := es.Publish(ctx, event); err != nil { - return user, err - } - - return user, nil -} - -func (es *eventStore) Identify(ctx context.Context, session authn.Session) (string, error) { - userID, err := es.svc.Identify(ctx, session) - if err != nil { - return userID, err - } - - event := identifyUserEvent{ - userID: userID, - } - - if err := es.Publish(ctx, event); err != nil { - return userID, err - } - - return userID, nil -} - -func (es *eventStore) GenerateResetToken(ctx context.Context, email, host string) error { - err := es.svc.GenerateResetToken(ctx, email, host) - if err != nil { - return err - } - - event := generateResetTokenEvent{ - email: email, - host: host, - } - - return es.Publish(ctx, event) -} - -func (es *eventStore) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) { - token, err := es.svc.IssueToken(ctx, username, secret) - if err != nil { - return token, err - } - - event := issueTokenEvent{ - username: username, - } - - if err := es.Publish(ctx, event); err != nil { - return token, err - } - - return token, nil -} - -func (es *eventStore) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) { - token, err := es.svc.RefreshToken(ctx, session, refreshToken) - if err != nil { - return token, err - } - - event := refreshTokenEvent{} - - if err := es.Publish(ctx, event); err != nil { - return token, err - } - - return token, nil -} - -func (es *eventStore) ResetSecret(ctx context.Context, session authn.Session, secret string) error { - if err := es.svc.ResetSecret(ctx, session, secret); err != nil { - return err - } - - event := resetSecretEvent{} - - return es.Publish(ctx, event) -} - -func (es *eventStore) SendPasswordReset(ctx context.Context, host, email, user, token string) error { - if err := es.svc.SendPasswordReset(ctx, host, email, user, token); err != nil { - return err - } - - event := sendPasswordResetEvent{ - host: host, - email: email, - user: user, - } - - return es.Publish(ctx, event) -} - -func (es *eventStore) OAuthCallback(ctx context.Context, user users.User) (users.User, error) { - token, err := es.svc.OAuthCallback(ctx, user) - if err != nil { - return token, err - } - - event := oauthCallbackEvent{ - userID: user.ID, - } - - if err := es.Publish(ctx, event); err != nil { - return token, err - } - - return token, 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 := deleteUserEvent{ - id: id, - } - - return es.Publish(ctx, event) -} - -func (es *eventStore) OAuthAddUserPolicy(ctx context.Context, user users.User) error { - if err := es.svc.OAuthAddUserPolicy(ctx, user); err != nil { - return err - } - - event := addUserPolicyEvent{ - id: user.ID, - role: user.Role.String(), - } - - return es.Publish(ctx, event) -} diff --git a/users/hasher.go b/users/hasher.go deleted file mode 100644 index c8fa2a875..000000000 --- a/users/hasher.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package users - -// Hasher specifies an API for generating hashes of an arbitrary textual -// content. -// -//go:generate mockery --name Hasher --output=./mocks --filename hasher.go --quiet --note "Copyright (c) Abstract Machines" -type Hasher interface { - // Hash generates the hashed string from plain-text. - Hash(string) (string, error) - - // Compare compares plain-text version to the hashed one. An error should - // indicate failed comparison. - Compare(string, string) error -} diff --git a/users/hasher/doc.go b/users/hasher/doc.go deleted file mode 100644 index 98be99226..000000000 --- a/users/hasher/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package hasher contains the domain concept definitions needed to -// support Magistrala users password hasher sub-service functionality. -package hasher diff --git a/users/hasher/hasher.go b/users/hasher/hasher.go deleted file mode 100644 index 698acf703..000000000 --- a/users/hasher/hasher.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package hasher - -import ( - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/users" - "golang.org/x/crypto/bcrypt" -) - -const cost int = 10 - -var ( - errHashPassword = errors.New("generate hash from password failed") - errComparePassword = errors.New("compare hash and password failed") -) - -var _ users.Hasher = (*bcryptHasher)(nil) - -type bcryptHasher struct{} - -// New instantiates a bcrypt-based hasher implementation. -func New() users.Hasher { - return &bcryptHasher{} -} - -func (bh *bcryptHasher) Hash(pwd string) (string, error) { - hash, err := bcrypt.GenerateFromPassword([]byte(pwd), cost) - if err != nil { - return "", errors.Wrap(errHashPassword, err) - } - - return string(hash), nil -} - -func (bh *bcryptHasher) Compare(plain, hashed string) error { - if err := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(plain)); err != nil { - return errors.Wrap(errComparePassword, err) - } - - return nil -} diff --git a/users/middleware/authorization.go b/users/middleware/authorization.go deleted file mode 100644 index 24f796e68..000000000 --- a/users/middleware/authorization.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - - "github.com/absmach/magistrala" - mgauth "github.com/absmach/magistrala/auth" - "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/policies" - "github.com/absmach/magistrala/users" -) - -var _ users.Service = (*authorizationMiddleware)(nil) - -type authorizationMiddleware struct { - svc users.Service - authz mgauthz.Authorization - selfRegister bool -} - -// AuthorizationMiddleware adds authorization to the clients service. -func AuthorizationMiddleware(svc users.Service, authz mgauthz.Authorization, selfRegister bool) users.Service { - return &authorizationMiddleware{ - svc: svc, - authz: authz, - selfRegister: selfRegister, - } -} - -func (am *authorizationMiddleware) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) { - if selfRegister { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - } - - return am.svc.Register(ctx, session, user, selfRegister) -} - -func (am *authorizationMiddleware) View(ctx context.Context, session authn.Session, id string) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.View(ctx, session, id) -} - -func (am *authorizationMiddleware) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) { - return am.svc.ViewProfile(ctx, session) -} - -func (am *authorizationMiddleware) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.ListUsers(ctx, session, pm) -} - -func (am *authorizationMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.MembersPage, error) { - if session.DomainUserID == "" { - return users.MembersPage{}, svcerr.ErrDomainAuthorization - } - if err := am.checkSuperAdmin(ctx, session.UserID); err != nil { - switch objectKind { - case policies.GroupsKind: - if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, mgauth.SwitchToPermission(pm.Permission), policies.GroupType, objectID); err != nil { - return users.MembersPage{}, err - } - case policies.DomainsKind: - 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 { - return users.MembersPage{}, err - } - default: - return users.MembersPage{}, svcerr.ErrAuthorization - } - } - - return am.svc.ListMembers(ctx, session, objectKind, objectID, pm) -} - -func (am *authorizationMiddleware) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) { - return am.svc.SearchUsers(ctx, pm) -} - -func (am *authorizationMiddleware) Update(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.Update(ctx, session, user) -} - -func (am *authorizationMiddleware) UpdateTags(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.UpdateTags(ctx, session, user) -} - -func (am *authorizationMiddleware) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.UpdateEmail(ctx, session, id, email) -} - -func (am *authorizationMiddleware) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.UpdateUsername(ctx, session, id, username) -} - -func (am *authorizationMiddleware) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - return am.svc.UpdateProfilePicture(ctx, session, user) -} - -func (am *authorizationMiddleware) GenerateResetToken(ctx context.Context, email, host string) error { - return am.svc.GenerateResetToken(ctx, email, host) -} - -func (am *authorizationMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (users.User, error) { - return am.svc.UpdateSecret(ctx, session, oldSecret, newSecret) -} - -func (am *authorizationMiddleware) ResetSecret(ctx context.Context, session authn.Session, secret string) error { - return am.svc.ResetSecret(ctx, session, secret) -} - -func (am *authorizationMiddleware) SendPasswordReset(ctx context.Context, host, email, user, token string) error { - return am.svc.SendPasswordReset(ctx, host, email, user, token) -} - -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.authorize(ctx, "", policies.UserType, policies.UsersKind, user.ID, policies.MembershipPermission, policies.PlatformType, policies.MagistralaObject); err != nil { - return users.User{}, err - } - - return am.svc.UpdateRole(ctx, session, user) -} - -func (am *authorizationMiddleware) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.Enable(ctx, session, id) -} - -func (am *authorizationMiddleware) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.Disable(ctx, session, id) -} - -func (am *authorizationMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { - if err := am.checkSuperAdmin(ctx, session.UserID); err == nil { - session.SuperAdmin = true - } - - return am.svc.Delete(ctx, session, id) -} - -func (am *authorizationMiddleware) Identify(ctx context.Context, session authn.Session) (string, error) { - return am.svc.Identify(ctx, session) -} - -func (am *authorizationMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) { - return am.svc.IssueToken(ctx, username, secret) -} - -func (am *authorizationMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) { - return am.svc.RefreshToken(ctx, session, refreshToken) -} - -func (am *authorizationMiddleware) OAuthCallback(ctx context.Context, user users.User) (users.User, error) { - return am.svc.OAuthCallback(ctx, user) -} - -func (am *authorizationMiddleware) OAuthAddUserPolicy(ctx context.Context, user users.User) error { - if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, user.ID, policies.MembershipPermission, policies.PlatformType, policies.MagistralaObject); err == nil { - return nil - } - return am.svc.OAuthAddUserPolicy(ctx, user) -} - -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/users/middleware/doc.go b/users/middleware/doc.go deleted file mode 100644 index ce2aef485..000000000 --- a/users/middleware/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package middleware provides middleware for Magistrala Users service. -package middleware diff --git a/users/middleware/logging.go b/users/middleware/logging.go deleted file mode 100644 index d261b722a..000000000 --- a/users/middleware/logging.go +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/users" -) - -var _ users.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc users.Service -} - -// LoggingMiddleware adds logging facilities to the users service. -func LoggingMiddleware(svc users.Service, logger *slog.Logger) users.Service { - return &loggingMiddleware{logger, svc} -} - -// Register logs the user request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (u users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("username", user.Credentials.Username), - slog.String("first_name", user.FirstName), - slog.String("last_name", user.LastName), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Register user failed", args...) - return - } - args = append(args, slog.String("user_id", u.ID)) - lm.logger.Info("Register user completed successfully", args...) - }(time.Now()) - return lm.svc.Register(ctx, session, user, selfRegister) -} - -// 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - } - if t.AccessType != "" { - args = append(args, slog.String("access_type", t.AccessType)) - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Issue token failed", args...) - return - } - lm.logger.Info("Issue token completed successfully", args...) - }(time.Now()) - return lm.svc.IssueToken(ctx, username, secret) -} - -// 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - } - if t.AccessType != "" { - args = append(args, slog.String("access_type", t.AccessType)) - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Refresh token failed", args...) - return - } - lm.logger.Info("Refresh token completed successfully", args...) - }(time.Now()) - return lm.svc.RefreshToken(ctx, session, refreshToken) -} - -// View logs the view_user request. It logs the user 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 authn.Session, id string) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", id), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View user failed", args...) - return - } - lm.logger.Info("View user completed successfully", args...) - }(time.Now()) - return lm.svc.View(ctx, session, id) -} - -// ViewProfile logs the view_profile request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ViewProfile(ctx context.Context, session authn.Session) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", c.ID), - slog.String("username", c.Credentials.Username), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View profile failed", args...) - return - } - lm.logger.Info("View profile completed successfully", args...) - }(time.Now()) - return lm.svc.ViewProfile(ctx, session) -} - -// ListUsers logs the list_users 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) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (cp users.UsersPage, 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.Any("error", err)) - lm.logger.Warn("List users failed", args...) - return - } - lm.logger.Info("List users completed successfully", args...) - }(time.Now()) - return lm.svc.ListUsers(ctx, session, pm) -} - -// SearchUsers logs the search_users request. It logs the page metadata and the time it took to complete the request. -func (lm *loggingMiddleware) SearchUsers(ctx context.Context, cp users.Page) (mp users.UsersPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("page", - slog.Uint64("limit", cp.Limit), - slog.Uint64("offset", cp.Offset), - slog.Uint64("total", mp.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Search users failed to complete successfully", args...) - return - } - lm.logger.Info("Search users completed successfully", args...) - }(time.Now()) - return lm.svc.SearchUsers(ctx, cp) -} - -// Update logs the update_user request. It logs the user 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 authn.Session, user users.User) (u users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", u.ID), - slog.String("username", u.Credentials.Username), - slog.String("first_name", u.FirstName), - slog.String("last_name", u.LastName), - slog.Any("metadata", u.Metadata), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update user failed", args...) - return - } - lm.logger.Info("Update user completed successfully", args...) - }(time.Now()) - return lm.svc.Update(ctx, session, user) -} - -// UpdateTags logs the update_user_tags request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateTags(ctx context.Context, session authn.Session, user users.User) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", c.ID), - slog.Any("tags", c.Tags), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update user tags failed", args...) - return - } - lm.logger.Info("Update user tags completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateTags(ctx, session, user) -} - -// UpdateEmail logs the update_user_email request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", c.ID), - slog.String("email", c.Email), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update user email failed", args...) - return - } - lm.logger.Info("Update user email completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateEmail(ctx, session, id, email) -} - -// UpdateSecret logs the update_user_secret request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", c.ID), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update user secret failed", args...) - return - } - lm.logger.Info("Update user secret completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateSecret(ctx, session, oldSecret, newSecret) -} - -// UpdateUsername logs the update_usernames request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (u users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", u.ID), - slog.String("username", u.Credentials.Username), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update user names failed", args...) - return - } - lm.logger.Info("Update user names completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateUsername(ctx, session, id, username) -} - -// UpdateProfilePicture logs the update_profile_picture request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (u users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", user.ID), - slog.String("profile_picture", user.ProfilePicture), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update profile picture failed", args...) - return - } - lm.logger.Info("Update profile picture completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateProfilePicture(ctx, session, user) -} - -// GenerateResetToken logs the generate_reset_token request. It logs the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) GenerateResetToken(ctx context.Context, email, host string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("host", host), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Generate reset token failed", args...) - return - } - lm.logger.Info("Generate reset token completed successfully", args...) - }(time.Now()) - return lm.svc.GenerateResetToken(ctx, email, host) -} - -// ResetSecret logs the reset_secret request. It logs the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ResetSecret(ctx context.Context, session authn.Session, secret string) (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("Reset secret failed", args...) - return - } - lm.logger.Info("Reset secret completed successfully", args...) - }(time.Now()) - return lm.svc.ResetSecret(ctx, session, secret) -} - -// SendPasswordReset logs the send_password_reset request. It logs the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) SendPasswordReset(ctx context.Context, host, email, user, token string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("host", host), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Send password reset failed", args...) - return - } - lm.logger.Info("Send password reset completed successfully", args...) - }(time.Now()) - return lm.svc.SendPasswordReset(ctx, host, email, user, token) -} - -// UpdateRole logs the update_user_role request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) UpdateRole(ctx context.Context, session authn.Session, user users.User) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", user.ID), - slog.String("role", user.Role.String()), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update user role failed", args...) - return - } - lm.logger.Info("Update user role completed successfully", args...) - }(time.Now()) - return lm.svc.UpdateRole(ctx, session, user) -} - -// Enable logs the enable_user request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) Enable(ctx context.Context, session authn.Session, id string) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", id), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Enable user failed", args...) - return - } - lm.logger.Info("Enable user completed successfully", args...) - }(time.Now()) - return lm.svc.Enable(ctx, session, id) -} - -// Disable logs the disable_user request. It logs the user id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) Disable(ctx context.Context, session authn.Session, id string) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("user", - slog.String("id", id), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Disable user failed", args...) - return - } - lm.logger.Info("Disable user completed successfully", args...) - }(time.Now()) - return lm.svc.Disable(ctx, session, id) -} - -// ListMembers logs the list_members 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) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, cp users.Page) (mp users.MembersPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("object", - slog.String("kind", objectKind), - slog.String("id", objectID), - ), - slog.Group("page", - slog.Uint64("limit", cp.Limit), - slog.Uint64("offset", cp.Offset), - slog.Uint64("total", mp.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List members failed", args...) - return - } - lm.logger.Info("List members completed successfully", args...) - }(time.Now()) - return lm.svc.ListMembers(ctx, session, objectKind, objectID, cp) -} - -// Identify logs the identify request. It logs the time it took to complete the request. -func (lm *loggingMiddleware) Identify(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("user_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Identify user failed", args...) - return - } - lm.logger.Info("Identify user completed successfully", args...) - }(time.Now()) - return lm.svc.Identify(ctx, session) -} - -func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, user users.User) (c users.User, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("user_id", user.ID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("OAuth callback failed", args...) - return - } - lm.logger.Info("OAuth callback completed successfully", args...) - }(time.Now()) - return lm.svc.OAuthCallback(ctx, user) -} - -// Delete logs the delete_user request. It logs the user id and token and the time it took to complete the request. -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("user_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Delete user failed to complete successfully", args...) - return - } - lm.logger.Info("Delete user completed successfully", args...) - }(time.Now()) - return lm.svc.Delete(ctx, session, id) -} - -// OAuthAddUserPolicy logs the add_user_policy request. It logs the user id and the time it took to complete the request. -func (lm *loggingMiddleware) OAuthAddUserPolicy(ctx context.Context, user users.User) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("user_id", user.ID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Add user policy failed", args...) - return - } - lm.logger.Info("Add user policy completed successfully", args...) - }(time.Now()) - return lm.svc.OAuthAddUserPolicy(ctx, user) -} diff --git a/users/middleware/metrics.go b/users/middleware/metrics.go deleted file mode 100644 index ab6321ac9..000000000 --- a/users/middleware/metrics.go +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/users" - "github.com/go-kit/kit/metrics" -) - -var _ users.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc users.Service -} - -// MetricsMiddleware instruments policies service by tracking request count and latency. -func MetricsMiddleware(svc users.Service, counter metrics.Counter, latency metrics.Histogram) users.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -// Register instruments Register method with metrics. -func (ms *metricsMiddleware) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "register_user").Add(1) - ms.latency.With("method", "register_user").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Register(ctx, session, user, selfRegister) -} - -// IssueToken instruments IssueToken method with metrics. -func (ms *metricsMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) { - defer func(begin time.Time) { - ms.counter.With("method", "issue_token").Add(1) - ms.latency.With("method", "issue_token").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.IssueToken(ctx, username, secret) -} - -// RefreshToken instruments RefreshToken method with metrics. -func (ms *metricsMiddleware) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (token *magistrala.Token, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "refresh_token").Add(1) - ms.latency.With("method", "refresh_token").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.RefreshToken(ctx, session, refreshToken) -} - -// View instruments View method with metrics. -func (ms *metricsMiddleware) View(ctx context.Context, session authn.Session, id string) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_user").Add(1) - ms.latency.With("method", "view_user").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.View(ctx, session, id) -} - -// ViewProfile instruments ViewProfile method with metrics. -func (ms *metricsMiddleware) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_profile").Add(1) - ms.latency.With("method", "view_profile").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ViewProfile(ctx, session) -} - -// ListUsers instruments ListUsers method with metrics. -func (ms *metricsMiddleware) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_users").Add(1) - ms.latency.With("method", "list_users").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListUsers(ctx, session, pm) -} - -// SearchUsers instruments SearchUsers method with metrics. -func (ms *metricsMiddleware) SearchUsers(ctx context.Context, pm users.Page) (mp users.UsersPage, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "search_users").Add(1) - ms.latency.With("method", "search_users").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.SearchUsers(ctx, pm) -} - -// Update instruments Update method with metrics. -func (ms *metricsMiddleware) Update(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_user").Add(1) - ms.latency.With("method", "update_user").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Update(ctx, session, user) -} - -// UpdateTags instruments UpdateTags method with metrics. -func (ms *metricsMiddleware) UpdateTags(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_user_tags").Add(1) - ms.latency.With("method", "update_user_tags").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateTags(ctx, session, user) -} - -// UpdateEmail instruments UpdateEmail method with metrics. -func (ms *metricsMiddleware) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_user_email").Add(1) - ms.latency.With("method", "update_user_email").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateEmail(ctx, session, id, email) -} - -// UpdateSecret instruments UpdateSecret method with metrics. -func (ms *metricsMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_user_secret").Add(1) - ms.latency.With("method", "update_user_secret").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateSecret(ctx, session, oldSecret, newSecret) -} - -// UpdateUsername instruments UpdateUsername method with metrics. -func (ms *metricsMiddleware) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_usernames").Add(1) - ms.latency.With("method", "update_usernames").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateUsername(ctx, session, id, username) -} - -// UpdateProfilePicture instruments UpdateProfilePicture method with metrics. -func (ms *metricsMiddleware) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_profile_picture").Add(1) - ms.latency.With("method", "update_profile_picture").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateProfilePicture(ctx, session, user) -} - -// GenerateResetToken instruments GenerateResetToken method with metrics. -func (ms *metricsMiddleware) GenerateResetToken(ctx context.Context, email, host string) error { - defer func(begin time.Time) { - ms.counter.With("method", "generate_reset_token").Add(1) - ms.latency.With("method", "generate_reset_token").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.GenerateResetToken(ctx, email, host) -} - -// ResetSecret instruments ResetSecret method with metrics. -func (ms *metricsMiddleware) ResetSecret(ctx context.Context, session authn.Session, secret string) error { - defer func(begin time.Time) { - ms.counter.With("method", "reset_secret").Add(1) - ms.latency.With("method", "reset_secret").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ResetSecret(ctx, session, secret) -} - -// SendPasswordReset instruments SendPasswordReset method with metrics. -func (ms *metricsMiddleware) SendPasswordReset(ctx context.Context, host, email, user, token string) error { - defer func(begin time.Time) { - ms.counter.With("method", "send_password_reset").Add(1) - ms.latency.With("method", "send_password_reset").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.SendPasswordReset(ctx, host, email, user, token) -} - -// UpdateRole instruments UpdateRole method with metrics. -func (ms *metricsMiddleware) UpdateRole(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "update_user_role").Add(1) - ms.latency.With("method", "update_user_role").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.UpdateRole(ctx, session, user) -} - -// Enable instruments Enable method with metrics. -func (ms *metricsMiddleware) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "enable_user").Add(1) - ms.latency.With("method", "enable_user").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Enable(ctx, session, id) -} - -// Disable instruments Disable method with metrics. -func (ms *metricsMiddleware) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "disable_user").Add(1) - ms.latency.With("method", "disable_user").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Disable(ctx, session, id) -} - -// ListMembers instruments ListMembers method with metrics. -func (ms *metricsMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (mp users.MembersPage, err error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_members").Add(1) - ms.latency.With("method", "list_members").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.ListMembers(ctx, session, objectKind, objectID, pm) -} - -// Identify instruments Identify method with metrics. -func (ms *metricsMiddleware) Identify(ctx context.Context, session authn.Session) (string, error) { - defer func(begin time.Time) { - ms.counter.With("method", "identify").Add(1) - ms.latency.With("method", "identify").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Identify(ctx, session) -} - -// OAuthCallback instruments OAuthCallback method with metrics. -func (ms *metricsMiddleware) OAuthCallback(ctx context.Context, user users.User) (users.User, error) { - defer func(begin time.Time) { - ms.counter.With("method", "oauth_callback").Add(1) - ms.latency.With("method", "oauth_callback").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.OAuthCallback(ctx, user) -} - -// Delete instruments Delete method with metrics. -func (ms *metricsMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { - defer func(begin time.Time) { - ms.counter.With("method", "delete_user").Add(1) - ms.latency.With("method", "delete_user").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.Delete(ctx, session, id) -} - -// OAuthAddUserPolicy instruments OAuthAddUserPolicy method with metrics. -func (ms *metricsMiddleware) OAuthAddUserPolicy(ctx context.Context, user users.User) error { - defer func(begin time.Time) { - ms.counter.With("method", "add_user_policy").Add(1) - ms.latency.With("method", "add_user_policy").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return ms.svc.OAuthAddUserPolicy(ctx, user) -} diff --git a/users/mocks/doc.go b/users/mocks/doc.go deleted file mode 100644 index 16ed198af..000000000 --- a/users/mocks/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package mocks contains mocks for testing purposes. -package mocks diff --git a/users/mocks/emailer.go b/users/mocks/emailer.go deleted file mode 100644 index 77e226a62..000000000 --- a/users/mocks/emailer.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Emailer is an autogenerated mock type for the Emailer type -type Emailer struct { - mock.Mock -} - -// SendPasswordReset provides a mock function with given fields: To, host, user, token -func (_m *Emailer) SendPasswordReset(To []string, host string, user string, token string) error { - ret := _m.Called(To, host, user, token) - - if len(ret) == 0 { - panic("no return value specified for SendPasswordReset") - } - - var r0 error - if rf, ok := ret.Get(0).(func([]string, string, string, string) error); ok { - r0 = rf(To, host, user, token) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewEmailer creates a new instance of Emailer. 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 NewEmailer(t interface { - mock.TestingT - Cleanup(func()) -}) *Emailer { - mock := &Emailer{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/users/mocks/hasher.go b/users/mocks/hasher.go deleted file mode 100644 index 4c4425b25..000000000 --- a/users/mocks/hasher.go +++ /dev/null @@ -1,72 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Hasher is an autogenerated mock type for the Hasher type -type Hasher struct { - mock.Mock -} - -// Compare provides a mock function with given fields: _a0, _a1 -func (_m *Hasher) Compare(_a0 string, _a1 string) error { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for Compare") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Hash provides a mock function with given fields: _a0 -func (_m *Hasher) Hash(_a0 string) (string, error) { - ret := _m.Called(_a0) - - if len(ret) == 0 { - panic("no return value specified for Hash") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(string) (string, error)); ok { - return rf(_a0) - } - if rf, ok := ret.Get(0).(func(string) string); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(_a0) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewHasher creates a new instance of Hasher. 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 NewHasher(t interface { - mock.TestingT - Cleanup(func()) -}) *Hasher { - mock := &Hasher{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/users/mocks/repository.go b/users/mocks/repository.go deleted file mode 100644 index 739c96caa..000000000 --- a/users/mocks/repository.go +++ /dev/null @@ -1,375 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - users "github.com/absmach/magistrala/users" - 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, user -func (_m *Repository) ChangeStatus(ctx context.Context, user users.User) (users.User, error) { - ret := _m.Called(ctx, user) - - if len(ret) == 0 { - panic("no return value specified for ChangeStatus") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok { - return rf(ctx, user) - } - if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok { - r0 = rf(ctx, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok { - r1 = rf(ctx, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CheckSuperAdmin provides a mock function with given fields: ctx, adminID -func (_m *Repository) CheckSuperAdmin(ctx context.Context, adminID string) error { - ret := _m.Called(ctx, adminID) - - if len(ret) == 0 { - panic("no return value specified for CheckSuperAdmin") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, adminID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// 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 users.Page) (users.UsersPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAll") - } - - var r0 users.UsersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.Page) (users.UsersPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, users.Page) users.UsersPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(users.UsersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.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 users.Page) (users.UsersPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAllByIDs") - } - - var r0 users.UsersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.Page) (users.UsersPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, users.Page) users.UsersPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(users.UsersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.Page) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveByEmail provides a mock function with given fields: ctx, email -func (_m *Repository) RetrieveByEmail(ctx context.Context, email string) (users.User, error) { - ret := _m.Called(ctx, email) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByEmail") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (users.User, error)); ok { - return rf(ctx, email) - } - if rf, ok := ret.Get(0).(func(context.Context, string) users.User); ok { - r0 = rf(ctx, email) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, email) - } 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) (users.User, error) { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByID") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (users.User, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string) users.User); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveByUsername provides a mock function with given fields: ctx, username -func (_m *Repository) RetrieveByUsername(ctx context.Context, username string) (users.User, error) { - ret := _m.Called(ctx, username) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByUsername") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (users.User, error)); ok { - return rf(ctx, username) - } - if rf, ok := ret.Get(0).(func(context.Context, string) users.User); ok { - r0 = rf(ctx, username) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, username) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, user -func (_m *Repository) Save(ctx context.Context, user users.User) (users.User, error) { - ret := _m.Called(ctx, user) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok { - return rf(ctx, user) - } - if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok { - r0 = rf(ctx, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok { - r1 = rf(ctx, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// SearchUsers provides a mock function with given fields: ctx, pm -func (_m *Repository) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for SearchUsers") - } - - var r0 users.UsersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.Page) (users.UsersPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, users.Page) users.UsersPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(users.UsersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.Page) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Update provides a mock function with given fields: ctx, user -func (_m *Repository) Update(ctx context.Context, user users.User) (users.User, error) { - ret := _m.Called(ctx, user) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok { - return rf(ctx, user) - } - if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok { - r0 = rf(ctx, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok { - r1 = rf(ctx, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateSecret provides a mock function with given fields: ctx, user -func (_m *Repository) UpdateSecret(ctx context.Context, user users.User) (users.User, error) { - ret := _m.Called(ctx, user) - - if len(ret) == 0 { - panic("no return value specified for UpdateSecret") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok { - return rf(ctx, user) - } - if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok { - r0 = rf(ctx, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok { - r1 = rf(ctx, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateUsername provides a mock function with given fields: ctx, user -func (_m *Repository) UpdateUsername(ctx context.Context, user users.User) (users.User, error) { - ret := _m.Called(ctx, user) - - if len(ret) == 0 { - panic("no return value specified for UpdateUsername") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok { - return rf(ctx, user) - } - if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok { - r0 = rf(ctx, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok { - r1 = rf(ctx, user) - } 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/users/mocks/service.go b/users/mocks/service.go deleted file mode 100644 index 83dfe9e68..000000000 --- a/users/mocks/service.go +++ /dev/null @@ -1,662 +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" - - magistrala "github.com/absmach/magistrala" - - mock "github.com/stretchr/testify/mock" - - users "github.com/absmach/magistrala/users" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// 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) (users.User, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for Disable") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (users.User, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) users.User); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(users.User) - } - - 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) (users.User, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for Enable") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (users.User, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) users.User); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(users.User) - } - - 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 -} - -// GenerateResetToken provides a mock function with given fields: ctx, email, host -func (_m *Service) GenerateResetToken(ctx context.Context, email string, host string) error { - ret := _m.Called(ctx, email, host) - - if len(ret) == 0 { - panic("no return value specified for GenerateResetToken") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, email, host) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Identify provides a mock function with given fields: ctx, session -func (_m *Service) Identify(ctx context.Context, session authn.Session) (string, error) { - ret := _m.Called(ctx, session) - - 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, 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 { - 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 -} - -// 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) { - ret := _m.Called(ctx, identity, secret) - - if len(ret) == 0 { - panic("no return value specified for IssueToken") - } - - var r0 *magistrala.Token - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (*magistrala.Token, error)); ok { - return rf(ctx, identity, secret) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) *magistrala.Token); ok { - r0 = rf(ctx, identity, secret) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.Token) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, identity, secret) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListMembers provides a mock function with given fields: ctx, session, objectKind, objectID, pm -func (_m *Service) ListMembers(ctx context.Context, session authn.Session, objectKind string, objectID string, pm users.Page) (users.MembersPage, error) { - ret := _m.Called(ctx, session, objectKind, objectID, pm) - - if len(ret) == 0 { - panic("no return value specified for ListMembers") - } - - var r0 users.MembersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, users.Page) (users.MembersPage, error)); ok { - return rf(ctx, session, objectKind, objectID, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, users.Page) users.MembersPage); ok { - r0 = rf(ctx, session, objectKind, objectID, pm) - } else { - r0 = ret.Get(0).(users.MembersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string, users.Page) error); ok { - r1 = rf(ctx, session, objectKind, objectID, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListUsers provides a mock function with given fields: ctx, session, pm -func (_m *Service) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) { - ret := _m.Called(ctx, session, pm) - - if len(ret) == 0 { - panic("no return value specified for ListUsers") - } - - var r0 users.UsersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.Page) (users.UsersPage, error)); ok { - return rf(ctx, session, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.Page) users.UsersPage); ok { - r0 = rf(ctx, session, pm) - } else { - r0 = ret.Get(0).(users.UsersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.Page) error); ok { - r1 = rf(ctx, session, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// OAuthAddUserPolicy provides a mock function with given fields: ctx, user -func (_m *Service) OAuthAddUserPolicy(ctx context.Context, user users.User) error { - ret := _m.Called(ctx, user) - - if len(ret) == 0 { - panic("no return value specified for OAuthAddUserPolicy") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, users.User) error); ok { - r0 = rf(ctx, user) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// OAuthCallback provides a mock function with given fields: ctx, user -func (_m *Service) OAuthCallback(ctx context.Context, user users.User) (users.User, error) { - ret := _m.Called(ctx, user) - - if len(ret) == 0 { - panic("no return value specified for OAuthCallback") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.User) (users.User, error)); ok { - return rf(ctx, user) - } - if rf, ok := ret.Get(0).(func(context.Context, users.User) users.User); ok { - r0 = rf(ctx, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.User) error); ok { - r1 = rf(ctx, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// 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) { - ret := _m.Called(ctx, session, refreshToken) - - if len(ret) == 0 { - panic("no return value specified for RefreshToken") - } - - var r0 *magistrala.Token - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (*magistrala.Token, error)); ok { - return rf(ctx, session, refreshToken) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) *magistrala.Token); ok { - r0 = rf(ctx, session, refreshToken) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*magistrala.Token) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok { - r1 = rf(ctx, session, refreshToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Register provides a mock function with given fields: ctx, session, user, selfRegister -func (_m *Service) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) { - ret := _m.Called(ctx, session, user, selfRegister) - - if len(ret) == 0 { - panic("no return value specified for Register") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User, bool) (users.User, error)); ok { - return rf(ctx, session, user, selfRegister) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User, bool) users.User); ok { - r0 = rf(ctx, session, user, selfRegister) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User, bool) error); ok { - r1 = rf(ctx, session, user, selfRegister) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ResetSecret provides a mock function with given fields: ctx, session, secret -func (_m *Service) ResetSecret(ctx context.Context, session authn.Session, secret string) error { - ret := _m.Called(ctx, session, secret) - - if len(ret) == 0 { - panic("no return value specified for ResetSecret") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok { - r0 = rf(ctx, session, secret) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SearchUsers provides a mock function with given fields: ctx, pm -func (_m *Service) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for SearchUsers") - } - - var r0 users.UsersPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, users.Page) (users.UsersPage, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, users.Page) users.UsersPage); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(users.UsersPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, users.Page) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// SendPasswordReset provides a mock function with given fields: ctx, host, email, user, token -func (_m *Service) SendPasswordReset(ctx context.Context, host string, email string, user string, token string) error { - ret := _m.Called(ctx, host, email, user, token) - - if len(ret) == 0 { - panic("no return value specified for SendPasswordReset") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { - r0 = rf(ctx, host, email, user, token) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Update provides a mock function with given fields: ctx, session, user -func (_m *Service) Update(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - ret := _m.Called(ctx, session, user) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) (users.User, error)); ok { - return rf(ctx, session, user) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) users.User); ok { - r0 = rf(ctx, session, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User) error); ok { - r1 = rf(ctx, session, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateEmail provides a mock function with given fields: ctx, session, id, email -func (_m *Service) UpdateEmail(ctx context.Context, session authn.Session, id string, email string) (users.User, error) { - ret := _m.Called(ctx, session, id, email) - - if len(ret) == 0 { - panic("no return value specified for UpdateEmail") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (users.User, error)); ok { - return rf(ctx, session, id, email) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) users.User); ok { - r0 = rf(ctx, session, id, email) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { - r1 = rf(ctx, session, id, email) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateProfilePicture provides a mock function with given fields: ctx, session, user -func (_m *Service) UpdateProfilePicture(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - ret := _m.Called(ctx, session, user) - - if len(ret) == 0 { - panic("no return value specified for UpdateProfilePicture") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) (users.User, error)); ok { - return rf(ctx, session, user) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) users.User); ok { - r0 = rf(ctx, session, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User) error); ok { - r1 = rf(ctx, session, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateRole provides a mock function with given fields: ctx, session, user -func (_m *Service) UpdateRole(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - ret := _m.Called(ctx, session, user) - - if len(ret) == 0 { - panic("no return value specified for UpdateRole") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) (users.User, error)); ok { - return rf(ctx, session, user) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) users.User); ok { - r0 = rf(ctx, session, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User) error); ok { - r1 = rf(ctx, session, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateSecret provides a mock function with given fields: ctx, session, oldSecret, newSecret -func (_m *Service) UpdateSecret(ctx context.Context, session authn.Session, oldSecret string, newSecret string) (users.User, error) { - ret := _m.Called(ctx, session, oldSecret, newSecret) - - if len(ret) == 0 { - panic("no return value specified for UpdateSecret") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (users.User, error)); ok { - return rf(ctx, session, oldSecret, newSecret) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) users.User); ok { - r0 = rf(ctx, session, oldSecret, newSecret) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { - r1 = rf(ctx, session, oldSecret, newSecret) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateTags provides a mock function with given fields: ctx, session, user -func (_m *Service) UpdateTags(ctx context.Context, session authn.Session, user users.User) (users.User, error) { - ret := _m.Called(ctx, session, user) - - if len(ret) == 0 { - panic("no return value specified for UpdateTags") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) (users.User, error)); ok { - return rf(ctx, session, user) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, users.User) users.User); ok { - r0 = rf(ctx, session, user) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, users.User) error); ok { - r1 = rf(ctx, session, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateUsername provides a mock function with given fields: ctx, session, id, username -func (_m *Service) UpdateUsername(ctx context.Context, session authn.Session, id string, username string) (users.User, error) { - ret := _m.Called(ctx, session, id, username) - - if len(ret) == 0 { - panic("no return value specified for UpdateUsername") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (users.User, error)); ok { - return rf(ctx, session, id, username) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) users.User); ok { - r0 = rf(ctx, session, id, username) - } else { - r0 = ret.Get(0).(users.User) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok { - r1 = rf(ctx, session, id, username) - } 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) (users.User, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for View") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (users.User, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) users.User); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(users.User) - } - - 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 -} - -// ViewProfile provides a mock function with given fields: ctx, session -func (_m *Service) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) { - ret := _m.Called(ctx, session) - - if len(ret) == 0 { - panic("no return value specified for ViewProfile") - } - - var r0 users.User - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session) (users.User, error)); ok { - return rf(ctx, session) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session) users.User); ok { - r0 = rf(ctx, session) - } else { - r0 = ret.Get(0).(users.User) - } - - 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 -} - -// 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/users/postgres/doc.go b/users/postgres/doc.go deleted file mode 100644 index b4f616d7d..000000000 --- a/users/postgres/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres contains the database implementation of users repository layer. -package postgres diff --git a/users/postgres/init.go b/users/postgres/init.go deleted file mode 100644 index 99e5c3801..000000000 --- a/users/postgres/init.go +++ /dev/null @@ -1,91 +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" -) - -// Migration of Users service. -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 - // Role 0 to imply user role and 1 to imply admin role - Up: []string{ - `CREATE TABLE IF NOT EXISTS clients ( - id VARCHAR(36) PRIMARY KEY, - name VARCHAR(254) NOT NULL UNIQUE, - domain_id VARCHAR(36), - identity VARCHAR(254) NOT NULL UNIQUE, - secret TEXT 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), - role SMALLINT DEFAULT 0 CHECK (status >= 0) - )`, - }, - Down: []string{ - `DROP TABLE IF EXISTS clients`, - }, - }, - { - // To support creation of clients from Oauth2 provider - Id: "clients_02", - Up: []string{ - `ALTER TABLE clients ALTER COLUMN secret DROP NOT NULL`, - }, - Down: []string{}, - }, - { - Id: "clients_03", - Up: []string{ - `ALTER TABLE clients - ADD COLUMN username VARCHAR(254) UNIQUE, - ADD COLUMN first_name VARCHAR(254) NOT NULL DEFAULT '', - ADD COLUMN last_name VARCHAR(254) NOT NULL DEFAULT '', - ADD COLUMN profile_picture TEXT`, - `ALTER TABLE clients RENAME COLUMN identity TO email`, - `ALTER TABLE clients DROP COLUMN name`, - }, - Down: []string{ - `ALTER TABLE clients - DROP COLUMN username, - DROP COLUMN first_name, - DROP COLUMN last_name, - DROP COLUMN profile_picture`, - `ALTER TABLE clients RENAME COLUMN email TO identity`, - `ALTER TABLE clients ADD COLUMN name VARCHAR(254) NOT NULL UNIQUE`, - }, - }, - { - Id: "clients_04", - Up: []string{ - `ALTER TABLE IF EXISTS clients RENAME TO users`, - }, - Down: []string{ - `ALTER TABLE IF EXISTS users RENAME TO clients`, - }, - }, - { - Id: "clients_05", - Up: []string{ - `ALTER TABLE users ALTER COLUMN first_name DROP DEFAULT`, - `ALTER TABLE users ALTER COLUMN last_name DROP DEFAULT`, - }, - Down: []string{ - `ALTER TABLE users ALTER COLUMN first_name SET DEFAULT ''`, - `ALTER TABLE users ALTER COLUMN last_name SET DEFAULT ''`, - }, - }, - }, - } -} diff --git a/users/postgres/setup_test.go b/users/postgres/setup_test.go deleted file mode 100644 index a8cd27f56..000000000 --- a/users/postgres/setup_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "database/sql" - "fmt" - "log" - "os" - "testing" - "time" - - pgclient "github.com/absmach/magistrala/pkg/postgres" - upostgres "github.com/absmach/magistrala/users/postgres" - "github.com/jmoiron/sqlx" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "go.opentelemetry.io/otel" -) - -var ( - db *sqlx.DB - database pgclient.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") - - // exponential backoff-retry, because the application in the container might not be ready to accept connections yet - pool.MaxWait = 120 * time.Second - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err := sql.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := pgclient.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = pgclient.Setup(dbConfig, *upostgres.Migration()); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - database = pgclient.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/users/postgres/users.go b/users/postgres/users.go deleted file mode 100644 index f2daae364..000000000 --- a/users/postgres/users.go +++ /dev/null @@ -1,678 +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/users" - "github.com/jackc/pgtype" -) - -type userRepo struct { - Repository users.UserRepository -} - -func NewRepository(db postgres.Database) users.Repository { - return &userRepo{ - Repository: users.UserRepository{DB: db}, - } -} - -func (repo *userRepo) Save(ctx context.Context, c users.User) (users.User, error) { - q := `INSERT INTO users (id, tags, email, secret, metadata, created_at, status, role, first_name, last_name, username, profile_picture) - VALUES (:id, :tags, :email, :secret, :metadata, :created_at, :status, :role, :first_name, :last_name, :username, :profile_picture) - RETURNING id, tags, email, metadata, created_at, status, role, first_name, last_name, username, profile_picture` - - dbu, err := toDBUser(c) - if err != nil { - return users.User{}, errors.Wrap(repoerr.ErrCreateEntity, err) - } - - row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu) - if err != nil { - return users.User{}, postgres.HandleError(repoerr.ErrCreateEntity, err) - } - - defer row.Close() - - row.Next() - - dbu = DBUser{} - if err := row.StructScan(&dbu); err != nil { - return users.User{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - - user, err := ToUser(dbu) - if err != nil { - return users.User{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - - return user, nil -} - -func (repo *userRepo) CheckSuperAdmin(ctx context.Context, adminID string) error { - q := "SELECT 1 FROM users WHERE id = $1 AND role = $2" - rows, err := repo.Repository.DB.QueryContext(ctx, q, adminID, users.AdminRole) - if err != nil { - return postgres.HandleError(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - if rows.Next() { - if err := rows.Err(); err != nil { - return postgres.HandleError(repoerr.ErrViewEntity, err) - } - return nil - } - - return repoerr.ErrNotFound -} - -func (repo *userRepo) RetrieveByID(ctx context.Context, id string) (users.User, error) { - q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username, profile_picture - FROM users WHERE id = :id` - - dbu := DBUser{ - ID: id, - } - - rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu) - if err != nil { - return users.User{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - dbu = DBUser{} - if rows.Next() { - if err = rows.StructScan(&dbu); err != nil { - return users.User{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - - user, err := ToUser(dbu) - if err != nil { - return users.User{}, errors.Wrap(repoerr.ErrFailedOpDB, err) - } - - return user, nil - } - - return users.User{}, repoerr.ErrNotFound -} - -func (repo *userRepo) RetrieveAll(ctx context.Context, pm users.Page) (users.UsersPage, error) { - query, err := PageQuery(pm) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - q := fmt.Sprintf(`SELECT u.id, u.tags, u.email, u.metadata, u.status, u.role, u.first_name, u.last_name, u.username, - u.created_at, u.updated_at, u.profile_picture, COALESCE(u.updated_by, '') AS updated_by - FROM users u %s ORDER BY u.created_at LIMIT :limit OFFSET :offset;`, query) - - dbPage, err := ToDBUsersPage(pm) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - var items []users.User - for rows.Next() { - dbu := DBUser{} - if err := rows.StructScan(&dbu); err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - c, err := ToUser(dbu) - if err != nil { - return users.UsersPage{}, err - } - - items = append(items, c) - } - - cq := fmt.Sprintf(`SELECT COUNT(*) FROM users u %s;`, query) - - total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - page := users.UsersPage{ - Page: users.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - Users: items, - } - - return page, nil -} - -func (repo *userRepo) UpdateUsername(ctx context.Context, user users.User) (users.User, error) { - q := `UPDATE users SET username = :username, updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, tags, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username, email` - - dbu, err := toDBUser(user) - if err != nil { - return users.User{}, errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu) - if err != nil { - return users.User{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - - defer row.Close() - - dbu = DBUser{ - ID: user.ID, - Username: stringToNullString(user.Credentials.Username), - UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true}, - } - - if ok := row.Next(); !ok { - return users.User{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) - } - - if err := row.StructScan(&dbu); err != nil { - return users.User{}, err - } - - return ToUser(dbu) -} - -func (repo *userRepo) Update(ctx context.Context, user users.User) (users.User, error) { - var query []string - var upq string - if user.FirstName != "" { - query = append(query, "first_name = :first_name,") - } - if user.LastName != "" { - query = append(query, "last_name = :last_name,") - } - if user.Metadata != nil { - query = append(query, "metadata = :metadata,") - } - if len(user.Tags) > 0 { - query = append(query, "tags = :tags,") - } - if user.Role != users.AllRole { - query = append(query, "role = :role,") - } - - if user.ProfilePicture != "" { - query = append(query, "profile_picture = :profile_picture,") - } - - if user.Email != "" { - query = append(query, "email = :email,") - } - - if len(query) > 0 { - upq = strings.Join(query, " ") - } - - q := fmt.Sprintf(`UPDATE users SET %s updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, tags, metadata, status, created_at, updated_at, updated_by, last_name, first_name, username, profile_picture, email, role`, upq) - - user.Status = users.EnabledStatus - return repo.update(ctx, user, q) -} - -func (repo *userRepo) update(ctx context.Context, user users.User, query string) (users.User, error) { - dbu, err := toDBUser(user) - if err != nil { - return users.User{}, errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - row, err := repo.Repository.DB.NamedQueryContext(ctx, query, dbu) - if err != nil { - return users.User{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) - } - defer row.Close() - - dbu = DBUser{} - if row.Next() { - if err := row.StructScan(&dbu); err != nil { - return users.User{}, errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - return ToUser(dbu) - } - - return users.User{}, repoerr.ErrNotFound -} - -func (repo *userRepo) UpdateSecret(ctx context.Context, user users.User) (users.User, error) { - q := `UPDATE users SET secret = :secret, updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id AND status = :status - RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username` - user.Status = users.EnabledStatus - return repo.update(ctx, user, q) -} - -func (repo *userRepo) ChangeStatus(ctx context.Context, user users.User) (users.User, error) { - q := `UPDATE users SET status = :status, updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id - RETURNING id, tags, email, metadata, status, created_at, updated_at, updated_by, first_name, last_name, username` - - return repo.update(ctx, user, q) -} - -func (repo *userRepo) Delete(ctx context.Context, id string) error { - q := "DELETE FROM users AS u WHERE u.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 -} - -func (repo *userRepo) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) { - query, err := PageQuery(pm) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - tq := query - query = applyOrdering(query, pm) - - q := fmt.Sprintf(`SELECT u.id, u.username, u.first_name, u.last_name, u.created_at, u.updated_at FROM users u %s LIMIT :limit OFFSET :offset;`, query) - - dbPage, err := ToDBUsersPage(pm) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - - rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - var items []users.User - for rows.Next() { - dbu := DBUser{} - if err := rows.StructScan(&dbu); err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - c, err := ToUser(dbu) - if err != nil { - return users.UsersPage{}, err - } - - items = append(items, c) - } - - cq := fmt.Sprintf(`SELECT COUNT(*) FROM users u %s;`, tq) - - total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - page := users.UsersPage{ - Users: items, - Page: users.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - } - - return page, nil -} - -func (repo *userRepo) RetrieveAllByIDs(ctx context.Context, pm users.Page) (users.UsersPage, error) { - if (len(pm.IDs) == 0) && (pm.Domain == "") { - return users.UsersPage{ - Page: users.Page{Total: pm.Total, Offset: pm.Offset, Limit: pm.Limit}, - }, nil - } - query, err := PageQuery(pm) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - query = applyOrdering(query, pm) - - q := fmt.Sprintf(`SELECT u.id, u.username, u.tags, u.email, u.metadata, u.status, u.role, u.first_name, u.last_name, - u.created_at, u.updated_at, COALESCE(u.updated_by, '') AS updated_by FROM users u %s ORDER BY u.created_at LIMIT :limit OFFSET :offset;`, query) - - dbPage, err := ToDBUsersPage(pm) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - rows, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbPage) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) - } - defer rows.Close() - - var items []users.User - for rows.Next() { - dbu := DBUser{} - if err := rows.StructScan(&dbu); err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - c, err := ToUser(dbu) - if err != nil { - return users.UsersPage{}, err - } - - items = append(items, c) - } - cq := fmt.Sprintf(`SELECT COUNT(*) FROM users u %s;`, query) - - total, err := postgres.Total(ctx, repo.Repository.DB, cq, dbPage) - if err != nil { - return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - page := users.UsersPage{ - Users: items, - Page: users.Page{ - Total: total, - Offset: pm.Offset, - Limit: pm.Limit, - }, - } - - return page, nil -} - -func (repo *userRepo) RetrieveByEmail(ctx context.Context, email string) (users.User, error) { - q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username - FROM users WHERE email = :email AND status = :status` - - dbu := DBUser{ - Email: email, - Status: users.EnabledStatus, - } - - row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu) - if err != nil { - return users.User{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - defer row.Close() - - dbu = DBUser{} - if row.Next() { - if err := row.StructScan(&dbu); err != nil { - return users.User{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - return ToUser(dbu) - } - - return users.User{}, repoerr.ErrNotFound -} - -func (repo *userRepo) RetrieveByUsername(ctx context.Context, username string) (users.User, error) { - q := `SELECT id, tags, email, secret, metadata, created_at, updated_at, updated_by, status, role, first_name, last_name, username - FROM users WHERE username = :username AND status = :status` - - dbu := DBUser{ - Username: sql.NullString{String: username, Valid: username != ""}, - Status: users.EnabledStatus, - } - - row, err := repo.Repository.DB.NamedQueryContext(ctx, q, dbu) - if err != nil { - return users.User{}, postgres.HandleError(repoerr.ErrViewEntity, err) - } - defer row.Close() - - dbu = DBUser{} - if row.Next() { - if err := row.StructScan(&dbu); err != nil { - return users.User{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - return ToUser(dbu) - } - - return users.User{}, repoerr.ErrNotFound -} - -type DBUser struct { - ID string `db:"id"` - Domain string `db:"domain_id"` - Secret string `db:"secret"` - Metadata []byte `db:"metadata,omitempty"` - Tags pgtype.TextArray `db:"tags,omitempty"` // Tags - 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 users.Status `db:"status,omitempty"` - Role *users.Role `db:"role,omitempty"` - Username sql.NullString `db:"username, omitempty"` - FirstName sql.NullString `db:"first_name, omitempty"` - LastName sql.NullString `db:"last_name, omitempty"` - ProfilePicture sql.NullString `db:"profile_picture, omitempty"` - Email string `db:"email,omitempty"` -} - -func toDBUser(u users.User) (DBUser, error) { - data := []byte("{}") - if len(u.Metadata) > 0 { - b, err := json.Marshal(u.Metadata) - if err != nil { - return DBUser{}, errors.Wrap(repoerr.ErrMalformedEntity, err) - } - data = b - } - var tags pgtype.TextArray - if err := tags.Set(u.Tags); err != nil { - return DBUser{}, err - } - var updatedBy *string - if u.UpdatedBy != "" { - updatedBy = &u.UpdatedBy - } - var updatedAt sql.NullTime - if u.UpdatedAt != (time.Time{}) { - updatedAt = sql.NullTime{Time: u.UpdatedAt, Valid: true} - } - - return DBUser{ - ID: u.ID, - Tags: tags, - Secret: u.Credentials.Secret, - Metadata: data, - CreatedAt: u.CreatedAt, - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - Status: u.Status, - Role: &u.Role, - LastName: stringToNullString(u.LastName), - FirstName: stringToNullString(u.FirstName), - Username: stringToNullString(u.Credentials.Username), - ProfilePicture: stringToNullString(u.ProfilePicture), - Email: u.Email, - }, nil -} - -func ToUser(dbu DBUser) (users.User, error) { - var metadata users.Metadata - if dbu.Metadata != nil { - if err := json.Unmarshal([]byte(dbu.Metadata), &metadata); err != nil { - return users.User{}, errors.Wrap(repoerr.ErrMalformedEntity, err) - } - } - var tags []string - for _, e := range dbu.Tags.Elements { - tags = append(tags, e.String) - } - var updatedBy string - if dbu.UpdatedBy != nil { - updatedBy = *dbu.UpdatedBy - } - var updatedAt time.Time - if dbu.UpdatedAt.Valid { - updatedAt = dbu.UpdatedAt.Time - } - - user := users.User{ - ID: dbu.ID, - FirstName: nullStringString(dbu.FirstName), - LastName: nullStringString(dbu.LastName), - Credentials: users.Credentials{ - Username: nullStringString(dbu.Username), - Secret: dbu.Secret, - }, - Email: dbu.Email, - Metadata: metadata, - CreatedAt: dbu.CreatedAt, - UpdatedAt: updatedAt, - UpdatedBy: updatedBy, - Status: dbu.Status, - Tags: tags, - ProfilePicture: nullStringString(dbu.ProfilePicture), - } - if dbu.Role != nil { - user.Role = *dbu.Role - } - return user, nil -} - -type DBUsersPage struct { - Total uint64 `db:"total"` - Limit uint64 `db:"limit"` - Offset uint64 `db:"offset"` - FirstName string `db:"first_name"` - LastName string `db:"last_name"` - Username string `db:"username"` - Id string `db:"id"` - Email string `db:"email"` - Metadata []byte `db:"metadata"` - Tag string `db:"tag"` - GroupID string `db:"group_id"` - Role users.Role `db:"role"` - Status users.Status `db:"status"` -} - -func ToDBUsersPage(pm users.Page) (DBUsersPage, error) { - _, data, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return DBUsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - return DBUsersPage{ - FirstName: pm.FirstName, - LastName: pm.LastName, - Username: pm.Username, - Email: pm.Email, - Id: pm.Id, - Metadata: data, - Total: pm.Total, - Offset: pm.Offset, - Limit: pm.Limit, - Status: pm.Status, - Tag: pm.Tag, - Role: pm.Role, - }, nil -} - -func PageQuery(pm users.Page) (string, error) { - mq, _, err := postgres.CreateMetadataQuery("", pm.Metadata) - if err != nil { - return "", errors.Wrap(errors.ErrMalformedEntity, err) - } - - var query []string - if pm.FirstName != "" { - query = append(query, "first_name ILIKE '%' || :first_name || '%'") - } - if pm.LastName != "" { - query = append(query, "last_name ILIKE '%' || :last_name || '%'") - } - if pm.Username != "" { - query = append(query, "username ILIKE '%' || :username || '%'") - } - if pm.Email != "" { - query = append(query, "email ILIKE '%' || :email || '%'") - } - 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 pm.Role != users.AllRole { - query = append(query, "u.role = :role") - } - - 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 != users.AllStatus { - query = append(query, "u.status = :status") - } - - var emq string - if len(query) > 0 { - emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND ")) - } - - return emq, nil -} - -func applyOrdering(emq string, pm users.Page) string { - switch pm.Order { - case "username", "first_name", "email", "last_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 stringToNullString(s string) sql.NullString { - if s == "" { - return sql.NullString{} - } - - return sql.NullString{ - String: s, - Valid: true, - } -} - -func nullStringString(ns sql.NullString) string { - if ns.Valid { - return ns.String - } - return "" -} diff --git a/users/postgres/users_test.go b/users/postgres/users_test.go deleted file mode 100644 index 671512adb..000000000 --- a/users/postgres/users_test.go +++ /dev/null @@ -1,1898 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/0x6flab/namegenerator" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/absmach/magistrala/users" - cpostgres "github.com/absmach/magistrala/users/postgres" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const maxNameSize = 254 - -var ( - invalidName = strings.Repeat("m", maxNameSize+10) - password = "$tr0ngPassw0rd" - namesgen = namegenerator.NewGenerator() - emailSuffix = "@example.com" -) - -func TestUsersSave(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - - repo := cpostgres.NewRepository(database) - - uid := testsutil.GenerateUUID(t) - - first_name := namesgen.Generate() - last_name := namesgen.Generate() - username := namesgen.Generate() - - email := first_name + "@example.com" - - cases := []struct { - desc string - user users.User - err error - }{ - { - desc: "add new user successfully", - user: users.User{ - ID: uid, - FirstName: first_name, - LastName: last_name, - Email: email, - Credentials: users.Credentials{ - Username: username, - Secret: password, - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - }, - err: nil, - }, - { - desc: "add user with duplicate user email", - user: users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: first_name, - LastName: last_name, - Email: email, - Credentials: users.Credentials{ - Username: namesgen.Generate(), - Secret: password, - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - }, - err: repoerr.ErrConflict, - }, - { - desc: "add user with duplicate user name", - user: users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: namesgen.Generate(), - LastName: last_name, - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Username: username, - Secret: password, - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - }, - err: repoerr.ErrConflict, - }, - { - desc: "add user with invalid user id", - user: users.User{ - ID: invalidName, - FirstName: namesgen.Generate(), - LastName: namesgen.Generate(), - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Username: username, - Secret: password, - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "add user with invalid user name", - user: users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: first_name, - LastName: last_name, - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Username: invalidName, - Secret: password, - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - }, - err: errors.ErrMalformedEntity, - }, - { - desc: "add user with a missing username", - user: users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: first_name, - LastName: last_name, - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Secret: password, - }, - Metadata: users.Metadata{}, - }, - err: nil, - }, - { - desc: "add user with a missing user secret", - user: users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: namesgen.Generate(), - LastName: namesgen.Generate(), - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Username: namesgen.Generate(), - }, - Metadata: users.Metadata{}, - }, - err: nil, - }, - { - desc: "add a user with invalid metadata", - user: users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: namesgen.Generate(), - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Username: username, - Secret: password, - }, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - }, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - rUser, err := repo.Save(context.Background(), tc.user) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - rUser.Credentials.Secret = tc.user.Credentials.Secret - assert.Equal(t, tc.user, rUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.user, rUser)) - } - } -} - -func TestIsPlatformAdmin(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - - repo := cpostgres.NewRepository(database) - - first_name := namesgen.Generate() - last_name := namesgen.Generate() - username := namesgen.Generate() - email := first_name + "@example.com" - - cases := []struct { - desc string - user users.User - err error - }{ - { - desc: "authorize check for super user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: first_name, - LastName: last_name, - Email: email, - Credentials: users.Credentials{ - Username: username, - Secret: password, - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - Role: users.AdminRole, - }, - err: nil, - }, - { - desc: "unauthorize user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: first_name, - LastName: last_name, - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Username: namesgen.Generate(), - Secret: password, - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - Role: users.UserRole, - }, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - _, err := repo.Save(context.Background(), tc.user) - require.Nil(t, err, fmt.Sprintf("%s: save user unexpected error: %s", tc.desc, err)) - err = repo.CheckSuperAdmin(context.Background(), tc.user.ID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestRetrieveByID(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - - repo := cpostgres.NewRepository(database) - - user := users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: namesgen.Generate(), - LastName: namesgen.Generate(), - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Username: namesgen.Generate(), - Secret: password, - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - } - - _, err := repo.Save(context.Background(), user) - require.Nil(t, err, fmt.Sprintf("failed to save users %s", user.ID)) - - cases := []struct { - desc string - userID string - err error - }{ - { - desc: "retrieve existing user", - userID: user.ID, - err: nil, - }, - { - desc: "retrieve non-existing user", - userID: invalidName, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve with empty user id", - userID: "", - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - _, err := repo.RetrieveByID(context.Background(), tc.userID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.err, err)) - } -} - -func TestRetrieveAll(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - - repo := cpostgres.NewRepository(database) - - num := 200 - var items, enabledUsers []users.User - for i := 0; i < num; i++ { - user := users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: namesgen.Generate(), - LastName: namesgen.Generate(), - Email: namesgen.Generate() + "@example.com", - Credentials: users.Credentials{ - Username: namesgen.Generate(), - Secret: "", - }, - Metadata: users.Metadata{}, - Status: users.EnabledStatus, - Tags: []string{"tag1"}, - } - if i%50 == 0 { - user.Metadata = map[string]interface{}{ - "key": "value", - } - user.Role = users.AdminRole - user.Status = users.DisabledStatus - } - _, err := repo.Save(context.Background(), user) - require.Nil(t, err, fmt.Sprintf("failed to save user %s", user.ID)) - items = append(items, user) - if user.Status == users.EnabledStatus { - enabledUsers = append(enabledUsers, user) - } - } - - cases := []struct { - desc string - pageMeta users.Page - page users.UsersPage - err error - }{ - { - desc: "retrieve first page of users", - pageMeta: users.Page{ - Offset: 0, - Limit: 50, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 200, - Offset: 0, - Limit: 50, - }, - Users: items[0:50], - }, - err: nil, - }, - { - desc: "retrieve second page of users", - pageMeta: users.Page{ - Offset: 50, - Limit: 200, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 200, - Offset: 50, - Limit: 200, - }, - Users: items[50:200], - }, - err: nil, - }, - { - desc: "retrieve users with limit", - pageMeta: users.Page{ - Offset: 0, - Limit: 50, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: uint64(num), - Offset: 0, - Limit: 50, - }, - Users: items[:50], - }, - }, - { - desc: "retrieve with offset out of range", - pageMeta: users.Page{ - Offset: 1000, - Limit: 200, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 200, - Offset: 1000, - Limit: 200, - }, - Users: []users.User{}, - }, - err: nil, - }, - { - desc: "retrieve with limit out of range", - pageMeta: users.Page{ - Offset: 0, - Limit: 1000, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 200, - Offset: 0, - Limit: 1000, - }, - Users: items, - }, - err: nil, - }, - { - desc: "retrieve with empty page", - pageMeta: users.Page{}, - page: users.UsersPage{ - Page: users.Page{ - Total: 196, // number of enabled users - Offset: 0, - Limit: 0, - }, - Users: []users.User{}, - }, - err: nil, - }, - { - desc: "retrieve with user id", - pageMeta: users.Page{ - IDs: []string{items[0].ID}, - Offset: 0, - Limit: 3, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 3, - }, - Users: []users.User{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve with invalid user id", - pageMeta: users.Page{ - IDs: []string{invalidName}, - Offset: 0, - Limit: 3, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 3, - }, - Users: []users.User{}, - }, - err: nil, - }, - { - desc: "retrieve with first name", - pageMeta: users.Page{ - FirstName: items[0].FirstName, - Offset: 0, - Limit: 3, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 3, - }, - Users: []users.User{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve with username", - pageMeta: users.Page{ - Username: items[0].Credentials.Username, - Offset: 0, - Limit: 3, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 3, - }, - Users: []users.User{items[0]}, - }, - err: nil, - }, - { - desc: "retrieve with enabled status", - pageMeta: users.Page{ - Status: users.EnabledStatus, - Offset: 0, - Limit: 200, - Role: users.AllRole, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 196, - Offset: 0, - Limit: 200, - }, - Users: enabledUsers, - }, - err: nil, - }, - { - desc: "retrieve with disabled status", - pageMeta: users.Page{ - Status: users.DisabledStatus, - Offset: 0, - Limit: 200, - Role: users.AllRole, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 4, - Offset: 0, - Limit: 200, - }, - Users: []users.User{items[0], items[50], items[100], items[150]}, - }, - }, - { - desc: "retrieve with all status", - pageMeta: users.Page{ - Status: users.AllStatus, - Offset: 0, - Limit: 200, - Role: users.AllRole, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 200, - Offset: 0, - Limit: 200, - }, - Users: items, - }, - }, - { - desc: "retrieve by tags", - pageMeta: users.Page{ - Tag: "tag1", - Offset: 0, - Limit: 200, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 200, - Offset: 0, - Limit: 200, - }, - Users: items, - }, - err: nil, - }, - { - desc: "retrieve with invalid first name", - pageMeta: users.Page{ - FirstName: invalidName, - Offset: 0, - Limit: 3, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 3, - }, - Users: []users.User{}, - }, - }, - { - desc: "retrieve with metadata", - pageMeta: users.Page{ - Metadata: map[string]interface{}{ - "key": "value", - }, - Offset: 0, - Limit: 200, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 4, - Offset: 0, - Limit: 200, - }, - Users: []users.User{items[0], items[50], items[100], items[150]}, - }, - err: nil, - }, - { - desc: "retrieve with invalid metadata", - pageMeta: users.Page{ - Metadata: map[string]interface{}{ - "key": "value1", - }, - Offset: 0, - Limit: 200, - Role: users.AllRole, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 200, - }, - Users: []users.User{}, - }, - err: nil, - }, - { - desc: "retrieve with role", - pageMeta: users.Page{ - Role: users.AdminRole, - Offset: 0, - Limit: 200, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 4, - Offset: 0, - Limit: 200, - }, - Users: []users.User{items[0], items[50], items[100], items[150]}, - }, - err: nil, - }, - { - desc: "retrieve with invalid role", - pageMeta: users.Page{ - Role: users.AdminRole + 2, - Offset: 0, - Limit: 200, - Status: users.AllStatus, - }, - page: users.UsersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 200, - }, - Users: []users.User{}, - }, - err: nil, - }, - } - - for _, tc := range cases { - page, err := repo.RetrieveAll(context.Background(), tc.pageMeta) - - assert.Equal(t, tc.page.Total, page.Total, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Total, page.Total)) - assert.Equal(t, tc.page.Offset, page.Offset, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Offset, page.Offset)) - assert.Equal(t, tc.page.Limit, page.Limit, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Limit, page.Limit)) - assert.Equal(t, tc.page.Page, page.Page, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.page, page)) - assert.ElementsMatch(t, tc.page.Users, page.Users, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.page.Users, page.Users)) - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestSearch(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - nUsers := uint64(200) - expectedUsers := []users.User{} - for i := 0; i < int(nUsers); i++ { - user := generateUser(t, users.EnabledStatus, repo) - - expectedUsers = append(expectedUsers, users.User{ - ID: user.ID, - FirstName: user.FirstName, - LastName: user.LastName, - Credentials: users.Credentials{ - Username: user.Credentials.Username, - }, - CreatedAt: user.CreatedAt, - }) - } - - page, err := repo.RetrieveAll(context.Background(), users.Page{Offset: 0, Limit: nUsers}) - require.Nil(t, err, fmt.Sprintf("retrieve all users unexpected error: %s", err)) - assert.Equal(t, nUsers, page.Total) - - cases := []struct { - desc string - page users.Page - response users.UsersPage - err error - }{ - { - desc: "with empty page", - page: users.Page{}, - response: users.UsersPage{ - Users: []users.User(nil), - Page: users.Page{ - Total: nUsers, - Offset: 0, - Limit: 0, - }, - }, - err: nil, - }, - { - desc: "with offset only", - page: users.Page{ - Offset: 50, - }, - response: users.UsersPage{ - Users: []users.User(nil), - Page: users.Page{ - Total: nUsers, - Offset: 50, - Limit: 0, - }, - }, - err: nil, - }, - { - desc: "with limit only", - page: users.Page{ - Limit: 10, - Order: "name", - Dir: "asc", - }, - response: users.UsersPage{ - Users: expectedUsers[0:10], - Page: users.Page{ - Total: nUsers, - Offset: 0, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "retrieve all users", - page: users.Page{ - Offset: 0, - Limit: nUsers, - }, - response: users.UsersPage{ - Page: users.Page{ - Total: nUsers, - Offset: 0, - Limit: nUsers, - }, - Users: expectedUsers, - }, - }, - { - desc: "with offset and limit", - page: users.Page{ - Offset: 10, - Limit: 10, - Order: "name", - Dir: "asc", - }, - response: users.UsersPage{ - Users: expectedUsers[10:20], - Page: users.Page{ - Total: nUsers, - Offset: 10, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "with offset out of range and limit", - page: users.Page{ - Offset: 1000, - Limit: 50, - }, - response: users.UsersPage{ - Page: users.Page{ - Total: nUsers, - Offset: 1000, - Limit: 50, - }, - Users: []users.User(nil), - }, - }, - { - desc: "with offset and limit out of range", - page: users.Page{ - Offset: 190, - Limit: 50, - Order: "name", - Dir: "asc", - }, - response: users.UsersPage{ - Page: users.Page{ - Total: nUsers, - Offset: 190, - Limit: 50, - }, - Users: expectedUsers[190:200], - }, - }, - { - desc: "with shorter name", - page: users.Page{ - FirstName: expectedUsers[0].FirstName[:4], - Offset: 0, - Limit: 10, - Order: "first_name", - Dir: "asc", - }, - response: users.UsersPage{ - Users: findUsers(expectedUsers, expectedUsers[0].FirstName[:4], 0, 10), - Page: users.Page{ - Total: nUsers, - Offset: 0, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "with longer name", - page: users.Page{ - FirstName: expectedUsers[0].FirstName, - Offset: 0, - Limit: 10, - }, - response: users.UsersPage{ - Users: []users.User{expectedUsers[0]}, - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "with name SQL injected", - page: users.Page{ - FirstName: fmt.Sprintf("%s' OR '1'='1", expectedUsers[0].FirstName[:1]), - Offset: 0, - Limit: 10, - }, - response: users.UsersPage{ - Users: []users.User(nil), - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "with shorter email", - page: users.Page{ - Email: expectedUsers[0].FirstName[:4], - Offset: 0, - Limit: 10, - Order: "first_name", - Dir: "asc", - }, - response: users.UsersPage{ - Users: findUsers(expectedUsers, expectedUsers[0].FirstName[:4], 0, 10), - Page: users.Page{ - Total: nUsers, - Offset: 0, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "with Identity SQL injected", - page: users.Page{ - Email: fmt.Sprintf("%s' OR '1'='1", expectedUsers[0].FirstName[:1]), - Offset: 0, - Limit: 10, - }, - response: users.UsersPage{ - Users: []users.User(nil), - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "with unknown name", - page: users.Page{ - FirstName: namesgen.Generate(), - Offset: 0, - Limit: 10, - }, - response: users.UsersPage{ - Users: []users.User(nil), - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "with unknown email", - page: users.Page{ - Email: namesgen.Generate(), - Offset: 0, - Limit: 10, - }, - response: users.UsersPage{ - Users: []users.User(nil), - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, - }, - err: nil, - }, - { - desc: "with name in asc order", - page: users.Page{ - Order: "first_name", - Dir: "asc", - FirstName: expectedUsers[0].FirstName[:1], - Offset: 0, - Limit: 10, - }, - response: users.UsersPage{}, - err: nil, - }, - { - desc: "with name in desc order", - page: users.Page{ - Order: "first_name", - Dir: "desc", - FirstName: expectedUsers[0].FirstName[:1], - Offset: 0, - Limit: 10, - }, - response: users.UsersPage{}, - err: nil, - }, - { - desc: "with last name in asc order", - page: users.Page{ - LastName: expectedUsers[0].LastName[:1], - Order: "last_name", - Dir: "asc", - }, - response: users.UsersPage{ - Users: []users.User{expectedUsers[0]}, - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 1, - }, - }, - err: nil, - }, - { - desc: "with username in asc order", - page: users.Page{ - Username: expectedUsers[0].Credentials.Username[:1], - Order: "username", - Dir: "asc", - }, - response: users.UsersPage{ - Users: []users.User{expectedUsers[0]}, - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 1, - }, - }, - err: nil, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - switch response, err := repo.SearchUsers(context.Background(), c.page); { - case err == nil: - if c.page.Order != "" && c.page.Dir != "" { - c.response = response - } - assert.Nil(t, err) - assert.Equal(t, c.response.Total, response.Total) - assert.Equal(t, c.response.Limit, response.Limit) - assert.Equal(t, c.response.Offset, response.Offset) - assert.ElementsMatch(t, response.Users, c.response.Users) - default: - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) - } - }) - } -} - -func TestUpdate(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - user1 := generateUser(t, users.EnabledStatus, repo) - user2 := generateUser(t, users.DisabledStatus, repo) - - cases := []struct { - desc string - update string - user users.User - err error - }{ - { - desc: "update metadata for enabled user", - update: "metadata", - user: users.User{ - ID: user1.ID, - Metadata: users.Metadata{ - "update": namesgen.Generate(), - }, - }, - err: nil, - }, - { - desc: "update malformed metadata for enabled user", - update: "metadata", - user: users.User{ - ID: user1.ID, - Metadata: users.Metadata{ - "update": make(chan int), - }, - }, - err: repoerr.ErrUpdateEntity, - }, - { - desc: "update metadata for disabled user", - update: "metadata", - user: users.User{ - ID: user2.ID, - Metadata: users.Metadata{ - "update": namesgen.Generate(), - }, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update first name for enabled user", - update: "first_name", - user: users.User{ - ID: user1.ID, - FirstName: namesgen.Generate(), - }, - err: nil, - }, - { - desc: "update first name for disabled user", - update: "first_name", - user: users.User{ - ID: user2.ID, - FirstName: namesgen.Generate(), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update metadata for invalid user", - update: "metadata", - user: users.User{ - ID: testsutil.GenerateUUID(t), - Metadata: users.Metadata{ - "update": namesgen.Generate(), - }, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update first name for empty user", - update: "first_name", - user: users.User{ - FirstName: namesgen.Generate(), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update last name for enabled user", - update: "last_name", - user: users.User{ - ID: user1.ID, - LastName: namesgen.Generate(), - }, - err: nil, - }, - { - desc: "update last name for disabled user", - update: "last_name", - user: users.User{ - ID: user2.ID, - LastName: namesgen.Generate(), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update last name for invalid user", - update: "last_name", - user: users.User{ - ID: testsutil.GenerateUUID(t), - LastName: namesgen.Generate(), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update tags for enabled user", - user: users.User{ - ID: user1.ID, - Tags: namesgen.GenerateMultiple(5), - }, - err: nil, - }, - { - desc: "update tags for disabled user", - user: users.User{ - ID: user2.ID, - Tags: namesgen.GenerateMultiple(5), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update tags for invalid user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - Tags: namesgen.GenerateMultiple(5), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update profile picture for enabled user", - user: users.User{ - ID: user1.ID, - ProfilePicture: namesgen.Generate(), - }, - err: nil, - }, - { - desc: "update profile picture for disabled user", - user: users.User{ - ID: user2.ID, - ProfilePicture: namesgen.Generate(), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update profile picture for invalid user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - ProfilePicture: namesgen.Generate(), - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update role for enabled user", - user: users.User{ - ID: user1.ID, - Role: users.AdminRole, - }, - err: nil, - }, - { - desc: "update role for disabled user", - user: users.User{ - ID: user2.ID, - Role: users.AdminRole, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update role for invalid user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - Role: users.AdminRole, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update email for enabled user", - user: users.User{ - ID: user1.ID, - Email: namesgen.Generate() + emailSuffix, - }, - err: nil, - }, - { - desc: "update email for disabled user", - user: users.User{ - ID: user2.ID, - Email: namesgen.Generate() + emailSuffix, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "update email for invalid user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - Email: namesgen.Generate() + emailSuffix, - }, - err: repoerr.ErrNotFound, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - c.user.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) - c.user.UpdatedBy = testsutil.GenerateUUID(t) - expected, err := repo.Update(context.Background(), c.user) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) - if err == nil { - switch c.update { - case "metadata": - assert.Equal(t, c.user.Metadata, expected.Metadata) - case "first_name": - assert.Equal(t, c.user.FirstName, expected.FirstName) - case "last_name": - assert.Equal(t, c.user.LastName, expected.LastName) - case "tags": - assert.Equal(t, c.user.Tags, expected.Tags) - case "profile_picture": - assert.Equal(t, c.user.ProfilePicture, expected.ProfilePicture) - case "role": - assert.Equal(t, c.user.Role, expected.Role) - case "email": - assert.Equal(t, c.user.Email, expected.Email) - } - assert.Equal(t, c.user.UpdatedAt, expected.UpdatedAt) - assert.Equal(t, c.user.UpdatedBy, expected.UpdatedBy) - } - }) - } -} - -func TestUpdateUsername(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - user1 := generateUser(t, users.EnabledStatus, repo) - user2 := generateUser(t, users.DisabledStatus, repo) - - cases := []struct { - desc string - user users.User - err error - }{ - { - desc: "for enabled user", - user: users.User{ - ID: user1.ID, - Credentials: users.Credentials{ - Username: namesgen.Generate(), - }, - }, - err: nil, - }, - { - desc: "for enabled user with existing username", - user: users.User{ - ID: user1.ID, - Credentials: users.Credentials{ - Username: user2.Credentials.Username, - }, - }, - err: repoerr.ErrConflict, - }, - { - desc: "for disabled user", - user: users.User{ - ID: user2.ID, - Credentials: users.Credentials{ - Username: namesgen.Generate(), - }, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "for invalid user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - Credentials: users.Credentials{ - Username: namesgen.Generate(), - }, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "for empty user", - user: users.User{}, - err: repoerr.ErrNotFound, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - c.user.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) - c.user.UpdatedBy = testsutil.GenerateUUID(t) - expected, err := repo.UpdateUsername(context.Background(), c.user) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) - if err == nil { - assert.Equal(t, c.user.Credentials.Username, expected.Credentials.Username) - assert.Equal(t, c.user.UpdatedAt, expected.UpdatedAt) - assert.Equal(t, c.user.UpdatedBy, expected.UpdatedBy) - } - }) - } -} - -func TestUpdateSecret(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - user1 := generateUser(t, users.EnabledStatus, repo) - user2 := generateUser(t, users.DisabledStatus, repo) - - cases := []struct { - desc string - user users.User - err error - }{ - { - desc: "for enabled user", - user: users.User{ - ID: user1.ID, - Credentials: users.Credentials{ - Secret: "newpassword", - }, - }, - err: nil, - }, - { - desc: "for disabled user", - user: users.User{ - ID: user2.ID, - Credentials: users.Credentials{ - Secret: "newpassword", - }, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "for invalid user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - Credentials: users.Credentials{ - Secret: "newpassword", - }, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "for empty user", - user: users.User{}, - err: repoerr.ErrNotFound, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - c.user.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) - c.user.UpdatedBy = testsutil.GenerateUUID(t) - _, err := repo.UpdateSecret(context.Background(), c.user) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) - if err == nil { - rc, err := repo.RetrieveByID(context.Background(), c.user.ID) - require.Nil(t, err, fmt.Sprintf("retrieve user by id during update of secret unexpected error: %s", err)) - assert.Equal(t, c.user.Credentials.Secret, rc.Credentials.Secret) - assert.Equal(t, c.user.UpdatedAt, rc.UpdatedAt) - assert.Equal(t, c.user.UpdatedBy, rc.UpdatedBy) - } - }) - } -} - -func TestChangeStatus(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - user1 := generateUser(t, users.EnabledStatus, repo) - user2 := generateUser(t, users.DisabledStatus, repo) - - cases := []struct { - desc string - user users.User - err error - }{ - { - desc: "for an enabled user", - user: users.User{ - ID: user1.ID, - Status: users.DisabledStatus, - }, - err: nil, - }, - { - desc: "for a disabled user", - user: users.User{ - ID: user2.ID, - Status: users.EnabledStatus, - }, - err: nil, - }, - { - desc: "for invalid user", - user: users.User{ - ID: testsutil.GenerateUUID(t), - Status: users.DisabledStatus, - }, - err: repoerr.ErrNotFound, - }, - { - desc: "for empty user", - user: users.User{}, - err: repoerr.ErrNotFound, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - c.user.UpdatedAt = time.Now().UTC().Truncate(time.Millisecond) - c.user.UpdatedBy = testsutil.GenerateUUID(t) - expected, err := repo.ChangeStatus(context.Background(), c.user) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) - if err == nil { - assert.Equal(t, c.user.Status, expected.Status) - assert.Equal(t, c.user.UpdatedAt, expected.UpdatedAt) - assert.Equal(t, c.user.UpdatedBy, expected.UpdatedBy) - } - }) - } -} - -func TestDelete(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - user := generateUser(t, users.EnabledStatus, repo) - - cases := []struct { - desc string - id string - err error - }{ - { - desc: "delete user successfully", - id: user.ID, - err: nil, - }, - { - desc: "delete user with invalid id", - id: testsutil.GenerateUUID(t), - err: repoerr.ErrNotFound, - }, - { - desc: "delete user with empty id", - id: "", - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - err := repo.Delete(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)) - } -} - -func TestRetrieveByIDs(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - num := 200 - - var items []users.User - for i := 0; i < num; i++ { - user := generateUser(t, users.EnabledStatus, repo) - items = append(items, user) - } - - page, err := repo.RetrieveAll(context.Background(), users.Page{Offset: 0, Limit: uint64(num)}) - require.Nil(t, err, fmt.Sprintf("retrieve all users unexpected error: %s", err)) - assert.Equal(t, uint64(num), page.Total) - - cases := []struct { - desc string - page users.Page - response users.UsersPage - err error - }{ - { - desc: "successfully", - page: users.Page{ - Offset: 0, - Limit: 10, - IDs: getIDs(items[0:3]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 3, - Offset: 0, - Limit: 10, - }, - Users: items[0:3], - }, - err: nil, - }, - { - desc: "with empty ids", - page: users.Page{ - Offset: 0, - Limit: 10, - IDs: []string{}, - }, - response: users.UsersPage{ - Page: users.Page{ - Offset: 0, - Limit: 10, - }, - Users: []users.User(nil), - }, - err: nil, - }, - { - desc: "with offset only", - page: users.Page{ - Offset: 10, - IDs: getIDs(items[0:20]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 20, - Offset: 10, - Limit: 0, - }, - Users: []users.User(nil), - }, - err: nil, - }, - { - desc: "with limit only", - page: users.Page{ - Limit: 10, - IDs: getIDs(items[0:20]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 20, - Offset: 0, - Limit: 10, - }, - Users: items[0:10], - }, - err: nil, - }, - { - desc: "with offset out of range", - page: users.Page{ - Offset: 1000, - Limit: 50, - IDs: getIDs(items[0:20]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 20, - Offset: 1000, - Limit: 50, - }, - Users: []users.User(nil), - }, - err: nil, - }, - { - desc: "with offset and limit out of range", - page: users.Page{ - Offset: 15, - Limit: 10, - IDs: getIDs(items[0:20]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 20, - Offset: 15, - Limit: 10, - }, - Users: items[15:20], - }, - err: nil, - }, - { - desc: "with limit out of range", - page: users.Page{ - Offset: 0, - Limit: 1000, - IDs: getIDs(items[0:20]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 20, - Offset: 0, - Limit: 1000, - }, - Users: items[:20], - }, - err: nil, - }, - { - desc: "with first name", - page: users.Page{ - Offset: 0, - Limit: 10, - FirstName: items[0].FirstName, - IDs: getIDs(items[0:20]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Users: []users.User{items[0]}, - }, - err: nil, - }, - { - desc: "with metadata", - page: users.Page{ - Offset: 0, - Limit: 10, - Metadata: items[0].Metadata, - IDs: getIDs(items[0:20]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 10, - }, - Users: []users.User{items[0]}, - }, - err: nil, - }, - { - desc: "with invalid metadata", - page: users.Page{ - Offset: 0, - Limit: 10, - Metadata: map[string]interface{}{ - "key": make(chan int), - }, - IDs: getIDs(items[0:20]), - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 10, - }, - Users: []users.User(nil), - }, - err: errors.ErrMalformedEntity, - }, - } - - for _, c := range cases { - switch response, err := repo.RetrieveAllByIDs(context.Background(), c.page); { - case err == nil: - assert.Nil(t, err, fmt.Sprintf("%s: expected %s got %s\n", c.desc, c.err, err)) - assert.Equal(t, c.response.Total, response.Total) - assert.Equal(t, c.response.Limit, response.Limit) - assert.Equal(t, c.response.Offset, response.Offset) - assert.ElementsMatch(t, response.Users, c.response.Users) - default: - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s to contain %s\n", err, c.err)) - } - } -} - -func TestRetrieveByEmail(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - user := generateUser(t, users.EnabledStatus, repo) - - cases := []struct { - desc string - email string - response users.User - err error - }{ - { - desc: "successfully", - email: user.Email, - response: user, - err: nil, - }, - { - desc: "with invalid user id", - email: testsutil.GenerateUUID(t), - response: users.User{}, - err: repoerr.ErrNotFound, - }, - { - desc: "with empty user id", - email: "", - response: users.User{}, - err: repoerr.ErrNotFound, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - usr, err := repo.RetrieveByEmail(context.Background(), c.email) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s got %s\n", c.err, err)) - if err == nil { - assert.Equal(t, user.ID, usr.ID) - assert.Equal(t, user.FirstName, usr.FirstName) - assert.Equal(t, user.LastName, usr.LastName) - assert.Equal(t, user.Metadata, usr.Metadata) - assert.Equal(t, user.Email, usr.Email) - assert.Equal(t, user.Credentials.Username, usr.Credentials.Username) - assert.Equal(t, user.Status, usr.Status) - } - }) - } -} - -func TestRetrieveByUsername(t *testing.T) { - t.Cleanup(func() { - _, err := db.Exec("DELETE FROM users") - require.Nil(t, err, fmt.Sprintf("clean users unexpected error: %s", err)) - }) - repo := cpostgres.NewRepository(database) - - user := generateUser(t, users.EnabledStatus, repo) - - cases := []struct { - desc string - username string - response users.User - err error - }{ - { - desc: "successfully", - username: user.Credentials.Username, - response: user, - err: nil, - }, - { - desc: "with invalid user id", - username: testsutil.GenerateUUID(t), - response: users.User{}, - err: repoerr.ErrNotFound, - }, - { - desc: "with empty user id", - username: "", - response: users.User{}, - err: repoerr.ErrNotFound, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - usr, err := repo.RetrieveByUsername(context.Background(), c.username) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected %s got %s\n", c.err, err)) - if err == nil { - assert.Equal(t, user.ID, usr.ID) - assert.Equal(t, user.FirstName, usr.FirstName) - assert.Equal(t, user.LastName, usr.LastName) - assert.Equal(t, user.Metadata, usr.Metadata) - assert.Equal(t, user.Email, usr.Email) - assert.Equal(t, user.Credentials.Username, usr.Credentials.Username) - assert.Equal(t, user.Status, usr.Status) - } - }) - } -} - -func findUsers(usrs []users.User, query string, offset, limit uint64) []users.User { - rUsers := []users.User{} - for _, user := range usrs { - if strings.Contains(user.FirstName, query) { - rUsers = append(rUsers, user) - } - } - - if offset > uint64(len(rUsers)) { - return []users.User{} - } - - if limit > uint64(len(rUsers)) { - return rUsers[offset:] - } - - return rUsers[offset:limit] -} - -func generateUser(t *testing.T, status users.Status, repo users.Repository) users.User { - usr := users.User{ - ID: testsutil.GenerateUUID(t), - FirstName: namesgen.Generate(), - LastName: namesgen.Generate(), - Email: namesgen.Generate() + emailSuffix, - Credentials: users.Credentials{ - Username: namesgen.Generate(), - Secret: testsutil.GenerateUUID(t), - }, - Tags: namesgen.GenerateMultiple(5), - Metadata: users.Metadata{ - "name": namesgen.Generate(), - }, - Status: status, - CreatedAt: time.Now().UTC().Truncate(time.Millisecond), - } - user, err := repo.Save(context.Background(), usr) - require.Nil(t, err, fmt.Sprintf("add new user: expected nil got %s\n", err)) - - return user -} - -func getIDs(usrs []users.User) []string { - var ids []string - for _, user := range usrs { - ids = append(ids, user.ID) - } - - return ids -} diff --git a/users/roles.go b/users/roles.go deleted file mode 100644 index 4cb493d1f..000000000 --- a/users/roles.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package users - -import ( - "encoding/json" - "strings" - - "github.com/absmach/magistrala/pkg/apiutil" -) - -// Role represents User role. -type Role uint8 - -// Possible User role values. -const ( - UserRole Role = iota - AdminRole - - // AllRole is used for querying purposes to list users irrespective - // of their role - both admin and user. It is never stored in the - // database as the actual user role and should always be the largest - // value in this enumeration. - AllRole -) - -// String representation of the possible role values. -const ( - Admin = "admin" - user = "user" -) - -// String converts user role to string literal. -func (cs Role) String() string { - switch cs { - case AdminRole: - return Admin - case UserRole: - return user - case AllRole: - return All - default: - return Unknown - } -} - -// ToRole converts string value to a valid User role. -func ToRole(status string) (Role, error) { - switch status { - case "", user: - return UserRole, nil - case Admin: - return AdminRole, nil - case All: - return AllRole, nil - default: - return Role(0), apiutil.ErrInvalidRole - } -} - -func (r Role) MarshalJSON() ([]byte, error) { - return json.Marshal(r.String()) -} - -func (r *Role) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToRole(str) - *r = val - return err -} diff --git a/users/service.go b/users/service.go deleted file mode 100644 index f6318f872..000000000 --- a/users/service.go +++ /dev/null @@ -1,695 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package users - -import ( - "context" - "net/mail" - "time" - - "github.com/absmach/magistrala" - mgauth "github.com/absmach/magistrala/auth" - "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" - "golang.org/x/sync/errgroup" -) - -var ( - errIssueToken = errors.New("failed to issue token") - errFailedPermissionsList = errors.New("failed to list permissions") - errRecoveryToken = errors.New("failed to generate password recovery token") - errLoginDisableUser = errors.New("failed to login in disabled user") -) - -type service struct { - token magistrala.TokenServiceClient - users Repository - idProvider magistrala.IDProvider - policies policies.Service - hasher Hasher - email Emailer -} - -// 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 { - return service{ - token: token, - users: urepo, - policies: policyService, - hasher: hasher, - email: emailer, - idProvider: idp, - } -} - -func (svc service) Register(ctx context.Context, session authn.Session, u User, selfRegister bool) (uc User, err error) { - if !selfRegister { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{}, err - } - } - - userID, err := svc.idProvider.ID() - if err != nil { - return User{}, err - } - - if u.Credentials.Secret != "" { - hash, err := svc.hasher.Hash(u.Credentials.Secret) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrMalformedEntity, err) - } - u.Credentials.Secret = hash - } - - if u.Status != DisabledStatus && u.Status != EnabledStatus { - return User{}, errors.Wrap(svcerr.ErrMalformedEntity, svcerr.ErrInvalidStatus) - } - if u.Role != UserRole && u.Role != AdminRole { - return User{}, errors.Wrap(svcerr.ErrMalformedEntity, svcerr.ErrInvalidRole) - } - u.ID = userID - u.CreatedAt = time.Now() - - if err := svc.addUserPolicy(ctx, u.ID, u.Role); err != nil { - return User{}, err - } - 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) - } - } - }() - user, err := svc.users.Save(ctx, u) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrCreateEntity, err) - } - return user, nil -} - -func (svc service) IssueToken(ctx context.Context, identity, secret string) (*magistrala.Token, error) { - var dbUser User - var err error - - if _, parseErr := mail.ParseAddress(identity); parseErr != nil { - dbUser, err = svc.users.RetrieveByUsername(ctx, identity) - } else { - dbUser, err = svc.users.RetrieveByEmail(ctx, identity) - } - - if err != nil { - return &magistrala.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) - } - - token, err := svc.token.Issue(ctx, &magistrala.IssueReq{UserId: dbUser.ID, Type: uint32(mgauth.AccessKey)}) - if err != nil { - return &magistrala.Token{}, errors.Wrap(errIssueToken, err) - } - - return token, nil -} - -func (svc service) RefreshToken(ctx context.Context, session authn.Session, refreshToken string) (*magistrala.Token, error) { - dbUser, err := svc.users.RetrieveByID(ctx, session.UserID) - if err != nil { - return &magistrala.Token{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - if dbUser.Status == DisabledStatus { - return &magistrala.Token{}, errors.Wrap(svcerr.ErrAuthentication, errLoginDisableUser) - } - - return svc.token.Refresh(ctx, &magistrala.RefreshReq{RefreshToken: refreshToken}) -} - -func (svc service) View(ctx context.Context, session authn.Session, id string) (User, error) { - user, err := svc.users.RetrieveByID(ctx, id) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - if session.UserID != id { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{ - FirstName: user.FirstName, - LastName: user.LastName, - ID: user.ID, - Credentials: Credentials{Username: user.Credentials.Username}, - }, nil - } - } - - user.Credentials.Secret = "" - - return user, nil -} - -func (svc service) ViewProfile(ctx context.Context, session authn.Session) (User, error) { - user, err := svc.users.RetrieveByID(ctx, session.UserID) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - user.Credentials.Secret = "" - - return user, nil -} - -func (svc service) ListUsers(ctx context.Context, session authn.Session, pm Page) (UsersPage, error) { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return UsersPage{}, err - } - - pm.Role = AllRole - pg, err := svc.users.RetrieveAll(ctx, pm) - if err != nil { - return UsersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - return pg, err -} - -func (svc service) SearchUsers(ctx context.Context, pm Page) (UsersPage, error) { - page := Page{ - Offset: pm.Offset, - Limit: pm.Limit, - FirstName: pm.FirstName, - LastName: pm.LastName, - Username: pm.Username, - Id: pm.Id, - Role: UserRole, - } - - cp, err := svc.users.SearchUsers(ctx, page) - if err != nil { - return UsersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - return cp, nil -} - -func (svc service) Update(ctx context.Context, session authn.Session, usr User) (User, error) { - if session.UserID != usr.ID { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{}, err - } - } - - user := User{ - ID: usr.ID, - FirstName: usr.FirstName, - LastName: usr.LastName, - Metadata: usr.Metadata, - Role: AllRole, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - - user, err := svc.users.Update(ctx, user) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return user, nil -} - -func (svc service) UpdateTags(ctx context.Context, session authn.Session, usr User) (User, error) { - if session.UserID != usr.ID { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{}, err - } - } - - user := User{ - ID: usr.ID, - Tags: usr.Tags, - Role: AllRole, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - user, err := svc.users.Update(ctx, user) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - - return user, nil -} - -func (svc service) UpdateProfilePicture(ctx context.Context, session authn.Session, usr User) (User, error) { - if session.UserID != usr.ID { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{}, err - } - } - - user := User{ - ID: usr.ID, - ProfilePicture: usr.ProfilePicture, - Role: AllRole, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - - user, err := svc.users.Update(ctx, user) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - - return user, nil -} - -func (svc service) UpdateEmail(ctx context.Context, session authn.Session, userID, email string) (User, error) { - if session.UserID != userID { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{}, err - } - } - - user := User{ - ID: userID, - Email: email, - Role: AllRole, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - user, err := svc.users.Update(ctx, user) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return user, nil -} - -func (svc service) GenerateResetToken(ctx context.Context, email, host string) error { - user, err := svc.users.RetrieveByEmail(ctx, email) - if err != nil { - return errors.Wrap(svcerr.ErrViewEntity, err) - } - issueReq := &magistrala.IssueReq{ - UserId: user.ID, - Type: uint32(mgauth.RecoveryKey), - } - token, err := svc.token.Issue(ctx, issueReq) - if err != nil { - return errors.Wrap(errRecoveryToken, err) - } - - return svc.SendPasswordReset(ctx, host, email, user.Credentials.Username, token.AccessToken) -} - -func (svc service) ResetSecret(ctx context.Context, session authn.Session, secret string) error { - u, err := svc.users.RetrieveByID(ctx, session.UserID) - if err != nil { - return errors.Wrap(svcerr.ErrViewEntity, err) - } - - secret, err = svc.hasher.Hash(secret) - if err != nil { - return errors.Wrap(svcerr.ErrMalformedEntity, err) - } - u = User{ - ID: u.ID, - Email: u.Email, - Credentials: Credentials{ - Secret: secret, - }, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - if _, err := svc.users.UpdateSecret(ctx, u); err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - return nil -} - -func (svc service) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (User, error) { - dbUser, err := svc.users.RetrieveByID(ctx, session.UserID) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - if _, err := svc.IssueToken(ctx, dbUser.Credentials.Username, oldSecret); err != nil { - return User{}, err - } - newSecret, err = svc.hasher.Hash(newSecret) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrMalformedEntity, err) - } - dbUser.Credentials.Secret = newSecret - dbUser.UpdatedAt = time.Now() - dbUser.UpdatedBy = session.UserID - - dbUser, err = svc.users.UpdateSecret(ctx, dbUser) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - - return dbUser, nil -} - -func (svc service) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (User, error) { - if session.UserID != id { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{}, err - } - } - - usr := User{ - ID: id, - Credentials: Credentials{ - Username: username, - }, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - updatedUser, err := svc.users.UpdateUsername(ctx, usr) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return updatedUser, nil -} - -func (svc service) SendPasswordReset(_ context.Context, host, email, user, token string) error { - to := []string{email} - return svc.email.SendPasswordReset(to, host, user, token) -} - -func (svc service) UpdateRole(ctx context.Context, session authn.Session, usr User) (User, error) { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{}, err - } - user := User{ - ID: usr.ID, - Role: usr.Role, - UpdatedAt: time.Now(), - UpdatedBy: session.UserID, - } - - if err := svc.updateUserPolicy(ctx, usr.ID, usr.Role); err != nil { - return User{}, err - } - - u, err := svc.users.Update(ctx, user) - if err != nil { - // If failed to update role in DB, then revert back to platform admin policies in spicedb - if errRollback := svc.updateUserPolicy(ctx, usr.ID, UserRole); errRollback != nil { - return User{}, errors.Wrap(errRollback, err) - } - return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return u, nil -} - -func (svc service) Enable(ctx context.Context, session authn.Session, id string) (User, error) { - u := User{ - ID: id, - UpdatedAt: time.Now(), - Status: EnabledStatus, - } - user, err := svc.changeUserStatus(ctx, session, u) - if err != nil { - return User{}, errors.Wrap(ErrEnableClient, err) - } - - return user, nil -} - -func (svc service) Disable(ctx context.Context, session authn.Session, id string) (User, error) { - user := User{ - ID: id, - UpdatedAt: time.Now(), - Status: DisabledStatus, - } - user, err := svc.changeUserStatus(ctx, session, user) - if err != nil { - return User{}, err - } - - return user, nil -} - -func (svc service) changeUserStatus(ctx context.Context, session authn.Session, user User) (User, error) { - if session.UserID != user.ID { - if err := svc.checkSuperAdmin(ctx, session); err != nil { - return User{}, err - } - } - dbu, err := svc.users.RetrieveByID(ctx, user.ID) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - if dbu.Status == user.Status { - return User{}, errors.ErrStatusAlreadyAssigned - } - user.UpdatedBy = session.UserID - - user, err = svc.users.ChangeStatus(ctx, user) - if err != nil { - return User{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return user, nil -} - -func (svc service) Delete(ctx context.Context, session authn.Session, id string) error { - user := User{ - ID: id, - UpdatedAt: time.Now(), - Status: DeletedStatus, - } - - if _, err := svc.changeUserStatus(ctx, session, user); err != nil { - return err - } - - return nil -} - -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.DomainsKind: - objectType = policies.DomainType - case policies.GroupsKind: - fallthrough - default: - objectType = policies.GroupType - } - - duids, err := svc.policies.ListAllSubjects(ctx, policies.Policy{ - SubjectType: policies.UserType, - Permission: pm.Permission, - Object: objectID, - ObjectType: objectType, - }) - if err != nil { - return MembersPage{}, errors.Wrap(svcerr.ErrNotFound, err) - } - if len(duids.Policies) == 0 { - return MembersPage{ - Page: Page{Total: 0, Offset: pm.Offset, Limit: pm.Limit}, - }, nil - } - - var userIDs []string - - for _, domainUserID := range duids.Policies { - _, userID := mgauth.DecodeDomainUserID(domainUserID) - userIDs = append(userIDs, userID) - } - pm.IDs = userIDs - - up, err := svc.users.RetrieveAll(ctx, pm) - if err != nil { - return MembersPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - - for i, u := range up.Users { - up.Users[i] = User{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - Credentials: Credentials{ - Username: u.Credentials.Username, - }, - CreatedAt: u.CreatedAt, - UpdatedAt: u.UpdatedAt, - Status: u.Status, - } - } - - if pm.ListPerms && len(up.Users) > 0 { - g, ctx := errgroup.WithContext(ctx) - - for i := range up.Users { - // Copying loop variable "i" to avoid "loop variable captured by func literal" - iter := i - g.Go(func() error { - return svc.retrieveObjectUsersPermissions(ctx, session.DomainID, objectType, objectID, &up.Users[iter]) - }) - } - - if err := g.Wait(); err != nil { - return MembersPage{}, err - } - } - - return MembersPage{ - Page: up.Page, - Members: up.Users, - }, nil -} - -func (svc service) retrieveObjectUsersPermissions(ctx context.Context, domainID, objectType, objectID string, user *User) error { - userID := mgauth.EncodeDomainUserID(domainID, user.ID) - permissions, err := svc.listObjectUserPermission(ctx, userID, objectType, objectID) - if err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - user.Permissions = permissions - return nil -} - -func (svc service) listObjectUserPermission(ctx context.Context, userID, objectType, objectID string) ([]string, error) { - permissions, err := svc.policies.ListPermissions(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Object: objectID, - ObjectType: objectType, - }, []string{}) - if err != nil { - return []string{}, errors.Wrap(errFailedPermissionsList, err) - } - return permissions, nil -} - -func (svc *service) checkSuperAdmin(ctx context.Context, session authn.Session) error { - if !session.SuperAdmin { - if err := svc.users.CheckSuperAdmin(ctx, session.UserID); err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - } - - return nil -} - -func (svc service) OAuthCallback(ctx context.Context, user User) (User, error) { - ruser, err := svc.users.RetrieveByEmail(ctx, user.Email) - if err != nil { - switch errors.Contains(err, repoerr.ErrNotFound) { - case true: - ruser, err = svc.Register(ctx, authn.Session{}, user, true) - if err != nil { - return User{}, err - } - default: - return User{}, err - } - } - - return User{ - ID: ruser.ID, - Role: ruser.Role, - }, nil -} - -func (svc service) OAuthAddUserPolicy(ctx context.Context, user User) error { - return svc.addUserPolicy(ctx, user.ID, user.Role) -} - -func (svc service) Identify(ctx context.Context, session authn.Session) (string, error) { - return session.UserID, nil -} - -func (svc service) addUserPolicy(ctx context.Context, userID string, role Role) error { - policyList := []policies.Policy{} - - policyList = append(policyList, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.MemberRelation, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }) - - if role == AdminRole { - policyList = append(policyList, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.AdministratorRelation, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }) - } - err := svc.policies.AddPolicies(ctx, policyList) - if err != nil { - return errors.Wrap(svcerr.ErrAddPolicies, err) - } - - return nil -} - -func (svc service) addUserPolicyRollback(ctx context.Context, userID string, role Role) error { - policyList := []policies.Policy{} - - policyList = append(policyList, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.MemberRelation, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }) - - if role == AdminRole { - policyList = append(policyList, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.AdministratorRelation, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }) - } - err := svc.policies.DeletePolicies(ctx, policyList) - if err != nil { - return errors.Wrap(svcerr.ErrDeletePolicies, err) - } - - return nil -} - -func (svc service) updateUserPolicy(ctx context.Context, userID string, role Role) error { - switch role { - case AdminRole: - err := svc.policies.AddPolicy(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.AdministratorRelation, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }) - if err != nil { - return errors.Wrap(svcerr.ErrAddPolicies, err) - } - - return nil - case UserRole: - fallthrough - default: - err := svc.policies.DeletePolicyFilter(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Relation: policies.AdministratorRelation, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }) - if err != nil { - return errors.Wrap(svcerr.ErrDeletePolicies, err) - } - - return nil - } -} diff --git a/users/service_test.go b/users/service_test.go deleted file mode 100644 index 8c891afc0..000000000 --- a/users/service_test.go +++ /dev/null @@ -1,2048 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package users_test - -import ( - "context" - "fmt" - "strings" - "testing" - - "github.com/absmach/magistrala" - mgauth "github.com/absmach/magistrala/auth" - authmocks "github.com/absmach/magistrala/auth/mocks" - "github.com/absmach/magistrala/internal/testsutil" - "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/absmach/magistrala/users" - "github.com/absmach/magistrala/users/hasher" - "github.com/absmach/magistrala/users/mocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - idProvider = uuid.New() - phasher = hasher.New() - secret = "strongsecret" - validCMetadata = users.Metadata{"role": "user"} - userID = "d8dd12ef-aa2a-43fe-8ef2-2e4fe514360f" - user = users.User{ - ID: userID, - FirstName: "firstname", - LastName: "lastname", - Tags: []string{"tag1", "tag2"}, - Credentials: users.Credentials{Username: "username", Secret: secret}, - Email: "useremail@email.com", - Metadata: validCMetadata, - Status: users.EnabledStatus, - } - basicUser = users.User{ - Credentials: users.Credentials{ - Username: "username", - }, - ID: userID, - FirstName: "firstname", - LastName: "lastname", - } - validToken = "token" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" - wrongID = testsutil.GenerateUUID(&testing.T{}) - errHashPassword = errors.New("generate hash from password failed") -) - -func newService() (users.Service, *authmocks.TokenServiceClient, *mocks.Repository, *policymocks.Service, *mocks.Emailer) { - cRepo := new(mocks.Repository) - policies := new(policymocks.Service) - e := new(mocks.Emailer) - tokenClient := new(authmocks.TokenServiceClient) - return users.NewService(tokenClient, cRepo, policies, e, phasher, idProvider), tokenClient, cRepo, policies, e -} - -func newServiceMinimal() (users.Service, *mocks.Repository) { - cRepo := new(mocks.Repository) - policies := new(policymocks.Service) - e := new(mocks.Emailer) - tokenUser := new(authmocks.TokenServiceClient) - return users.NewService(tokenUser, cRepo, policies, e, phasher, idProvider), cRepo -} - -func TestRegister(t *testing.T) { - svc, _, cRepo, policies, _ := newService() - - cases := []struct { - desc string - user users.User - addPoliciesResponseErr error - deletePoliciesResponseErr error - saveErr error - err error - }{ - { - desc: "register new user successfully", - user: user, - err: nil, - }, - { - desc: "register existing user", - user: user, - saveErr: repoerr.ErrConflict, - err: repoerr.ErrConflict, - }, - { - desc: "register a new enabled user with name", - user: users.User{ - FirstName: "userWithName", - Email: "newuserwithname@example.com", - Credentials: users.Credentials{ - Secret: secret, - }, - Status: users.EnabledStatus, - }, - err: nil, - }, - { - desc: "register a new disabled user with name", - user: users.User{ - FirstName: "userWithName", - Email: "newuserwithname@example.com", - Credentials: users.Credentials{ - Secret: secret, - }, - }, - err: nil, - }, - { - desc: "register a new user with all fields", - user: users.User{ - FirstName: "newuserwithallfields", - Tags: []string{"tag1", "tag2"}, - Email: "newuserwithallfields@example.com", - Credentials: users.Credentials{ - Secret: secret, - }, - Metadata: users.Metadata{ - "name": "newuserwithallfields", - }, - Status: users.EnabledStatus, - }, - err: nil, - }, - { - desc: "register a new user with missing email", - user: users.User{ - FirstName: "userWithMissingEmail", - Credentials: users.Credentials{ - Secret: secret, - }, - }, - saveErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "register a new user with missing secret", - user: users.User{ - FirstName: "userWithMissingSecret", - Email: "userwithmissingsecret@example.com", - Credentials: users.Credentials{ - Secret: "", - }, - }, - err: nil, - }, - { - desc: " register a user with a secret that is too long", - user: users.User{ - FirstName: "userWithLongSecret", - Email: "userwithlongsecret@example.com", - Credentials: users.Credentials{ - Secret: strings.Repeat("a", 73), - }, - }, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "register a new user with invalid status", - user: users.User{ - FirstName: "userWithInvalidStatus", - Email: "user with invalid status", - Credentials: users.Credentials{ - Secret: secret, - }, - Status: users.AllStatus, - }, - err: svcerr.ErrInvalidStatus, - }, - { - desc: "register a new user with invalid role", - user: users.User{ - FirstName: "userWithInvalidRole", - Email: "userwithinvalidrole@example.com", - Credentials: users.Credentials{ - Secret: secret, - }, - Role: 2, - }, - err: svcerr.ErrInvalidRole, - }, - { - desc: "register a new user with failed to add policies with err", - user: users.User{ - FirstName: "userWithFailedToAddPolicies", - Email: "userwithfailedpolicies@example.com", - Credentials: users.Credentials{ - Secret: secret, - }, - Role: users.AdminRole, - }, - addPoliciesResponseErr: svcerr.ErrAddPolicies, - err: svcerr.ErrAddPolicies, - }, - { - desc: "register a new user with failed to delete policies with err", - user: users.User{ - FirstName: "userWithFailedToDeletePolicies", - Email: "userwithfailedtodelete@example.com", - Credentials: users.Credentials{ - Secret: secret, - }, - Role: users.AdminRole, - }, - deletePoliciesResponseErr: svcerr.ErrConflict, - saveErr: repoerr.ErrConflict, - err: svcerr.ErrConflict, - }, - } - - for _, tc := range cases { - policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesResponseErr) - policyCall1 := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePoliciesResponseErr) - repoCall := cRepo.On("Save", context.Background(), mock.Anything).Return(tc.user, tc.saveErr) - expected, err := svc.Register(context.Background(), authn.Session{}, tc.user, true) - 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.user.ID = expected.ID - tc.user.CreatedAt = expected.CreatedAt - tc.user.UpdatedAt = expected.UpdatedAt - tc.user.Credentials.Secret = expected.Credentials.Secret - tc.user.UpdatedBy = expected.UpdatedBy - assert.Equal(t, tc.user, expected, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.user, expected)) - 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() - } - - svc, _, cRepo, policies, _ = newService() - - cases2 := []struct { - desc string - user users.User - session authn.Session - addPoliciesResponseErr error - deletePoliciesResponseErr error - saveErr error - checkSuperAdminErr error - err error - }{ - { - desc: "register new user successfully as admin", - user: user, - session: authn.Session{UserID: validID, SuperAdmin: true}, - err: nil, - }, - { - desc: "register a new user as admin with failed check on super admin", - user: user, - session: authn.Session{UserID: validID, SuperAdmin: false}, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - for _, tc := range cases2 { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesResponseErr) - policyCall1 := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePoliciesResponseErr) - repoCall1 := cRepo.On("Save", context.Background(), mock.Anything).Return(tc.user, tc.saveErr) - expected, err := svc.Register(context.Background(), authn.Session{UserID: validID}, tc.user, false) - 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.user.ID = expected.ID - tc.user.CreatedAt = expected.CreatedAt - tc.user.UpdatedAt = expected.UpdatedAt - tc.user.Credentials.Secret = expected.Credentials.Secret - tc.user.UpdatedBy = expected.UpdatedBy - assert.Equal(t, tc.user, expected, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.user, expected)) - ok := repoCall1.Parent.AssertCalled(t, "Save", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) - } - repoCall1.Unset() - policyCall.Unset() - policyCall1.Unset() - repoCall.Unset() - } -} - -func TestViewUser(t *testing.T) { - svc, cRepo := newServiceMinimal() - - cases := []struct { - desc string - token string - reqUserID string - userID string - retrieveByIDResponse users.User - response users.User - identifyErr error - authorizeErr error - retrieveByIDErr error - checkSuperAdminErr error - err error - }{ - { - desc: "view user as normal user successfully", - retrieveByIDResponse: user, - response: user, - token: validToken, - reqUserID: user.ID, - userID: user.ID, - err: nil, - checkSuperAdminErr: svcerr.ErrAuthorization, - }, - { - desc: "view user as normal user with failed to retrieve user", - retrieveByIDResponse: users.User{}, - token: validToken, - reqUserID: user.ID, - userID: user.ID, - retrieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - checkSuperAdminErr: svcerr.ErrAuthorization, - }, - { - desc: "view user as admin user successfully", - retrieveByIDResponse: user, - response: user, - token: validToken, - reqUserID: user.ID, - userID: user.ID, - err: nil, - }, - { - desc: "view user as admin user with failed check on super admin", - token: validToken, - retrieveByIDResponse: basicUser, - response: basicUser, - reqUserID: user.ID, - userID: "", - checkSuperAdminErr: svcerr.ErrAuthorization, - err: nil, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall1 := cRepo.On("RetrieveByID", context.Background(), tc.userID).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) - rUser, err := svc.View(context.Background(), authn.Session{UserID: tc.reqUserID}, tc.userID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - tc.response.Credentials.Secret = "" - assert.Equal(t, tc.response, rUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rUser)) - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.userID) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - } - repoCall1.Unset() - repoCall.Unset() - } -} - -func TestListUsers(t *testing.T) { - svc, cRepo := newServiceMinimal() - - cases := []struct { - desc string - token string - page users.Page - retrieveAllResponse users.UsersPage - response users.UsersPage - size uint64 - retrieveAllErr error - superAdminErr error - err error - }{ - { - desc: "list clients as admin successfully", - page: users.Page{ - Total: 1, - }, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - response: users.UsersPage{ - Page: users.Page{ - Total: 1, - }, - Users: []users.User{user}, - }, - token: validToken, - err: nil, - }, - { - desc: "list clients as admin with failed to retrieve clients", - page: users.Page{ - Total: 1, - }, - retrieveAllResponse: users.UsersPage{}, - token: validToken, - retrieveAllErr: repoerr.ErrNotFound, - err: svcerr.ErrViewEntity, - }, - { - desc: "list clients as admin with failed check on super admin", - page: users.Page{ - Total: 1, - }, - token: validToken, - superAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "list clients as normal user with failed to retrieve clients", - page: users.Page{ - Total: 1, - }, - retrieveAllResponse: users.UsersPage{}, - token: validToken, - retrieveAllErr: repoerr.ErrNotFound, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.superAdminErr) - repoCall1 := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) - page, err := svc.ListUsers(context.Background(), authn.Session{UserID: user.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)) - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "RetrieveAll", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestSearchUsers(t *testing.T) { - svc, cRepo := newServiceMinimal() - cases := []struct { - desc string - token string - page users.Page - response users.UsersPage - responseErr error - err error - }{ - { - desc: "search clients with valid token", - token: validToken, - page: users.Page{Offset: 0, FirstName: "username", Limit: 100}, - response: users.UsersPage{ - Page: users.Page{Total: 1, Offset: 0, Limit: 100}, - Users: []users.User{user}, - }, - }, - { - desc: "search clients with id", - token: validToken, - page: users.Page{Offset: 0, Id: "d8dd12ef-aa2a-43fe-8ef2-2e4fe514360f", Limit: 100}, - response: users.UsersPage{ - Page: users.Page{Total: 1, Offset: 0, Limit: 100}, - Users: []users.User{user}, - }, - }, - { - desc: "search clients with random name", - token: validToken, - page: users.Page{Offset: 0, FirstName: "randomname", Limit: 100}, - response: users.UsersPage{ - Page: users.Page{Total: 0, Offset: 0, Limit: 100}, - Users: []users.User{}, - }, - }, - { - desc: "search clients with repo failed", - token: validToken, - page: users.Page{Offset: 0, FirstName: "randomname", Limit: 100}, - response: users.UsersPage{ - Page: users.Page{Total: 0, Offset: 0, Limit: 0}, - }, - responseErr: repoerr.ErrViewEntity, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("SearchUsers", context.Background(), mock.Anything).Return(tc.response, tc.responseErr) - page, err := svc.SearchUsers(context.Background(), tc.page) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, page, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) - repoCall.Unset() - } -} - -func TestUpdateUser(t *testing.T) { - svc, cRepo := newServiceMinimal() - - user1 := user - user2 := user - user1.FirstName = "Updated user" - user2.Metadata = users.Metadata{"role": "test"} - adminID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - user users.User - session authn.Session - updateResponse users.User - token string - updateErr error - checkSuperAdminErr error - err error - }{ - { - desc: "update user name successfully as normal user", - user: user1, - session: authn.Session{UserID: user1.ID}, - updateResponse: user1, - token: validToken, - err: nil, - }, - { - desc: "update metadata successfully as normal user", - user: user2, - session: authn.Session{UserID: user2.ID}, - updateResponse: user2, - token: validToken, - err: nil, - }, - { - desc: "update user name as normal user with repo error on update", - user: user1, - session: authn.Session{UserID: user1.ID}, - updateResponse: users.User{}, - token: validToken, - updateErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - { - desc: "update user name as admin successfully", - user: user1, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - updateResponse: user1, - token: validToken, - err: nil, - }, - { - desc: "update user metadata as admin successfully", - user: user2, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - updateResponse: user2, - token: validToken, - err: nil, - }, - { - desc: "update user with failed check on super admin", - user: user1, - session: authn.Session{UserID: adminID}, - token: validToken, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "update user name as admin with repo error on update", - user: user1, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - updateResponse: users.User{}, - token: validToken, - updateErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall1 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateResponse, tc.err) - updatedUser, err := svc.Update(context.Background(), tc.session, tc.user) - 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, updatedUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateResponse, updatedUser)) - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "Update", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateTags(t *testing.T) { - svc, cRepo := newServiceMinimal() - - user.Tags = []string{"updated"} - adminID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - user users.User - session authn.Session - updateUserTagsResponse users.User - updateUserTagsErr error - checkSuperAdminErr error - err error - }{ - { - desc: "update user tags as normal user successfully", - user: user, - session: authn.Session{UserID: user.ID}, - updateUserTagsResponse: user, - err: nil, - }, - { - desc: "update user tags as normal user with repo error on update", - user: user, - session: authn.Session{UserID: user.ID}, - updateUserTagsResponse: users.User{}, - updateUserTagsErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - { - desc: "update user tags as admin successfully", - user: user, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - err: nil, - }, - { - desc: "update user tags as admin with failed check on super admin", - user: user, - session: authn.Session{UserID: adminID}, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "update user tags as admin with repo error on update", - user: user, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - updateUserTagsResponse: users.User{}, - updateUserTagsErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall1 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateUserTagsResponse, tc.updateUserTagsErr) - updatedUser, err := svc.UpdateTags(context.Background(), tc.session, tc.user) - 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.updateUserTagsResponse, updatedUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateUserTagsResponse, updatedUser)) - - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "Update", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateRole(t *testing.T) { - svc, _, cRepo, policies, _ := newService() - - user2 := user - user.Role = users.AdminRole - user2.Role = users.UserRole - - cases := []struct { - desc string - user users.User - session authn.Session - updateRoleResponse users.User - deletePolicyErr error - addPolicyErr error - updateRoleErr error - checkSuperAdminErr error - err error - }{ - { - desc: "update user role successfully", - user: user, - session: authn.Session{UserID: validID, SuperAdmin: true}, - updateRoleResponse: user, - err: nil, - }, - { - desc: "update user role with failed check on super admin", - user: user, - session: authn.Session{UserID: validID, SuperAdmin: false}, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "update user role with failed to add policies", - user: user, - session: authn.Session{UserID: validID, SuperAdmin: true}, - addPolicyErr: errors.ErrMalformedEntity, - err: svcerr.ErrAddPolicies, - }, - { - desc: "update user role to user role successfully ", - user: user2, - session: authn.Session{UserID: validID, SuperAdmin: true}, - updateRoleResponse: user2, - err: nil, - }, - { - desc: "update user role to user role with failed to delete policies", - user: user2, - session: authn.Session{UserID: validID, SuperAdmin: true}, - deletePolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "update user role to user role with failed to delete policies with error", - user: user2, - session: authn.Session{UserID: validID, SuperAdmin: true}, - deletePolicyErr: svcerr.ErrMalformedEntity, - err: svcerr.ErrDeletePolicies, - }, - { - desc: "Update user with failed repo update and roll back", - user: user, - session: authn.Session{UserID: validID, SuperAdmin: true}, - updateRoleErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "Update user with failed repo update and failedroll back", - user: user, - session: authn.Session{UserID: validID, SuperAdmin: true}, - deletePolicyErr: svcerr.ErrAuthorization, - updateRoleErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - policyCall := policies.On("AddPolicy", context.Background(), mock.Anything).Return(tc.addPolicyErr) - policyCall1 := policies.On("DeletePolicyFilter", context.Background(), mock.Anything).Return(tc.deletePolicyErr) - repoCall1 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateRoleResponse, tc.updateRoleErr) - - updatedUser, err := svc.UpdateRole(context.Background(), tc.session, tc.user) - 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.updateRoleResponse, updatedUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateRoleResponse, updatedUser)) - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "Update", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) - } - repoCall.Unset() - policyCall.Unset() - policyCall1.Unset() - repoCall1.Unset() - } -} - -func TestUpdateSecret(t *testing.T) { - svc, authUser, cRepo, _, _ := newService() - - newSecret := "newstrongSecret" - rUser := user - rUser.Credentials.Secret, _ = phasher.Hash(user.Credentials.Secret) - responseUser := user - responseUser.Credentials.Secret = newSecret - - cases := []struct { - desc string - oldSecret string - newSecret string - session authn.Session - retrieveByIDResponse users.User - retrieveByEmailResponse users.User - updateSecretResponse users.User - issueResponse *magistrala.Token - response users.User - retrieveByIDErr error - retrieveByEmailErr error - updateSecretErr error - issueErr error - err error - }{ - { - desc: "update user secret with valid token", - oldSecret: user.Credentials.Secret, - newSecret: newSecret, - session: authn.Session{UserID: user.ID}, - retrieveByEmailResponse: rUser, - retrieveByIDResponse: user, - updateSecretResponse: responseUser, - issueResponse: &magistrala.Token{AccessToken: validToken}, - response: responseUser, - err: nil, - }, - { - desc: "update user secret with failed to retrieve user by ID", - oldSecret: user.Credentials.Secret, - newSecret: newSecret, - session: authn.Session{UserID: user.ID}, - retrieveByIDResponse: users.User{}, - retrieveByIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "update user secret with failed to retrieve user by email", - oldSecret: user.Credentials.Secret, - newSecret: newSecret, - session: authn.Session{UserID: user.ID}, - retrieveByIDResponse: user, - retrieveByEmailResponse: users.User{}, - retrieveByEmailErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "update user secret with invalod old secret", - oldSecret: "invalid", - newSecret: newSecret, - session: authn.Session{UserID: user.ID}, - retrieveByIDResponse: user, - retrieveByEmailResponse: rUser, - err: svcerr.ErrLogin, - }, - { - desc: "update user secret with too long new secret", - oldSecret: user.Credentials.Secret, - newSecret: strings.Repeat("a", 73), - session: authn.Session{UserID: user.ID}, - retrieveByIDResponse: user, - retrieveByEmailResponse: rUser, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "update user secret with failed to update secret", - oldSecret: user.Credentials.Secret, - newSecret: newSecret, - session: authn.Session{UserID: user.ID}, - retrieveByIDResponse: user, - retrieveByEmailResponse: rUser, - updateSecretResponse: users.User{}, - updateSecretErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("RetrieveByID", context.Background(), user.ID).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) - repoCall1 := cRepo.On("RetrieveByUsername", context.Background(), user.Credentials.Username).Return(tc.retrieveByEmailResponse, tc.retrieveByEmailErr) - repoCall2 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.updateSecretResponse, tc.updateSecretErr) - authCall := authUser.On("Issue", context.Background(), mock.Anything).Return(tc.issueResponse, tc.issueErr) - updatedUser, err := svc.UpdateSecret(context.Background(), tc.session, tc.oldSecret, tc.newSecret) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - assert.Equal(t, tc.response, updatedUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, updatedUser)) - if tc.err == nil { - ok := repoCall.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.response.ID) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - ok = repoCall1.Parent.AssertCalled(t, "RetrieveByUsername", context.Background(), tc.response.Credentials.Username) - assert.True(t, ok, fmt.Sprintf("RetrieveByUsername was not called on %s", tc.desc)) - ok = repoCall2.Parent.AssertCalled(t, "UpdateSecret", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("UpdateSecret was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - authCall.Unset() - } -} - -func TestUpdateEmail(t *testing.T) { - svc, cRepo := newServiceMinimal() - - user2 := user - user2.Email = "updated@example.com" - - cases := []struct { - desc string - email string - token string - reqUserID string - id string - updateEmailResponse users.User - updateEmailErr error - checkSuperAdminErr error - err error - }{ - { - desc: "update user as normal user successfully", - email: "updated@example.com", - token: validToken, - reqUserID: user.ID, - id: user.ID, - updateEmailResponse: user2, - err: nil, - }, - { - desc: "update user email as normal user with repo error on update", - email: "updated@example.com", - token: validToken, - reqUserID: user.ID, - id: user.ID, - updateEmailResponse: users.User{}, - updateEmailErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - { - desc: "update user email as admin successfully", - email: "updated@example.com", - token: validToken, - id: user.ID, - err: nil, - }, - { - desc: "update user email as admin with repo error on update", - email: "updated@exmaple.com", - token: validToken, - reqUserID: user.ID, - id: user.ID, - updateEmailResponse: users.User{}, - updateEmailErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - { - desc: "update user as admin user with failed check on super admin", - email: "updated@exmaple.com", - token: validToken, - reqUserID: user.ID, - id: "", - updateEmailResponse: users.User{}, - updateEmailErr: errors.ErrMalformedEntity, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall1 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateEmailResponse, tc.updateEmailErr) - updatedUser, err := svc.UpdateEmail(context.Background(), authn.Session{DomainUserID: tc.reqUserID, UserID: validID, DomainID: validID}, tc.id, tc.email) - 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.updateEmailResponse, updatedUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateEmailResponse, updatedUser)) - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "Update", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateProfilePicture(t *testing.T) { - svc, cRepo := newServiceMinimal() - - user.ProfilePicture = "https://example.com/profile.jpg" - adminID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - user users.User - session authn.Session - updateProfilePicResponse users.User - updateProfilePicErr error - checkSuperAdminErr error - err error - }{ - { - desc: "update profile picture as normal user successfully", - user: user, - session: authn.Session{UserID: user.ID}, - updateProfilePicResponse: user, - err: nil, - }, - { - desc: "update profile picture as normal user with repo error on update", - user: user, - session: authn.Session{UserID: user.ID}, - updateProfilePicResponse: users.User{}, - updateProfilePicErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - { - desc: "update profile picture as admin successfully", - user: user, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - err: nil, - }, - { - desc: "update profile picture as admin with failed check on super admin", - user: user, - session: authn.Session{UserID: adminID}, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "update profile picture as admin with repo error on update", - user: user, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - updateProfilePicResponse: users.User{}, - updateProfilePicErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall1 := cRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateProfilePicResponse, tc.updateProfilePicErr) - updatedUser, err := svc.UpdateProfilePicture(context.Background(), tc.session, tc.user) - 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.updateProfilePicResponse, updatedUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateProfilePicResponse, updatedUser)) - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "Update", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateUsername(t *testing.T) { - svc, cRepo := newServiceMinimal() - - nuser := user - nuser.Credentials.Username = "newusername" - adminID := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - user users.User - session authn.Session - updateUsernameResponse users.User - updateUsernameErr error - checkSuperAdminErr error - err error - }{ - { - desc: "update username as normal user successfully", - user: user, - session: authn.Session{UserID: user.ID}, - updateUsernameResponse: nuser, - err: nil, - }, - { - desc: "update username as normal user with repo error on update", - user: user, - session: authn.Session{UserID: user.ID}, - updateUsernameResponse: users.User{}, - updateUsernameErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - { - desc: "update username as admin successfully", - user: user, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - updateUsernameResponse: nuser, - err: nil, - }, - { - desc: "update username as admin with failed check on super admin", - user: user, - session: authn.Session{UserID: adminID}, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "update username as admin with repo error on update", - user: user, - session: authn.Session{UserID: adminID, SuperAdmin: true}, - updateUsernameResponse: users.User{}, - updateUsernameErr: errors.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall1 := cRepo.On("UpdateUsername", context.Background(), mock.Anything).Return(tc.updateUsernameResponse, tc.updateUsernameErr) - updatedUser, err := svc.UpdateUsername(context.Background(), tc.session, tc.user.ID, tc.user.Credentials.Username) - 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.updateUsernameResponse, updatedUser, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.updateUsernameResponse, updatedUser)) - if tc.err == nil { - ok := repoCall1.Parent.AssertCalled(t, "UpdateUsername", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("UpdateUsername was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestEnableUser(t *testing.T) { - svc, cRepo := newServiceMinimal() - - enabledUser1 := users.User{ID: testsutil.GenerateUUID(t), Credentials: users.Credentials{Username: "user1@example.com", Secret: "password"}, Status: users.EnabledStatus} - disabledUser1 := users.User{ID: testsutil.GenerateUUID(t), Credentials: users.Credentials{Username: "user3@example.com", Secret: "password"}, Status: users.DisabledStatus} - endisabledUser1 := disabledUser1 - endisabledUser1.Status = users.EnabledStatus - - cases := []struct { - desc string - id string - user users.User - retrieveByIDResponse users.User - changeStatusResponse users.User - response users.User - retrieveByIDErr error - changeStatusErr error - checkSuperAdminErr error - err error - }{ - { - desc: "enable disabled user", - id: disabledUser1.ID, - user: disabledUser1, - retrieveByIDResponse: disabledUser1, - changeStatusResponse: endisabledUser1, - response: endisabledUser1, - err: nil, - }, - { - desc: "enable disabled user with normal user token", - id: disabledUser1.ID, - user: disabledUser1, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "enable disabled user with failed to retrieve user by ID", - id: disabledUser1.ID, - user: disabledUser1, - retrieveByIDResponse: users.User{}, - retrieveByIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "enable already enabled user", - id: enabledUser1.ID, - user: enabledUser1, - retrieveByIDResponse: enabledUser1, - err: errors.ErrStatusAlreadyAssigned, - }, - { - desc: "enable disabled user with failed to change status", - id: disabledUser1.ID, - user: disabledUser1, - retrieveByIDResponse: disabledUser1, - changeStatusResponse: users.User{}, - changeStatusErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall1 := cRepo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) - repoCall2 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) - - _, err := svc.Enable(context.Background(), authn.Session{}, tc.id) - 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 { - ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - ok = repoCall2.Parent.AssertCalled(t, "ChangeStatus", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("ChangeStatus was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - } -} - -func TestDisableUser(t *testing.T) { - svc, cRepo := newServiceMinimal() - - enabledUser1 := users.User{ID: testsutil.GenerateUUID(t), Credentials: users.Credentials{Username: "user1@example.com", Secret: "password"}, Status: users.EnabledStatus} - disabledUser1 := users.User{ID: testsutil.GenerateUUID(t), Credentials: users.Credentials{Username: "user3@example.com", Secret: "password"}, Status: users.DisabledStatus} - disenabledUser1 := enabledUser1 - disenabledUser1.Status = users.DisabledStatus - - cases := []struct { - desc string - id string - user users.User - retrieveByIDResponse users.User - changeStatusResponse users.User - response users.User - retrieveByIDErr error - changeStatusErr error - checkSuperAdminErr error - err error - }{ - { - desc: "disable enabled user", - id: enabledUser1.ID, - user: enabledUser1, - retrieveByIDResponse: enabledUser1, - changeStatusResponse: disenabledUser1, - response: disenabledUser1, - err: nil, - }, - { - desc: "disable enabled user with normal user token", - id: enabledUser1.ID, - user: enabledUser1, - checkSuperAdminErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "disable enabled user with failed to retrieve user by ID", - id: enabledUser1.ID, - user: enabledUser1, - retrieveByIDResponse: users.User{}, - retrieveByIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "disable already disabled user", - id: disabledUser1.ID, - user: disabledUser1, - retrieveByIDResponse: disabledUser1, - err: errors.ErrStatusAlreadyAssigned, - }, - { - desc: "disable enabled user with failed to change status", - id: enabledUser1.ID, - user: enabledUser1, - changeStatusResponse: users.User{}, - changeStatusErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall1 := cRepo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) - repoCall2 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) - - _, err := svc.Disable(context.Background(), authn.Session{}, tc.id) - 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 { - ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - ok = repoCall2.Parent.AssertCalled(t, "ChangeStatus", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("ChangeStatus was not called on %s", tc.desc)) - } - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - } -} - -func TestDeleteUser(t *testing.T) { - svc, cRepo := newServiceMinimal() - - enabledUser1 := users.User{ID: testsutil.GenerateUUID(t), Credentials: users.Credentials{Username: "user1@example.com", Secret: "password"}, Status: users.EnabledStatus} - deletedUser1 := users.User{ID: testsutil.GenerateUUID(t), Credentials: users.Credentials{Username: "user3@example.com", Secret: "password"}, Status: users.DeletedStatus} - disenabledUser1 := enabledUser1 - disenabledUser1.Status = users.DeletedStatus - - cases := []struct { - desc string - id string - session authn.Session - user users.User - retrieveByIDResponse users.User - changeStatusResponse users.User - response users.User - retrieveByIDErr error - changeStatusErr error - checkSuperAdminErr error - err error - }{ - { - desc: "delete enabled user", - id: enabledUser1.ID, - user: enabledUser1, - session: authn.Session{UserID: validID, SuperAdmin: true}, - retrieveByIDResponse: enabledUser1, - changeStatusResponse: disenabledUser1, - response: disenabledUser1, - err: nil, - }, - { - desc: "delete enabled user with failed to retrieve user by ID", - id: enabledUser1.ID, - user: enabledUser1, - session: authn.Session{UserID: validID, SuperAdmin: true}, - retrieveByIDResponse: users.User{}, - retrieveByIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "delete already deleted user", - id: deletedUser1.ID, - user: deletedUser1, - session: authn.Session{UserID: validID, SuperAdmin: true}, - retrieveByIDResponse: deletedUser1, - err: errors.ErrStatusAlreadyAssigned, - }, - { - desc: "delete enabled user with failed to change status", - id: enabledUser1.ID, - user: enabledUser1, - session: authn.Session{UserID: validID, SuperAdmin: true}, - retrieveByIDResponse: enabledUser1, - changeStatusResponse: users.User{}, - changeStatusErr: repoerr.ErrMalformedEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - for _, tc := range cases { - repoCall2 := cRepo.On("CheckSuperAdmin", context.Background(), mock.Anything).Return(tc.checkSuperAdminErr) - repoCall3 := cRepo.On("RetrieveByID", context.Background(), tc.id).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) - repoCall4 := cRepo.On("ChangeStatus", context.Background(), mock.Anything).Return(tc.changeStatusResponse, tc.changeStatusErr) - err := svc.Delete(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)) - if tc.err == nil { - ok := repoCall3.Parent.AssertCalled(t, "RetrieveByID", context.Background(), tc.id) - assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) - ok = repoCall4.Parent.AssertCalled(t, "ChangeStatus", context.Background(), mock.Anything) - assert.True(t, ok, fmt.Sprintf("ChangeStatus was not called on %s", tc.desc)) - } - repoCall2.Unset() - repoCall3.Unset() - repoCall4.Unset() - } -} - -func TestListMembers(t *testing.T) { - svc, _, cRepo, policies, _ := newService() - - validPolicy := fmt.Sprintf("%s_%s", validID, user.ID) - permissionsUser := basicUser - permissionsUser.Permissions = []string{"read"} - - cases := []struct { - desc string - groupID string - objectKind string - objectID string - page users.Page - listAllSubjectsReq policysvc.Policy - listAllSubjectsResponse policysvc.PolicyPage - retrieveAllResponse users.UsersPage - listPermissionsResponse policysvc.Permissions - response users.MembersPage - listAllSubjectsErr error - retrieveAllErr error - identifyErr error - listPermissionErr error - err error - }{ - { - desc: "list members with no policies successfully of the things kind", - groupID: validID, - objectKind: policysvc.ThingsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsResponse: policysvc.PolicyPage{}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ThingType, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 100, - }, - }, - err: nil, - }, - { - desc: "list members with policies successsfully of the things kind", - groupID: validID, - objectKind: policysvc.ThingsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ThingType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{user}, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Members: []users.User{basicUser}, - }, - err: nil, - }, - { - desc: "list members with policies successsfully of the things kind with permissions", - groupID: validID, - objectKind: policysvc.ThingsKind, - 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, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{basicUser}, - }, - listPermissionsResponse: []string{"read"}, - response: users.MembersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Members: []users.User{permissionsUser}, - }, - err: nil, - }, - { - desc: "list members with policies of the things kind with permissionswith failed list permissions", - groupID: validID, - objectKind: policysvc.ThingsKind, - 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, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{user}, - }, - listPermissionsResponse: []string{}, - response: users.MembersPage{}, - listPermissionErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "list members with of the things kind with failed to list all subjects", - groupID: validID, - objectKind: policysvc.ThingsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ThingType, - }, - listAllSubjectsErr: repoerr.ErrNotFound, - listAllSubjectsResponse: policysvc.PolicyPage{}, - err: repoerr.ErrNotFound, - }, - { - desc: "list members with of the things kind with failed to retrieve all", - groupID: validID, - objectKind: policysvc.ThingsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.ThingType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{}, - response: users.MembersPage{}, - retrieveAllErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "list members with no policies successfully of the domain kind", - groupID: validID, - objectKind: policysvc.DomainsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsResponse: policysvc.PolicyPage{}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.DomainType, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 100, - }, - }, - err: nil, - }, - { - desc: "list members with policies successsfully of the domains kind", - groupID: validID, - objectKind: policysvc.DomainsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.DomainType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{basicUser}, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Members: []users.User{basicUser}, - }, - err: nil, - }, - { - desc: "list members with no policies successfully of the groups kind", - groupID: validID, - objectKind: policysvc.GroupsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsResponse: policysvc.PolicyPage{}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.GroupType, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 0, - Offset: 0, - Limit: 100, - }, - }, - err: nil, - }, - { - desc: "list members with policies successsfully of the groups kind", - - groupID: validID, - objectKind: policysvc.GroupsKind, - objectID: validID, - page: users.Page{Offset: 0, Limit: 100, Permission: "read"}, - listAllSubjectsReq: policysvc.Policy{ - SubjectType: policysvc.UserType, - Permission: "read", - Object: validID, - ObjectType: policysvc.GroupType, - }, - listAllSubjectsResponse: policysvc.PolicyPage{Policies: []string{validPolicy}}, - retrieveAllResponse: users.UsersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Users: []users.User{user}, - }, - response: users.MembersPage{ - Page: users.Page{ - Total: 1, - Offset: 0, - Limit: 100, - }, - Members: []users.User{basicUser}, - }, - err: nil, - }, - } - - for _, tc := range cases { - policyCall := policies.On("ListAllSubjects", context.Background(), tc.listAllSubjectsReq).Return(tc.listAllSubjectsResponse, tc.listAllSubjectsErr) - repoCall := cRepo.On("RetrieveAll", context.Background(), mock.Anything).Return(tc.retrieveAllResponse, tc.retrieveAllErr) - policyCall1 := policies.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(tc.listPermissionsResponse, tc.listPermissionErr) - page, err := svc.ListMembers(context.Background(), authn.Session{}, tc.objectKind, tc.objectID, 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() - policyCall1.Unset() - } -} - -func TestIssueToken(t *testing.T) { - svc, auth, cRepo, _, _ := newService() - - rUser := user - rUser2 := user - rUser3 := user - rUser.Credentials.Secret, _ = phasher.Hash(user.Credentials.Secret) - rUser2.Credentials.Secret = "wrongsecret" - rUser3.Credentials.Secret, _ = phasher.Hash("wrongsecret") - - cases := []struct { - desc string - user users.User - retrieveByUsernameResponse users.User - issueResponse *magistrala.Token - retrieveByUsernameErr error - issueErr error - err error - }{ - { - desc: "issue token for an existing user", - user: user, - retrieveByUsernameResponse: rUser, - issueResponse: &magistrala.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"}, - err: nil, - }, - { - desc: "issue token for a non-existing user", - user: user, - retrieveByUsernameResponse: users.User{}, - retrieveByUsernameErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "issue token for a user with wrong secret", - user: user, - retrieveByUsernameResponse: rUser3, - err: svcerr.ErrLogin, - }, - { - desc: "issue token with empty domain id", - user: user, - retrieveByUsernameResponse: rUser, - issueResponse: &magistrala.Token{}, - issueErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "issue token with grpc error", - user: user, - retrieveByUsernameResponse: rUser, - issueResponse: &magistrala.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() - }) - } -} - -func TestRefreshToken(t *testing.T) { - svc, authsvc, crepo, _, _ := newService() - - rUser := user - rUser.Credentials.Secret, _ = phasher.Hash(user.Credentials.Secret) - - cases := []struct { - desc string - session authn.Session - refreshResp *magistrala.Token - refresErr error - repoResp users.User - repoErr error - err error - }{ - { - 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"}, - 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{}, - refresErr: svcerr.ErrAuthentication, - repoResp: rUser, - err: svcerr.ErrAuthentication, - }, - { - desc: "refresh token with refresh token for a non-existing client", - session: authn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, - repoErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "refresh token with refresh token for a disable user", - session: authn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, - repoResp: users.User{Status: users.DisabledStatus}, - err: svcerr.ErrAuthentication, - }, - { - desc: "refresh token with empty domain id", - session: authn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}, - refreshResp: &magistrala.Token{}, - refresErr: svcerr.ErrAuthentication, - repoResp: rUser, - err: svcerr.ErrAuthentication, - }, - } - - 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() - }) - } -} - -func TestGenerateResetToken(t *testing.T) { - svc, auth, cRepo, _, e := newService() - - cases := []struct { - desc string - email string - host string - retrieveByEmailResponse users.User - issueResponse *magistrala.Token - retrieveByEmailErr error - issueErr error - err error - }{ - { - desc: "generate reset token for existing user", - email: "existingemail@example.com", - host: "examplehost", - retrieveByEmailResponse: user, - issueResponse: &magistrala.Token{AccessToken: validToken, RefreshToken: &validToken, AccessType: "3"}, - err: nil, - }, - { - desc: "generate reset token for user with non-existing user", - email: "example@example.com", - host: "examplehost", - retrieveByEmailResponse: users.User{ - ID: testsutil.GenerateUUID(t), - Email: "", - }, - retrieveByEmailErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "generate reset token with failed to issue token", - email: "existingemail@example.com", - host: "examplehost", - retrieveByEmailResponse: user, - issueResponse: &magistrala.Token{}, - issueErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := cRepo.On("RetrieveByEmail", context.Background(), tc.email).Return(tc.retrieveByEmailResponse, tc.retrieveByEmailErr) - authCall := auth.On("Issue", context.Background(), mock.Anything).Return(tc.issueResponse, tc.issueErr) - svcCall := e.On("SendPasswordReset", []string{tc.email}, tc.host, user.Credentials.Username, validToken).Return(tc.err) - err := svc.GenerateResetToken(context.Background(), tc.email, tc.host) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Parent.AssertCalled(t, "RetrieveByEmail", context.Background(), tc.email) - repoCall.Unset() - authCall.Unset() - svcCall.Unset() - }) - } -} - -func TestResetSecret(t *testing.T) { - svc, cRepo := newServiceMinimal() - - user := users.User{ - ID: "userID", - Email: "test@example.com", - Credentials: users.Credentials{ - Secret: "Strongsecret", - }, - } - - cases := []struct { - desc string - newSecret string - session authn.Session - retrieveByIDResponse users.User - updateSecretResponse users.User - retrieveByIDErr error - updateSecretErr error - err error - }{ - { - desc: "reset secret with successfully", - newSecret: "newStrongSecret", - session: authn.Session{UserID: validID, SuperAdmin: true}, - retrieveByIDResponse: user, - updateSecretResponse: users.User{ - ID: "userID", - Email: "test@example.com", - Credentials: users.Credentials{ - Secret: "newStrongSecret", - }, - }, - err: nil, - }, - { - desc: "reset secret with invalid ID", - newSecret: "newStrongSecret", - session: authn.Session{UserID: validID, SuperAdmin: true}, - retrieveByIDResponse: users.User{}, - retrieveByIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - { - desc: "reset secret with empty email", - session: authn.Session{UserID: validID, SuperAdmin: true}, - newSecret: "newStrongSecret", - retrieveByIDResponse: users.User{ - ID: "userID", - Email: "", - }, - err: nil, - }, - { - desc: "reset secret with failed to update secret", - newSecret: "newStrongSecret", - session: authn.Session{UserID: validID, SuperAdmin: true}, - retrieveByIDResponse: user, - updateSecretResponse: users.User{}, - updateSecretErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrAuthorization, - }, - { - desc: "reset secret with a too long secret", - newSecret: strings.Repeat("strongSecret", 10), - session: authn.Session{UserID: validID, SuperAdmin: true}, - retrieveByIDResponse: user, - err: errHashPassword, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) - repoCall1 := cRepo.On("UpdateSecret", context.Background(), mock.Anything).Return(tc.updateSecretResponse, tc.updateSecretErr) - err := svc.ResetSecret(context.Background(), tc.session, tc.newSecret) - 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 { - repoCall1.Parent.AssertCalled(t, "UpdateSecret", context.Background(), mock.Anything) - repoCall.Parent.AssertCalled(t, "RetrieveByID", context.Background(), validID) - } - repoCall1.Unset() - repoCall.Unset() - }) - } -} - -func TestViewProfile(t *testing.T) { - svc, cRepo := newServiceMinimal() - - user := users.User{ - ID: "userID", - Email: "existingEmail", - Credentials: users.Credentials{ - Secret: "Strongsecret", - }, - } - cases := []struct { - desc string - user users.User - session authn.Session - retrieveByIDResponse users.User - retrieveByIDErr error - err error - }{ - { - desc: "view profile successfully", - user: user, - session: authn.Session{UserID: validID}, - retrieveByIDResponse: user, - err: nil, - }, - { - desc: "view profile with invalid ID", - user: user, - session: authn.Session{UserID: wrongID}, - retrieveByIDResponse: users.User{}, - retrieveByIDErr: repoerr.ErrNotFound, - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := cRepo.On("RetrieveByID", context.Background(), mock.Anything).Return(tc.retrieveByIDResponse, tc.retrieveByIDErr) - _, err := svc.ViewProfile(context.Background(), tc.session) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Parent.AssertCalled(t, "RetrieveByID", context.Background(), mock.Anything) - repoCall.Unset() - }) - } -} - -func TestOAuthCallback(t *testing.T) { - svc, _, cRepo, policies, _ := newService() - - cases := []struct { - desc string - user users.User - retrieveByEmailResponse users.User - retrieveByEmailErr error - saveResponse users.User - addPoliciesErr error - err error - }{ - { - desc: "oauth signin callback with already existing user", - user: users.User{ - Email: "test@example.com", - }, - retrieveByEmailResponse: users.User{ - ID: testsutil.GenerateUUID(t), - Role: users.UserRole, - }, - err: nil, - }, - { - desc: "oauth signup callback with user not found", - user: users.User{ - Email: "test@example.com", - }, - retrieveByEmailErr: repoerr.ErrNotFound, - saveResponse: users.User{ - ID: testsutil.GenerateUUID(t), - Role: users.UserRole, - }, - err: nil, - }, - { - desc: "oauth signup callback with malformed entity", - user: users.User{ - Email: "test@example.com", - }, - retrieveByEmailErr: repoerr.ErrMalformedEntity, - err: repoerr.ErrMalformedEntity, - }, - { - desc: "oauth signup callback with failed to register user", - user: users.User{ - Email: "test@example.com", - }, - addPoliciesErr: svcerr.ErrAuthorization, - retrieveByEmailErr: repoerr.ErrNotFound, - err: svcerr.ErrAuthorization, - }, - { - desc: "oauth signin callback with user not in the platform", - user: users.User{ - Email: "test@example.com", - }, - retrieveByEmailResponse: users.User{ - ID: testsutil.GenerateUUID(t), - Role: users.UserRole, - }, - err: nil, - }, - } - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := cRepo.On("RetrieveByEmail", context.Background(), tc.user.Email).Return(tc.retrieveByEmailResponse, tc.retrieveByEmailErr) - repoCall1 := cRepo.On("Save", context.Background(), mock.Anything).Return(tc.saveResponse, nil) - policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesErr) - _, err := svc.OAuthCallback(context.Background(), tc.user) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Parent.AssertCalled(t, "RetrieveByEmail", context.Background(), tc.user.Email) - repoCall.Unset() - repoCall1.Unset() - policyCall.Unset() - }) - } -} diff --git a/users/status.go b/users/status.go deleted file mode 100644 index 974cec227..000000000 --- a/users/status.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package users - -import ( - "encoding/json" - "strings" - - svcerr "github.com/absmach/magistrala/pkg/errors/service" -) - -// Status represents User status. -type Status uint8 - -// Possible User status values. -const ( - // EnabledStatus represents enabled User. - EnabledStatus Status = iota - // DisabledStatus represents disabled User. - DisabledStatus - // DeletedStatus represents a user that will be deleted. - DeletedStatus - - // AllStatus is used for querying purposes to list users irrespective - // of their status - both enabled and disabled. It is never stored in the - // database as the actual User status and should always be the largest - // value in this enumeration. - AllStatus -) - -// String representation of the possible status values. -const ( - Disabled = "disabled" - Enabled = "enabled" - Deleted = "deleted" - All = "all" - Unknown = "unknown" -) - -// String converts user/group status to string literal. -func (s Status) String() string { - switch s { - case DisabledStatus: - return Disabled - case EnabledStatus: - return Enabled - case DeletedStatus: - return Deleted - case AllStatus: - return All - default: - return Unknown - } -} - -// ToStatus converts string value to a valid User/Group status. -func ToStatus(status string) (Status, error) { - switch status { - case "", Enabled: - return EnabledStatus, nil - case Disabled: - return DisabledStatus, nil - case Deleted: - return DeletedStatus, nil - case All: - return AllStatus, nil - } - return Status(0), svcerr.ErrInvalidStatus -} - -// Custom Marshaller for Uesr/Groups. -func (s Status) MarshalJSON() ([]byte, error) { - return json.Marshal(s.String()) -} - -// Custom Unmarshaler for User/Groups. -func (s *Status) UnmarshalJSON(data []byte) error { - str := strings.Trim(string(data), "\"") - val, err := ToStatus(str) - *s = val - return err -} diff --git a/users/tracing/doc.go b/users/tracing/doc.go deleted file mode 100644 index 5aa1b44b9..000000000 --- a/users/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala Users service. -// -// This package provides tracing middleware for Magistrala Users service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala Users 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/users/tracing/tracing.go b/users/tracing/tracing.go deleted file mode 100644 index 81ad0dcb5..000000000 --- a/users/tracing/tracing.go +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/authn" - users "github.com/absmach/magistrala/users" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ users.Service = (*tracingMiddleware)(nil) - -type tracingMiddleware struct { - tracer trace.Tracer - svc users.Service -} - -// New returns a new group service with tracing capabilities. -func New(svc users.Service, tracer trace.Tracer) users.Service { - return &tracingMiddleware{tracer, svc} -} - -// Register traces the "Register" operation of the wrapped users.Service. -func (tm *tracingMiddleware) Register(ctx context.Context, session authn.Session, user users.User, selfRegister bool) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_register_user", trace.WithAttributes(attribute.String("email", user.Email))) - defer span.End() - - return tm.svc.Register(ctx, session, user, selfRegister) -} - -// IssueToken traces the "IssueToken" operation of the wrapped users.Service. -func (tm *tracingMiddleware) IssueToken(ctx context.Context, username, secret string) (*magistrala.Token, error) { - ctx, span := tm.tracer.Start(ctx, "svc_issue_token", trace.WithAttributes(attribute.String("username", username))) - defer span.End() - - return tm.svc.IssueToken(ctx, username, secret) -} - -// 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) { - ctx, span := tm.tracer.Start(ctx, "svc_refresh_token", trace.WithAttributes(attribute.String("refresh_token", refreshToken))) - defer span.End() - - return tm.svc.RefreshToken(ctx, session, refreshToken) -} - -// View traces the "View" operation of the wrapped users.Service. -func (tm *tracingMiddleware) View(ctx context.Context, session authn.Session, id string) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_view_user", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return tm.svc.View(ctx, session, id) -} - -// ListUsers traces the "ListUsers" operation of the wrapped users.Service. -func (tm *tracingMiddleware) ListUsers(ctx context.Context, session authn.Session, pm users.Page) (users.UsersPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_users", trace.WithAttributes( - attribute.Int64("offset", int64(pm.Offset)), - attribute.Int64("limit", int64(pm.Limit)), - attribute.String("direction", pm.Dir), - attribute.String("order", pm.Order), - )) - - defer span.End() - - return tm.svc.ListUsers(ctx, session, pm) -} - -// SearchUsers traces the "SearchUsers" operation of the wrapped users.Service. -func (tm *tracingMiddleware) SearchUsers(ctx context.Context, pm users.Page) (users.UsersPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_search_users", trace.WithAttributes( - attribute.Int64("offset", int64(pm.Offset)), - attribute.Int64("limit", int64(pm.Limit)), - attribute.String("direction", pm.Dir), - attribute.String("order", pm.Order), - )) - defer span.End() - - return tm.svc.SearchUsers(ctx, pm) -} - -// Update traces the "Update" operation of the wrapped users.Service. -func (tm *tracingMiddleware) Update(ctx context.Context, session authn.Session, cli users.User) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_user", trace.WithAttributes( - attribute.String("id", cli.ID), - attribute.String("first_name", cli.FirstName), - attribute.String("last_name", cli.LastName), - )) - defer span.End() - - return tm.svc.Update(ctx, session, cli) -} - -// UpdateTags traces the "UpdateTags" operation of the wrapped users.Service. -func (tm *tracingMiddleware) UpdateTags(ctx context.Context, session authn.Session, cli users.User) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_user_tags", trace.WithAttributes( - attribute.String("id", cli.ID), - attribute.StringSlice("tags", cli.Tags), - )) - defer span.End() - - return tm.svc.UpdateTags(ctx, session, cli) -} - -// UpdateEmail traces the "UpdateEmail" operation of the wrapped users.Service. -func (tm *tracingMiddleware) UpdateEmail(ctx context.Context, session authn.Session, id, email string) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_user_email", trace.WithAttributes( - attribute.String("id", id), - attribute.String("email", email), - )) - defer span.End() - - return tm.svc.UpdateEmail(ctx, session, id, email) -} - -// UpdateSecret traces the "UpdateSecret" operation of the wrapped users.Service. -func (tm *tracingMiddleware) UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_user_secret") - defer span.End() - - return tm.svc.UpdateSecret(ctx, session, oldSecret, newSecret) -} - -// UpdateUsername traces the "UpdateUsername" operation of the wrapped users.Service. -func (tm *tracingMiddleware) UpdateUsername(ctx context.Context, session authn.Session, id, username string) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_usernames", trace.WithAttributes( - attribute.String("id", id), - attribute.String("username", username), - )) - defer span.End() - - return tm.svc.UpdateUsername(ctx, session, id, username) -} - -// UpdateProfilePicture traces the "UpdateProfilePicture" operation of the wrapped users.Service. -func (tm *tracingMiddleware) UpdateProfilePicture(ctx context.Context, session authn.Session, usr users.User) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_profile_picture", trace.WithAttributes(attribute.String("id", usr.ID))) - defer span.End() - - return tm.svc.UpdateProfilePicture(ctx, session, usr) -} - -// GenerateResetToken traces the "GenerateResetToken" operation of the wrapped users.Service. -func (tm *tracingMiddleware) GenerateResetToken(ctx context.Context, email, host string) error { - ctx, span := tm.tracer.Start(ctx, "svc_generate_reset_token", trace.WithAttributes( - attribute.String("email", email), - attribute.String("host", host), - )) - defer span.End() - - return tm.svc.GenerateResetToken(ctx, email, host) -} - -// ResetSecret traces the "ResetSecret" operation of the wrapped users.Service. -func (tm *tracingMiddleware) ResetSecret(ctx context.Context, session authn.Session, secret string) error { - ctx, span := tm.tracer.Start(ctx, "svc_reset_secret") - defer span.End() - - return tm.svc.ResetSecret(ctx, session, secret) -} - -// SendPasswordReset traces the "SendPasswordReset" operation of the wrapped users.Service. -func (tm *tracingMiddleware) SendPasswordReset(ctx context.Context, host, email, user, token string) error { - ctx, span := tm.tracer.Start(ctx, "svc_send_password_reset", trace.WithAttributes( - attribute.String("email", email), - attribute.String("user", user), - )) - defer span.End() - - return tm.svc.SendPasswordReset(ctx, host, email, user, token) -} - -// ViewProfile traces the "ViewProfile" operation of the wrapped users.Service. -func (tm *tracingMiddleware) ViewProfile(ctx context.Context, session authn.Session) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_view_profile") - defer span.End() - - return tm.svc.ViewProfile(ctx, session) -} - -// UpdateRole traces the "UpdateRole" operation of the wrapped users.Service. -func (tm *tracingMiddleware) UpdateRole(ctx context.Context, session authn.Session, cli users.User) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_update_user_role", trace.WithAttributes( - attribute.String("id", cli.ID), - attribute.StringSlice("tags", cli.Tags), - )) - defer span.End() - - return tm.svc.UpdateRole(ctx, session, cli) -} - -// Enable traces the "Enable" operation of the wrapped users.Service. -func (tm *tracingMiddleware) Enable(ctx context.Context, session authn.Session, id string) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_enable_user", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return tm.svc.Enable(ctx, session, id) -} - -// Disable traces the "Disable" operation of the wrapped users.Service. -func (tm *tracingMiddleware) Disable(ctx context.Context, session authn.Session, id string) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_disable_user", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return tm.svc.Disable(ctx, session, id) -} - -// ListMembers traces the "ListMembers" operation of the wrapped users.Service. -func (tm *tracingMiddleware) ListMembers(ctx context.Context, session authn.Session, objectKind, objectID string, pm users.Page) (users.MembersPage, error) { - ctx, span := tm.tracer.Start(ctx, "svc_list_members", trace.WithAttributes(attribute.String("object_kind", objectKind)), trace.WithAttributes(attribute.String("object_id", objectID))) - defer span.End() - - return tm.svc.ListMembers(ctx, session, objectKind, objectID, pm) -} - -// Identify traces the "Identify" operation of the wrapped users.Service. -func (tm *tracingMiddleware) Identify(ctx context.Context, session authn.Session) (string, error) { - ctx, span := tm.tracer.Start(ctx, "svc_identify", trace.WithAttributes(attribute.String("user_id", session.UserID))) - defer span.End() - - return tm.svc.Identify(ctx, session) -} - -// OAuthCallback traces the "OAuthCallback" operation of the wrapped users.Service. -func (tm *tracingMiddleware) OAuthCallback(ctx context.Context, user users.User) (users.User, error) { - ctx, span := tm.tracer.Start(ctx, "svc_oauth_callback", trace.WithAttributes( - attribute.String("user_id", user.ID), - )) - defer span.End() - - return tm.svc.OAuthCallback(ctx, user) -} - -// Delete traces the "Delete" operation of the wrapped users.Service. -func (tm *tracingMiddleware) Delete(ctx context.Context, session authn.Session, id string) error { - ctx, span := tm.tracer.Start(ctx, "svc_delete_user", trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return tm.svc.Delete(ctx, session, id) -} - -// OAuthAddUserPolicy traces the "OAuthAddUserPolicy" operation of the wrapped users.Service. -func (tm *tracingMiddleware) OAuthAddUserPolicy(ctx context.Context, user users.User) error { - ctx, span := tm.tracer.Start(ctx, "svc_add_user_policy", trace.WithAttributes( - attribute.String("id", user.ID), - )) - defer span.End() - - return tm.svc.OAuthAddUserPolicy(ctx, user) -} diff --git a/users/users.go b/users/users.go deleted file mode 100644 index 8fe96042c..000000000 --- a/users/users.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package users - -import ( - "context" - "net/mail" - "time" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/postgres" -) - -type User struct { - ID string `json:"id"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - Tags []string `json:"tags,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Status Status `json:"status"` // 0 for enabled, 1 for disabled - Role Role `json:"role"` // 0 for normal user, 1 for admin - ProfilePicture string `json:"profile_picture,omitempty"` // profile picture URL - Credentials Credentials `json:"credentials,omitempty"` - Permissions []string `json:"permissions,omitempty"` - Email string `json:"email,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` -} - -type Credentials struct { - Username string `json:"username,omitempty"` // username or profile name - Secret string `json:"secret,omitempty"` // password or token -} - -type UsersPage struct { - Page - Users []User -} - -// Metadata represents arbitrary JSON. -type Metadata map[string]interface{} - -// MembersPage contains page related metadata as well as list of members that -// belong to this page. -type MembersPage struct { - Page - Members []User -} - -// UserRepository struct implements the Repository interface. -type UserRepository struct { - DB postgres.Database -} - -//go:generate mockery --name Repository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" -type Repository interface { - // RetrieveByID retrieves user by their unique ID. - RetrieveByID(ctx context.Context, id string) (User, error) - - // RetrieveAll retrieves all users. - RetrieveAll(ctx context.Context, pm Page) (UsersPage, error) - - // RetrieveByEmail retrieves user by its unique credentials. - RetrieveByEmail(ctx context.Context, email string) (User, error) - - // RetrieveByUsername retrieves user by its unique credentials. - RetrieveByUsername(ctx context.Context, username string) (User, error) - - // Update updates the user name and metadata. - Update(ctx context.Context, user User) (User, error) - - // UpdateUsername updates the User's names. - UpdateUsername(ctx context.Context, user User) (User, error) - - // UpdateSecret updates secret for user with given email. - UpdateSecret(ctx context.Context, user User) (User, error) - - // ChangeStatus changes user status to enabled or disabled - ChangeStatus(ctx context.Context, user User) (User, error) - - // Delete deletes user with given id - Delete(ctx context.Context, id string) error - - // Searchusers retrieves users based on search criteria. - SearchUsers(ctx context.Context, pm Page) (UsersPage, error) - - // RetrieveAllByIDs retrieves for given user IDs . - RetrieveAllByIDs(ctx context.Context, pm Page) (UsersPage, error) - - CheckSuperAdmin(ctx context.Context, adminID string) error - - // Save persists the user account. A non-nil error is returned to indicate - // operation failure. - Save(ctx context.Context, user User) (User, error) -} - -// Validate returns an error if user representation is invalid. -func (u User) Validate() error { - if !isEmail(u.Email) { - return errors.ErrMalformedEntity - } - return nil -} - -func isEmail(email string) bool { - _, err := mail.ParseAddress(email) - return err == nil -} - -// Page contains page metadata that helps navigation. -type Page struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Id string `json:"id,omitempty"` - Order string `json:"order,omitempty"` - Dir string `json:"dir,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` - Domain string `json:"domain,omitempty"` - Tag string `json:"tag,omitempty"` - Permission string `json:"permission,omitempty"` - Status Status `json:"status,omitempty"` - IDs []string `json:"ids,omitempty"` - Role Role `json:"-"` - ListPerms bool `json:"-"` - Username string `json:"username,omitempty"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - Email string `json:"email,omitempty"` -} - -// 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 --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" -type Service interface { - // Register creates new user. In case of the failed registration, a - // non-nil error value is returned. - Register(ctx context.Context, session authn.Session, user User, selfRegister bool) (User, error) - - // View retrieves user info for a given user ID and an authorized token. - View(ctx context.Context, session authn.Session, id string) (User, error) - - // ViewProfile retrieves user info for a given token. - ViewProfile(ctx context.Context, session authn.Session) (User, error) - - // 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(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. - SearchUsers(ctx context.Context, pm Page) (UsersPage, error) - - // Update updates the user's name and metadata. - Update(ctx context.Context, session authn.Session, user User) (User, error) - - // UpdateTags updates the user's tags. - UpdateTags(ctx context.Context, session authn.Session, user User) (User, error) - - // UpdateEmail updates the user's email. - UpdateEmail(ctx context.Context, session authn.Session, id, email string) (User, error) - - // UpdateUsername updates the user's username. - UpdateUsername(ctx context.Context, session authn.Session, id, username string) (User, error) - - // UpdateProfilePicture updates the user's profile picture. - UpdateProfilePicture(ctx context.Context, session authn.Session, user User) (User, error) - - // GenerateResetToken email where mail will be sent. - // host is used for generating reset link. - GenerateResetToken(ctx context.Context, email, host string) error - - // UpdateSecret updates the user's secret. - UpdateSecret(ctx context.Context, session authn.Session, oldSecret, newSecret string) (User, error) - - // ResetSecret change users secret in reset flow. - // token can be authentication token or secret reset token. - ResetSecret(ctx context.Context, session authn.Session, secret string) error - - // SendPasswordReset sends reset password link to email. - SendPasswordReset(ctx context.Context, host, email, user, token string) error - - // UpdateRole updates the user's Role. - UpdateRole(ctx context.Context, session authn.Session, user User) (User, error) - - // Enable logically enables the user identified with the provided ID. - Enable(ctx context.Context, session authn.Session, id string) (User, error) - - // Disable logically disables the user identified with the provided ID. - Disable(ctx context.Context, session authn.Session, id string) (User, error) - - // Delete deletes user with given ID. - Delete(ctx context.Context, session authn.Session, id string) error - - // Identify returns the user id from the given token. - 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) - - // 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) - - // 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. - OAuthCallback(ctx context.Context, user User) (User, error) - - // OAuthAddUserPolicy adds a policy to the user for an OAuth request. - OAuthAddUserPolicy(ctx context.Context, user User) error -} diff --git a/uuid.go b/uuid.go deleted file mode 100644 index 29c5b2948..000000000 --- a/uuid.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package magistrala - -// IDProvider specifies an API for generating unique identifiers. -type IDProvider interface { - // ID generates the unique identifier. - ID() (string, error) -} diff --git a/ws/README.md b/ws/README.md deleted file mode 100644 index 61784314d..000000000 --- a/ws/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# WebSocket adapter - -WebSocket adapter provides a [WebSocket](https://en.wikipedia.org/wiki/WebSocket#:~:text=WebSocket%20is%20a%20computer%20communications,protocol%20is%20known%20as%20WebSockets.) API for sending and receiving messages through the platform. - -## 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_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 | "" | - -## 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. -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 ws -make ws - -# copy binary to bin -make install - -# set the environment variables and run the service -MG_WS_ADAPTER_LOG_LEVEL=info \ -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_MESSAGE_BROKER_URL=nats://localhost:4222 \ -MG_JAEGER_URL=http://localhost:14268/api/traces \ -MG_JAEGER_TRACE_RATIO=1.0 \ -MG_SEND_TELEMETRY=true \ -MG_WS_ADAPTER_INSTANCE_ID="" \ -$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. - -## Usage - -For more information about service capabilities and its usage, please check out the [WebSocket section](https://docs.magistrala.abstractmachines.fr/messaging/#websocket). diff --git a/ws/adapter.go b/ws/adapter.go deleted file mode 100644 index e92b04120..000000000 --- a/ws/adapter.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package ws - -import ( - "context" - "fmt" - - "github.com/absmach/magistrala" - "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" -) - -const chansPrefix = "channels" - -var ( - // errFailedMessagePublish indicates that message publishing failed. - errFailedMessagePublish = errors.New("failed to publish message") - - // ErrFailedSubscription indicates that client couldn't subscribe to specified channel. - ErrFailedSubscription = errors.New("failed to subscribe to a channel") - - // 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 = errors.New("empty topic") -) - -// Service specifies web socket service API. -type Service interface { - // Subscribe subscribes message from the broker using the thingKey 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 -} - -var _ Service = (*adapterService)(nil) - -type adapterService struct { - things magistrala.ThingsServiceClient - pubsub messaging.PubSub -} - -// New instantiates the WS adapter implementation. -func New(thingsClient magistrala.ThingsServiceClient, pubsub messaging.PubSub) Service { - return &adapterService{ - things: thingsClient, - pubsub: pubsub, - } -} - -func (svc *adapterService) Subscribe(ctx context.Context, thingKey, chanID, subtopic string, c *Client) error { - if chanID == "" || thingKey == "" { - return svcerr.ErrAuthentication - } - - thingID, err := svc.authorize(ctx, thingKey, chanID, policies.SubscribePermission) - if err != nil { - return svcerr.ErrAuthorization - } - - c.id = thingID - - subject := fmt.Sprintf("%s.%s", chansPrefix, chanID) - if subtopic != "" { - subject = fmt.Sprintf("%s.%s", subject, subtopic) - } - - subCfg := messaging.SubscriberConfig{ - ID: thingID, - Topic: subject, - Handler: c, - } - if err := svc.pubsub.Subscribe(ctx, subCfg); err != nil { - return ErrFailedSubscription - } - - 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, - ChannelId: chanID, - } - res, err := svc.things.Authorize(ctx, ar) - if err != nil { - return "", errors.Wrap(svcerr.ErrAuthorization, err) - } - if !res.GetAuthorized() { - return "", errors.Wrap(svcerr.ErrAuthorization, err) - } - - return res.GetId(), nil -} diff --git a/ws/adapter_test.go b/ws/adapter_test.go deleted file mode 100644 index 40323a2aa..000000000 --- a/ws/adapter_test.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package ws_test - -import ( - "context" - "fmt" - "testing" - - "github.com/absmach/magistrala" - "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" -) - -const ( - chanID = "1" - invalidID = "invalidID" - invalidKey = "invalidKey" - id = "1" - thingKey = "thing_key" - subTopic = "subtopic" - protocol = "ws" -) - -var msg = messaging.Message{ - Channel: chanID, - Publisher: id, - Subtopic: "", - Protocol: protocol, - Payload: []byte(`[{"n":"current","t":-5,"v":1.2}]`), -} - -func newService() (ws.Service, *mocks.PubSub, *thmocks.ThingsServiceClient) { - pubsub := new(mocks.PubSub) - things := new(thmocks.ThingsServiceClient) - - return ws.New(things, pubsub), pubsub, things -} - -func TestSubscribe(t *testing.T) { - svc, pubsub, things := newService() - - c := ws.NewClient(nil) - - cases := []struct { - desc string - thingKey string - chanID string - subtopic string - err error - }{ - { - desc: "subscribe to channel with valid thingKey, chanID, subtopic", - thingKey: thingKey, - chanID: chanID, - subtopic: subTopic, - err: nil, - }, - { - desc: "subscribe again to channel with valid thingKey, chanID, subtopic", - thingKey: thingKey, - chanID: chanID, - subtopic: subTopic, - err: nil, - }, - { - desc: "subscribe to channel with subscribe set to fail", - thingKey: thingKey, - chanID: chanID, - subtopic: subTopic, - err: ws.ErrFailedSubscription, - }, - { - desc: "subscribe to channel with invalid chanID and invalid thingKey", - thingKey: invalidKey, - chanID: invalidID, - subtopic: subTopic, - err: ws.ErrFailedSubscription, - }, - { - desc: "subscribe to channel with empty channel", - thingKey: thingKey, - chanID: "", - subtopic: subTopic, - err: svcerr.ErrAuthentication, - }, - { - desc: "subscribe to channel with empty thingKey", - thingKey: "", - chanID: chanID, - subtopic: subTopic, - err: svcerr.ErrAuthentication, - }, - { - desc: "subscribe to channel with empty thingKey and empty channel", - thingKey: "", - chanID: "", - subtopic: subTopic, - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - thingID := testsutil.GenerateUUID(t) - subConfig := messaging.SubscriberConfig{ - ID: thingID, - 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) - 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/doc.go b/ws/api/doc.go deleted file mode 100644 index 2424852cc..000000000 --- a/ws/api/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains API-related concerns: endpoint definitions, middlewares -// and all resource representations. -package api diff --git a/ws/api/endpoint_test.go b/ws/api/endpoint_test.go deleted file mode 100644 index 1bc1faf13..000000000 --- a/ws/api/endpoint_test.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" - - "github.com/absmach/magistrala" - mglog "github.com/absmach/magistrala/logger" - "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" - "github.com/absmach/mgate/pkg/websockets" - "github.com/gorilla/websocket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -const ( - chanID = "30315311-56ba-484d-b500-c1e08305511f" - id = "1" - thingKey = "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) { - pubsub := new(mocks.PubSub) - return ws.New(things, pubsub), pubsub -} - -func newHTTPServer(svc ws.Service) *httptest.Server { - mux := api.MakeHandler(context.Background(), svc, mglog.NewMock(), instanceID) - return httptest.NewServer(mux) -} - -func newProxyHTPPServer(svc session.Handler, targetServer *httptest.Server) (*httptest.Server, error) { - turl := strings.ReplaceAll(targetServer.URL, "http", "ws") - mp, err := websockets.NewProxy("", turl, mglog.NewMock(), svc) - if err != nil { - return nil, err - } - return httptest.NewServer(http.HandlerFunc(mp.Handler)), nil -} - -func makeURL(tsURL, chanID, subtopic, thingKey string, header bool) (string, error) { - u, _ := url.Parse(tsURL) - u.Scheme = protocol - - if chanID == "0" || chanID == "" { - 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") - } - - subtopicPart := "" - if subtopic != "" { - subtopicPart = fmt.Sprintf("/%s", subtopic) - } - if header { - 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 -} - -func handshake(tsURL, chanID, subtopic, thingKey string, addHeader bool) (*websocket.Conn, *http.Response, error) { - header := http.Header{} - if addHeader { - header.Add("Authorization", thingKey) - } - - turl, _ := makeURL(tsURL, chanID, subtopic, thingKey, 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) - target := newHTTPServer(svc) - defer target.Close() - handler := ws.NewHandler(pubsub, mglog.NewMock(), things) - 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: "connect and send message", - chanID: id, - subtopic: "", - header: true, - thingKey: thingKey, - 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 that cannot be published", - chanID: id, - subtopic: "", - header: true, - thingKey: thingKey, - 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 nested subtopic", - chanID: id, - subtopic: "subtopic/nested", - header: true, - thingKey: thingKey, - 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 to empty channel", - chanID: "", - subtopic: "", - header: true, - thingKey: thingKey, - status: http.StatusBadGateway, - msg: []byte{}, - }, - { - desc: "connect with empty thingKey", - chanID: id, - subtopic: "", - header: true, - thingKey: "", - 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, - }, - } - - 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) - 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 { - assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error %s\n", tc.desc, err)) - - err = conn.WriteMessage(websocket.TextMessage, tc.msg) - assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error %s\n", tc.desc, err)) - } - }) - } -} diff --git a/ws/api/endpoints.go b/ws/api/endpoints.go deleted file mode 100644 index 040133a9d..000000000 --- a/ws/api/endpoints.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "fmt" - "net/http" - "net/url" - "regexp" - "strings" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/ws" - "github.com/go-chi/chi/v5" -) - -var channelPartRegExp = regexp.MustCompile(`^/channels/([\w\-]+)/messages(/[^?]*)?(\?.*)?$`) - -func handshake(ctx context.Context, svc ws.Service) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - req, err := decodeRequest(r) - if err != nil { - encodeError(w, err) - return - } - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - logger.Warn(fmt.Sprintf("Failed to upgrade connection to websocket: %s", err.Error())) - return - } - req.conn = conn - client := ws.NewClient(conn) - - if err := svc.Subscribe(ctx, req.thingKey, req.chanID, req.subtopic, client); err != nil { - req.conn.Close() - return - } - - logger.Debug(fmt.Sprintf("Successfully upgraded communication to WS on channel %s", req.chanID)) - } -} - -func decodeRequest(r *http.Request) (connReq, error) { - authKey := r.Header.Get("Authorization") - if authKey == "" { - authKeys := r.URL.Query()["authorization"] - if len(authKeys) == 0 { - logger.Debug("Missing authorization key.") - return connReq{}, errUnauthorizedAccess - } - authKey = authKeys[0] - } - - chanID := chi.URLParam(r, "chanID") - - req := connReq{ - thingKey: authKey, - chanID: chanID, - } - - channelParts := channelPartRegExp.FindStringSubmatch(r.RequestURI) - if len(channelParts) < 2 { - logger.Warn("Empty channel id or malformed url") - return connReq{}, errors.ErrMalformedEntity - } - - subtopic, err := parseSubTopic(channelParts[2]) - if err != nil { - return connReq{}, err - } - - req.subtopic = subtopic - - return req, nil -} - -func parseSubTopic(subtopic string) (string, error) { - if subtopic == "" { - return subtopic, nil - } - - subtopic, err := url.QueryUnescape(subtopic) - if err != nil { - return "", errMalformedSubtopic - } - - subtopic = strings.ReplaceAll(subtopic, "/", ".") - - elems := strings.Split(subtopic, ".") - filteredElems := []string{} - for _, elem := range elems { - if elem == "" { - continue - } - - if len(elem) > 1 && (strings.Contains(elem, "*") || strings.Contains(elem, ">")) { - return "", errMalformedSubtopic - } - - filteredElems = append(filteredElems, elem) - } - - subtopic = strings.Join(filteredElems, ".") - - return subtopic, nil -} - -func encodeError(w http.ResponseWriter, err error) { - var statusCode int - - switch err { - case ws.ErrEmptyTopic: - statusCode = http.StatusBadRequest - case errUnauthorizedAccess: - statusCode = http.StatusForbidden - case errMalformedSubtopic, errors.ErrMalformedEntity: - statusCode = http.StatusBadRequest - default: - statusCode = http.StatusNotFound - } - logger.Warn(fmt.Sprintf("Failed to authorize: %s", err.Error())) - w.WriteHeader(statusCode) -} diff --git a/ws/api/logging.go b/ws/api/logging.go deleted file mode 100644 index 5c693a45e..000000000 --- a/ws/api/logging.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/ws" -) - -var _ ws.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc ws.Service -} - -// LoggingMiddleware adds logging facilities to the websocket service. -func LoggingMiddleware(svc ws.Service, logger *slog.Logger) ws.Service { - return &loggingMiddleware{logger, svc} -} - -// 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("channel_id", chanID), - } - if subtopic != "" { - args = append(args, "subtopic", subtopic) - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Subscibe failed", args...) - return - } - lm.logger.Info("Subscribe completed successfully", args...) - }(time.Now()) - - return lm.svc.Subscribe(ctx, thingKey, chanID, subtopic, c) -} diff --git a/ws/api/metrics.go b/ws/api/metrics.go deleted file mode 100644 index a1a8d5932..000000000 --- a/ws/api/metrics.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "context" - "time" - - "github.com/absmach/magistrala/ws" - "github.com/go-kit/kit/metrics" -) - -var _ ws.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc ws.Service -} - -// MetricsMiddleware instruments adapter by tracking request count and latency. -func MetricsMiddleware(svc ws.Service, counter metrics.Counter, latency metrics.Histogram) ws.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -// Subscribe instruments Subscribe method with metrics. -func (mm *metricsMiddleware) Subscribe(ctx context.Context, thingKey, 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) -} diff --git a/ws/api/requests.go b/ws/api/requests.go deleted file mode 100644 index cc3f50dcd..000000000 --- a/ws/api/requests.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import "github.com/gorilla/websocket" - -type connReq struct { - thingKey string - chanID string - subtopic string - conn *websocket.Conn -} diff --git a/ws/api/transport.go b/ws/api/transport.go deleted file mode 100644 index 1398d2066..000000000 --- a/ws/api/transport.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "errors" - "log/slog" - "net/http" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/ws" - "github.com/go-chi/chi/v5" - "github.com/gorilla/websocket" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -const ( - service = "ws" - readwriteBufferSize = 1024 -) - -var ( - errUnauthorizedAccess = errors.New("missing or invalid credentials provided") - errMalformedSubtopic = errors.New("malformed subtopic") -) - -var ( - upgrader = websocket.Upgrader{ - ReadBufferSize: readwriteBufferSize, - WriteBufferSize: readwriteBufferSize, - CheckOrigin: func(r *http.Request) bool { return true }, - } - logger *slog.Logger -) - -// MakeHandler returns http handler with handshake endpoint. -func MakeHandler(ctx context.Context, svc ws.Service, l *slog.Logger, instanceID string) http.Handler { - logger = l - - mux := chi.NewRouter() - mux.Get("/channels/{chanID}/messages", handshake(ctx, svc)) - mux.Get("/channels/{chanID}/messages/*", handshake(ctx, svc)) - - mux.Get("/health", magistrala.Health(service, instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} diff --git a/ws/client.go b/ws/client.go deleted file mode 100644 index cf33a105a..000000000 --- a/ws/client.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package ws - -import ( - "github.com/absmach/magistrala/pkg/messaging" - "github.com/gorilla/websocket" -) - -// Client handles messaging and websocket connection. -type Client struct { - conn *websocket.Conn - id string -} - -// NewClient returns a new websocket client. -func NewClient(c *websocket.Conn) *Client { - return &Client{ - conn: c, - id: "", - } -} - -// Cancel handles the websocket connection after unsubscribing. -func (c *Client) Cancel() error { - if c.conn == nil { - return nil - } - return c.conn.Close() -} - -// Handle handles the sending and receiving of messages via the broker. -func (c *Client) Handle(msg *messaging.Message) error { - // To prevent publisher from receiving its own published message - if msg.GetPublisher() == c.id { - return nil - } - - return c.conn.WriteMessage(websocket.TextMessage, msg.GetPayload()) -} diff --git a/ws/client_test.go b/ws/client_test.go deleted file mode 100644 index 7e6dbce8e..000000000 --- a/ws/client_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package ws_test - -import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/absmach/magistrala/ws" - "github.com/gorilla/websocket" - "github.com/stretchr/testify/assert" -) - -const expectedCount = uint64(1) - -var ( - msgChan = make(chan []byte) - c *ws.Client - count uint64 - - upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { return true }, - } -) - -func handler(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - defer conn.Close() - for { - _, message, err := conn.ReadMessage() - if err != nil { - break - } - atomic.AddUint64(&count, 1) - msgChan <- message - } -} - -func TestHandle(t *testing.T) { - s := httptest.NewServer(http.HandlerFunc(handler)) - defer s.Close() - - // Convert http://127.0.0.1 to ws://127.0.0.1 - u := strings.Replace(s.URL, "http", "ws", 1) - - // Connect to the server - wsConn, _, err := websocket.DefaultDialer.Dial(u, nil) - if err != nil { - t.Fatalf("%v", err) - } - defer wsConn.Close() - - c = ws.NewClient(wsConn) - - cases := []struct { - desc string - publisher string - expectedPayload []byte - expectMsg bool - }{ - { - desc: "handling with different id from ws.Client", - publisher: msg.Publisher, - expectedPayload: msg.Payload, - expectMsg: true, - }, - { - desc: "handling with same id as ws.Client (empty by default) drops message", - publisher: "", - expectedPayload: []byte{}, - expectMsg: false, - }, - } - - for _, tc := range cases { - msg.Publisher = tc.publisher - err = c.Handle(&msg) - assert.Nil(t, err, fmt.Sprintf("expected nil error from handle, got: %s", err)) - receivedMsg := []byte{} - switch tc.expectMsg { - case true: - rec := <-msgChan // Wait for the message to be received. - receivedMsg = rec - case false: - time.Sleep(100 * time.Millisecond) // Give time to server to process c.Handle call. - } - assert.Equal(t, tc.expectedPayload, receivedMsg, fmt.Sprintf("%s: expected %+v, got %+v", tc.desc, &msg, receivedMsg)) - } - c := atomic.LoadUint64(&count) - assert.Equal(t, expectedCount, c, fmt.Sprintf("expected message count %d, got %d", expectedCount, c)) -} diff --git a/ws/doc.go b/ws/doc.go deleted file mode 100644 index 67c9b3caf..000000000 --- a/ws/doc.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package ws provides domain concept definitions required to support -// Magistrala WebSocket adapter service functionality. -// -// This package defines the core domain concepts and types necessary to handle -// WebSocket connections and messages in the context of a Magistrala WebSocket -// adapter service. It abstracts the underlying complexities of WebSocket -// communication and provides a structured approach to working with WebSocket -// clients and servers. -// -// For more details about Magistrala messaging and WebSocket adapter service, -// please refer to the documentation at https://docs.magistrala.abstractmachines.fr/messaging/#websocket. -package ws diff --git a/ws/handler.go b/ws/handler.go deleted file mode 100644 index 56a39da81..000000000 --- a/ws/handler.go +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package ws - -import ( - "context" - "fmt" - "log/slog" - "net/url" - "regexp" - "strings" - "time" - - "github.com/absmach/magistrala" - "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" - "github.com/absmach/mgate/pkg/session" -) - -var _ session.Handler = (*handler)(nil) - -const protocol = "websocket" - -// Log message formats. -const ( - LogInfoSubscribed = "subscribed with client_id %s to topics %s" - LogInfoUnsubscribed = "unsubscribed client_id %s from topics %s" - LogInfoConnected = "connected with client_id %s" - LogInfoDisconnected = "disconnected client_id %s and username %s" - LogInfoPublished = "published with client_id %s to the topic %s" -) - -// Error wrappers for MQTT errors. -var ( - errMalformedSubtopic = errors.New("malformed subtopic") - errClientNotInitialized = errors.New("client is not initialized") - errMalformedTopic = errors.New("malformed topic") - errMissingTopicPub = errors.New("failed to publish due to missing topic") - errMissingTopicSub = errors.New("failed to subscribe due to missing topic") - errFailedSubscribe = errors.New("failed to subscribe") - errFailedPublish = errors.New("failed to publish") - errFailedParseSubtopic = errors.New("failed to parse subtopic") - errFailedPublishToMsgBroker = errors.New("failed to publish to magistrala message broker") -) - -var channelRegExp = regexp.MustCompile(`^\/?channels\/([\w\-]+)\/messages(\/[^?]*)?(\?.*)?$`) - -// Event implements events.Event interface. -type handler struct { - pubsub messaging.PubSub - things magistrala.ThingsServiceClient - logger *slog.Logger -} - -// NewHandler creates new Handler entity. -func NewHandler(pubsub messaging.PubSub, logger *slog.Logger, thingsClient magistrala.ThingsServiceClient) session.Handler { - return &handler{ - logger: logger, - pubsub: pubsub, - things: thingsClient, - } -} - -// AuthConnect is called on device connection, -// prior forwarding to the ws server. -func (h *handler) AuthConnect(ctx context.Context) error { - return nil -} - -// AuthPublish is called on device publish, -// prior forwarding to the ws server. -func (h *handler) AuthPublish(ctx context.Context, topic *string, payload *[]byte) error { - if topic == nil { - return errMissingTopicPub - } - s, ok := session.FromContext(ctx) - if !ok { - return errClientNotInitialized - } - - var token string - switch { - case strings.HasPrefix(string(s.Password), "Thing"): - token = strings.ReplaceAll(string(s.Password), "Thing ", "") - default: - token = string(s.Password) - } - - return h.authAccess(ctx, token, *topic, policies.PublishPermission) -} - -// AuthSubscribe is called on device publish, -// prior forwarding to the MQTT broker. -func (h *handler) AuthSubscribe(ctx context.Context, topics *[]string) error { - s, ok := session.FromContext(ctx) - if !ok { - return errClientNotInitialized - } - if topics == nil || *topics == nil { - return errMissingTopicSub - } - - var token string - switch { - case strings.HasPrefix(string(s.Password), "Thing"): - token = strings.ReplaceAll(string(s.Password), "Thing ", "") - default: - token = string(s.Password) - } - - for _, v := range *topics { - if err := h.authAccess(ctx, token, v, policies.SubscribePermission); err != nil { - return err - } - } - - return nil -} - -// Connect - after client successfully connected. -func (h *handler) Connect(ctx context.Context) error { - return nil -} - -// Publish - after client successfully published. -func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) error { - s, ok := session.FromContext(ctx) - if !ok { - return errors.Wrap(errFailedPublish, errClientNotInitialized) - } - h.logger.Info(fmt.Sprintf(LogInfoPublished, s.ID, *topic)) - - if len(*payload) == 0 { - return errFailedMessagePublish - } - - // 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) - } - - var token string - switch { - case strings.HasPrefix(string(s.Password), "Thing"): - token = strings.ReplaceAll(string(s.Password), "Thing ", "") - default: - token = string(s.Password) - } - - ar := &magistrala.ThingsAuthzReq{ - Permission: policies.PublishPermission, - ThingKey: token, - ChannelId: chanID, - } - res, err := h.things.Authorize(ctx, ar) - if err != nil { - return err - } - if !res.GetAuthorized() { - return svcerr.ErrAuthorization - } - - msg := messaging.Message{ - Protocol: protocol, - Channel: chanID, - Subtopic: subtopic, - Publisher: res.GetId(), - Payload: *payload, - Created: time.Now().UnixNano(), - } - - if err := h.pubsub.Publish(ctx, msg.GetChannel(), &msg); err != nil { - return errors.Wrap(errFailedPublishToMsgBroker, err) - } - - return nil -} - -// Subscribe - after client successfully subscribed. -func (h *handler) Subscribe(ctx context.Context, topics *[]string) error { - s, ok := session.FromContext(ctx) - if !ok { - return errors.Wrap(errFailedSubscribe, errClientNotInitialized) - } - h.logger.Info(fmt.Sprintf(LogInfoSubscribed, s.ID, strings.Join(*topics, ","))) - return nil -} - -// Unsubscribe - after client unsubscribed. -func (h *handler) Unsubscribe(ctx context.Context, topics *[]string) error { - s, ok := session.FromContext(ctx) - if !ok { - return errors.Wrap(errFailedUnsubscribe, errClientNotInitialized) - } - - h.logger.Info(fmt.Sprintf(LogInfoUnsubscribed, s.ID, strings.Join(*topics, ","))) - return nil -} - -// Disconnect - connection with broker or client lost. -func (h *handler) Disconnect(ctx context.Context) error { - return nil -} - -func (h *handler) authAccess(ctx context.Context, password, topic, action string) error { - // Topics are in the format: - // channels//messages//.../ct/ - if !channelRegExp.MatchString(topic) { - return errMalformedTopic - } - - channelParts := channelRegExp.FindStringSubmatch(topic) - if len(channelParts) < 1 { - return errMalformedTopic - } - - chanID := channelParts[1] - - ar := &magistrala.ThingsAuthzReq{ - Permission: action, - ThingKey: password, - ChannelId: chanID, - } - res, err := h.things.Authorize(ctx, ar) - if err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - if !res.GetAuthorized() { - return errors.Wrap(svcerr.ErrAuthorization, err) - } - - return nil -} - -func parseSubtopic(subtopic string) (string, error) { - if subtopic == "" { - return subtopic, nil - } - - subtopic, err := url.QueryUnescape(subtopic) - if err != nil { - return "", errMalformedSubtopic - } - subtopic = strings.ReplaceAll(subtopic, "/", ".") - - elems := strings.Split(subtopic, ".") - filteredElems := []string{} - for _, elem := range elems { - if elem == "" { - continue - } - - if len(elem) > 1 && (strings.Contains(elem, "*") || strings.Contains(elem, ">")) { - return "", errMalformedSubtopic - } - - filteredElems = append(filteredElems, elem) - } - - subtopic = strings.Join(filteredElems, ".") - return subtopic, nil -} diff --git a/ws/tracing/doc.go b/ws/tracing/doc.go deleted file mode 100644 index 2d65dbe4e..000000000 --- a/ws/tracing/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing provides tracing instrumentation for Magistrala WebSocket adapter service. -// -// This package provides tracing middleware for Magistrala WebSocket adapter service. -// It can be used to trace incoming requests and add tracing capabilities to -// Magistrala WebSocket adapter 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/ws/tracing/tracing.go b/ws/tracing/tracing.go deleted file mode 100644 index ed7e62c9c..000000000 --- a/ws/tracing/tracing.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/ws" - "go.opentelemetry.io/otel/trace" -) - -var _ ws.Service = (*tracingMiddleware)(nil) - -const ( - publishOP = "publish_op" - subscribeOP = "subscribe_op" - unsubscribeOP = "unsubscribe_op" -) - -type tracingMiddleware struct { - tracer trace.Tracer - svc ws.Service -} - -// New returns a new websocket service with tracing capabilities. -func New(tracer trace.Tracer, svc ws.Service) ws.Service { - return &tracingMiddleware{ - tracer: tracer, - svc: svc, - } -} - -// 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 { - ctx, span := tm.tracer.Start(ctx, subscribeOP) - defer span.End() - - return tm.svc.Subscribe(ctx, thingKey, chanID, subtopic, client) -}