From ab3f8799f27ef95a69869d8785d20596ba83b193 Mon Sep 17 00:00:00 2001 From: JeffMboya Date: Tue, 19 Nov 2024 15:57:42 +0300 Subject: [PATCH] Remove unnecessary files Signed-off-by: JeffMboya --- docker/addons/vault/scripts/.dockerignore | 9 - .../addons/vault/scripts/.github/CODEOWNERS | 1 - .../.github/ISSUE_TEMPLATE/bug_report.yml | 52 - .../scripts/.github/ISSUE_TEMPLATE/config.yml | 11 - .../ISSUE_TEMPLATE/feature_request.yml | 39 - .../scripts/.github/PULL_REQUEST_TEMPLATE.md | 69 - .../vault/scripts/.github/dependabot.yml | 33 - .../scripts/.github/workflows/api-tests.yml | 244 - .../vault/scripts/.github/workflows/build.yml | 62 - .../workflows/check-generated-files.yml | 217 - .../.github/workflows/check-license.yaml | 40 - .../scripts/.github/workflows/swagger-ui.yaml | 31 - .../vault/scripts/.github/workflows/tests.yml | 390 -- docker/addons/vault/scripts/.gitignore | 20 - docker/addons/vault/scripts/ADOPTERS.md | 36 - docker/addons/vault/scripts/CONTRIBUTING.md | 87 - docker/addons/vault/scripts/LICENSE | 191 - docker/addons/vault/scripts/MAINTAINERS | 30 - docker/addons/vault/scripts/Makefile | 259 - docker/addons/vault/scripts/README.md | 191 - docker/addons/vault/scripts/api.go | 16 - .../vault/scripts/api/asyncapi/mqtt.yml | 112 - .../vault/scripts/api/asyncapi/websocket.yml | 144 - .../vault/scripts/api/openapi/README.md | 5 - .../addons/vault/scripts/api/openapi/auth.yml | 909 ---- .../vault/scripts/api/openapi/bootstrap.yml | 689 --- .../vault/scripts/api/openapi/certs.yml | 313 -- .../addons/vault/scripts/api/openapi/http.yml | 182 - .../vault/scripts/api/openapi/invitations.yml | 537 -- .../vault/scripts/api/openapi/journal.yml | 286 -- .../vault/scripts/api/openapi/notifiers.yml | 292 -- .../vault/scripts/api/openapi/provision.yml | 129 - .../vault/scripts/api/openapi/readers.yml | 314 -- .../api/openapi/schemas/HealthInfo.yml | 30 - .../vault/scripts/api/openapi/things.yml | 2070 -------- .../vault/scripts/api/openapi/twins.yml | 431 -- .../vault/scripts/api/openapi/users.yml | 2310 --------- docker/addons/vault/scripts/auth.pb.go | 993 ---- docker/addons/vault/scripts/auth.proto | 98 - docker/addons/vault/scripts/auth/README.md | 159 - docker/addons/vault/scripts/auth/api/doc.go | 5 - .../scripts/auth/api/grpc/auth/client.go | 111 - .../vault/scripts/auth/api/grpc/auth/doc.go | 5 - .../scripts/auth/api/grpc/auth/endpoint.go | 52 - .../auth/api/grpc/auth/endpoint_test.go | 228 - .../scripts/auth/api/grpc/auth/requests.go | 51 - .../scripts/auth/api/grpc/auth/responses.go | 15 - .../scripts/auth/api/grpc/auth/server.go | 83 - .../scripts/auth/api/grpc/auth/setup_test.go | 24 - .../scripts/auth/api/grpc/domains/client.go | 67 - .../scripts/auth/api/grpc/domains/doc.go | 5 - .../scripts/auth/api/grpc/domains/endpoint.go | 26 - .../auth/api/grpc/domains/endpoint_test.go | 104 - .../scripts/auth/api/grpc/domains/requests.go | 20 - .../auth/api/grpc/domains/responses.go | 8 - .../scripts/auth/api/grpc/domains/server.go | 50 - .../auth/api/grpc/domains/setup_test.go | 24 - .../scripts/auth/api/grpc/token/client.go | 95 - .../vault/scripts/auth/api/grpc/token/doc.go | 5 - .../scripts/auth/api/grpc/token/endpoint.go | 56 - .../auth/api/grpc/token/endpoint_test.go | 171 - .../scripts/auth/api/grpc/token/requests.go | 37 - .../scripts/auth/api/grpc/token/responses.go | 10 - .../scripts/auth/api/grpc/token/server.go | 76 - .../scripts/auth/api/grpc/token/setup_test.go | 24 - .../vault/scripts/auth/api/grpc/utils.go | 72 - .../addons/vault/scripts/auth/api/http/doc.go | 3 - .../scripts/auth/api/http/domains/decode.go | 201 - .../scripts/auth/api/http/domains/endpoint.go | 225 - .../auth/api/http/domains/endpoint_test.go | 1310 ----- .../scripts/auth/api/http/domains/requests.go | 231 - .../auth/api/http/domains/responses.go | 185 - .../auth/api/http/domains/transport.go | 105 - .../scripts/auth/api/http/keys/endpoint.go | 87 - .../auth/api/http/keys/endpoint_test.go | 338 -- .../scripts/auth/api/http/keys/requests.go | 48 - .../auth/api/http/keys/requests_test.go | 88 - .../scripts/auth/api/http/keys/responses.go | 71 - .../scripts/auth/api/http/keys/transport.go | 72 - .../vault/scripts/auth/api/http/transport.go | 28 - .../addons/vault/scripts/auth/api/logging.go | 303 -- .../addons/vault/scripts/auth/api/metrics.go | 156 - docker/addons/vault/scripts/auth/domains.go | 209 - .../addons/vault/scripts/auth/domains_test.go | 186 - .../addons/vault/scripts/auth/events/doc.go | 6 - .../vault/scripts/auth/events/events.go | 296 -- .../vault/scripts/auth/events/streams.go | 221 - .../vault/scripts/auth/jwt/token_test.go | 250 - .../vault/scripts/auth/jwt/tokenizer.go | 145 - docker/addons/vault/scripts/auth/keys.go | 98 - docker/addons/vault/scripts/auth/keys_test.go | 60 - .../addons/vault/scripts/auth/mocks/authz.go | 49 - .../vault/scripts/auth/mocks/domains.go | 306 -- .../scripts/auth/mocks/domains_client.go | 118 - .../addons/vault/scripts/auth/mocks/keys.go | 106 - .../vault/scripts/auth/mocks/service.go | 406 -- .../vault/scripts/auth/mocks/token_client.go | 192 - .../addons/vault/scripts/auth/postgres/doc.go | 6 - .../vault/scripts/auth/postgres/domains.go | 633 --- .../scripts/auth/postgres/domains_test.go | 1148 ----- .../vault/scripts/auth/postgres/init.go | 62 - .../addons/vault/scripts/auth/postgres/key.go | 111 - .../vault/scripts/auth/postgres/key_test.go | 271 - .../vault/scripts/auth/postgres/setup_test.go | 95 - docker/addons/vault/scripts/auth/service.go | 906 ---- .../addons/vault/scripts/auth/service_test.go | 2427 --------- docker/addons/vault/scripts/auth/tokenizer.go | 13 - .../addons/vault/scripts/auth/tracing/doc.go | 12 - .../vault/scripts/auth/tracing/tracing.go | 157 - docker/addons/vault/scripts/auth_grpc.pb.go | 484 -- .../addons/vault/scripts/bootstrap/README.md | 122 - .../addons/vault/scripts/bootstrap/api/doc.go | 5 - .../vault/scripts/bootstrap/api/endpoint.go | 290 -- .../scripts/bootstrap/api/endpoint_test.go | 1418 ------ .../vault/scripts/bootstrap/api/requests.go | 163 - .../scripts/bootstrap/api/requests_test.go | 313 -- .../vault/scripts/bootstrap/api/responses.go | 144 - .../vault/scripts/bootstrap/api/transport.go | 284 -- .../addons/vault/scripts/bootstrap/configs.go | 120 - docker/addons/vault/scripts/bootstrap/doc.go | 6 - .../scripts/bootstrap/events/consumer/doc.go | 6 - .../bootstrap/events/consumer/events.go | 24 - .../bootstrap/events/consumer/streams.go | 148 - .../vault/scripts/bootstrap/events/doc.go | 6 - .../scripts/bootstrap/events/producer/doc.go | 6 - .../bootstrap/events/producer/events.go | 274 -- .../bootstrap/events/producer/setup_test.go | 61 - .../bootstrap/events/producer/streams.go | 235 - .../bootstrap/events/producer/streams_test.go | 1482 ------ .../bootstrap/middleware/authorization.go | 145 - .../scripts/bootstrap/middleware/logging.go | 295 -- .../scripts/bootstrap/middleware/metrics.go | 172 - .../scripts/bootstrap/mocks/config_reader.go | 59 - .../vault/scripts/bootstrap/mocks/configs.go | 354 -- .../vault/scripts/bootstrap/mocks/doc.go | 5 - .../vault/scripts/bootstrap/mocks/service.go | 335 -- .../scripts/bootstrap/postgres/configs.go | 778 --- .../bootstrap/postgres/configs_test.go | 913 ---- .../vault/scripts/bootstrap/postgres/doc.go | 6 - .../vault/scripts/bootstrap/postgres/init.go | 108 - .../scripts/bootstrap/postgres/setup_test.go | 86 - .../addons/vault/scripts/bootstrap/reader.go | 95 - .../vault/scripts/bootstrap/reader_test.go | 126 - .../addons/vault/scripts/bootstrap/service.go | 508 -- .../vault/scripts/bootstrap/service_test.go | 1113 ----- .../addons/vault/scripts/bootstrap/state.go | 26 - .../vault/scripts/bootstrap/tracing/doc.go | 12 - .../scripts/bootstrap/tracing/tracing.go | 182 - docker/addons/vault/scripts/certs/README.md | 129 - docker/addons/vault/scripts/certs/api/doc.go | 5 - .../vault/scripts/certs/api/endpoint.go | 108 - .../vault/scripts/certs/api/endpoint_test.go | 672 --- .../addons/vault/scripts/certs/api/logging.go | 132 - .../addons/vault/scripts/certs/api/metrics.go | 81 - .../vault/scripts/certs/api/requests.go | 91 - .../vault/scripts/certs/api/responses.go | 73 - .../vault/scripts/certs/api/transport.go | 136 - docker/addons/vault/scripts/certs/certs.go | 84 - .../addons/vault/scripts/certs/certs_test.go | 93 - docker/addons/vault/scripts/certs/doc.go | 6 - .../addons/vault/scripts/certs/mocks/doc.go | 5 - .../addons/vault/scripts/certs/mocks/pki.go | 257 - .../vault/scripts/certs/mocks/service.go | 172 - .../scripts/certs/pki/amcerts/am_certs.go | 118 - .../vault/scripts/certs/pki/amcerts/doc.go | 4 - .../vault/scripts/certs/pki/vault/doc.go | 8 - .../vault/scripts/certs/pki/vault/vault.go | 269 - docker/addons/vault/scripts/certs/service.go | 185 - .../vault/scripts/certs/service_test.go | 345 -- .../addons/vault/scripts/certs/tracing/doc.go | 12 - .../vault/scripts/certs/tracing/tracing.go | 79 - docker/addons/vault/scripts/cli/README.md | 411 -- docker/addons/vault/scripts/cli/bootstrap.go | 216 - .../vault/scripts/cli/bootstrap_test.go | 622 --- docker/addons/vault/scripts/cli/certs.go | 96 - docker/addons/vault/scripts/cli/certs_test.go | 272 -- docker/addons/vault/scripts/cli/channels.go | 376 -- .../addons/vault/scripts/cli/channels_test.go | 1137 ----- .../addons/vault/scripts/cli/commands_test.go | 72 - docker/addons/vault/scripts/cli/config.go | 311 -- docker/addons/vault/scripts/cli/consumers.go | 100 - .../vault/scripts/cli/consumers_test.go | 273 -- docker/addons/vault/scripts/cli/doc.go | 6 - docker/addons/vault/scripts/cli/domains.go | 263 - .../addons/vault/scripts/cli/domains_test.go | 669 --- docker/addons/vault/scripts/cli/groups.go | 348 -- .../addons/vault/scripts/cli/groups_test.go | 985 ---- docker/addons/vault/scripts/cli/health.go | 30 - .../addons/vault/scripts/cli/health_test.go | 84 - .../addons/vault/scripts/cli/invitations.go | 148 - .../vault/scripts/cli/invitations_test.go | 376 -- docker/addons/vault/scripts/cli/journal.go | 50 - .../addons/vault/scripts/cli/journal_test.go | 102 - docker/addons/vault/scripts/cli/message.go | 72 - .../addons/vault/scripts/cli/message_test.go | 165 - docker/addons/vault/scripts/cli/provision.go | 404 -- docker/addons/vault/scripts/cli/sdk.go | 14 - docker/addons/vault/scripts/cli/setup_test.go | 120 - docker/addons/vault/scripts/cli/things.go | 359 -- .../addons/vault/scripts/cli/things_test.go | 1243 ----- docker/addons/vault/scripts/cli/users.go | 537 -- docker/addons/vault/scripts/cli/users_test.go | 1446 ------ docker/addons/vault/scripts/cli/utils.go | 105 - docker/addons/vault/scripts/cmd/auth/main.go | 233 - .../vault/scripts/cmd/bootstrap/main.go | 257 - docker/addons/vault/scripts/cmd/certs/main.go | 168 - docker/addons/vault/scripts/cmd/cli/main.go | 263 - docker/addons/vault/scripts/cmd/coap/main.go | 160 - docker/addons/vault/scripts/cmd/http/main.go | 207 - .../vault/scripts/cmd/invitations/main.go | 196 - .../addons/vault/scripts/cmd/journal/main.go | 193 - docker/addons/vault/scripts/cmd/mqtt/main.go | 288 -- .../vault/scripts/cmd/postgres-reader/main.go | 165 - .../vault/scripts/cmd/postgres-writer/main.go | 154 - .../vault/scripts/cmd/provision/main.go | 190 - .../addons/vault/scripts/cmd/things/main.go | 291 -- .../scripts/cmd/timescale-reader/main.go | 163 - .../scripts/cmd/timescale-writer/main.go | 156 - docker/addons/vault/scripts/cmd/users/main.go | 387 -- docker/addons/vault/scripts/cmd/ws/main.go | 193 - docker/addons/vault/scripts/coap/README.md | 80 - docker/addons/vault/scripts/coap/adapter.go | 116 - docker/addons/vault/scripts/coap/api/doc.go | 6 - .../addons/vault/scripts/coap/api/logging.go | 93 - .../addons/vault/scripts/coap/api/metrics.go | 62 - .../vault/scripts/coap/api/transport.go | 227 - docker/addons/vault/scripts/coap/client.go | 105 - .../vault/scripts/coap/tracing/adapter.go | 63 - .../addons/vault/scripts/coap/tracing/doc.go | 12 - docker/addons/vault/scripts/config.toml | 23 - .../addons/vault/scripts/consumers/README.md | 18 - .../vault/scripts/consumers/consumer.go | 30 - docker/addons/vault/scripts/consumers/doc.go | 6 - .../vault/scripts/consumers/messages.go | 159 - .../scripts/consumers/notifiers/README.md | 23 - .../scripts/consumers/notifiers/api/doc.go | 6 - .../consumers/notifiers/api/endpoint.go | 103 - .../consumers/notifiers/api/endpoint_test.go | 548 --- .../consumers/notifiers/api/logging.go | 131 - .../consumers/notifiers/api/metrics.go | 81 - .../consumers/notifiers/api/requests.go | 55 - .../consumers/notifiers/api/responses.go | 88 - .../consumers/notifiers/api/transport.go | 131 - .../vault/scripts/consumers/notifiers/doc.go | 6 - .../scripts/consumers/notifiers/mocks/doc.go | 5 - .../consumers/notifiers/mocks/notifier.go | 47 - .../consumers/notifiers/mocks/repository.go | 133 - .../consumers/notifiers/mocks/service.go | 151 - .../scripts/consumers/notifiers/notifier.go | 22 - .../consumers/notifiers/postgres/database.go | 74 - .../consumers/notifiers/postgres/doc.go | 6 - .../consumers/notifiers/postgres/init.go | 28 - .../notifiers/postgres/setup_test.go | 89 - .../notifiers/postgres/subscriptions.go | 164 - .../notifiers/postgres/subscriptions_test.go | 263 - .../scripts/consumers/notifiers/service.go | 175 - .../consumers/notifiers/service_test.go | 359 -- .../consumers/notifiers/smtp/notifier.go | 40 - .../consumers/notifiers/subscriptions.go | 48 - .../consumers/notifiers/tracing/doc.go | 12 - .../notifiers/tracing/subscriptions.go | 73 - .../scripts/consumers/tracing/consumers.go | 132 - .../vault/scripts/consumers/writers/README.md | 16 - .../scripts/consumers/writers/api/doc.go | 6 - .../scripts/consumers/writers/api/logging.go | 47 - .../scripts/consumers/writers/api/metrics.go | 41 - .../consumers/writers/api/transport.go | 21 - .../vault/scripts/consumers/writers/doc.go | 6 - .../consumers/writers/postgres/README.md | 77 - .../consumers/writers/postgres/consumer.go | 213 - .../writers/postgres/consumer_test.go | 112 - .../scripts/consumers/writers/postgres/doc.go | 6 - .../consumers/writers/postgres/init.go | 46 - .../consumers/writers/postgres/setup_test.go | 85 - .../consumers/writers/timescale/README.md | 76 - .../consumers/writers/timescale/consumer.go | 198 - .../writers/timescale/consumer_test.go | 112 - .../consumers/writers/timescale/doc.go | 6 - .../consumers/writers/timescale/init.go | 39 - .../consumers/writers/timescale/setup_test.go | 85 - docker/addons/vault/scripts/doc.go | 6 - docker/addons/vault/scripts/docker/.env | 481 -- docker/addons/vault/scripts/docker/Dockerfile | 24 - .../vault/scripts/docker/Dockerfile.dev | 8 - docker/addons/vault/scripts/docker/README.md | 134 - .../addons/bootstrap/docker-compose.yml | 85 - .../scripts/docker/addons/certs/config.yml | 20 - .../docker/addons/certs/docker-compose.yml | 124 - .../docker/addons/journal/docker-compose.yml | 67 - .../addons/postgres-reader/docker-compose.yml | 80 - .../docker/addons/postgres-writer/config.toml | 19 - .../addons/postgres-writer/docker-compose.yml | 63 - .../addons/prometheus/docker-compose.yml | 53 - .../addons/prometheus/grafana/dashboard.yml | 15 - .../addons/prometheus/grafana/datasource.yml | 12 - .../prometheus/grafana/example-dashboard.json | 1317 ----- .../addons/prometheus/metrics/prometheus.yml | 22 - .../addons/provision/configs/config.toml | 74 - .../addons/provision/docker-compose.yml | 46 - .../timescale-reader/docker-compose.yml | 80 - .../addons/timescale-writer/config.toml | 8 - .../timescale-writer/docker-compose.yml | 65 - .../scripts/docker/addons/vault/README.md | 290 -- .../scripts/docker/addons/vault/config.hcl | 10 - .../docker/addons/vault/docker-compose.yml | 39 - .../scripts/docker/addons/vault/entrypoint.sh | 25 - .../docker/addons/vault/scripts/.gitignore | 5 - ...magistrala_things_certs_issue.template.hcl | 32 - .../docker/addons/vault/scripts/vault_cmd.sh | 24 - .../addons/vault/scripts/vault_copy_certs.sh | 86 - .../addons/vault/scripts/vault_copy_env.sh | 46 - .../vault/scripts/vault_create_approle.sh | 122 - .../docker/addons/vault/scripts/vault_init.sh | 46 - .../addons/vault/scripts/vault_set_pki.sh | 251 - .../addons/vault/scripts/vault_unseal.sh | 46 - .../vault/scripts/docker/docker-compose.yml | 774 --- .../vault/scripts/docker/nats/nats.conf | 27 - .../vault/scripts/docker/nginx/.gitignore | 5 - .../vault/scripts/docker/nginx/entrypoint.sh | 26 - .../vault/scripts/docker/nginx/nginx-key.conf | 211 - .../scripts/docker/nginx/nginx-x509.conf | 232 - .../nginx/snippets/http_access_log.conf | 8 - .../nginx/snippets/mqtt-upstream-cluster.conf | 9 - .../nginx/snippets/mqtt-upstream-single.conf | 6 - .../snippets/mqtt-ws-upstream-cluster.conf | 9 - .../snippets/mqtt-ws-upstream-single.conf | 6 - .../docker/nginx/snippets/proxy-headers.conf | 15 - .../docker/nginx/snippets/ssl-client.conf | 5 - .../scripts/docker/nginx/snippets/ssl.conf | 16 - .../nginx/snippets/stream_access_log.conf | 7 - .../nginx/snippets/verify-ssl-client.conf | 9 - .../docker/nginx/snippets/ws-upgrade.conf | 9 - .../vault/scripts/docker/spicedb/schema.zed | 78 - .../vault/scripts/docker/ssl/.gitignore | 7 - .../addons/vault/scripts/docker/ssl/Makefile | 170 - .../vault/scripts/docker/ssl/authorization.js | 181 - .../vault/scripts/docker/ssl/certs/ca.crt | 23 - .../vault/scripts/docker/ssl/certs/ca.key | 28 - .../docker/ssl/certs/magistrala-server.crt | 26 - .../docker/ssl/certs/magistrala-server.key | 52 - .../vault/scripts/docker/ssl/dhparam.pem | 8 - .../docker/templates/smtp-notifier.tmpl | 8 - .../vault/scripts/docker/templates/users.tmpl | 13 - .../vault/scripts/docker/vernemq/Dockerfile | 56 - .../scripts/docker/vernemq/bin/vernemq.sh | 352 -- .../scripts/docker/vernemq/files/vm.args | 15 - docker/addons/vault/scripts/go.mod | 176 - docker/addons/vault/scripts/go.sum | 653 --- docker/addons/vault/scripts/health.go | 78 - docker/addons/vault/scripts/http/README.md | 71 - docker/addons/vault/scripts/http/api/doc.go | 6 - .../addons/vault/scripts/http/api/endpoint.go | 23 - .../vault/scripts/http/api/endpoint_test.go | 198 - .../addons/vault/scripts/http/api/request.go | 25 - .../addons/vault/scripts/http/api/response.go | 26 - .../vault/scripts/http/api/transport.go | 79 - docker/addons/vault/scripts/http/doc.go | 6 - docker/addons/vault/scripts/http/handler.go | 208 - .../addons/vault/scripts/internal/api/auth.go | 49 - .../vault/scripts/internal/api/common.go | 228 - .../vault/scripts/internal/api/common_test.go | 338 -- .../addons/vault/scripts/internal/api/doc.go | 6 - .../vault/scripts/internal/clients/doc.go | 6 - .../scripts/internal/clients/redis/doc.go | 9 - .../scripts/internal/clients/redis/redis.go | 16 - .../vault/scripts/internal/email/README.md | 21 - .../vault/scripts/internal/email/doc.go | 6 - .../vault/scripts/internal/email/email.go | 110 - .../scripts/internal/groups/api/decode.go | 281 -- .../internal/groups/api/decode_test.go | 769 --- .../vault/scripts/internal/groups/api/doc.go | 6 - .../internal/groups/api/endpoint_test.go | 1195 ----- .../scripts/internal/groups/api/endpoints.go | 383 -- .../scripts/internal/groups/api/requests.go | 164 - .../internal/groups/api/requests_test.go | 404 -- .../scripts/internal/groups/api/responses.go | 231 - .../scripts/internal/groups/events/doc.go | 5 - .../scripts/internal/groups/events/events.go | 271 - .../scripts/internal/groups/events/streams.go | 212 - .../groups/middleware/authorization.go | 179 - .../scripts/internal/groups/middleware/doc.go | 5 - .../internal/groups/middleware/logging.go | 251 - .../internal/groups/middleware/metrics.go | 130 - .../scripts/internal/groups/postgres/doc.go | 5 - .../internal/groups/postgres/groups.go | 502 -- .../internal/groups/postgres/groups_test.go | 1212 ----- .../scripts/internal/groups/postgres/init.go | 38 - .../internal/groups/postgres/setup_test.go | 94 - .../vault/scripts/internal/groups/service.go | 586 --- .../scripts/internal/groups/service_test.go | 1460 ------ .../vault/scripts/internal/groups/status.go | 58 - .../scripts/internal/groups/status_test.go | 50 - .../scripts/internal/groups/tracing/doc.go | 12 - .../internal/groups/tracing/tracing.go | 113 - .../scripts/internal/testsutil/common.go | 19 - .../vault/scripts/invitations/README.md | 80 - .../vault/scripts/invitations/api/doc.go | 4 - .../vault/scripts/invitations/api/endpoint.go | 154 - .../scripts/invitations/api/endpoint_test.go | 672 --- .../vault/scripts/invitations/api/requests.go | 72 - .../scripts/invitations/api/requests_test.go | 182 - .../scripts/invitations/api/responses.go | 110 - .../scripts/invitations/api/transport.go | 172 - .../addons/vault/scripts/invitations/doc.go | 7 - .../vault/scripts/invitations/invitations.go | 149 - .../scripts/invitations/invitations_test.go | 75 - .../invitations/middleware/authorization.go | 125 - .../scripts/invitations/middleware/doc.go | 9 - .../scripts/invitations/middleware/logging.go | 127 - .../scripts/invitations/middleware/metrics.go | 77 - .../scripts/invitations/middleware/tracing.go | 85 - .../vault/scripts/invitations/mocks/doc.go | 5 - .../scripts/invitations/mocks/repository.go | 177 - .../scripts/invitations/mocks/service.go | 162 - .../vault/scripts/invitations/postgres/doc.go | 5 - .../scripts/invitations/postgres/init.go | 48 - .../invitations/postgres/invitations.go | 254 - .../invitations/postgres/invitations_test.go | 811 --- .../invitations/postgres/setup_test.go | 96 - .../vault/scripts/invitations/service.go | 142 - .../vault/scripts/invitations/service_test.go | 515 -- .../addons/vault/scripts/invitations/state.go | 74 - .../vault/scripts/invitations/state_test.go | 95 - .../addons/vault/scripts/journal/api/doc.go | 6 - .../vault/scripts/journal/api/endpoint.go | 31 - .../scripts/journal/api/endpoint_test.go | 282 -- .../vault/scripts/journal/api/requests.go | 32 - .../scripts/journal/api/requests_test.go | 126 - .../vault/scripts/journal/api/responses.go | 29 - .../vault/scripts/journal/api/transport.go | 129 - docker/addons/vault/scripts/journal/doc.go | 7 - .../vault/scripts/journal/events/consumer.go | 85 - .../scripts/journal/events/consumer_test.go | 280 -- .../vault/scripts/journal/events/doc.go | 7 - .../addons/vault/scripts/journal/journal.go | 158 - .../vault/scripts/journal/journal_test.go | 143 - .../vault/scripts/journal/middleware/doc.go | 6 - .../scripts/journal/middleware/logging.go | 70 - .../scripts/journal/middleware/metrics.go | 48 - .../scripts/journal/middleware/tracing.go | 46 - .../addons/vault/scripts/journal/mocks/doc.go | 5 - .../vault/scripts/journal/mocks/repository.go | 77 - .../vault/scripts/journal/mocks/service.go | 77 - .../vault/scripts/journal/postgres/doc.go | 5 - .../vault/scripts/journal/postgres/init.go | 36 - .../vault/scripts/journal/postgres/journal.go | 178 - .../scripts/journal/postgres/journal_test.go | 724 --- .../scripts/journal/postgres/setup_test.go | 93 - .../addons/vault/scripts/journal/service.go | 83 - .../vault/scripts/journal/service_test.go | 208 - docker/addons/vault/scripts/logger/doc.go | 6 - docker/addons/vault/scripts/logger/exit.go | 11 - docker/addons/vault/scripts/logger/logger.go | 25 - .../vault/scripts/logger/logger_test.go | 63 - docker/addons/vault/scripts/logger/mock.go | 16 - docker/addons/vault/scripts/mqtt/README.md | 83 - docker/addons/vault/scripts/mqtt/doc.go | 6 - .../addons/vault/scripts/mqtt/events/doc.go | 6 - .../vault/scripts/mqtt/events/events.go | 22 - .../vault/scripts/mqtt/events/streams.go | 61 - docker/addons/vault/scripts/mqtt/forwarder.go | 75 - docker/addons/vault/scripts/mqtt/handler.go | 270 - .../addons/vault/scripts/mqtt/handler_test.go | 461 -- docker/addons/vault/scripts/mqtt/mocks/doc.go | 5 - .../addons/vault/scripts/mqtt/mocks/events.go | 66 - .../vault/scripts/mqtt/mocks/publisher.go | 25 - .../addons/vault/scripts/mqtt/tracing/doc.go | 12 - .../vault/scripts/mqtt/tracing/forwarder.go | 63 - docker/addons/vault/scripts/pkg/README.md | 3 - .../vault/scripts/pkg/apiutil/errors.go | 209 - .../vault/scripts/pkg/apiutil/responses.go | 10 - .../addons/vault/scripts/pkg/apiutil/token.go | 37 - .../vault/scripts/pkg/apiutil/token_test.go | 112 - .../vault/scripts/pkg/apiutil/transport.go | 123 - .../scripts/pkg/apiutil/transport_test.go | 364 -- .../addons/vault/scripts/pkg/authn/authn.go | 22 - .../vault/scripts/pkg/authn/authsvc/authn.go | 46 - docker/addons/vault/scripts/pkg/authn/doc.go | 4 - .../vault/scripts/pkg/authn/mocks/authn.go | 60 - .../vault/scripts/pkg/authz/authsvc/authz.go | 60 - .../addons/vault/scripts/pkg/authz/authz.go | 50 - docker/addons/vault/scripts/pkg/authz/doc.go | 4 - .../vault/scripts/pkg/authz/mocks/authz.go | 50 - docker/addons/vault/scripts/pkg/doc.go | 6 - .../addons/vault/scripts/pkg/errors/README.md | 5 - docker/addons/vault/scripts/pkg/errors/doc.go | 5 - .../addons/vault/scripts/pkg/errors/errors.go | 128 - .../vault/scripts/pkg/errors/errors_test.go | 352 -- .../scripts/pkg/errors/repository/types.go | 39 - .../vault/scripts/pkg/errors/sdk_errors.go | 123 - .../scripts/pkg/errors/sdk_errors_test.go | 206 - .../vault/scripts/pkg/errors/service/types.go | 78 - .../addons/vault/scripts/pkg/errors/types.go | 32 - .../addons/vault/scripts/pkg/events/events.go | 87 - .../scripts/pkg/events/mocks/publisher.go | 67 - .../scripts/pkg/events/mocks/subscriber.go | 67 - .../vault/scripts/pkg/events/nats/doc.go | 8 - .../scripts/pkg/events/nats/publisher.go | 79 - .../scripts/pkg/events/nats/publisher_test.go | 325 -- .../scripts/pkg/events/nats/setup_test.go | 81 - .../scripts/pkg/events/nats/subscriber.go | 138 - .../vault/scripts/pkg/events/rabbitmq/doc.go | 8 - .../scripts/pkg/events/rabbitmq/publisher.go | 73 - .../pkg/events/rabbitmq/publisher_test.go | 326 -- .../scripts/pkg/events/rabbitmq/setup_test.go | 79 - .../scripts/pkg/events/rabbitmq/subscriber.go | 122 - .../vault/scripts/pkg/events/redis/doc.go | 8 - .../scripts/pkg/events/redis/publisher.go | 118 - .../pkg/events/redis/publisher_test.go | 321 -- .../scripts/pkg/events/redis/setup_test.go | 77 - .../scripts/pkg/events/redis/subscriber.go | 125 - .../scripts/pkg/events/store/store_nats.go | 41 - .../pkg/events/store/store_rabbitmq.go | 41 - .../scripts/pkg/events/store/store_redis.go | 41 - docker/addons/vault/scripts/pkg/groups/doc.go | 6 - .../addons/vault/scripts/pkg/groups/errors.go | 17 - .../addons/vault/scripts/pkg/groups/groups.go | 133 - .../vault/scripts/pkg/groups/mocks/doc.go | 5 - .../scripts/pkg/groups/mocks/repository.go | 253 - .../vault/scripts/pkg/groups/mocks/service.go | 314 -- .../addons/vault/scripts/pkg/groups/page.go | 17 - .../addons/vault/scripts/pkg/groups/status.go | 83 - .../vault/scripts/pkg/grpcclient/client.go | 80 - .../scripts/pkg/grpcclient/client_test.go | 179 - .../vault/scripts/pkg/grpcclient/connect.go | 153 - .../scripts/pkg/grpcclient/connect_test.go | 114 - .../vault/scripts/pkg/grpcclient/doc.go | 6 - docker/addons/vault/scripts/pkg/jaeger/doc.go | 6 - .../vault/scripts/pkg/jaeger/provider.go | 77 - .../vault/scripts/pkg/messaging/README.md | 9 - .../pkg/messaging/brokers/brokers_nats.go | 41 - .../pkg/messaging/brokers/brokers_rabbitmq.go | 41 - .../messaging/brokers/tracing/brokers_nats.go | 31 - .../brokers/tracing/brokers_rabbitmq.go | 31 - .../scripts/pkg/messaging/handler/logging.go | 90 - .../scripts/pkg/messaging/handler/metrics.go | 86 - .../scripts/pkg/messaging/handler/tracing.go | 116 - .../vault/scripts/pkg/messaging/message.pb.go | 195 - .../vault/scripts/pkg/messaging/message.proto | 17 - .../scripts/pkg/messaging/mocks/pubsub.go | 103 - .../vault/scripts/pkg/messaging/mqtt/docs.go | 11 - .../scripts/pkg/messaging/mqtt/publisher.go | 61 - .../scripts/pkg/messaging/mqtt/pubsub.go | 230 - .../scripts/pkg/messaging/mqtt/pubsub_test.go | 474 -- .../scripts/pkg/messaging/mqtt/setup_test.go | 121 - .../vault/scripts/pkg/messaging/nats/doc.go | 11 - .../scripts/pkg/messaging/nats/options.go | 56 - .../scripts/pkg/messaging/nats/publisher.go | 88 - .../scripts/pkg/messaging/nats/pubsub.go | 174 - .../scripts/pkg/messaging/nats/pubsub_test.go | 297 -- .../scripts/pkg/messaging/nats/setup_test.go | 80 - .../scripts/pkg/messaging/nats/tracing/doc.go | 12 - .../pkg/messaging/nats/tracing/publisher.go | 52 - .../pkg/messaging/nats/tracing/pubsub.go | 96 - .../vault/scripts/pkg/messaging/pubsub.go | 82 - .../scripts/pkg/messaging/rabbitmq/doc.go | 11 - .../scripts/pkg/messaging/rabbitmq/options.go | 60 - .../pkg/messaging/rabbitmq/publisher.go | 95 - .../scripts/pkg/messaging/rabbitmq/pubsub.go | 191 - .../pkg/messaging/rabbitmq/pubsub_test.go | 460 -- .../pkg/messaging/rabbitmq/setup_test.go | 131 - .../pkg/messaging/rabbitmq/tracing/doc.go | 12 - .../messaging/rabbitmq/tracing/publisher.go | 54 - .../pkg/messaging/rabbitmq/tracing/pubsub.go | 96 - .../scripts/pkg/messaging/tracing/doc.go | 12 - .../scripts/pkg/messaging/tracing/tracing.go | 44 - docker/addons/vault/scripts/pkg/oauth2/doc.go | 6 - .../vault/scripts/pkg/oauth2/google/doc.go | 6 - .../scripts/pkg/oauth2/google/provider.go | 132 - .../scripts/pkg/oauth2/mocks/provider.go | 180 - .../addons/vault/scripts/pkg/oauth2/oauth2.go | 46 - .../addons/vault/scripts/pkg/policies/doc.go | 5 - .../vault/scripts/pkg/policies/evaluator.go | 64 - .../scripts/pkg/policies/mocks/evaluator.go | 49 - .../scripts/pkg/policies/mocks/service.go | 301 -- .../vault/scripts/pkg/policies/service.go | 104 - .../vault/scripts/pkg/policies/spicedb/doc.go | 5 - .../scripts/pkg/policies/spicedb/evaluator.go | 64 - .../scripts/pkg/policies/spicedb/service.go | 950 ---- .../vault/scripts/pkg/postgres/common.go | 53 - .../addons/vault/scripts/pkg/postgres/doc.go | 9 - .../vault/scripts/pkg/postgres/errors.go | 39 - .../vault/scripts/pkg/postgres/postgres.go | 65 - .../vault/scripts/pkg/postgres/tracing.go | 130 - .../vault/scripts/pkg/prometheus/doc.go | 6 - .../vault/scripts/pkg/prometheus/metrics.go | 31 - docker/addons/vault/scripts/pkg/sdk/README.md | 5 - .../addons/vault/scripts/pkg/sdk/go/README.md | 83 - .../vault/scripts/pkg/sdk/go/bootstrap.go | 322 -- .../scripts/pkg/sdk/go/bootstrap_test.go | 1347 ----- .../addons/vault/scripts/pkg/sdk/go/certs.go | 108 - .../vault/scripts/pkg/sdk/go/certs_test.go | 463 -- .../vault/scripts/pkg/sdk/go/channels.go | 307 -- .../vault/scripts/pkg/sdk/go/channels_test.go | 2900 ----------- .../vault/scripts/pkg/sdk/go/consumers.go | 89 - .../scripts/pkg/sdk/go/consumers_test.go | 468 -- docker/addons/vault/scripts/pkg/sdk/go/doc.go | 5 - .../vault/scripts/pkg/sdk/go/domains.go | 204 - .../vault/scripts/pkg/sdk/go/domains_test.go | 1136 ----- .../addons/vault/scripts/pkg/sdk/go/groups.go | 256 - .../vault/scripts/pkg/sdk/go/groups_test.go | 2038 -------- .../addons/vault/scripts/pkg/sdk/go/health.go | 65 - .../vault/scripts/pkg/sdk/go/health_test.go | 144 - .../vault/scripts/pkg/sdk/go/invitations.go | 129 - .../scripts/pkg/sdk/go/invitations_test.go | 575 --- .../vault/scripts/pkg/sdk/go/journal.go | 57 - .../vault/scripts/pkg/sdk/go/journal_test.go | 257 - .../vault/scripts/pkg/sdk/go/message.go | 104 - .../vault/scripts/pkg/sdk/go/message_test.go | 402 -- .../vault/scripts/pkg/sdk/go/metadata.go | 6 - .../vault/scripts/pkg/sdk/go/requests.go | 58 - .../vault/scripts/pkg/sdk/go/responses.go | 85 - docker/addons/vault/scripts/pkg/sdk/go/sdk.go | 1453 ------ .../vault/scripts/pkg/sdk/go/setup_test.go | 257 - .../addons/vault/scripts/pkg/sdk/go/things.go | 302 -- .../vault/scripts/pkg/sdk/go/things_test.go | 2202 --------- .../addons/vault/scripts/pkg/sdk/go/tokens.go | 61 - .../vault/scripts/pkg/sdk/go/tokens_test.go | 185 - .../addons/vault/scripts/pkg/sdk/go/users.go | 426 -- .../vault/scripts/pkg/sdk/go/users_test.go | 2765 ----------- .../addons/vault/scripts/pkg/sdk/mocks/sdk.go | 3021 ------------ .../vault/scripts/pkg/server/coap/coap.go | 60 - .../vault/scripts/pkg/server/coap/doc.go | 5 - docker/addons/vault/scripts/pkg/server/doc.go | 5 - .../vault/scripts/pkg/server/grpc/doc.go | 5 - .../vault/scripts/pkg/server/grpc/grpc.go | 152 - .../vault/scripts/pkg/server/http/doc.go | 5 - .../vault/scripts/pkg/server/http/http.go | 71 - .../addons/vault/scripts/pkg/server/server.go | 90 - .../vault/scripts/pkg/transformers/README.md | 10 - .../vault/scripts/pkg/transformers/doc.go | 6 - .../scripts/pkg/transformers/json/README.md | 54 - .../scripts/pkg/transformers/json/doc.go | 5 - .../pkg/transformers/json/example_test.go | 73 - .../scripts/pkg/transformers/json/message.go | 23 - .../scripts/pkg/transformers/json/time.go | 152 - .../pkg/transformers/json/transformer.go | 195 - .../pkg/transformers/json/transformer_test.go | 256 - .../scripts/pkg/transformers/senml/README.md | 4 - .../scripts/pkg/transformers/senml/doc.go | 5 - .../scripts/pkg/transformers/senml/message.go | 21 - .../pkg/transformers/senml/transformer.go | 94 - .../transformers/senml/transformer_test.go | 151 - .../scripts/pkg/transformers/transformer.go | 32 - .../pkg/transformers/transformer_test.go | 140 - .../addons/vault/scripts/pkg/ulid/README.md | 3 - docker/addons/vault/scripts/pkg/ulid/doc.go | 5 - docker/addons/vault/scripts/pkg/ulid/ulid.go | 41 - .../addons/vault/scripts/pkg/uuid/README.md | 3 - docker/addons/vault/scripts/pkg/uuid/doc.go | 5 - docker/addons/vault/scripts/pkg/uuid/mock.go | 35 - docker/addons/vault/scripts/pkg/uuid/uuid.go | 32 - .../addons/vault/scripts/provision/README.md | 194 - .../addons/vault/scripts/provision/api/doc.go | 6 - .../vault/scripts/provision/api/endpoint.go | 54 - .../scripts/provision/api/endpoint_test.go | 223 - .../vault/scripts/provision/api/logging.go | 77 - .../vault/scripts/provision/api/requests.go | 48 - .../scripts/provision/api/requests_test.go | 110 - .../vault/scripts/provision/api/responses.go | 55 - .../vault/scripts/provision/api/transport.go | 83 - .../addons/vault/scripts/provision/config.go | 104 - .../vault/scripts/provision/config_test.go | 222 - .../scripts/provision/configs/config.toml | 47 - docker/addons/vault/scripts/provision/doc.go | 6 - .../vault/scripts/provision/mocks/service.go | 122 - .../addons/vault/scripts/provision/service.go | 425 -- .../vault/scripts/provision/service_test.go | 232 - docker/addons/vault/scripts/readers/README.md | 7 - .../addons/vault/scripts/readers/api/doc.go | 6 - .../vault/scripts/readers/api/endpoint.go | 41 - .../scripts/readers/api/endpoint_test.go | 1024 ---- .../vault/scripts/readers/api/logging.go | 56 - .../vault/scripts/readers/api/metrics.go | 39 - .../vault/scripts/readers/api/requests.go | 71 - .../vault/scripts/readers/api/responses.go | 31 - .../vault/scripts/readers/api/transport.go | 281 -- docker/addons/vault/scripts/readers/doc.go | 5 - .../addons/vault/scripts/readers/messages.go | 84 - .../addons/vault/scripts/readers/mocks/doc.go | 5 - .../vault/scripts/readers/mocks/messages.go | 57 - .../vault/scripts/readers/postgres/README.md | 101 - .../vault/scripts/readers/postgres/doc.go | 6 - .../vault/scripts/readers/postgres/init.go | 80 - .../scripts/readers/postgres/messages.go | 199 - .../scripts/readers/postgres/messages_test.go | 687 --- .../scripts/readers/postgres/setup_test.go | 83 - .../vault/scripts/readers/timescale/README.md | 99 - .../vault/scripts/readers/timescale/doc.go | 6 - .../vault/scripts/readers/timescale/init.go | 80 - .../scripts/readers/timescale/messages.go | 204 - .../readers/timescale/messages_test.go | 810 --- .../scripts/readers/timescale/setup_test.go | 84 - docker/addons/vault/scripts/scripts/ci.sh | 117 - .../vault/scripts/scripts/csv/channels.csv | 3 - .../vault/scripts/scripts/csv/things.csv | 10 - .../vault/scripts/scripts/provision-dev.sh | 50 - docker/addons/vault/scripts/scripts/run.sh | 70 - docker/addons/vault/scripts/things/README.md | 122 - docker/addons/vault/scripts/things/api/doc.go | 6 - .../vault/scripts/things/api/grpc/client.go | 105 - .../vault/scripts/things/api/grpc/doc.go | 5 - .../vault/scripts/things/api/grpc/endpoint.go | 31 - .../scripts/things/api/grpc/endpoint_test.go | 208 - .../vault/scripts/things/api/grpc/request.go | 11 - .../scripts/things/api/grpc/responses.go | 9 - .../vault/scripts/things/api/grpc/server.go | 83 - .../vault/scripts/things/api/http/channels.go | 298 -- .../vault/scripts/things/api/http/clients.go | 380 -- .../scripts/things/api/http/endpoints.go | 530 -- .../scripts/things/api/http/endpoints_test.go | 3356 ------------- .../vault/scripts/things/api/http/requests.go | 255 - .../scripts/things/api/http/requests_test.go | 612 --- .../scripts/things/api/http/responses.go | 310 -- .../scripts/things/api/http/transport.go | 27 - .../addons/vault/scripts/things/cache/doc.go | 6 - .../vault/scripts/things/cache/setup_test.go | 61 - .../vault/scripts/things/cache/things.go | 85 - .../vault/scripts/things/cache/things_test.go | 179 - docker/addons/vault/scripts/things/clients.go | 196 - docker/addons/vault/scripts/things/doc.go | 11 - docker/addons/vault/scripts/things/errors.go | 14 - .../addons/vault/scripts/things/events/doc.go | 6 - .../vault/scripts/things/events/events.go | 336 -- .../vault/scripts/things/events/streams.go | 266 - .../things/middleware/authorization.go | 200 - .../vault/scripts/things/middleware/doc.go | 5 - .../scripts/things/middleware/logging.go | 301 -- .../scripts/things/middleware/metrics.go | 150 - .../vault/scripts/things/mocks/cache.go | 94 - .../addons/vault/scripts/things/mocks/doc.go | 5 - .../vault/scripts/things/mocks/repository.go | 366 -- .../vault/scripts/things/mocks/service.go | 449 -- .../scripts/things/mocks/things_client.go | 118 - .../vault/scripts/things/postgres/clients.go | 574 --- .../scripts/things/postgres/clients_test.go | 428 -- .../vault/scripts/things/postgres/doc.go | 5 - .../vault/scripts/things/postgres/init.go | 41 - .../scripts/things/postgres/setup_test.go | 97 - docker/addons/vault/scripts/things/roles.go | 71 - .../addons/vault/scripts/things/roles_test.go | 175 - docker/addons/vault/scripts/things/service.go | 495 -- .../vault/scripts/things/service_test.go | 1393 ------ .../vault/scripts/things/standalone/doc.go | 9 - .../scripts/things/standalone/standalone.go | 4 - docker/addons/vault/scripts/things/status.go | 94 - .../vault/scripts/things/status_test.go | 246 - .../vault/scripts/things/tracing/doc.go | 12 - .../vault/scripts/things/tracing/tracing.go | 142 - .../scripts/tools/config/boilerplate.txt | 3 - .../vault/scripts/tools/config/codecov.yml | 10 - .../vault/scripts/tools/config/golangci.yml | 100 - .../vault/scripts/tools/config/mockery.yaml | 33 - docker/addons/vault/scripts/tools/doc.go | 5 - .../addons/vault/scripts/tools/e2e/Makefile | 15 - .../addons/vault/scripts/tools/e2e/README.md | 93 - .../vault/scripts/tools/e2e/cmd/main.go | 58 - docker/addons/vault/scripts/tools/e2e/doc.go | 5 - docker/addons/vault/scripts/tools/e2e/e2e.go | 639 --- .../vault/scripts/tools/mqtt-bench/Makefile | 15 - .../vault/scripts/tools/mqtt-bench/README.md | 109 - .../vault/scripts/tools/mqtt-bench/bench.go | 205 - .../vault/scripts/tools/mqtt-bench/client.go | 221 - .../scripts/tools/mqtt-bench/cmd/main.go | 77 - .../vault/scripts/tools/mqtt-bench/config.go | 68 - .../vault/scripts/tools/mqtt-bench/doc.go | 5 - .../vault/scripts/tools/mqtt-bench/results.go | 194 - .../tools/mqtt-bench/scripts/mqtt-bench.sh | 57 - .../tools/mqtt-bench/templates/reference.toml | 29 - .../vault/scripts/tools/provision/Makefile | 15 - .../vault/scripts/tools/provision/README.md | 146 - .../vault/scripts/tools/provision/cmd/main.go | 42 - .../vault/scripts/tools/provision/doc.go | 7 - .../scripts/tools/provision/provision.go | 298 -- docker/addons/vault/scripts/users/README.md | 132 - docker/addons/vault/scripts/users/api/doc.go | 6 - .../vault/scripts/users/api/endpoint_test.go | 4352 ----------------- .../vault/scripts/users/api/endpoints.go | 593 --- .../addons/vault/scripts/users/api/groups.go | 270 - .../vault/scripts/users/api/requests.go | 413 -- .../vault/scripts/users/api/requests_test.go | 858 ---- .../vault/scripts/users/api/responses.go | 241 - .../vault/scripts/users/api/transport.go | 29 - .../addons/vault/scripts/users/api/users.go | 736 --- .../vault/scripts/users/delete_handler.go | 109 - docker/addons/vault/scripts/users/doc.go | 11 - docker/addons/vault/scripts/users/emailer.go | 12 - .../addons/vault/scripts/users/emailer/doc.go | 6 - .../vault/scripts/users/emailer/emailer.go | 29 - docker/addons/vault/scripts/users/errors.go | 14 - .../addons/vault/scripts/users/events/doc.go | 6 - .../vault/scripts/users/events/events.go | 519 -- .../vault/scripts/users/events/streams.go | 389 -- docker/addons/vault/scripts/users/hasher.go | 17 - .../addons/vault/scripts/users/hasher/doc.go | 6 - .../vault/scripts/users/hasher/hasher.go | 43 - .../scripts/users/middleware/authorization.go | 234 - .../vault/scripts/users/middleware/doc.go | 5 - .../vault/scripts/users/middleware/logging.go | 508 -- .../vault/scripts/users/middleware/metrics.go | 247 - .../addons/vault/scripts/users/mocks/doc.go | 5 - .../vault/scripts/users/mocks/emailer.go | 44 - .../vault/scripts/users/mocks/hasher.go | 72 - .../vault/scripts/users/mocks/repository.go | 375 -- .../vault/scripts/users/mocks/service.go | 662 --- .../vault/scripts/users/postgres/doc.go | 5 - .../vault/scripts/users/postgres/init.go | 91 - .../scripts/users/postgres/setup_test.go | 93 - .../vault/scripts/users/postgres/users.go | 678 --- .../scripts/users/postgres/users_test.go | 1898 ------- docker/addons/vault/scripts/users/roles.go | 71 - docker/addons/vault/scripts/users/service.go | 695 --- .../vault/scripts/users/service_test.go | 2048 -------- docker/addons/vault/scripts/users/status.go | 83 - .../addons/vault/scripts/users/tracing/doc.go | 12 - .../vault/scripts/users/tracing/tracing.go | 255 - docker/addons/vault/scripts/users/users.go | 218 - docker/addons/vault/scripts/uuid.go | 10 - docker/addons/vault/scripts/ws/README.md | 71 - docker/addons/vault/scripts/ws/adapter.go | 102 - .../addons/vault/scripts/ws/adapter_test.go | 125 - docker/addons/vault/scripts/ws/api/doc.go | 6 - .../vault/scripts/ws/api/endpoint_test.go | 213 - .../addons/vault/scripts/ws/api/endpoints.go | 125 - docker/addons/vault/scripts/ws/api/logging.go | 46 - docker/addons/vault/scripts/ws/api/metrics.go | 41 - .../addons/vault/scripts/ws/api/requests.go | 13 - .../addons/vault/scripts/ws/api/transport.go | 50 - docker/addons/vault/scripts/ws/client.go | 41 - docker/addons/vault/scripts/ws/client_test.go | 102 - docker/addons/vault/scripts/ws/doc.go | 15 - docker/addons/vault/scripts/ws/handler.go | 275 -- docker/addons/vault/scripts/ws/tracing/doc.go | 12 - .../vault/scripts/ws/tracing/tracing.go | 40 - 834 files changed, 161603 deletions(-) delete mode 100644 docker/addons/vault/scripts/.dockerignore delete mode 100644 docker/addons/vault/scripts/.github/CODEOWNERS delete mode 100644 docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/config.yml delete mode 100644 docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 docker/addons/vault/scripts/.github/PULL_REQUEST_TEMPLATE.md delete mode 100644 docker/addons/vault/scripts/.github/dependabot.yml delete mode 100644 docker/addons/vault/scripts/.github/workflows/api-tests.yml delete mode 100644 docker/addons/vault/scripts/.github/workflows/build.yml delete mode 100644 docker/addons/vault/scripts/.github/workflows/check-generated-files.yml delete mode 100644 docker/addons/vault/scripts/.github/workflows/check-license.yaml delete mode 100644 docker/addons/vault/scripts/.github/workflows/swagger-ui.yaml delete mode 100644 docker/addons/vault/scripts/.github/workflows/tests.yml delete mode 100644 docker/addons/vault/scripts/.gitignore delete mode 100644 docker/addons/vault/scripts/ADOPTERS.md delete mode 100644 docker/addons/vault/scripts/CONTRIBUTING.md delete mode 100644 docker/addons/vault/scripts/LICENSE delete mode 100644 docker/addons/vault/scripts/MAINTAINERS delete mode 100644 docker/addons/vault/scripts/Makefile delete mode 100644 docker/addons/vault/scripts/README.md delete mode 100644 docker/addons/vault/scripts/api.go delete mode 100644 docker/addons/vault/scripts/api/asyncapi/mqtt.yml delete mode 100644 docker/addons/vault/scripts/api/asyncapi/websocket.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/README.md delete mode 100644 docker/addons/vault/scripts/api/openapi/auth.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/bootstrap.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/certs.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/http.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/invitations.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/journal.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/notifiers.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/provision.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/readers.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/schemas/HealthInfo.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/things.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/twins.yml delete mode 100644 docker/addons/vault/scripts/api/openapi/users.yml delete mode 100644 docker/addons/vault/scripts/auth.pb.go delete mode 100644 docker/addons/vault/scripts/auth.proto delete mode 100644 docker/addons/vault/scripts/auth/README.md delete mode 100644 docker/addons/vault/scripts/auth/api/doc.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/auth/client.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/auth/doc.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/auth/endpoint.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/auth/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/auth/requests.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/auth/responses.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/auth/server.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/auth/setup_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/domains/client.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/domains/doc.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/domains/endpoint.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/domains/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/domains/requests.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/domains/responses.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/domains/server.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/domains/setup_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/token/client.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/token/doc.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/token/endpoint.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/token/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/token/requests.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/token/responses.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/token/server.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/token/setup_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/grpc/utils.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/doc.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/domains/decode.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/domains/endpoint.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/domains/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/domains/requests.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/domains/responses.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/domains/transport.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/keys/endpoint.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/keys/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/keys/requests.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/keys/requests_test.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/keys/responses.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/keys/transport.go delete mode 100644 docker/addons/vault/scripts/auth/api/http/transport.go delete mode 100644 docker/addons/vault/scripts/auth/api/logging.go delete mode 100644 docker/addons/vault/scripts/auth/api/metrics.go delete mode 100644 docker/addons/vault/scripts/auth/domains.go delete mode 100644 docker/addons/vault/scripts/auth/domains_test.go delete mode 100644 docker/addons/vault/scripts/auth/events/doc.go delete mode 100644 docker/addons/vault/scripts/auth/events/events.go delete mode 100644 docker/addons/vault/scripts/auth/events/streams.go delete mode 100644 docker/addons/vault/scripts/auth/jwt/token_test.go delete mode 100644 docker/addons/vault/scripts/auth/jwt/tokenizer.go delete mode 100644 docker/addons/vault/scripts/auth/keys.go delete mode 100644 docker/addons/vault/scripts/auth/keys_test.go delete mode 100644 docker/addons/vault/scripts/auth/mocks/authz.go delete mode 100644 docker/addons/vault/scripts/auth/mocks/domains.go delete mode 100644 docker/addons/vault/scripts/auth/mocks/domains_client.go delete mode 100644 docker/addons/vault/scripts/auth/mocks/keys.go delete mode 100644 docker/addons/vault/scripts/auth/mocks/service.go delete mode 100644 docker/addons/vault/scripts/auth/mocks/token_client.go delete mode 100644 docker/addons/vault/scripts/auth/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/auth/postgres/domains.go delete mode 100644 docker/addons/vault/scripts/auth/postgres/domains_test.go delete mode 100644 docker/addons/vault/scripts/auth/postgres/init.go delete mode 100644 docker/addons/vault/scripts/auth/postgres/key.go delete mode 100644 docker/addons/vault/scripts/auth/postgres/key_test.go delete mode 100644 docker/addons/vault/scripts/auth/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/auth/service.go delete mode 100644 docker/addons/vault/scripts/auth/service_test.go delete mode 100644 docker/addons/vault/scripts/auth/tokenizer.go delete mode 100644 docker/addons/vault/scripts/auth/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/auth/tracing/tracing.go delete mode 100644 docker/addons/vault/scripts/auth_grpc.pb.go delete mode 100644 docker/addons/vault/scripts/bootstrap/README.md delete mode 100644 docker/addons/vault/scripts/bootstrap/api/doc.go delete mode 100644 docker/addons/vault/scripts/bootstrap/api/endpoint.go delete mode 100644 docker/addons/vault/scripts/bootstrap/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/bootstrap/api/requests.go delete mode 100644 docker/addons/vault/scripts/bootstrap/api/requests_test.go delete mode 100644 docker/addons/vault/scripts/bootstrap/api/responses.go delete mode 100644 docker/addons/vault/scripts/bootstrap/api/transport.go delete mode 100644 docker/addons/vault/scripts/bootstrap/configs.go delete mode 100644 docker/addons/vault/scripts/bootstrap/doc.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/consumer/doc.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/consumer/events.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/consumer/streams.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/doc.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/producer/doc.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/producer/events.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/producer/setup_test.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/producer/streams.go delete mode 100644 docker/addons/vault/scripts/bootstrap/events/producer/streams_test.go delete mode 100644 docker/addons/vault/scripts/bootstrap/middleware/authorization.go delete mode 100644 docker/addons/vault/scripts/bootstrap/middleware/logging.go delete mode 100644 docker/addons/vault/scripts/bootstrap/middleware/metrics.go delete mode 100644 docker/addons/vault/scripts/bootstrap/mocks/config_reader.go delete mode 100644 docker/addons/vault/scripts/bootstrap/mocks/configs.go delete mode 100644 docker/addons/vault/scripts/bootstrap/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/bootstrap/mocks/service.go delete mode 100644 docker/addons/vault/scripts/bootstrap/postgres/configs.go delete mode 100644 docker/addons/vault/scripts/bootstrap/postgres/configs_test.go delete mode 100644 docker/addons/vault/scripts/bootstrap/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/bootstrap/postgres/init.go delete mode 100644 docker/addons/vault/scripts/bootstrap/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/bootstrap/reader.go delete mode 100644 docker/addons/vault/scripts/bootstrap/reader_test.go delete mode 100644 docker/addons/vault/scripts/bootstrap/service.go delete mode 100644 docker/addons/vault/scripts/bootstrap/service_test.go delete mode 100644 docker/addons/vault/scripts/bootstrap/state.go delete mode 100644 docker/addons/vault/scripts/bootstrap/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/bootstrap/tracing/tracing.go delete mode 100644 docker/addons/vault/scripts/certs/README.md delete mode 100644 docker/addons/vault/scripts/certs/api/doc.go delete mode 100644 docker/addons/vault/scripts/certs/api/endpoint.go delete mode 100644 docker/addons/vault/scripts/certs/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/certs/api/logging.go delete mode 100644 docker/addons/vault/scripts/certs/api/metrics.go delete mode 100644 docker/addons/vault/scripts/certs/api/requests.go delete mode 100644 docker/addons/vault/scripts/certs/api/responses.go delete mode 100644 docker/addons/vault/scripts/certs/api/transport.go delete mode 100644 docker/addons/vault/scripts/certs/certs.go delete mode 100644 docker/addons/vault/scripts/certs/certs_test.go delete mode 100644 docker/addons/vault/scripts/certs/doc.go delete mode 100644 docker/addons/vault/scripts/certs/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/certs/mocks/pki.go delete mode 100644 docker/addons/vault/scripts/certs/mocks/service.go delete mode 100644 docker/addons/vault/scripts/certs/pki/amcerts/am_certs.go delete mode 100644 docker/addons/vault/scripts/certs/pki/amcerts/doc.go delete mode 100644 docker/addons/vault/scripts/certs/pki/vault/doc.go delete mode 100644 docker/addons/vault/scripts/certs/pki/vault/vault.go delete mode 100644 docker/addons/vault/scripts/certs/service.go delete mode 100644 docker/addons/vault/scripts/certs/service_test.go delete mode 100644 docker/addons/vault/scripts/certs/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/certs/tracing/tracing.go delete mode 100644 docker/addons/vault/scripts/cli/README.md delete mode 100644 docker/addons/vault/scripts/cli/bootstrap.go delete mode 100644 docker/addons/vault/scripts/cli/bootstrap_test.go delete mode 100644 docker/addons/vault/scripts/cli/certs.go delete mode 100644 docker/addons/vault/scripts/cli/certs_test.go delete mode 100644 docker/addons/vault/scripts/cli/channels.go delete mode 100644 docker/addons/vault/scripts/cli/channels_test.go delete mode 100644 docker/addons/vault/scripts/cli/commands_test.go delete mode 100644 docker/addons/vault/scripts/cli/config.go delete mode 100644 docker/addons/vault/scripts/cli/consumers.go delete mode 100644 docker/addons/vault/scripts/cli/consumers_test.go delete mode 100644 docker/addons/vault/scripts/cli/doc.go delete mode 100644 docker/addons/vault/scripts/cli/domains.go delete mode 100644 docker/addons/vault/scripts/cli/domains_test.go delete mode 100644 docker/addons/vault/scripts/cli/groups.go delete mode 100644 docker/addons/vault/scripts/cli/groups_test.go delete mode 100644 docker/addons/vault/scripts/cli/health.go delete mode 100644 docker/addons/vault/scripts/cli/health_test.go delete mode 100644 docker/addons/vault/scripts/cli/invitations.go delete mode 100644 docker/addons/vault/scripts/cli/invitations_test.go delete mode 100644 docker/addons/vault/scripts/cli/journal.go delete mode 100644 docker/addons/vault/scripts/cli/journal_test.go delete mode 100644 docker/addons/vault/scripts/cli/message.go delete mode 100644 docker/addons/vault/scripts/cli/message_test.go delete mode 100644 docker/addons/vault/scripts/cli/provision.go delete mode 100644 docker/addons/vault/scripts/cli/sdk.go delete mode 100644 docker/addons/vault/scripts/cli/setup_test.go delete mode 100644 docker/addons/vault/scripts/cli/things.go delete mode 100644 docker/addons/vault/scripts/cli/things_test.go delete mode 100644 docker/addons/vault/scripts/cli/users.go delete mode 100644 docker/addons/vault/scripts/cli/users_test.go delete mode 100644 docker/addons/vault/scripts/cli/utils.go delete mode 100644 docker/addons/vault/scripts/cmd/auth/main.go delete mode 100644 docker/addons/vault/scripts/cmd/bootstrap/main.go delete mode 100644 docker/addons/vault/scripts/cmd/certs/main.go delete mode 100644 docker/addons/vault/scripts/cmd/cli/main.go delete mode 100644 docker/addons/vault/scripts/cmd/coap/main.go delete mode 100644 docker/addons/vault/scripts/cmd/http/main.go delete mode 100644 docker/addons/vault/scripts/cmd/invitations/main.go delete mode 100644 docker/addons/vault/scripts/cmd/journal/main.go delete mode 100644 docker/addons/vault/scripts/cmd/mqtt/main.go delete mode 100644 docker/addons/vault/scripts/cmd/postgres-reader/main.go delete mode 100644 docker/addons/vault/scripts/cmd/postgres-writer/main.go delete mode 100644 docker/addons/vault/scripts/cmd/provision/main.go delete mode 100644 docker/addons/vault/scripts/cmd/things/main.go delete mode 100644 docker/addons/vault/scripts/cmd/timescale-reader/main.go delete mode 100644 docker/addons/vault/scripts/cmd/timescale-writer/main.go delete mode 100644 docker/addons/vault/scripts/cmd/users/main.go delete mode 100644 docker/addons/vault/scripts/cmd/ws/main.go delete mode 100644 docker/addons/vault/scripts/coap/README.md delete mode 100644 docker/addons/vault/scripts/coap/adapter.go delete mode 100644 docker/addons/vault/scripts/coap/api/doc.go delete mode 100644 docker/addons/vault/scripts/coap/api/logging.go delete mode 100644 docker/addons/vault/scripts/coap/api/metrics.go delete mode 100644 docker/addons/vault/scripts/coap/api/transport.go delete mode 100644 docker/addons/vault/scripts/coap/client.go delete mode 100644 docker/addons/vault/scripts/coap/tracing/adapter.go delete mode 100644 docker/addons/vault/scripts/coap/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/config.toml delete mode 100644 docker/addons/vault/scripts/consumers/README.md delete mode 100644 docker/addons/vault/scripts/consumers/consumer.go delete mode 100644 docker/addons/vault/scripts/consumers/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/messages.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/README.md delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/api/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/api/endpoint.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/api/logging.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/api/metrics.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/api/requests.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/api/responses.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/api/transport.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/mocks/notifier.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/mocks/repository.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/mocks/service.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/notifier.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/postgres/database.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/postgres/init.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/postgres/subscriptions.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/postgres/subscriptions_test.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/service.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/service_test.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/smtp/notifier.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/subscriptions.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/notifiers/tracing/subscriptions.go delete mode 100644 docker/addons/vault/scripts/consumers/tracing/consumers.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/README.md delete mode 100644 docker/addons/vault/scripts/consumers/writers/api/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/api/logging.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/api/metrics.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/api/transport.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/postgres/README.md delete mode 100644 docker/addons/vault/scripts/consumers/writers/postgres/consumer.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/postgres/consumer_test.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/postgres/init.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/timescale/README.md delete mode 100644 docker/addons/vault/scripts/consumers/writers/timescale/consumer.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/timescale/consumer_test.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/timescale/doc.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/timescale/init.go delete mode 100644 docker/addons/vault/scripts/consumers/writers/timescale/setup_test.go delete mode 100644 docker/addons/vault/scripts/doc.go delete mode 100644 docker/addons/vault/scripts/docker/.env delete mode 100644 docker/addons/vault/scripts/docker/Dockerfile delete mode 100644 docker/addons/vault/scripts/docker/Dockerfile.dev delete mode 100644 docker/addons/vault/scripts/docker/README.md delete mode 100644 docker/addons/vault/scripts/docker/addons/bootstrap/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/certs/config.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/certs/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/journal/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/postgres-reader/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/postgres-writer/config.toml delete mode 100644 docker/addons/vault/scripts/docker/addons/postgres-writer/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/prometheus/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/prometheus/grafana/dashboard.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/prometheus/grafana/datasource.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/prometheus/grafana/example-dashboard.json delete mode 100644 docker/addons/vault/scripts/docker/addons/prometheus/metrics/prometheus.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/provision/configs/config.toml delete mode 100644 docker/addons/vault/scripts/docker/addons/provision/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/timescale-reader/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/timescale-writer/config.toml delete mode 100644 docker/addons/vault/scripts/docker/addons/timescale-writer/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/vault/README.md delete mode 100644 docker/addons/vault/scripts/docker/addons/vault/config.hcl delete mode 100644 docker/addons/vault/scripts/docker/addons/vault/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/addons/vault/entrypoint.sh delete mode 100644 docker/addons/vault/scripts/docker/addons/vault/scripts/.gitignore delete mode 100644 docker/addons/vault/scripts/docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl delete mode 100644 docker/addons/vault/scripts/docker/addons/vault/scripts/vault_cmd.sh delete mode 100755 docker/addons/vault/scripts/docker/addons/vault/scripts/vault_copy_certs.sh delete mode 100755 docker/addons/vault/scripts/docker/addons/vault/scripts/vault_copy_env.sh delete mode 100755 docker/addons/vault/scripts/docker/addons/vault/scripts/vault_create_approle.sh delete mode 100755 docker/addons/vault/scripts/docker/addons/vault/scripts/vault_init.sh delete mode 100755 docker/addons/vault/scripts/docker/addons/vault/scripts/vault_set_pki.sh delete mode 100755 docker/addons/vault/scripts/docker/addons/vault/scripts/vault_unseal.sh delete mode 100644 docker/addons/vault/scripts/docker/docker-compose.yml delete mode 100644 docker/addons/vault/scripts/docker/nats/nats.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/.gitignore delete mode 100755 docker/addons/vault/scripts/docker/nginx/entrypoint.sh delete mode 100644 docker/addons/vault/scripts/docker/nginx/nginx-key.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/nginx-x509.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/http_access_log.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/mqtt-upstream-cluster.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/mqtt-upstream-single.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/mqtt-ws-upstream-cluster.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/mqtt-ws-upstream-single.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/proxy-headers.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/ssl-client.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/ssl.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/stream_access_log.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/verify-ssl-client.conf delete mode 100644 docker/addons/vault/scripts/docker/nginx/snippets/ws-upgrade.conf delete mode 100644 docker/addons/vault/scripts/docker/spicedb/schema.zed delete mode 100644 docker/addons/vault/scripts/docker/ssl/.gitignore delete mode 100644 docker/addons/vault/scripts/docker/ssl/Makefile delete mode 100644 docker/addons/vault/scripts/docker/ssl/authorization.js delete mode 100644 docker/addons/vault/scripts/docker/ssl/certs/ca.crt delete mode 100644 docker/addons/vault/scripts/docker/ssl/certs/ca.key delete mode 100644 docker/addons/vault/scripts/docker/ssl/certs/magistrala-server.crt delete mode 100644 docker/addons/vault/scripts/docker/ssl/certs/magistrala-server.key delete mode 100644 docker/addons/vault/scripts/docker/ssl/dhparam.pem delete mode 100644 docker/addons/vault/scripts/docker/templates/smtp-notifier.tmpl delete mode 100644 docker/addons/vault/scripts/docker/templates/users.tmpl delete mode 100644 docker/addons/vault/scripts/docker/vernemq/Dockerfile delete mode 100755 docker/addons/vault/scripts/docker/vernemq/bin/vernemq.sh delete mode 100644 docker/addons/vault/scripts/docker/vernemq/files/vm.args delete mode 100644 docker/addons/vault/scripts/go.mod delete mode 100644 docker/addons/vault/scripts/go.sum delete mode 100644 docker/addons/vault/scripts/health.go delete mode 100644 docker/addons/vault/scripts/http/README.md delete mode 100644 docker/addons/vault/scripts/http/api/doc.go delete mode 100644 docker/addons/vault/scripts/http/api/endpoint.go delete mode 100644 docker/addons/vault/scripts/http/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/http/api/request.go delete mode 100644 docker/addons/vault/scripts/http/api/response.go delete mode 100644 docker/addons/vault/scripts/http/api/transport.go delete mode 100644 docker/addons/vault/scripts/http/doc.go delete mode 100644 docker/addons/vault/scripts/http/handler.go delete mode 100644 docker/addons/vault/scripts/internal/api/auth.go delete mode 100644 docker/addons/vault/scripts/internal/api/common.go delete mode 100644 docker/addons/vault/scripts/internal/api/common_test.go delete mode 100644 docker/addons/vault/scripts/internal/api/doc.go delete mode 100644 docker/addons/vault/scripts/internal/clients/doc.go delete mode 100644 docker/addons/vault/scripts/internal/clients/redis/doc.go delete mode 100644 docker/addons/vault/scripts/internal/clients/redis/redis.go delete mode 100644 docker/addons/vault/scripts/internal/email/README.md delete mode 100644 docker/addons/vault/scripts/internal/email/doc.go delete mode 100644 docker/addons/vault/scripts/internal/email/email.go delete mode 100644 docker/addons/vault/scripts/internal/groups/api/decode.go delete mode 100644 docker/addons/vault/scripts/internal/groups/api/decode_test.go delete mode 100644 docker/addons/vault/scripts/internal/groups/api/doc.go delete mode 100644 docker/addons/vault/scripts/internal/groups/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/internal/groups/api/endpoints.go delete mode 100644 docker/addons/vault/scripts/internal/groups/api/requests.go delete mode 100644 docker/addons/vault/scripts/internal/groups/api/requests_test.go delete mode 100644 docker/addons/vault/scripts/internal/groups/api/responses.go delete mode 100644 docker/addons/vault/scripts/internal/groups/events/doc.go delete mode 100644 docker/addons/vault/scripts/internal/groups/events/events.go delete mode 100644 docker/addons/vault/scripts/internal/groups/events/streams.go delete mode 100644 docker/addons/vault/scripts/internal/groups/middleware/authorization.go delete mode 100644 docker/addons/vault/scripts/internal/groups/middleware/doc.go delete mode 100644 docker/addons/vault/scripts/internal/groups/middleware/logging.go delete mode 100644 docker/addons/vault/scripts/internal/groups/middleware/metrics.go delete mode 100644 docker/addons/vault/scripts/internal/groups/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/internal/groups/postgres/groups.go delete mode 100644 docker/addons/vault/scripts/internal/groups/postgres/groups_test.go delete mode 100644 docker/addons/vault/scripts/internal/groups/postgres/init.go delete mode 100644 docker/addons/vault/scripts/internal/groups/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/internal/groups/service.go delete mode 100644 docker/addons/vault/scripts/internal/groups/service_test.go delete mode 100644 docker/addons/vault/scripts/internal/groups/status.go delete mode 100644 docker/addons/vault/scripts/internal/groups/status_test.go delete mode 100644 docker/addons/vault/scripts/internal/groups/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/internal/groups/tracing/tracing.go delete mode 100644 docker/addons/vault/scripts/internal/testsutil/common.go delete mode 100644 docker/addons/vault/scripts/invitations/README.md delete mode 100644 docker/addons/vault/scripts/invitations/api/doc.go delete mode 100644 docker/addons/vault/scripts/invitations/api/endpoint.go delete mode 100644 docker/addons/vault/scripts/invitations/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/invitations/api/requests.go delete mode 100644 docker/addons/vault/scripts/invitations/api/requests_test.go delete mode 100644 docker/addons/vault/scripts/invitations/api/responses.go delete mode 100644 docker/addons/vault/scripts/invitations/api/transport.go delete mode 100644 docker/addons/vault/scripts/invitations/doc.go delete mode 100644 docker/addons/vault/scripts/invitations/invitations.go delete mode 100644 docker/addons/vault/scripts/invitations/invitations_test.go delete mode 100644 docker/addons/vault/scripts/invitations/middleware/authorization.go delete mode 100644 docker/addons/vault/scripts/invitations/middleware/doc.go delete mode 100644 docker/addons/vault/scripts/invitations/middleware/logging.go delete mode 100644 docker/addons/vault/scripts/invitations/middleware/metrics.go delete mode 100644 docker/addons/vault/scripts/invitations/middleware/tracing.go delete mode 100644 docker/addons/vault/scripts/invitations/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/invitations/mocks/repository.go delete mode 100644 docker/addons/vault/scripts/invitations/mocks/service.go delete mode 100644 docker/addons/vault/scripts/invitations/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/invitations/postgres/init.go delete mode 100644 docker/addons/vault/scripts/invitations/postgres/invitations.go delete mode 100644 docker/addons/vault/scripts/invitations/postgres/invitations_test.go delete mode 100644 docker/addons/vault/scripts/invitations/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/invitations/service.go delete mode 100644 docker/addons/vault/scripts/invitations/service_test.go delete mode 100644 docker/addons/vault/scripts/invitations/state.go delete mode 100644 docker/addons/vault/scripts/invitations/state_test.go delete mode 100644 docker/addons/vault/scripts/journal/api/doc.go delete mode 100644 docker/addons/vault/scripts/journal/api/endpoint.go delete mode 100644 docker/addons/vault/scripts/journal/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/journal/api/requests.go delete mode 100644 docker/addons/vault/scripts/journal/api/requests_test.go delete mode 100644 docker/addons/vault/scripts/journal/api/responses.go delete mode 100644 docker/addons/vault/scripts/journal/api/transport.go delete mode 100644 docker/addons/vault/scripts/journal/doc.go delete mode 100644 docker/addons/vault/scripts/journal/events/consumer.go delete mode 100644 docker/addons/vault/scripts/journal/events/consumer_test.go delete mode 100644 docker/addons/vault/scripts/journal/events/doc.go delete mode 100644 docker/addons/vault/scripts/journal/journal.go delete mode 100644 docker/addons/vault/scripts/journal/journal_test.go delete mode 100644 docker/addons/vault/scripts/journal/middleware/doc.go delete mode 100644 docker/addons/vault/scripts/journal/middleware/logging.go delete mode 100644 docker/addons/vault/scripts/journal/middleware/metrics.go delete mode 100644 docker/addons/vault/scripts/journal/middleware/tracing.go delete mode 100644 docker/addons/vault/scripts/journal/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/journal/mocks/repository.go delete mode 100644 docker/addons/vault/scripts/journal/mocks/service.go delete mode 100644 docker/addons/vault/scripts/journal/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/journal/postgres/init.go delete mode 100644 docker/addons/vault/scripts/journal/postgres/journal.go delete mode 100644 docker/addons/vault/scripts/journal/postgres/journal_test.go delete mode 100644 docker/addons/vault/scripts/journal/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/journal/service.go delete mode 100644 docker/addons/vault/scripts/journal/service_test.go delete mode 100644 docker/addons/vault/scripts/logger/doc.go delete mode 100644 docker/addons/vault/scripts/logger/exit.go delete mode 100644 docker/addons/vault/scripts/logger/logger.go delete mode 100644 docker/addons/vault/scripts/logger/logger_test.go delete mode 100644 docker/addons/vault/scripts/logger/mock.go delete mode 100644 docker/addons/vault/scripts/mqtt/README.md delete mode 100644 docker/addons/vault/scripts/mqtt/doc.go delete mode 100644 docker/addons/vault/scripts/mqtt/events/doc.go delete mode 100644 docker/addons/vault/scripts/mqtt/events/events.go delete mode 100644 docker/addons/vault/scripts/mqtt/events/streams.go delete mode 100644 docker/addons/vault/scripts/mqtt/forwarder.go delete mode 100644 docker/addons/vault/scripts/mqtt/handler.go delete mode 100644 docker/addons/vault/scripts/mqtt/handler_test.go delete mode 100644 docker/addons/vault/scripts/mqtt/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/mqtt/mocks/events.go delete mode 100644 docker/addons/vault/scripts/mqtt/mocks/publisher.go delete mode 100644 docker/addons/vault/scripts/mqtt/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/mqtt/tracing/forwarder.go delete mode 100644 docker/addons/vault/scripts/pkg/README.md delete mode 100644 docker/addons/vault/scripts/pkg/apiutil/errors.go delete mode 100644 docker/addons/vault/scripts/pkg/apiutil/responses.go delete mode 100644 docker/addons/vault/scripts/pkg/apiutil/token.go delete mode 100644 docker/addons/vault/scripts/pkg/apiutil/token_test.go delete mode 100644 docker/addons/vault/scripts/pkg/apiutil/transport.go delete mode 100644 docker/addons/vault/scripts/pkg/apiutil/transport_test.go delete mode 100644 docker/addons/vault/scripts/pkg/authn/authn.go delete mode 100644 docker/addons/vault/scripts/pkg/authn/authsvc/authn.go delete mode 100644 docker/addons/vault/scripts/pkg/authn/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/authn/mocks/authn.go delete mode 100644 docker/addons/vault/scripts/pkg/authz/authsvc/authz.go delete mode 100644 docker/addons/vault/scripts/pkg/authz/authz.go delete mode 100644 docker/addons/vault/scripts/pkg/authz/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/authz/mocks/authz.go delete mode 100644 docker/addons/vault/scripts/pkg/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/errors/README.md delete mode 100644 docker/addons/vault/scripts/pkg/errors/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/errors/errors.go delete mode 100644 docker/addons/vault/scripts/pkg/errors/errors_test.go delete mode 100644 docker/addons/vault/scripts/pkg/errors/repository/types.go delete mode 100644 docker/addons/vault/scripts/pkg/errors/sdk_errors.go delete mode 100644 docker/addons/vault/scripts/pkg/errors/sdk_errors_test.go delete mode 100644 docker/addons/vault/scripts/pkg/errors/service/types.go delete mode 100644 docker/addons/vault/scripts/pkg/errors/types.go delete mode 100644 docker/addons/vault/scripts/pkg/events/events.go delete mode 100644 docker/addons/vault/scripts/pkg/events/mocks/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/events/mocks/subscriber.go delete mode 100644 docker/addons/vault/scripts/pkg/events/nats/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/events/nats/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/events/nats/publisher_test.go delete mode 100644 docker/addons/vault/scripts/pkg/events/nats/setup_test.go delete mode 100644 docker/addons/vault/scripts/pkg/events/nats/subscriber.go delete mode 100644 docker/addons/vault/scripts/pkg/events/rabbitmq/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/events/rabbitmq/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/events/rabbitmq/publisher_test.go delete mode 100644 docker/addons/vault/scripts/pkg/events/rabbitmq/setup_test.go delete mode 100644 docker/addons/vault/scripts/pkg/events/rabbitmq/subscriber.go delete mode 100644 docker/addons/vault/scripts/pkg/events/redis/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/events/redis/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/events/redis/publisher_test.go delete mode 100644 docker/addons/vault/scripts/pkg/events/redis/setup_test.go delete mode 100644 docker/addons/vault/scripts/pkg/events/redis/subscriber.go delete mode 100644 docker/addons/vault/scripts/pkg/events/store/store_nats.go delete mode 100644 docker/addons/vault/scripts/pkg/events/store/store_rabbitmq.go delete mode 100644 docker/addons/vault/scripts/pkg/events/store/store_redis.go delete mode 100644 docker/addons/vault/scripts/pkg/groups/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/groups/errors.go delete mode 100644 docker/addons/vault/scripts/pkg/groups/groups.go delete mode 100644 docker/addons/vault/scripts/pkg/groups/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/groups/mocks/repository.go delete mode 100644 docker/addons/vault/scripts/pkg/groups/mocks/service.go delete mode 100644 docker/addons/vault/scripts/pkg/groups/page.go delete mode 100644 docker/addons/vault/scripts/pkg/groups/status.go delete mode 100644 docker/addons/vault/scripts/pkg/grpcclient/client.go delete mode 100644 docker/addons/vault/scripts/pkg/grpcclient/client_test.go delete mode 100644 docker/addons/vault/scripts/pkg/grpcclient/connect.go delete mode 100644 docker/addons/vault/scripts/pkg/grpcclient/connect_test.go delete mode 100644 docker/addons/vault/scripts/pkg/grpcclient/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/jaeger/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/jaeger/provider.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/README.md delete mode 100644 docker/addons/vault/scripts/pkg/messaging/brokers/brokers_nats.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/brokers/brokers_rabbitmq.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/brokers/tracing/brokers_nats.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/brokers/tracing/brokers_rabbitmq.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/handler/logging.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/handler/metrics.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/handler/tracing.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/message.pb.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/message.proto delete mode 100644 docker/addons/vault/scripts/pkg/messaging/mocks/pubsub.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/mqtt/docs.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/mqtt/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/mqtt/pubsub.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/mqtt/pubsub_test.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/mqtt/setup_test.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/options.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/pubsub.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/pubsub_test.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/setup_test.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/tracing/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/nats/tracing/pubsub.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/pubsub.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/options.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/pubsub.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/pubsub_test.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/setup_test.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/publisher.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/pubsub.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/messaging/tracing/tracing.go delete mode 100644 docker/addons/vault/scripts/pkg/oauth2/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/oauth2/google/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/oauth2/google/provider.go delete mode 100644 docker/addons/vault/scripts/pkg/oauth2/mocks/provider.go delete mode 100644 docker/addons/vault/scripts/pkg/oauth2/oauth2.go delete mode 100644 docker/addons/vault/scripts/pkg/policies/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/policies/evaluator.go delete mode 100644 docker/addons/vault/scripts/pkg/policies/mocks/evaluator.go delete mode 100644 docker/addons/vault/scripts/pkg/policies/mocks/service.go delete mode 100644 docker/addons/vault/scripts/pkg/policies/service.go delete mode 100644 docker/addons/vault/scripts/pkg/policies/spicedb/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/policies/spicedb/evaluator.go delete mode 100644 docker/addons/vault/scripts/pkg/policies/spicedb/service.go delete mode 100644 docker/addons/vault/scripts/pkg/postgres/common.go delete mode 100644 docker/addons/vault/scripts/pkg/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/postgres/errors.go delete mode 100644 docker/addons/vault/scripts/pkg/postgres/postgres.go delete mode 100644 docker/addons/vault/scripts/pkg/postgres/tracing.go delete mode 100644 docker/addons/vault/scripts/pkg/prometheus/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/prometheus/metrics.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/README.md delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/README.md delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/bootstrap.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/bootstrap_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/certs.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/certs_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/channels.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/channels_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/consumers.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/consumers_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/domains.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/domains_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/groups.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/groups_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/health.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/health_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/invitations.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/invitations_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/journal.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/journal_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/message.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/message_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/metadata.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/requests.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/responses.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/sdk.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/setup_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/things.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/things_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/tokens.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/tokens_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/users.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/go/users_test.go delete mode 100644 docker/addons/vault/scripts/pkg/sdk/mocks/sdk.go delete mode 100644 docker/addons/vault/scripts/pkg/server/coap/coap.go delete mode 100644 docker/addons/vault/scripts/pkg/server/coap/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/server/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/server/grpc/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/server/grpc/grpc.go delete mode 100644 docker/addons/vault/scripts/pkg/server/http/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/server/http/http.go delete mode 100644 docker/addons/vault/scripts/pkg/server/server.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/README.md delete mode 100644 docker/addons/vault/scripts/pkg/transformers/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/json/README.md delete mode 100644 docker/addons/vault/scripts/pkg/transformers/json/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/json/example_test.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/json/message.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/json/time.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/json/transformer.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/json/transformer_test.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/senml/README.md delete mode 100644 docker/addons/vault/scripts/pkg/transformers/senml/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/senml/message.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/senml/transformer.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/senml/transformer_test.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/transformer.go delete mode 100644 docker/addons/vault/scripts/pkg/transformers/transformer_test.go delete mode 100644 docker/addons/vault/scripts/pkg/ulid/README.md delete mode 100644 docker/addons/vault/scripts/pkg/ulid/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/ulid/ulid.go delete mode 100644 docker/addons/vault/scripts/pkg/uuid/README.md delete mode 100644 docker/addons/vault/scripts/pkg/uuid/doc.go delete mode 100644 docker/addons/vault/scripts/pkg/uuid/mock.go delete mode 100644 docker/addons/vault/scripts/pkg/uuid/uuid.go delete mode 100644 docker/addons/vault/scripts/provision/README.md delete mode 100644 docker/addons/vault/scripts/provision/api/doc.go delete mode 100644 docker/addons/vault/scripts/provision/api/endpoint.go delete mode 100644 docker/addons/vault/scripts/provision/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/provision/api/logging.go delete mode 100644 docker/addons/vault/scripts/provision/api/requests.go delete mode 100644 docker/addons/vault/scripts/provision/api/requests_test.go delete mode 100644 docker/addons/vault/scripts/provision/api/responses.go delete mode 100644 docker/addons/vault/scripts/provision/api/transport.go delete mode 100644 docker/addons/vault/scripts/provision/config.go delete mode 100644 docker/addons/vault/scripts/provision/config_test.go delete mode 100644 docker/addons/vault/scripts/provision/configs/config.toml delete mode 100644 docker/addons/vault/scripts/provision/doc.go delete mode 100644 docker/addons/vault/scripts/provision/mocks/service.go delete mode 100644 docker/addons/vault/scripts/provision/service.go delete mode 100644 docker/addons/vault/scripts/provision/service_test.go delete mode 100644 docker/addons/vault/scripts/readers/README.md delete mode 100644 docker/addons/vault/scripts/readers/api/doc.go delete mode 100644 docker/addons/vault/scripts/readers/api/endpoint.go delete mode 100644 docker/addons/vault/scripts/readers/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/readers/api/logging.go delete mode 100644 docker/addons/vault/scripts/readers/api/metrics.go delete mode 100644 docker/addons/vault/scripts/readers/api/requests.go delete mode 100644 docker/addons/vault/scripts/readers/api/responses.go delete mode 100644 docker/addons/vault/scripts/readers/api/transport.go delete mode 100644 docker/addons/vault/scripts/readers/doc.go delete mode 100644 docker/addons/vault/scripts/readers/messages.go delete mode 100644 docker/addons/vault/scripts/readers/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/readers/mocks/messages.go delete mode 100644 docker/addons/vault/scripts/readers/postgres/README.md delete mode 100644 docker/addons/vault/scripts/readers/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/readers/postgres/init.go delete mode 100644 docker/addons/vault/scripts/readers/postgres/messages.go delete mode 100644 docker/addons/vault/scripts/readers/postgres/messages_test.go delete mode 100644 docker/addons/vault/scripts/readers/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/readers/timescale/README.md delete mode 100644 docker/addons/vault/scripts/readers/timescale/doc.go delete mode 100644 docker/addons/vault/scripts/readers/timescale/init.go delete mode 100644 docker/addons/vault/scripts/readers/timescale/messages.go delete mode 100644 docker/addons/vault/scripts/readers/timescale/messages_test.go delete mode 100644 docker/addons/vault/scripts/readers/timescale/setup_test.go delete mode 100755 docker/addons/vault/scripts/scripts/ci.sh delete mode 100644 docker/addons/vault/scripts/scripts/csv/channels.csv delete mode 100644 docker/addons/vault/scripts/scripts/csv/things.csv delete mode 100755 docker/addons/vault/scripts/scripts/provision-dev.sh delete mode 100755 docker/addons/vault/scripts/scripts/run.sh delete mode 100644 docker/addons/vault/scripts/things/README.md delete mode 100644 docker/addons/vault/scripts/things/api/doc.go delete mode 100644 docker/addons/vault/scripts/things/api/grpc/client.go delete mode 100644 docker/addons/vault/scripts/things/api/grpc/doc.go delete mode 100644 docker/addons/vault/scripts/things/api/grpc/endpoint.go delete mode 100644 docker/addons/vault/scripts/things/api/grpc/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/things/api/grpc/request.go delete mode 100644 docker/addons/vault/scripts/things/api/grpc/responses.go delete mode 100644 docker/addons/vault/scripts/things/api/grpc/server.go delete mode 100644 docker/addons/vault/scripts/things/api/http/channels.go delete mode 100644 docker/addons/vault/scripts/things/api/http/clients.go delete mode 100644 docker/addons/vault/scripts/things/api/http/endpoints.go delete mode 100644 docker/addons/vault/scripts/things/api/http/endpoints_test.go delete mode 100644 docker/addons/vault/scripts/things/api/http/requests.go delete mode 100644 docker/addons/vault/scripts/things/api/http/requests_test.go delete mode 100644 docker/addons/vault/scripts/things/api/http/responses.go delete mode 100644 docker/addons/vault/scripts/things/api/http/transport.go delete mode 100644 docker/addons/vault/scripts/things/cache/doc.go delete mode 100644 docker/addons/vault/scripts/things/cache/setup_test.go delete mode 100644 docker/addons/vault/scripts/things/cache/things.go delete mode 100644 docker/addons/vault/scripts/things/cache/things_test.go delete mode 100644 docker/addons/vault/scripts/things/clients.go delete mode 100644 docker/addons/vault/scripts/things/doc.go delete mode 100644 docker/addons/vault/scripts/things/errors.go delete mode 100644 docker/addons/vault/scripts/things/events/doc.go delete mode 100644 docker/addons/vault/scripts/things/events/events.go delete mode 100644 docker/addons/vault/scripts/things/events/streams.go delete mode 100644 docker/addons/vault/scripts/things/middleware/authorization.go delete mode 100644 docker/addons/vault/scripts/things/middleware/doc.go delete mode 100644 docker/addons/vault/scripts/things/middleware/logging.go delete mode 100644 docker/addons/vault/scripts/things/middleware/metrics.go delete mode 100644 docker/addons/vault/scripts/things/mocks/cache.go delete mode 100644 docker/addons/vault/scripts/things/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/things/mocks/repository.go delete mode 100644 docker/addons/vault/scripts/things/mocks/service.go delete mode 100644 docker/addons/vault/scripts/things/mocks/things_client.go delete mode 100644 docker/addons/vault/scripts/things/postgres/clients.go delete mode 100644 docker/addons/vault/scripts/things/postgres/clients_test.go delete mode 100644 docker/addons/vault/scripts/things/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/things/postgres/init.go delete mode 100644 docker/addons/vault/scripts/things/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/things/roles.go delete mode 100644 docker/addons/vault/scripts/things/roles_test.go delete mode 100644 docker/addons/vault/scripts/things/service.go delete mode 100644 docker/addons/vault/scripts/things/service_test.go delete mode 100644 docker/addons/vault/scripts/things/standalone/doc.go delete mode 100644 docker/addons/vault/scripts/things/standalone/standalone.go delete mode 100644 docker/addons/vault/scripts/things/status.go delete mode 100644 docker/addons/vault/scripts/things/status_test.go delete mode 100644 docker/addons/vault/scripts/things/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/things/tracing/tracing.go delete mode 100644 docker/addons/vault/scripts/tools/config/boilerplate.txt delete mode 100644 docker/addons/vault/scripts/tools/config/codecov.yml delete mode 100644 docker/addons/vault/scripts/tools/config/golangci.yml delete mode 100644 docker/addons/vault/scripts/tools/config/mockery.yaml delete mode 100644 docker/addons/vault/scripts/tools/doc.go delete mode 100644 docker/addons/vault/scripts/tools/e2e/Makefile delete mode 100644 docker/addons/vault/scripts/tools/e2e/README.md delete mode 100644 docker/addons/vault/scripts/tools/e2e/cmd/main.go delete mode 100644 docker/addons/vault/scripts/tools/e2e/doc.go delete mode 100644 docker/addons/vault/scripts/tools/e2e/e2e.go delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/Makefile delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/README.md delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/bench.go delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/client.go delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/cmd/main.go delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/config.go delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/doc.go delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/results.go delete mode 100755 docker/addons/vault/scripts/tools/mqtt-bench/scripts/mqtt-bench.sh delete mode 100644 docker/addons/vault/scripts/tools/mqtt-bench/templates/reference.toml delete mode 100644 docker/addons/vault/scripts/tools/provision/Makefile delete mode 100644 docker/addons/vault/scripts/tools/provision/README.md delete mode 100644 docker/addons/vault/scripts/tools/provision/cmd/main.go delete mode 100644 docker/addons/vault/scripts/tools/provision/doc.go delete mode 100644 docker/addons/vault/scripts/tools/provision/provision.go delete mode 100644 docker/addons/vault/scripts/users/README.md delete mode 100644 docker/addons/vault/scripts/users/api/doc.go delete mode 100644 docker/addons/vault/scripts/users/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/users/api/endpoints.go delete mode 100644 docker/addons/vault/scripts/users/api/groups.go delete mode 100644 docker/addons/vault/scripts/users/api/requests.go delete mode 100644 docker/addons/vault/scripts/users/api/requests_test.go delete mode 100644 docker/addons/vault/scripts/users/api/responses.go delete mode 100644 docker/addons/vault/scripts/users/api/transport.go delete mode 100644 docker/addons/vault/scripts/users/api/users.go delete mode 100644 docker/addons/vault/scripts/users/delete_handler.go delete mode 100644 docker/addons/vault/scripts/users/doc.go delete mode 100644 docker/addons/vault/scripts/users/emailer.go delete mode 100644 docker/addons/vault/scripts/users/emailer/doc.go delete mode 100644 docker/addons/vault/scripts/users/emailer/emailer.go delete mode 100644 docker/addons/vault/scripts/users/errors.go delete mode 100644 docker/addons/vault/scripts/users/events/doc.go delete mode 100644 docker/addons/vault/scripts/users/events/events.go delete mode 100644 docker/addons/vault/scripts/users/events/streams.go delete mode 100644 docker/addons/vault/scripts/users/hasher.go delete mode 100644 docker/addons/vault/scripts/users/hasher/doc.go delete mode 100644 docker/addons/vault/scripts/users/hasher/hasher.go delete mode 100644 docker/addons/vault/scripts/users/middleware/authorization.go delete mode 100644 docker/addons/vault/scripts/users/middleware/doc.go delete mode 100644 docker/addons/vault/scripts/users/middleware/logging.go delete mode 100644 docker/addons/vault/scripts/users/middleware/metrics.go delete mode 100644 docker/addons/vault/scripts/users/mocks/doc.go delete mode 100644 docker/addons/vault/scripts/users/mocks/emailer.go delete mode 100644 docker/addons/vault/scripts/users/mocks/hasher.go delete mode 100644 docker/addons/vault/scripts/users/mocks/repository.go delete mode 100644 docker/addons/vault/scripts/users/mocks/service.go delete mode 100644 docker/addons/vault/scripts/users/postgres/doc.go delete mode 100644 docker/addons/vault/scripts/users/postgres/init.go delete mode 100644 docker/addons/vault/scripts/users/postgres/setup_test.go delete mode 100644 docker/addons/vault/scripts/users/postgres/users.go delete mode 100644 docker/addons/vault/scripts/users/postgres/users_test.go delete mode 100644 docker/addons/vault/scripts/users/roles.go delete mode 100644 docker/addons/vault/scripts/users/service.go delete mode 100644 docker/addons/vault/scripts/users/service_test.go delete mode 100644 docker/addons/vault/scripts/users/status.go delete mode 100644 docker/addons/vault/scripts/users/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/users/tracing/tracing.go delete mode 100644 docker/addons/vault/scripts/users/users.go delete mode 100644 docker/addons/vault/scripts/uuid.go delete mode 100644 docker/addons/vault/scripts/ws/README.md delete mode 100644 docker/addons/vault/scripts/ws/adapter.go delete mode 100644 docker/addons/vault/scripts/ws/adapter_test.go delete mode 100644 docker/addons/vault/scripts/ws/api/doc.go delete mode 100644 docker/addons/vault/scripts/ws/api/endpoint_test.go delete mode 100644 docker/addons/vault/scripts/ws/api/endpoints.go delete mode 100644 docker/addons/vault/scripts/ws/api/logging.go delete mode 100644 docker/addons/vault/scripts/ws/api/metrics.go delete mode 100644 docker/addons/vault/scripts/ws/api/requests.go delete mode 100644 docker/addons/vault/scripts/ws/api/transport.go delete mode 100644 docker/addons/vault/scripts/ws/client.go delete mode 100644 docker/addons/vault/scripts/ws/client_test.go delete mode 100644 docker/addons/vault/scripts/ws/doc.go delete mode 100644 docker/addons/vault/scripts/ws/handler.go delete mode 100644 docker/addons/vault/scripts/ws/tracing/doc.go delete mode 100644 docker/addons/vault/scripts/ws/tracing/tracing.go diff --git a/docker/addons/vault/scripts/.dockerignore b/docker/addons/vault/scripts/.dockerignore deleted file mode 100644 index 28a32337..00000000 --- a/docker/addons/vault/scripts/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -.git -.github -build -docker -metrics -scripts diff --git a/docker/addons/vault/scripts/.github/CODEOWNERS b/docker/addons/vault/scripts/.github/CODEOWNERS deleted file mode 100644 index bc8cb187..00000000 --- a/docker/addons/vault/scripts/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @absmach/magistrala diff --git a/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/bug_report.yml b/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index ef96f9a1..00000000 --- a/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: Bug Report -description: File a bug/issue report. Make sure to search to see if an issue already exists for the bug you encountered. -title: "Bug: " -labels: ["bug", "needs-review", "help wanted"] -body: - - type: textarea - attributes: - label: What were you trying to achieve? - description: A clear and concise description of what the bug is. - validations: - required: true - - type: textarea - attributes: - label: What are the expected results? - description: A concise description of what you expected to happen. - validations: - required: true - - type: textarea - attributes: - label: What are the received results? - description: A concise description of what you received. - validations: - required: true - - type: textarea - attributes: - label: Steps To Reproduce - description: What are the steps to reproduce the issue? - placeholder: | - 1. In this environment... - 2. With this config... - 3. Run '...' - 4. See error... - validations: - required: false - - type: textarea - attributes: - label: In what environment did you encounter the issue? - description: A concise description of the environment you encountered the issue in. - validations: - required: true - - type: textarea - attributes: - label: Additional information you deem important - description: | - Links? References? Anything that will give us more context about the issue you are encountering! - - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - validations: - required: false diff --git a/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/config.yml b/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 2fb1e566..00000000 --- a/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -blank_issues_enabled: false -contact_links: - - name: Google group - url: https://groups.google.com/forum/#!forum/mainflux - about: Join the Magistrala community on Google group. - - name: Gitter - url: https://gitter.im/mainflux/mainflux - about: Join the Magistrala community on Gitter. diff --git a/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/feature_request.yml b/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index db34ad62..00000000 --- a/docker/addons/vault/scripts/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: Feature Request -description: File a feature request. Make sure to search to see if a request already exists for the feature you are requesting. -title: "Feature: <title>" -labels: ["enchancement", "needs-review"] -body: - - type: textarea - attributes: - label: Is your feature request related to a problem? Please describe. - description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - validations: - required: true - - type: textarea - attributes: - label: Describe the feature you are requesting, as well as the possible use case(s) for it. - description: A clear and concise description of what you want to happen. - validations: - required: true - - type: dropdown - attributes: - label: Indicate the importance of this feature to you. - description: This will help us prioritize the feature request. - options: - - Must-have - - Should-have - - Nice-to-have - validations: - required: true - - type: textarea - attributes: - label: Anything else? - description: | - Links? References? Anything that will give us more context about the feature that you are requesting. - - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - validations: - required: false diff --git a/docker/addons/vault/scripts/.github/PULL_REQUEST_TEMPLATE.md b/docker/addons/vault/scripts/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index bbe61bd7..00000000 --- a/docker/addons/vault/scripts/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,69 +0,0 @@ -<!-- Copyright (c) Abstract Machines -SPDX-License-Identifier: Apache-2.0 --> - -<!-- - -Pull request title should be `MG-XXX - description` or `NOISSUE - description` where XXX is ID of the issue that this PR relate to. -Please review the [CONTRIBUTING.md](https://github.com/absmach/magistrala/blob/main/CONTRIBUTING.md) file for detailed contributing guidelines. - -For Work In Progress Pull Requests, please use the Draft PR feature, see https://github.blog/2019-02-14-introducing-draft-pull-requests/ for further details. - -For a timely review/response, please avoid force-pushing additional commits if your PR already received reviews or comments. - -- Provide tests for your changes. -- Use descriptive commit messages. -- Comment your code where appropriate. -- Squash your commits -- Update any related documentation. ---> - -# What type of PR is this? - -<!--This represents the type of PR you are submitting. - -For example: -This is a bug fix because it fixes the following issue: #1234 -This is a feature because it adds the following functionality: ... -This is a refactor because it changes the following functionality: ... -This is a documentation update because it updates the following documentation: ... -This is a dependency update because it updates the following dependencies: ... -This is an optimization because it improves the following functionality: ... ---> - -## What does this do? - -<!-- -Please provide a brief description of what this PR is intended to do. -Include List any changes that modify/break current functionality. ---> - -## Which issue(s) does this PR fix/relate to? - -<!-- -For pull requests that relate or close an issue, please include them below. We like to follow [Github's guidance on linking issues to pull requests](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). - -For example having the text: "Resolves #1234" would connect the current pull request to issue 1234. And when we merge the pull request, Github will automatically close the issue. ---> - -- Related Issue # -- Resolves # - -## Have you included tests for your changes? - -<!--If you have not included tests, please explain why. -For example: -Yes, I have included tests for my changes. -No, I have not included tests because I do not know how to. ---> - -## Did you document any new/modified feature? - -<!--If you have not included documentation, please explain why. -For example: -Yes, I have updated the documentation for the new feature. -No, I have not updated the documentation because I do not know how to. ---> - -### Notes - -<!--Please provide any additional information you feel is important.--> diff --git a/docker/addons/vault/scripts/.github/dependabot.yml b/docker/addons/vault/scripts/.github/dependabot.yml deleted file mode 100644 index 46473890..00000000 --- a/docker/addons/vault/scripts/.github/dependabot.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "./.github/workflows" - schedule: - interval: "monthly" - day: "monday" - timezone: "Europe/Paris" - groups: - gh-dependency: - patterns: - - "*" - - - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "weekly" - day: "monday" - timezone: "Europe/Paris" - - - package-ecosystem: "docker" - directory: "./docker" - schedule: - interval: "monthly" - day: "monday" - timezone: "Europe/Paris" - groups: - docker-dependency: - patterns: - - "*" diff --git a/docker/addons/vault/scripts/.github/workflows/api-tests.yml b/docker/addons/vault/scripts/.github/workflows/api-tests.yml deleted file mode 100644 index c5f566c9..00000000 --- a/docker/addons/vault/scripts/.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/docker/addons/vault/scripts/.github/workflows/build.yml b/docker/addons/vault/scripts/.github/workflows/build.yml deleted file mode 100644 index e67e30a9..00000000 --- a/docker/addons/vault/scripts/.github/workflows/build.yml +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: Continuous Delivery - -on: - push: - branches: - - main - -jobs: - build-and-push: - name: Build and Push - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Fetch tags for the build - run: | - git fetch --prune --unshallow --tags - - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache-dependency-path: "go.sum" - - - name: Run tests - run: | - make test - - - name: Upload coverage - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV }} - files: ./coverage/*.out - codecov_yml_path: tools/codecov.yml - verbose: true - - - name: Set up Docker Build - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - registry: docker.io - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_TOKEN }} - - - 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 - - - name: Build and push Dockers - run: | - make latest -j $(nproc) diff --git a/docker/addons/vault/scripts/.github/workflows/check-generated-files.yml b/docker/addons/vault/scripts/.github/workflows/check-generated-files.yml deleted file mode 100644 index c0ed4cd1..00000000 --- a/docker/addons/vault/scripts/.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/docker/addons/vault/scripts/.github/workflows/check-license.yaml b/docker/addons/vault/scripts/.github/workflows/check-license.yaml deleted file mode 100644 index 7b97d2b8..00000000 --- a/docker/addons/vault/scripts/.github/workflows/check-license.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: Check License Header - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - check-license: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Check License Header - run: | - CHECK="" - for file in $(grep -rl --exclude-dir={.git,build,**vernemq**} \ - --exclude=\*.{crt,key,pem,zed,hcl,md,json,csv,mod,sum,tmpl,args} \ - --exclude={CODEOWNERS,LICENSE,MAINTAINERS} \ - .); do - - if ! head -n 5 "$file" | grep -q "Copyright (c) Abstract Machines"; then - CHECK="$CHECK $file" - fi - done - - if [ "$CHECK" ]; then - echo "License header check failed. Fix the following files:" - echo "$CHECK" - exit 1 - else - echo "All files have the correct license header!" - fi diff --git a/docker/addons/vault/scripts/.github/workflows/swagger-ui.yaml b/docker/addons/vault/scripts/.github/workflows/swagger-ui.yaml deleted file mode 100644 index 26fb1364..00000000 --- a/docker/addons/vault/scripts/.github/workflows/swagger-ui.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: Deploy GitHub Pages - -on: - push: - branches: - - main - -jobs: - swagger-ui: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Swagger UI action - id: swagger-ui-action - uses: blokovi/swagger-ui-action@main - with: - dir: "./api/openapi" - pattern: "*.yml" - debug: "true" - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: swagger-ui - cname: docs.api.magistrala.abstractmachines.fr diff --git a/docker/addons/vault/scripts/.github/workflows/tests.yml b/docker/addons/vault/scripts/.github/workflows/tests.yml deleted file mode 100644 index 9d178422..00000000 --- a/docker/addons/vault/scripts/.github/workflows/tests.yml +++ /dev/null @@ -1,390 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: CI Pipeline - -on: - pull_request: - branches: - - main - -jobs: - lint-and-build: # Linting and building are combined to save time for setting up Go - name: Lint and Build - runs-on: ubuntu-latest - - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - 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: - version: v1.60.3 - args: --config ./tools/config/golangci.yml - - - name: Build all Binaries - 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 - needs: lint-and-build - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache-dependency-path: "go.sum" - - - name: Check for changes in specific paths - uses: dorny/paths-filter@v3 - id: changes - with: - base: main - filters: | - 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/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "auth/**" - - "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/**" - - pkg-events: - - "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/**" - - "logger/**" - - "pkg/sdk/**" - - readers: - - "readers/**" - - "cmd/postgres-reader/**" - - "cmd/timescale-reader/**" - - "auth.pb.go" - - "auth_grpc.pb.go" - - "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: | - go test --race -v -count=1 -coverprofile=coverage/provision.out ./provision/... - - - name: Run readers tests - if: steps.changes.outputs.readers == 'true' || steps.changes.outputs.workflow == 'true' - 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: - token: ${{ secrets.CODECOV }} - files: ./coverage/*.out - codecov_yml_path: tools/codecov.yml - verbose: true diff --git a/docker/addons/vault/scripts/.gitignore b/docker/addons/vault/scripts/.gitignore deleted file mode 100644 index 3817d806..00000000 --- a/docker/addons/vault/scripts/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# build dirs -build - -# tools -tools/e2e/e2e -tools/mqtt-bench/mqtt-bench -tools/provision/provision -tools/provision/mgconn.toml - -# coverage files -coverage - -# Schemathesis -.hypothesis - -# Ignore Vault data directory as it contains runtime-generated data -docker/addons/vault/data/ diff --git a/docker/addons/vault/scripts/ADOPTERS.md b/docker/addons/vault/scripts/ADOPTERS.md deleted file mode 100644 index 96c96423..00000000 --- a/docker/addons/vault/scripts/ADOPTERS.md +++ /dev/null @@ -1,36 +0,0 @@ -# Adopters - -As Magistrala Community grows, we'd like to keep track of Magistrala adopters to grow the community, contact other users, share experiences and best practices. - -To accomplish this, we created a public ledger. The list of organizations and users who consider themselves as Magistrala adopters and that **publicly/officially** shared information and/or details of their adoption journey(optional). -Where users themselves directly maintain the list. - -## Adding yourself as an adopter -If you are using Magistrala, please consider adding yourself as an adopter with a brief description of your use case by opening a pull request to this file and adding a section describing your adoption of Magistrala technology. - -**Please send PRs to add or remove organizations/users** - -### Format - -``` -N: Name of user (company or individual) -D: Short Use Case Description (optional) -L: Link with further information (optional) -T: Type of adaptation: Evaluation, Core Technology, Production Usage (optional) -``` - -## Requirements -* You must represent the user or organization listed. Do NOT add entries on behalf of other organizations or individuals. -Pull request commit must be [signed](https://docs.github.com/en/github/authenticating-to-github/signing-commits) and auto-checked with [ Developer Certificate of Origin (DCO)](https://probot.github.io/apps/dco/) -* There is no minimum requirement or adaptation size, but we request to list permanent deployments only, i.e., no demo or trial deployments. Commercial or production use is not required. A well-done home lab setup can be equally impressive as a large-scale commercial deployment. - - -**The list of organizations/users that have publicly shared the usage of Magistrala:** - -**Note**: Several other organizations/users couldn't publicly share their usage details but are active project contributors and Magistrala Community members. - - -## Adopters list (alphabetical) - - -**Note:** The list is maintained by the users themselves. If you find yourself on this list, and you think it's inappropriate. Please contact [project maintainers](https://github.com/absmach/magistrala/blob/main/MAINTAINERS) and you will be permanently removed from the list. diff --git a/docker/addons/vault/scripts/CONTRIBUTING.md b/docker/addons/vault/scripts/CONTRIBUTING.md deleted file mode 100644 index 35a196aa..00000000 --- a/docker/addons/vault/scripts/CONTRIBUTING.md +++ /dev/null @@ -1,87 +0,0 @@ -# Contributing to Magistrala - -The following is a set of guidelines to contribute to Magistrala and its libraries, which are -hosted on the [Abstract Machines Organization](https://github.com/absmach) on GitHub. - -This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0). -By participating, you are expected to uphold this code. Please report unacceptable behavior to -[abuse@magistrala.com](mailto:abuse@magistrala.com). - -## Reporting issues - -Reporting issues are a great way to contribute to the project. We are perpetually grateful about a well-written, -thorough bug report. - -Before raising a new issue, check [our issue -list](https://github.com/absmach/magistrala/issues) to determine if it already contains the -problem that you are facing. - -A good bug report shouldn't leave others needing to chase you for more information. Please be as detailed as possible. The following questions might serve as a template for writing a detailed -report: - -- What were you trying to achieve? -- What are the expected results? -- What are the received results? -- What are the steps to reproduce the issue? -- In what environment did you encounter the issue? - -## Pull requests - -Good pull requests (e.g. patches, improvements, new features) are a fantastic help. They should -remain focused in scope and avoid unrelated commits. - -**Please ask first** before embarking on any significant pull request (e.g. implementing new features, -refactoring code etc.), otherwise you risk spending a lot of time working on something that the -maintainers might not want to merge into the project. - -Please adhere to the coding conventions used throughout the project. If in doubt, consult the -[Effective Go](https://golang.org/doc/effective_go.html) style guide. - -To contribute to the project, [fork](https://help.github.com/articles/fork-a-repo/) it, -clone your fork repository, and configure the remotes: - -``` -git clone https://github.com/<your-username>/magistrala.git -cd magistrala -git remote add upstream https://github.com/absmach/magistrala.git -``` - -If your cloned repository is behind the upstream commits, then get the latest changes from upstream: - -``` -git checkout master -git pull --rebase upstream main -``` - -Create a new topic branch from `master` using the naming convention `MG-[issue-number]` -to help us keep track of your contribution scope: - -``` -git checkout -b MG-[issue-number] -``` - -Commit your changes in logical chunks. When you are ready to commit, make sure -to write a Good Commit Messageâ„¢. Consult the [Erlang's contributing guide](https://github.com/erlang/otp/wiki/Writing-good-commit-messages) -if you're unsure of what constitutes a Good Commit Messageâ„¢. Use [interactive rebase](https://help.github.com/articles/about-git-rebase) -to group your commits into logical units of work before making it public. - -Note that every commit you make must be signed. By signing off your work you indicate that you -are accepting the [Developer Certificate of Origin](https://developercertificate.org/). - -Use your real name (sorry, no pseudonyms or anonymous contributions). If you set your `user.name` -and `user.email` git configs, you can sign your commit automatically with `git commit -s`. - -Locally merge (or rebase) the upstream development branch into your topic branch: - -``` -git pull --rebase upstream main -``` - -Push your topic branch up to your fork: - -``` -git push origin MG-[issue-number] -``` - -[Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title -and detailed description. diff --git a/docker/addons/vault/scripts/LICENSE b/docker/addons/vault/scripts/LICENSE deleted file mode 100644 index 0cb81525..00000000 --- a/docker/addons/vault/scripts/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2015-2020 Magistrala - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docker/addons/vault/scripts/MAINTAINERS b/docker/addons/vault/scripts/MAINTAINERS deleted file mode 100644 index 8df02cf4..00000000 --- a/docker/addons/vault/scripts/MAINTAINERS +++ /dev/null @@ -1,30 +0,0 @@ -# Magistrala follows the timeless, highly efficient and totally unfair system -# known as [Benevolent dictator for -# life](https://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with -# Drasko DRASKOVIC in the role of BDFL. - -[bdfl] - - [[drasko]] - Name = "Drasko Draskovic" - Email = "draasko.draskovic@abstractmachines.fr" - GitHub = "drasko" - -# However, this role serves only in dead-lock events, or in a special and very rare cases -# when BDFL completely disagrees with the decisions made. -# In the normal flow of events, decisions on the project design are made through discussions, -# most often on the Pull Requests. -# -# Maintainers have the special role in the project in managing and accepting PRs, -# overall leading the project and making design decisions on the maintained subsystems. -# -# A reference list of all maintainers of the Magistrala project. - -# ADD YOURSELF HERE IN ALPHABETICAL ORDER - -[maintainers] - - [[dusan]] - Name = "Dusan Borovcanin" - Email = "dusan.borovcanin@abstractmachines.fr" - GitHub = "dborovcanin" diff --git a/docker/addons/vault/scripts/Makefile b/docker/addons/vault/scripts/Makefile deleted file mode 100644 index 3819259b..00000000 --- a/docker/addons/vault/scripts/Makefile +++ /dev/null @@ -1,259 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -MG_DOCKER_IMAGE_NAME_PREFIX ?= magistrala -BUILD_DIR = build -SERVICES = auth users things http coap ws postgres-writer postgres-reader timescale-writer \ - timescale-reader cli bootstrap mqtt provision certs invitations journal -TEST_API_SERVICES = journal auth bootstrap certs http invitations notifiers provision readers things users -TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES)) -DOCKERS = $(addprefix docker_,$(SERVICES)) -DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES)) -CGO_ENABLED ?= 0 -GOARCH ?= amd64 -VERSION ?= $(shell git describe --abbrev=0 --tags 2>/dev/null || echo 'unknown') -COMMIT ?= $(shell git rev-parse HEAD) -TIME ?= $(shell date +%F_%T) -USER_REPO ?= $(shell git remote get-url origin | sed -e 's/.*\/\([^/]*\)\/\([^/]*\).*/\1_\2/' ) -empty:= -space:= $(empty) $(empty) -# Docker compose project name should follow this guidelines: https://docs.docker.com/compose/reference/#use--p-to-specify-a-project-name -DOCKER_PROJECT ?= $(shell echo $(subst $(space),,$(USER_REPO)) | tr -c -s '[:alnum:][=-=]' '_' | tr '[:upper:]' '[:lower:]') -DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config -DEFAULT_DOCKER_COMPOSE_COMMAND := up -GRPC_MTLS_CERT_FILES_EXISTS = 0 -MOCKERY_VERSION=v2.43.2 -ifneq ($(MG_MESSAGE_BROKER_TYPE),) - MG_MESSAGE_BROKER_TYPE := $(MG_MESSAGE_BROKER_TYPE) -else - MG_MESSAGE_BROKER_TYPE=nats -endif - -ifneq ($(MG_ES_TYPE),) - MG_ES_TYPE := $(MG_ES_TYPE) -else - MG_ES_TYPE=nats -endif - -define compile_service - CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \ - go build -tags $(MG_MESSAGE_BROKER_TYPE) --tags $(MG_ES_TYPE) -ldflags "-s -w \ - -X 'github.com/absmach/magistrala.BuildTime=$(TIME)' \ - -X 'github.com/absmach/magistrala.Version=$(VERSION)' \ - -X 'github.com/absmach/magistrala.Commit=$(COMMIT)'" \ - -o ${BUILD_DIR}/$(1) cmd/$(1)/main.go -endef - -define make_docker - $(eval svc=$(subst docker_,,$(1))) - - docker build \ - --no-cache \ - --build-arg SVC=$(svc) \ - --build-arg GOARCH=$(GOARCH) \ - --build-arg GOARM=$(GOARM) \ - --build-arg VERSION=$(VERSION) \ - --build-arg COMMIT=$(COMMIT) \ - --build-arg TIME=$(TIME) \ - --tag=$(MG_DOCKER_IMAGE_NAME_PREFIX)/$(svc) \ - -f docker/Dockerfile . -endef - -define make_docker_dev - $(eval svc=$(subst docker_dev_,,$(1))) - - docker build \ - --no-cache \ - --build-arg SVC=$(svc) \ - --tag=$(MG_DOCKER_IMAGE_NAME_PREFIX)/$(svc) \ - -f docker/Dockerfile.dev ./build -endef - -ADDON_SERVICES = bootstrap journal provision certs timescale-reader timescale-writer postgres-reader postgres-writer - -EXTERNAL_SERVICES = vault prometheus - -ifneq ($(filter run%,$(firstword $(MAKECMDGOALS))),) - temp_args := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) - DOCKER_COMPOSE_COMMAND := $(if $(filter $(DOCKER_COMPOSE_COMMANDS_SUPPORTED),$(temp_args)), $(filter $(DOCKER_COMPOSE_COMMANDS_SUPPORTED),$(temp_args)), $(DEFAULT_DOCKER_COMPOSE_COMMAND)) - $(eval $(DOCKER_COMPOSE_COMMAND):;@) -endif - -ifneq ($(filter run_addons%,$(firstword $(MAKECMDGOALS))),) - temp_args := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) - RUN_ADDON_ARGS := $(if $(filter-out $(DOCKER_COMPOSE_COMMANDS_SUPPORTED),$(temp_args)), $(filter-out $(DOCKER_COMPOSE_COMMANDS_SUPPORTED),$(temp_args)),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)) - $(eval $(RUN_ADDON_ARGS):;@) -endif - -ifneq ("$(wildcard docker/ssl/certs/*-grpc-*)","") -GRPC_MTLS_CERT_FILES_EXISTS = 1 -else -GRPC_MTLS_CERT_FILES_EXISTS = 0 -endif - -FILTERED_SERVICES = $(filter-out $(RUN_ADDON_ARGS), $(SERVICES)) - -all: $(SERVICES) - -.PHONY: all $(SERVICES) dockers dockers_dev latest release run run_addons grpc_mtls_certs check_mtls check_certs test_api mocks - -clean: - rm -rf ${BUILD_DIR} - -cleandocker: - # Stops containers and removes containers, networks, volumes, and images created by up - docker compose -f docker/docker-compose.yml -p $(DOCKER_PROJECT) down --rmi all -v --remove-orphans - -ifdef pv - # Remove unused volumes - docker volume ls -f name=$(MG_DOCKER_IMAGE_NAME_PREFIX) -f dangling=true -q | xargs -r docker volume rm -endif - -install: - for file in $(BUILD_DIR)/*; do \ - cp $$file $(GOBIN)/magistrala-`basename $$file`; \ - done - -mocks: - @which mockery > /dev/null || go install github.com/vektra/mockery/v2@$(MOCKERY_VERSION) - @unset MOCKERY_VERSION && go generate ./... - mockery --config ./tools/config/mockery.yaml - - -DIRS = consumers readers postgres internal -test: mocks - mkdir -p coverage - @for dir in $(DIRS); do \ - go test -v --race -count 1 -tags test -coverprofile=coverage/$$dir.out $$(go list ./... | grep $$dir | grep -v 'cmd'); \ - done - go test -v --race -count 1 -tags test -coverprofile=coverage/coverage.out $$(go list ./... | grep -v 'consumers\|readers\|postgres\|internal\|cmd') - -define test_api_service - $(eval svc=$(subst test_api_,,$(1))) - @which st > /dev/null || (echo "schemathesis not found, please install it from https://github.com/schemathesis/schemathesis#getting-started" && exit 1) - - @if [ -z "$(USER_TOKEN)" ]; then \ - echo "USER_TOKEN is not set"; \ - echo "Please set it to a valid token"; \ - exit 1; \ - fi - - @if [ "$(svc)" = "http" ] && [ -z "$(THING_SECRET)" ]; then \ - echo "THING_SECRET is not set"; \ - echo "Please set it to a valid secret"; \ - exit 1; \ - fi - - @if [ "$(svc)" = "http" ]; then \ - st run api/openapi/$(svc).yml \ - --checks all \ - --base-url $(2) \ - --header "Authorization: Thing $(THING_SECRET)" \ - --contrib-openapi-formats-uuid \ - --hypothesis-suppress-health-check=filter_too_much \ - --stateful=links; \ - else \ - st run api/openapi/$(svc).yml \ - --checks all \ - --base-url $(2) \ - --header "Authorization: Bearer $(USER_TOKEN)" \ - --contrib-openapi-formats-uuid \ - --hypothesis-suppress-health-check=filter_too_much \ - --stateful=links; \ - fi -endef - -test_api_users: TEST_API_URL := http://localhost:9002 -test_api_things: TEST_API_URL := http://localhost:9000 -test_api_http: TEST_API_URL := http://localhost:8008 -test_api_invitations: TEST_API_URL := http://localhost:9020 -test_api_auth: TEST_API_URL := http://localhost:8189 -test_api_bootstrap: TEST_API_URL := http://localhost:9013 -test_api_certs: TEST_API_URL := http://localhost:9019 -test_api_provision: TEST_API_URL := http://localhost:9016 -test_api_readers: TEST_API_URL := http://localhost:9009 # This can be the URL of any reader service. -test_api_journal: TEST_API_URL := http://localhost:9021 - -$(TEST_API): - $(call test_api_service,$(@),$(TEST_API_URL)) - -proto: - protoc -I. --go_out=. --go_opt=paths=source_relative pkg/messaging/*.proto - protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./*.proto - -$(FILTERED_SERVICES): - $(call compile_service,$(@)) - -$(DOCKERS): - $(call make_docker,$(@),$(GOARCH)) - -$(DOCKERS_DEV): - $(call make_docker_dev,$(@)) - -dockers: $(DOCKERS) -dockers_dev: $(DOCKERS_DEV) - -define docker_push - for svc in $(SERVICES); do \ - docker push $(MG_DOCKER_IMAGE_NAME_PREFIX)/$$svc:$(1); \ - done -endef - -changelog: - git log $(shell git describe --tags --abbrev=0)..HEAD --pretty=format:"- %s" - -latest: dockers - $(call docker_push,latest) - -release: - $(eval version = $(shell git describe --abbrev=0 --tags)) - git checkout $(version) - $(MAKE) dockers - for svc in $(SERVICES); do \ - docker tag $(MG_DOCKER_IMAGE_NAME_PREFIX)/$$svc $(MG_DOCKER_IMAGE_NAME_PREFIX)/$$svc:$(version); \ - done - $(call docker_push,$(version)) - -rundev: - cd scripts && ./run.sh - -grpc_mtls_certs: - $(MAKE) -C docker/ssl auth_grpc_certs things_grpc_certs - -check_tls: -ifeq ($(GRPC_TLS),true) - @unset GRPC_MTLS - @echo "gRPC TLS is enabled" - GRPC_MTLS= -else - @unset GRPC_TLS - GRPC_TLS= -endif - -check_mtls: -ifeq ($(GRPC_MTLS),true) - @unset GRPC_TLS - @echo "gRPC MTLS is enabled" - GRPC_TLS= -else - @unset GRPC_MTLS - GRPC_MTLS= -endif - -check_certs: check_mtls check_tls -ifeq ($(GRPC_MTLS_CERT_FILES_EXISTS),0) -ifeq ($(filter true,$(GRPC_MTLS) $(GRPC_TLS)),true) -ifeq ($(filter $(DEFAULT_DOCKER_COMPOSE_COMMAND),$(DOCKER_COMPOSE_COMMAND)),$(DEFAULT_DOCKER_COMPOSE_COMMAND)) - $(MAKE) -C docker/ssl auth_grpc_certs things_grpc_certs -endif -endif -endif - -run: check_certs - docker compose -f docker/docker-compose.yml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) - -run_addons: check_certs - $(foreach SVC,$(RUN_ADDON_ARGS),$(if $(filter $(SVC),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)),,$(error Invalid Service $(SVC)))) - @for SVC in $(RUN_ADDON_ARGS); do \ - MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \ - done diff --git a/docker/addons/vault/scripts/README.md b/docker/addons/vault/scripts/README.md deleted file mode 100644 index 6be4d54c..00000000 --- a/docker/addons/vault/scripts/README.md +++ /dev/null @@ -1,191 +0,0 @@ -# Magistrala - -[![Check License Header](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-license.yaml) -[![Check the consistency of generated files](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/check-generated-files.yml) -[![Continuous Delivery](https://github.com/absmach/magistrala/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/absmach/magistrala/actions/workflows/build.yml) -[![go report card][grc-badge]][grc-url] -[![coverage][cov-badge]][cov-url] -[![license][license]](LICENSE) -[![chat][gitter-badge]][gitter] - -![banner][banner] - -Magistrala is modern, scalable, secure, open-source, and patent-free IoT cloud platform written in Go. - -It accepts user and thing (sensor, actuator, application) connections over various network protocols (i.e. HTTP, MQTT, WebSocket, CoAP), thus making a seamless bridge between them. It is used as the IoT middleware for building complex IoT solutions. - -For more details, check out the [official documentation][docs]. -For extra bits and services see [our contrib repository][contrib]. - -## Features - -- Multi-protocol connectivity and bridging (HTTP, MQTT, WebSocket and CoAP; see [contrib repository][contrib] for LoRa and OPC UA) -- Device management and provisioning (Zero Touch provisioning) -- Mutual TLS Authentication (mTLS) using X.509 Certificates -- Fine-grained access control (policies, ABAC/RBAC) -- Message persistence (Timescale and PostgresSQL - see [contrib repository][contrib] for Cassandra, InfluxDB, and MongoDB support) -- Platform logging and instrumentation support (Prometheus and OpenTelemetry) -- Event sourcing -- Container-based deployment using [Docker][docker] and [Kubernetes][kubernetes] -- Edge [Agent][agent] and [Export][export] services for remote IoT gateway management and edge computing -- SDK -- CLI -- Small memory footprint and fast execution -- Domain-driven design architecture, high-quality code and test coverage - -## Prerequisites - -The following are needed to run Magistrala: - -- [Docker](https://docs.docker.com/install/) (version 26.0.0) - -Developing Magistrala will also require: - -- [Go](https://golang.org/doc/install) (version 1.21) -- [Protobuf](https://github.com/protocolbuffers/protobuf#protocol-compiler-installation) (version 25.1) - -## Install - -Once the prerequisites are installed, execute the following commands from the project's root: - -```bash -docker compose -f docker/docker-compose.yml --env-file docker/.env -p git_github_com_absmach_magistrala_git_ up -``` - -This will bring up the Magistrala docker services and interconnect them. This command can also be executed using the project's included Makefile: - -```bash -make run -``` - -If you want to run services from specific release checkout code from github and make sure that -`MG_RELEASE_TAG` in [.env](.env) is being set to match the release version - -```bash -git checkout tags/<release_number> -b <release_number> -# e.g. `git checkout tags/0.13.0 -b 0.13.0` -``` - -Check that `.env` file contains: - -```bash -MG_RELEASE_TAG=<release_number> -``` - -> `docker-compose` should be used for development and testing deployments. For production we suggest using [Kubernetes](https://docs.magistrala.abstractmachines.fr/kubernetes). - -## Usage - -The quickest way to start using Magistrala is via the CLI. The latest version can be downloaded from the [official releases page][releases]. - -It can also be built and used from the project's root directory: - -```bash -make cli -./build/cli version -``` - -Additional details on using the CLI can be found in the [CLI documentation](https://docs.magistrala.abstractmachines.fr/cli). - -## Documentation - -Official documentation is hosted at [Magistrala official docs page][docs]. Documentation is auto-generated, checkout the instructions on [official docs repository](https://github.com/absmach/magistrala-docs): - -If you spot an error or a need for corrections, please let us know - or even better: send us a PR. - -## Authors - -Main architect and BDFL of Magistrala project is [@drasko][drasko]. - -Additionally, [@nmarcetic][nikola] and [@janko-isidorovic][janko] assured overall architecture and design, while [@manuio][manu] and [@darkodraskovic][darko] helped with crafting initial implementation and continuously worked on the project evolutions. - -Besides them, Magistrala is constantly improved and actively developed by [@anovakovic01][alex], [@dusanb94][dusan], [@srados][sava], [@gsaleh][george], [@blokovi][iva], [@chombium][kole], [@mteodor][mirko], [@rodneyosodo][rodneyosodo] and a large set of contributors. - -Maintainers are listed in [MAINTAINERS](MAINTAINERS) file. - -The Magistrala team would like to give special thanks to [@mijicd][dejan] for his monumental work on designing and implementing a highly improved and optimized version of the platform, and [@malidukica][dusanm] for his effort on implementing the initial user interface. - -## Professional Support - -There are many companies offering professional support for the Magistrala system. - -If you need this kind of support, best is to reach out to [@drasko][drasko] directly, and he will point you out to the best-matching support team. - -## Contributing - -Thank you for your interest in Magistrala and the desire to contribute! - -1. Take a look at our [open issues](https://github.com/absmach/magistrala/issues). The [good-first-issue](https://github.com/absmach/magistrala/labels/good-first-issue) label is specifically for issues that are great for getting started. -2. Checkout the [contribution guide](CONTRIBUTING.md) to learn more about our style and conventions. -3. Make your changes compatible to our workflow. - -Also, explore our [contrib][contrib] repository for extra services such as Cassandra, InfluxDB, MongoDB readers and writers, LoRa, OPC UA support, Digital Twins, and more. If you have a contribution that is not a good fit for the core monorepo (it's specific to your use case, it's an additional feature or a new service, it's optional or an add-on), this is a great place to submit the pull request. - -### We're Hiring - -You like Magistrala and you would like to make it your day job? We're always looking for talented engineers interested in open-source, IoT and distributed systems. If you recognize yourself, reach out to [@drasko][drasko] - he will contact you back. - -> The best way to grab our attention is, of course, by sending PRs :sunglasses:. - -## Community - -- [Google group][forum] -- [Gitter][gitter] -- [Twitter][twitter] - -## License - -[Apache-2.0](LICENSE) - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fabsmach%2Fmagistrala.svg?type=large&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fabsmach%2Fmagistrala?ref=badge_large&issueType=license) -## Data Collection for Magistrala - -Magistrala is committed to continuously improving its services and ensuring a seamless experience for its users. To achieve this, we collect certain data from your deployments. Rest assured, this data is collected solely for the purpose of enhancing Magistrala and is not used with any malicious intent. The deployment summary can be found on our [website][callhome]. - -The collected data includes: - -- **IP Address** - Used for approximate location information on deployments. -- **Services Used** - To understand which features are popular and prioritize future developments. -- **Last Seen Time** - To ensure the stability and availability of Magistrala. -- **Magistrala Version** - To track the software version and deliver relevant updates. - -We take your privacy and data security seriously. All data collected is handled in accordance with our stringent privacy policies and industry best practices. - -Data collection is on by default and can be disabled by setting the env variable: -`MG_SEND_TELEMETRY=false` - -By utilizing Magistrala, you actively contribute to its improvement. Together, we can build a more robust and efficient IoT platform. Thank you for your trust in Magistrala! - -[banner]: https://github.com/absmach/magistrala-docs/blob/main/docs/img/gopherBanner.jpg -[docs]: https://docs.magistrala.abstractmachines.fr -[docker]: https://www.docker.com -[forum]: https://groups.google.com/forum/#!forum/mainflux -[gitter]: https://gitter.im/absmach/magistrala?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge -[gitter-badge]: https://badges.gitter.im/Join%20Chat.svg -[grc-badge]: https://goreportcard.com/badge/github.com/absmach/magistrala -[grc-url]: https://goreportcard.com/report/github.com/absmach/magistrala -[cov-badge]: https://codecov.io/gh/absmach/magistrala/graph/badge.svg?token=SEMDAO3L09 -[cov-url]: https://codecov.io/gh/absmach/magistrala -[license]: https://img.shields.io/badge/license-Apache%20v2.0-blue.svg -[twitter]: https://twitter.com/absmach -[agent]: https://github.com/absmach/agent -[export]: https://github.com/absmach/export -[kubernetes]: https://kubernetes.io/ -[releases]: https://github.com/absmach/magistrala/releases -[drasko]: https://github.com/drasko -[nikola]: https://github.com/nmarcetic -[dejan]: https://github.com/mijicd -[manu]: https://github.com/manuIO -[darko]: https://github.com/darkodraskovic -[janko]: https://github.com/janko-isidorovic -[alex]: https://github.com/anovakovic01 -[dusan]: https://github.com/dborovcanin -[sava]: https://github.com/srados -[george]: https://github.com/gesaleh -[iva]: https://github.com/blokovi -[kole]: https://github.com/chombium -[dusanm]: https://github.com/malidukica -[mirko]: https://github.com/mteodor -[rodneyosodo]: https://github.com/rodneyosodo -[callhome]: https://deployments.magistrala.abstractmachines.fr/ -[contrib]: https://www.github.com/absmach/mg-contrib diff --git a/docker/addons/vault/scripts/api.go b/docker/addons/vault/scripts/api.go deleted file mode 100644 index 0250ccd3..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/api/asyncapi/mqtt.yml b/docker/addons/vault/scripts/api/asyncapi/mqtt.yml deleted file mode 100644 index 4a4d1575..00000000 --- a/docker/addons/vault/scripts/api/asyncapi/mqtt.yml +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -asyncapi: '2.6.0' -id: 'https://github.com/absmach/magistrala/blob/main/api/asyncapi/mqtt.yml' -info: - title: Magistrala MQTT Adapter - version: '1.0.0' - contact: - name: Magistrala Team - url: 'https://github.com/absmach/magistrala' - email: info@abstractmachines.fr - description: | - 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. - Additionally, the MQTT adapter and the message broker are replicating the traffic between brokers. - - license: - name: Apache 2.0 - url: 'https://github.com/absmach/magistrala/blob/main/LICENSE' - - -defaultContentType: application/json - -servers: - dev: - url: localhost:{port} - protocol: mqtt - description: Test broker - variables: - port: - description: Secure connection (TLS) is available through port 8883. - default: '1883' - enum: - - '1883' - - '8883' - security: - - user-password: [] - -channels: - channels/{channelID}/messages/{subtopic}: - parameters: - channelID: - $ref: '#/components/parameters/channelID' - in: path - required: true - subtopic: - $ref: '#/components/parameters/subtopic' - in: path - required: false - - publish: - traits: - - $ref: '#/components/operationTraits/mqtt' - message: - $ref: '#/components/messages/jsonMsg' - subscribe: - traits: - - $ref: '#/components/operationTraits/mqtt' - message: - $ref: '#/components/messages/jsonMsg' - -components: - messages: - jsonMsg: - title: JSON Message - summary: Arbitrary JSON array or object. - contentType: application/json - payload: - $ref: "#/components/schemas/jsonMsg" - - schemas: - jsonMsg: - type: object - description: Arbitrary JSON object or array. SenML format is recommended. - example: | - ### SenML - ```json - [{"bn":"some-base-name:","bt":1641646520, "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}] - ``` - ### JSON - ```json - {"field_1":"val_1", "t": 1641646525} - ``` - ### JSON Array - ```json - [{"field_1":"val_1", "t": 1641646520},{"field_2":"val_2", "t": 1641646522}] - ``` - - parameters: - channelID: - description: Channel ID connected to the Thing ID defined in the username. - schema: - type: string - format: uuid - subtopic: - description: Arbitrary message subtopic. - schema: - type: string - default: '' - - securitySchemes: - user-password: - type: userPassword - description: | - username is thing ID connected to the channel defined in the mqtt topic and - password is thing key corresponding to the thing ID - - operationTraits: - mqtt: - bindings: - mqtt: - qos: 2 diff --git a/docker/addons/vault/scripts/api/asyncapi/websocket.yml b/docker/addons/vault/scripts/api/asyncapi/websocket.yml deleted file mode 100644 index 0f514c8a..00000000 --- a/docker/addons/vault/scripts/api/asyncapi/websocket.yml +++ /dev/null @@ -1,144 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -asyncapi: 2.6.0 -id: 'https://github.com/absmach/magistrala/blob/main/api/asyncapi/websocket.yml' -info: - title: Magistrala WebSocket adapter - description: WebSocket adapter provides a WebSocket API for sending messages through communication channels. WebSocket adapter uses [mProxy](https://github.com/absmach/mproxy) for proxying traffic between client and MQTT broker. - version: '1.0.0' - contact: - name: Magistrala Team - url: 'https://github.com/absmach/magistrala' - email: info@abstractmachines.fr - license: - name: Apache 2.0 - url: 'https://github.com/absmach/magistrala/blob/main/LICENSE' -tags: - - name: WebSocket -defaultContentType: application/json - -servers: - dev: - url: 'ws://{host}:{port}' - protocol: ws - description: Default WebSocket Adapter URL - variables: - host: - description: Hostname of the WebSocket adapter - default: localhost - port: - description: Magistrala WebSocket Adapter port - default: '8186' - -channels: - 'channels/{channelID}/messages/{subtopic}': - parameters: - channelID: - $ref: '#/components/parameters/channelID' - in: path - required: true - subtopic: - $ref: '#/components/parameters/subtopic' - in: path - required: false - publish: - summary: Publish messages to a channel - operationId: publishToChannel - message: - $ref: '#/components/messages/jsonMsg' - messageId: publishMessage - bindings: - ws: - method: POST - query: - subtopic: '{$request.query.subtopic}' - security: - - bearerAuth: [] - subscribe: - summary: Subscribe to receive messages from a channel - operationId: subscribeToChannel - message: - $ref: '#/components/messages/jsonMsg' - messageId: subscribeMessage - bindings: - ws: - method: GET - query: - subtopic: '{$request.query.subtopic}' - security: - - bearerAuth: [] - /version: - subscribe: - summary: Get the version of the Magistrala adapter - operationId: getVersion - bindings: - http: - method: GET - metrics: - description: Endpoint for getting service metrics. - subscribe: - operationId: metrics - summary: Service metrics - bindings: - http: - type: request - method: GET - -components: - messages: - jsonMsg: - title: JSON Message - summary: Arbitrary JSON array or object. - contentType: application/json - payload: - $ref: '#/components/schemas/jsonMsg' - schemas: - jsonMsg: - type: object - description: Arbitrary JSON object or array. SenML format is recommended. - example: > - ### SenML - - ```json - - [{"bn":"some-base-name:","bt":1641646520, "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}] - - ``` - - ### JSON - - ```json - - {"field_1":"val_1", "t": 1641646525} - - ``` - - ### JSON Array - - ```json - - [{"field_1":"val_1", "t": 1641646520},{"field_2":"val_2", "t": - 1641646522}] - - ``` - parameters: - channelID: - description: Channel ID connected to the Thing ID defined in the username. - schema: - type: string - format: uuid - subtopic: - description: Arbitrary message subtopic. - schema: - type: string - default: '' - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: uuid - description: | - * Thing access: "Authorization: Thing <thing_key>" diff --git a/docker/addons/vault/scripts/api/openapi/README.md b/docker/addons/vault/scripts/api/openapi/README.md deleted file mode 100644 index 09dbcfc0..00000000 --- a/docker/addons/vault/scripts/api/openapi/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Magistrala OpenAPI Specification - -This folder contains an OpenAPI specifications for Magistrala API. - -View specification in Swagger UI at [docs.api.magistrala.abstractmachines.fr](https://docs.api.magistrala.abstractmachines.fr) \ No newline at end of file diff --git a/docker/addons/vault/scripts/api/openapi/auth.yml b/docker/addons/vault/scripts/api/openapi/auth.yml deleted file mode 100644 index 5c1c3dca..00000000 --- a/docker/addons/vault/scripts/api/openapi/auth.yml +++ /dev/null @@ -1,909 +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: Auth - description: Everything about your Authentication and Authorization. - externalDocs: - description: Find out more about auth - url: https://docs.magistrala.abstractmachines.fr/ - - name: Keys - description: Everything about your Keys. - externalDocs: - description: Find out more about keys - 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" - - /policies: - post: - operationId: addPolicies - summary: Creates new policies. - description: | - Creates new policies. Only admin can use this endpoint. Therefore, you need an authentication token for the admin. - Also, only policies defined on the system are allowed to add. For more details, please see the docs for Authorization. - tags: - - Auth - requestBody: - $ref: "#/components/requestBodies/PoliciesReq" - responses: - "201": - description: Policies created. - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access token provided. - "404": - description: A non-existent entity request. - "409": - description: Failed due to using an existing email address. - "415": - description: Missing or invalid content type. - "500": - $ref: "#/components/responses/ServiceError" - - /policies/delete: - post: - operationId: deletePolicies - summary: Deletes policies. - description: | - Deletes policies. Only admin can use this endpoint. Therefore, you need an authentication token for the admin. - Also, only policies defined on the system are allowed to delete. For more details, please see the docs for Authorization. - tags: - - Auth - requestBody: - $ref: "#/components/requestBodies/PoliciesReq" - responses: - "204": - description: Policies deleted. - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "404": - description: A non-existent entity request. - "409": - description: Failed due to using an existing email address. - "415": - description: Missing or invalid content type. - "500": - $ref: "#/components/responses/ServiceError" - /users/{memberID}/domains: - get: - tags: - - Domains - summary: List users in a group - description: | - Retrieves a list of users in a domain. 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/MemberID" - - $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. - - PoliciesReqSchema: - type: object - properties: - object: - type: string - description: | - Specifies an object field for the field. - Object indicates application objects such as ThingID. - subjects: - type: array - minItems: 1 - uniqueItems: true - items: - type: string - policies: - type: array - minItems: 1 - uniqueItems: true - items: - type: string - - 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. - - PoliciesReq: - description: JSON-formatted document describing adding policies request. - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/PoliciesReqSchema" - - 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/<domainID_id>` - 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 <user_token>" - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/bootstrap.yml b/docker/addons/vault/scripts/api/openapi/bootstrap.yml deleted file mode 100644 index 42986042..00000000 --- a/docker/addons/vault/scripts/api/openapi/bootstrap.yml +++ /dev/null @@ -1,689 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.1 -info: - title: Magistrala Bootstrap service - description: | - HTTP API for managing platform things configuration. - 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:9013 - - url: https://localhost:9013 - -tags: - - name: configs - description: Everything about your Configs - externalDocs: - description: Find out more about Configs - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /{domainID}/things/configs: - post: - operationId: createConfig - summary: Adds new config - description: | - Adds new config to the list of config owned by user identified using - the provided access token. - tags: - - configs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - requestBody: - $ref: "#/components/requestBodies/ConfigCreateReq" - responses: - "201": - $ref: "#/components/responses/ConfigCreateRes" - "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" - "503": - description: Failed to receive response from the things service. - get: - operationId: getConfigs - summary: Retrieves managed configs - description: | - Retrieves a list of managed configs. Due to performance concerns, data - is retrieved in subsets. The API configs must ensure that the entire - dataset is consumed either by making subsequent requests, or by - increasing the subset size of the initial request. - tags: - - configs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/State" - - $ref: "#/components/parameters/Name" - responses: - "200": - $ref: "#/components/responses/ConfigListRes" - "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" - /{domainID}/things/configs/{configId}: - get: - operationId: getConfig - summary: Retrieves config info (with channels). - tags: - - configs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ConfigId" - responses: - "200": - $ref: "#/components/responses/ConfigRes" - "400": - description: Missing or invalid config. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "404": - description: Config does not exist. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - put: - operationId: updateConfig - summary: Updates config info - description: | - Update is performed by replacing the current resource data with values - provided in a request payload. Note that the owner, ID, external ID, - external key, Magistrala Thing ID and key cannot be changed. - tags: - - configs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ConfigId" - requestBody: - $ref: "#/components/requestBodies/ConfigUpdateReq" - responses: - "200": - description: Config updated. - "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: Config does not exist. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - delete: - operationId: removeConfig - summary: Removes a Config - description: | - Removes a Config. In case of successful removal the service will ensure - that the removed config is disconnected from all of the Magistrala channels. - tags: - - configs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ConfigId" - responses: - "204": - description: Config removed. - "400": - description: Failed due to malformed config ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Failed to perform authorization over the entity. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /{domainID}/things/configs/certs/{configId}: - patch: - operationId: updateConfigCerts - summary: Updates certs - description: | - Update is performed by replacing the current certificate data with values - provided in a request payload. - tags: - - configs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ConfigId" - requestBody: - $ref: "#/components/requestBodies/ConfigCertUpdateReq" - responses: - "200": - description: Config updated. - $ref: "#/components/responses/ConfigUpdateCertsRes" - "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: Config does not exist. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /{domainID}/things/configs/connections/{configId}: - put: - operationId: updateConfigConnections - summary: Updates channels the thing is connected to - description: | - Update connections performs update of the channel list corresponding - Thing is connected to. - tags: - - configs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ConfigId" - requestBody: - $ref: "#/components/requestBodies/ConfigConnUpdateReq" - responses: - "200": - description: Config updated. - "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: Config does not exist. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /things/bootstrap/{externalId}: - get: - operationId: getBootstrapConfig - summary: Retrieves configuration. - description: | - Retrieves a configuration with given external ID and external key. - tags: - - configs - security: - - bootstrapAuth: [] - parameters: - - $ref: "#/components/parameters/ExternalId" - responses: - "200": - $ref: "#/components/responses/BootstrapConfigRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid external key provided. - "404": - description: Failed to retrieve corresponding config. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /things/bootstrap/secure/{externalId}: - get: - operationId: getSecureBootstrapConfig - summary: Retrieves configuration. - description: | - Retrieves a configuration with given external ID and encrypted external key. - tags: - - configs - security: - - bootstrapEncAuth: [] - parameters: - - $ref: "#/components/parameters/ExternalId" - responses: - "200": - $ref: "#/components/responses/BootstrapConfigRes" - "400": - description: Failed due to malformed JSON. - "401": - description: Missing or invalid access token provided. - "404": - description: | - Failed to retrieve corresponding config. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /{domainID}/things/state/{configId}: - put: - operationId: updateConfigState - summary: Updates Config state. - description: | - Updating state represents enabling/disabling Config, i.e. connecting - and disconnecting corresponding Magistrala Thing to the list of Channels. - tags: - - configs - parameters: - - $ref: "auth.yml#/components/parameters/DomainID" - - $ref: "#/components/parameters/ConfigId" - requestBody: - $ref: "#/components/requestBodies/ConfigStateUpdateReq" - responses: - "204": - description: Config removed. - "400": - description: Failed due to malformed config's ID. - "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" - /health: - get: - summary: Retrieves service health check info. - tags: - - health - security: [] - responses: - "200": - $ref: "#/components/responses/HealthRes" - "500": - $ref: "#/components/responses/ServiceError" - -components: - schemas: - State: - type: integer - enum: [0, 1] - Config: - type: object - properties: - thing_id: - type: string - format: uuid - description: Corresponding Magistrala Thing ID. - magistrala_key: - type: string - format: uuid - description: Corresponding Magistrala Thing key. - channels: - type: array - minItems: 0 - items: - type: object - properties: - id: - type: string - format: uuid - description: Channel unique identifier. - name: - type: string - description: Name of the Channel. - metadata: - type: object - description: Custom metadata related to the Channel. - external_id: - type: string - description: External ID (MAC address or some unique identifier). - external_key: - type: string - description: External key. - content: - type: string - description: Free-form custom configuration. - state: - $ref: "#/components/schemas/State" - client_cert: - type: string - description: Client certificate. - ca_cert: - type: string - description: Issuing CA certificate. - required: - - external_id - - external_key - ConfigList: - type: object - properties: - total: - type: integer - description: Total number of results. - minimum: 0 - offset: - type: integer - description: Number of items to skip during retrieval. - minimum: 0 - default: 0 - limit: - type: integer - description: Size of the subset to retrieve. - maximum: 100 - default: 10 - configs: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Config" - required: - - configs - BootstrapConfig: - type: object - properties: - thing_id: - type: string - format: uuid - description: Corresponding Magistrala Thing ID. - thing_key: - type: string - format: uuid - description: Corresponding Magistrala Thing key. - channels: - type: array - minItems: 0 - items: - type: string - content: - type: string - description: Free-form custom configuration. - client_cert: - type: string - description: Client certificate. - client_key: - type: string - description: Key for the client_cert. - ca_cert: - type: string - description: Issuing CA certificate. - required: - - thing_id - - thing_key - - channels - - content - ConfigUpdateCerts: - 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. - ca_cert: - type: string - description: Issuing CA certificate. - required: - - thing_id - - thing_key - - channels - - content - - parameters: - ConfigId: - name: configId - description: Unique Config identifier. It's the ID of the corresponding Thing. - in: path - schema: - type: string - format: uuid - required: true - ExternalId: - name: externalId - description: Unique Config identifier provided by external entity. - in: path - schema: - type: string - 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 - State: - name: state - description: A state of items - in: query - schema: - $ref: "#/components/schemas/State" - required: false - Name: - name: name - description: Name of the config. Search by name is partial-match and case-insensitive. - in: query - schema: - type: string - required: false - - requestBodies: - ConfigCreateReq: - description: JSON-formatted document describing the new config. - required: true - content: - application/json: - schema: - type: object - properties: - external_id: - type: string - description: External ID (MAC address or some unique identifier). - external_key: - type: string - description: External key. - thing_id: - type: string - format: uuid - description: ID of the corresponding Magistrala Thing. - channels: - type: array - minItems: 0 - items: - type: string - format: uuid - content: - type: string - name: - type: string - client_cert: - type: string - description: Thing Certificate. - client_key: - type: string - description: Thing Private Key. - ca_cert: - type: string - required: - - external_id - - external_key - ConfigUpdateReq: - description: JSON-formatted document describing the updated thing. - content: - application/json: - schema: - type: object - properties: - content: - type: string - name: - type: string - required: - - content - - name - ConfigCertUpdateReq: - description: JSON-formatted document describing the updated thing. - content: - application/json: - schema: - type: object - properties: - client_cert: - type: string - client_key: - type: string - ca_cert: - type: string - ConfigConnUpdateReq: - description: Array if IDs the thing is be connected to. - content: - application/json: - schema: - type: object - properties: - channels: - type: array - minItems: 0 - items: - type: string - format: uuid - ConfigStateUpdateReq: - description: Update the state of the Config. - content: - application/json: - schema: - type: object - properties: - state: - $ref: "#/components/schemas/State" - - responses: - ConfigCreateRes: - description: Config registered. - headers: - Location: - content: - text/plain: - schema: - type: string - description: Created configuration's relative URL (i.e. /things/configs/{configId}). - ConfigListRes: - description: Data retrieved. Configs from this list don't contain channels. - content: - application/json: - schema: - $ref: "#/components/schemas/ConfigList" - ConfigRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Config" - links: - update: - operationId: updateConfig - parameters: - configId: $response.body#/id - updateCerts: - operationId: updateConfigCerts - parameters: - configId: $response.body#/id - updateConnections: - operationId: updateConfigConnections - parameters: - configId: $response.body#/id - updateState: - operationId: updateConfigState - parameters: - configId: $response.body#/id - delete: - operationId: removeConfig - parameters: - configId: $response.body#/id - BootstrapConfigRes: - description: | - Data retrieved. If secure, a response is encrypted using - the secret key, so the response is in the binary form. - content: - application/json: - schema: - $ref: "#/components/schemas/BootstrapConfig" - ServiceError: - description: Unexpected server-side error occurred. - HealthRes: - description: Service Health Check. - content: - application/health+json: - schema: - $ref: "./schemas/HealthInfo.yml" - ConfigUpdateCertsRes: - description: Data retrieved. Config certs updated. - content: - application/json: - schema: - $ref: "#/components/schemas/ConfigUpdateCerts" - - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * Users access: "Authorization: Bearer <user_token>" - - bootstrapAuth: - type: http - scheme: bearer - bearerFormat: string - description: | - * Things access: "Authorization: Thing <external_key>" - - bootstrapEncAuth: - type: http - scheme: bearer - bearerFormat: aes-sha256-uuid - description: | - * Things access: "Authorization: Thing <external_enc_key>" - Hex-encoded configuration external key encrypted using - the AES algorithm and SHA256 sum of the external key - itself as an encryption key. - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/certs.yml b/docker/addons/vault/scripts/api/openapi/certs.yml deleted file mode 100644 index b5ced937..00000000 --- a/docker/addons/vault/scripts/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 <user_token>" - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/http.yml b/docker/addons/vault/scripts/api/openapi/http.yml deleted file mode 100644 index f366458b..00000000 --- a/docker/addons/vault/scripts/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 <thing_key>" - - basicAuth: - type: http - scheme: basic - description: | - * Things access: "Authorization: Basic <base64-encoded_credentials>" - -security: - - bearerAuth: [] - - basicAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/invitations.yml b/docker/addons/vault/scripts/api/openapi/invitations.yml deleted file mode 100644 index 541e3685..00000000 --- a/docker/addons/vault/scripts/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_name> 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 <user_access_token>" - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/journal.yml b/docker/addons/vault/scripts/api/openapi/journal.yml deleted file mode 100644 index 16522274..00000000 --- a/docker/addons/vault/scripts/api/openapi/journal.yml +++ /dev/null @@ -1,286 +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/{entity_type}/{id}: - get: - tags: - - journal-log - summary: List 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/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: - entity_type: - name: entity_type - description: Type of entity, e.g. user, group, thing, etc. - in: path - schema: - type: string - enum: - - user - - group - - thing - - channel - required: true - example: user - - id: - name: id - description: Unique identifier for an entity, e.g. user, group, domain, etc. 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 <user_access_token>" - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/notifiers.yml b/docker/addons/vault/scripts/api/openapi/notifiers.yml deleted file mode 100644 index 62a681ea..00000000 --- a/docker/addons/vault/scripts/api/openapi/notifiers.yml +++ /dev/null @@ -1,292 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.1 -info: - title: Magistrala Notifiers service - description: | - HTTP API for Notifiers 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:9014 - - url: https://localhost:9014 - - url: http://localhost:9015 - - url: https://localhost:9015 - -tags: - - name: notifiers - description: Everything about your Notifiers - externalDocs: - description: Find out more about notifiers - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /subscriptions: - post: - operationId: createSubscription - summary: Create subscription - description: Creates a new subscription give a topic and contact. - tags: - - notifiers - requestBody: - $ref: "#/components/requestBodies/Create" - responses: - "201": - $ref: "#/components/responses/Create" - "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 topic and contact. - "415": - description: Missing or invalid content type. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - get: - operationId: listSubscriptions - summary: List subscriptions - description: List subscriptions given list parameters. - tags: - - notifiers - parameters: - - $ref: "#/components/parameters/Topic" - - $ref: "#/components/parameters/Contact" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Limit" - responses: - "200": - $ref: "#/components/responses/Page" - "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" - /subscriptions/{id}: - get: - operationId: viewSubscription - summary: Get subscription with the provided id - description: Retrieves a subscription with the provided id. - tags: - - notifiers - parameters: - - $ref: "#/components/parameters/Id" - responses: - "200": - $ref: "#/components/responses/View" - "400": - description: Failed due to malformed 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" - delete: - operationId: removeSubscription - summary: Delete subscription with the provided id - description: Removes a subscription with the provided id. - tags: - - notifiers - parameters: - - $ref: "#/components/parameters/Id" - responses: - "204": - description: Subscription removed - "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: - Subscription: - type: object - properties: - id: - type: string - format: ulid - example: 01EWDVKBQSG80B6PQRS9PAAY35 - description: ULID id of the subscription. - owner_id: - type: string - format: uuid - example: 18167738-f7a8-4e96-a123-58c3cd14de3a - description: An id of the owner who created subscription. - topic: - type: string - example: topic.subtopic - description: Topic to which the user subscribes. - contact: - type: string - example: user@example.com - description: The contact of the user to which the notification will be sent. - CreateSubscription: - type: object - properties: - topic: - type: string - example: topic.subtopic - description: Topic to which the user subscribes. - contact: - type: string - example: user@example.com - description: The contact of the user to which the notification will be sent. - Page: - type: object - properties: - subscriptions: - type: array - minItems: 0 - uniqueItems: true - items: - $ref: "#/components/schemas/Subscription" - 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. - - parameters: - Id: - name: id - description: Unique identifier. - in: path - schema: - type: string - format: ulid - 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 - Topic: - name: topic - description: Topic name. - in: query - schema: - type: string - required: false - Contact: - name: contact - description: Subscription contact. - in: query - schema: - type: string - required: false - - requestBodies: - Create: - description: JSON-formatted document describing the new subscription to be created - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/CreateSubscription" - - responses: - Create: - description: Created a new subscription. - headers: - Location: - content: - text/plain: - schema: - type: string - description: Created subscription relative URL - example: /subscriptions/{id} - View: - description: View subscription. - content: - application/json: - schema: - $ref: "#/components/schemas/Subscription" - links: - delete: - operationId: removeSubscription - parameters: - id: $response.body#/id - Page: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/Page" - 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 <user_token>" - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/provision.yml b/docker/addons/vault/scripts/api/openapi/provision.yml deleted file mode 100644 index 9b814e8b..00000000 --- a/docker/addons/vault/scripts/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 <user_token>" - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/readers.yml b/docker/addons/vault/scripts/api/openapi/readers.yml deleted file mode 100644 index 8cf7ea52..00000000 --- a/docker/addons/vault/scripts/api/openapi/readers.yml +++ /dev/null @@ -1,314 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -openapi: 3.0.1 -info: - title: Magistrala reader service - description: | - HTTP API for reading messages. - 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:9003 - - url: https://localhost:9003 - - url: http://localhost:9005 - - url: https://localhost:9005 - - url: http://localhost:9007 - - url: https://localhost:9007 - - url: http://localhost:9009 - - url: https://localhost:9009 - - url: http://localhost:9011 - - url: https://localhost:9011 - -tags: - - name: readers - description: Everything about your Readers - externalDocs: - description: Find out more about readers - url: https://docs.magistrala.abstractmachines.fr/ - -paths: - /{domainID}/channels/{chanId}/messages: - get: - operationId: getMessages - summary: Retrieves messages sent to single channel - description: | - Retrieves a list of messages sent to specific channel. Due to - performance concerns, data is retrieved in subsets. The API readers must - ensure that the entire dataset is consumed either by making subsequent - requests, or by increasing the subset size of the initial request. - tags: - - readers - parameters: - - $ref: "#/components/parameters/DomainID" - - $ref: "#/components/parameters/ChanId" - - $ref: "#/components/parameters/Limit" - - $ref: "#/components/parameters/Offset" - - $ref: "#/components/parameters/Publisher" - - $ref: "#/components/parameters/Name" - - $ref: "#/components/parameters/Value" - - $ref: "#/components/parameters/BoolValue" - - $ref: "#/components/parameters/StringValue" - - $ref: "#/components/parameters/DataValue" - - $ref: "#/components/parameters/From" - - $ref: "#/components/parameters/To" - - $ref: "#/components/parameters/Aggregation" - - $ref: "#/components/parameters/Interval" - responses: - "200": - $ref: "#/components/responses/MessagesPageRes" - "400": - description: Failed due to malformed query parameters. - "401": - description: Missing or invalid access token provided. - "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: - MessagesPage: - type: object - properties: - total: - type: number - description: Total number of items that are present on the system. - offset: - type: number - description: Number of items that were skipped during retrieval. - limit: - type: number - description: Size of the subset that was retrieved. - messages: - type: array - minItems: 0 - uniqueItems: true - items: - type: object - properties: - channel: - type: integer - description: Unique channel id. - publisher: - type: integer - description: Unique publisher id. - protocol: - type: string - description: Protocol name. - name: - type: string - description: Measured parameter name. - unit: - type: string - description: Value unit. - value: - type: number - description: Measured value in number. - stringValue: - type: string - description: Measured value in string format. - boolValue: - type: boolean - description: Measured value in boolean format. - dataValue: - type: string - description: Measured value in binary format. - valueSum: - type: number - description: Sum value. - time: - type: number - description: Time of measurement. - updateTime: - type: number - description: Time of updating measurement. - - parameters: - DomainID: - name: domainID - description: Unique domain identifier. - in: path - schema: - type: string - format: uuid - required: true - ChanId: - name: chanId - description: Unique channel identifier. - 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 - Publisher: - name: Publisher - description: Unique thing identifier. - in: query - schema: - type: string - format: uuid - required: false - Name: - name: name - description: SenML message name. - in: query - schema: - type: string - required: false - Value: - name: v - description: SenML message value. - in: query - schema: - type: string - required: false - BoolValue: - name: vb - description: SenML message bool value. - in: query - schema: - type: boolean - required: false - StringValue: - name: vs - description: SenML message string value. - in: query - schema: - type: string - required: false - DataValue: - name: vd - description: SenML message data value. - in: query - schema: - type: string - required: false - Comparator: - name: comparator - description: Value comparison operator. - in: query - schema: - type: string - default: eq - enum: - - eq - - lt - - le - - gt - - ge - required: false - From: - name: from - description: SenML message time in nanoseconds (integer part represents seconds). - in: query - schema: - type: number - example: 1709218556069 - required: false - To: - name: to - description: SenML message time in nanoseconds (integer part represents seconds). - in: query - schema: - type: number - example: 1709218757503 - required: false - Aggregation: - name: aggregation - description: Aggregation function. - in: query - schema: - type: string - enum: - - MAX - - AVG - - MIN - - SUM - - COUNT - - max - - min - - sum - - avg - - count - example: MAX - required: false - Interval: - name: interval - description: Aggregation interval. - in: query - schema: - type: string - example: 10s - required: false - - responses: - MessagesPageRes: - description: Data retrieved. - content: - application/json: - schema: - $ref: "#/components/schemas/MessagesPage" - 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 <user_token>" - - thingAuth: - type: http - scheme: bearer - bearerFormat: uuid - description: | - * Things access: "Authorization: Thing <thing_key>" - -security: - - bearerAuth: [] - - thingAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/schemas/HealthInfo.yml b/docker/addons/vault/scripts/api/openapi/schemas/HealthInfo.yml deleted file mode 100644 index 9c4e8585..00000000 --- a/docker/addons/vault/scripts/api/openapi/schemas/HealthInfo.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -type: object -properties: - status: - type: string - description: Service status. - enum: - - pass - version: - type: string - description: Service version. - example: v0.14.0 - commit: - type: string - description: Service commit hash. - example: 73362210dd2e04e389eaddb802cab3fe03976593 - description: - type: string - description: Service description. - example: <service_name> service - build_time: - type: string - description: Service build time. - example: 2024-02-01_12:18:15 - instance_id: - type: string - description: Service instance ID. - example: 8edbf8af-7db7-4218-bb4f-a8a929ff5266 diff --git a/docker/addons/vault/scripts/api/openapi/things.yml b/docker/addons/vault/scripts/api/openapi/things.yml deleted file mode 100644 index 852c8690..00000000 --- a/docker/addons/vault/scripts/api/openapi/things.yml +++ /dev/null @@ -1,2070 +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 identifier 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 identifier 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: array - minItems: 0 - items: - type: string - example: ["m_write", "g_add"] - description: Policy relations. - 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: "m_write" - 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: "m_write" - 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/<thing_id>` - 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/<channel_id>` - 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 <user_access_token>" - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/twins.yml b/docker/addons/vault/scripts/api/openapi/twins.yml deleted file mode 100644 index 36261f5f..00000000 --- a/docker/addons/vault/scripts/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 <user_token>" - -security: - - bearerAuth: [] diff --git a/docker/addons/vault/scripts/api/openapi/users.yml b/docker/addons/vault/scripts/api/openapi/users.yml deleted file mode 100644 index 48cf8b2a..00000000 --- a/docker/addons/vault/scripts/api/openapi/users.yml +++ /dev/null @@ -1,2310 +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" - - /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: "#/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" - - /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: "#/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_name> 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 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 - - 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/<user_id>` - 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/<group_id>` - 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 <user_access_token>" - - refreshAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - * User refresh token used to get another access token: "Authorization: Bearer <user_refresh_token>" -security: - - bearerAuth: [] - - refreshAuth: [] diff --git a/docker/addons/vault/scripts/auth.pb.go b/docker/addons/vault/scripts/auth.pb.go deleted file mode 100644 index d76fd94f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth.proto b/docker/addons/vault/scripts/auth.proto deleted file mode 100644 index 54015f11..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/README.md b/docker/addons/vault/scripts/auth/README.md deleted file mode 100644 index 4a991e0f..00000000 --- a/docker/addons/vault/scripts/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 | <http://jaeger:4318/v1/traces> | -| 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/docker/addons/vault/scripts/auth/api/doc.go b/docker/addons/vault/scripts/auth/api/doc.go deleted file mode 100644 index 3b92beda..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/auth/client.go b/docker/addons/vault/scripts/auth/api/grpc/auth/client.go deleted file mode 100644 index f53f4f57..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/auth/doc.go b/docker/addons/vault/scripts/auth/api/grpc/auth/doc.go deleted file mode 100644 index be7d6b2e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/auth/endpoint.go b/docker/addons/vault/scripts/auth/api/grpc/auth/endpoint.go deleted file mode 100644 index adc20eae..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/auth/endpoint_test.go b/docker/addons/vault/scripts/auth/api/grpc/auth/endpoint_test.go deleted file mode 100644 index 4b920617..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/auth/requests.go b/docker/addons/vault/scripts/auth/api/grpc/auth/requests.go deleted file mode 100644 index 41ef9a91..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/auth/responses.go b/docker/addons/vault/scripts/auth/api/grpc/auth/responses.go deleted file mode 100644 index dc9ad1cd..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/auth/server.go b/docker/addons/vault/scripts/auth/api/grpc/auth/server.go deleted file mode 100644 index 491b915d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/auth/setup_test.go b/docker/addons/vault/scripts/auth/api/grpc/auth/setup_test.go deleted file mode 100644 index b6ff6bdf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/domains/client.go b/docker/addons/vault/scripts/auth/api/grpc/domains/client.go deleted file mode 100644 index 1b952afc..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/domains/doc.go b/docker/addons/vault/scripts/auth/api/grpc/domains/doc.go deleted file mode 100644 index 4ae68997..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/domains/endpoint.go b/docker/addons/vault/scripts/auth/api/grpc/domains/endpoint.go deleted file mode 100644 index 5bbb047e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/domains/endpoint_test.go b/docker/addons/vault/scripts/auth/api/grpc/domains/endpoint_test.go deleted file mode 100644 index 3bddb691..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/domains/requests.go b/docker/addons/vault/scripts/auth/api/grpc/domains/requests.go deleted file mode 100644 index 8e989287..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/domains/responses.go b/docker/addons/vault/scripts/auth/api/grpc/domains/responses.go deleted file mode 100644 index 09b88308..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/domains/server.go b/docker/addons/vault/scripts/auth/api/grpc/domains/server.go deleted file mode 100644 index fdfc55ce..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/domains/setup_test.go b/docker/addons/vault/scripts/auth/api/grpc/domains/setup_test.go deleted file mode 100644 index d65f23e7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/token/client.go b/docker/addons/vault/scripts/auth/api/grpc/token/client.go deleted file mode 100644 index ffb8247a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/token/doc.go b/docker/addons/vault/scripts/auth/api/grpc/token/doc.go deleted file mode 100644 index a91e3873..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/token/endpoint.go b/docker/addons/vault/scripts/auth/api/grpc/token/endpoint.go deleted file mode 100644 index ba2566a3..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/token/endpoint_test.go b/docker/addons/vault/scripts/auth/api/grpc/token/endpoint_test.go deleted file mode 100644 index 8e0b8b7a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/token/requests.go b/docker/addons/vault/scripts/auth/api/grpc/token/requests.go deleted file mode 100644 index 24c4a4d8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/token/responses.go b/docker/addons/vault/scripts/auth/api/grpc/token/responses.go deleted file mode 100644 index cb62744e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/token/server.go b/docker/addons/vault/scripts/auth/api/grpc/token/server.go deleted file mode 100644 index a2432b32..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/token/setup_test.go b/docker/addons/vault/scripts/auth/api/grpc/token/setup_test.go deleted file mode 100644 index 8a8c2e0c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/grpc/utils.go b/docker/addons/vault/scripts/auth/api/grpc/utils.go deleted file mode 100644 index 5ad0cf4c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/doc.go b/docker/addons/vault/scripts/auth/api/http/doc.go deleted file mode 100644 index 59a5a1b4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/domains/decode.go b/docker/addons/vault/scripts/auth/api/http/domains/decode.go deleted file mode 100644 index e0c58ecc..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/domains/endpoint.go b/docker/addons/vault/scripts/auth/api/http/domains/endpoint.go deleted file mode 100644 index ffb00a36..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/domains/endpoint_test.go b/docker/addons/vault/scripts/auth/api/http/domains/endpoint_test.go deleted file mode 100644 index 2fe1fd7d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/domains/requests.go b/docker/addons/vault/scripts/auth/api/http/domains/requests.go deleted file mode 100644 index 5abbddd0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/domains/responses.go b/docker/addons/vault/scripts/auth/api/http/domains/responses.go deleted file mode 100644 index 3eb277ef..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/domains/transport.go b/docker/addons/vault/scripts/auth/api/http/domains/transport.go deleted file mode 100644 index 332e9b78..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/keys/endpoint.go b/docker/addons/vault/scripts/auth/api/http/keys/endpoint.go deleted file mode 100644 index 4c3d1b7e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/keys/endpoint_test.go b/docker/addons/vault/scripts/auth/api/http/keys/endpoint_test.go deleted file mode 100644 index 4ed62a34..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/keys/requests.go b/docker/addons/vault/scripts/auth/api/http/keys/requests.go deleted file mode 100644 index 53542c60..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/keys/requests_test.go b/docker/addons/vault/scripts/auth/api/http/keys/requests_test.go deleted file mode 100644 index 6172f243..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/keys/responses.go b/docker/addons/vault/scripts/auth/api/http/keys/responses.go deleted file mode 100644 index ca99b9ce..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/keys/transport.go b/docker/addons/vault/scripts/auth/api/http/keys/transport.go deleted file mode 100644 index 9554df3b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/http/transport.go b/docker/addons/vault/scripts/auth/api/http/transport.go deleted file mode 100644 index 5e31ee55..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/logging.go b/docker/addons/vault/scripts/auth/api/logging.go deleted file mode 100644 index 30182bb4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/api/metrics.go b/docker/addons/vault/scripts/auth/api/metrics.go deleted file mode 100644 index 1e2befa8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/domains.go b/docker/addons/vault/scripts/auth/domains.go deleted file mode 100644 index e9efc580..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/domains_test.go b/docker/addons/vault/scripts/auth/domains_test.go deleted file mode 100644 index 82875bcc..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/events/doc.go b/docker/addons/vault/scripts/auth/events/doc.go deleted file mode 100644 index a115b5f9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/events/events.go b/docker/addons/vault/scripts/auth/events/events.go deleted file mode 100644 index e0fe609a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/events/streams.go b/docker/addons/vault/scripts/auth/events/streams.go deleted file mode 100644 index 702242cf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/jwt/token_test.go b/docker/addons/vault/scripts/auth/jwt/token_test.go deleted file mode 100644 index 32eb72e2..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/jwt/tokenizer.go b/docker/addons/vault/scripts/auth/jwt/tokenizer.go deleted file mode 100644 index 20102140..00000000 --- a/docker/addons/vault/scripts/auth/jwt/tokenizer.go +++ /dev/null @@ -1,145 +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 = errors.New("invalid token issuer value") - // 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{}, err - } - 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/docker/addons/vault/scripts/auth/keys.go b/docker/addons/vault/scripts/auth/keys.go deleted file mode 100644 index aa21ee48..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/keys_test.go b/docker/addons/vault/scripts/auth/keys_test.go deleted file mode 100644 index aaf5d3b8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/mocks/authz.go b/docker/addons/vault/scripts/auth/mocks/authz.go deleted file mode 100644 index 79c2e127..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/mocks/domains.go b/docker/addons/vault/scripts/auth/mocks/domains.go deleted file mode 100644 index c9bc09c9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/mocks/domains_client.go b/docker/addons/vault/scripts/auth/mocks/domains_client.go deleted file mode 100644 index 7950316f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/mocks/keys.go b/docker/addons/vault/scripts/auth/mocks/keys.go deleted file mode 100644 index 6f75c2e0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/mocks/service.go b/docker/addons/vault/scripts/auth/mocks/service.go deleted file mode 100644 index 80ec2714..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/mocks/token_client.go b/docker/addons/vault/scripts/auth/mocks/token_client.go deleted file mode 100644 index ae2e03e7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/postgres/doc.go b/docker/addons/vault/scripts/auth/postgres/doc.go deleted file mode 100644 index ac5c81ae..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/postgres/domains.go b/docker/addons/vault/scripts/auth/postgres/domains.go deleted file mode 100644 index 40ef9682..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/postgres/domains_test.go b/docker/addons/vault/scripts/auth/postgres/domains_test.go deleted file mode 100644 index 1e1997a9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/postgres/init.go b/docker/addons/vault/scripts/auth/postgres/init.go deleted file mode 100644 index ae69c3a0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/postgres/key.go b/docker/addons/vault/scripts/auth/postgres/key.go deleted file mode 100644 index 8a638b29..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/postgres/key_test.go b/docker/addons/vault/scripts/auth/postgres/key_test.go deleted file mode 100644 index e415524b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/postgres/setup_test.go b/docker/addons/vault/scripts/auth/postgres/setup_test.go deleted file mode 100644 index 89a6b213..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/service.go b/docker/addons/vault/scripts/auth/service.go deleted file mode 100644 index 2e6addbe..00000000 --- a/docker/addons/vault/scripts/auth/service.go +++ /dev/null @@ -1,906 +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.Authorize(ctx, policies.Policy{ - Subject: EncodeDomainUserID(id, res.User), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }); err != nil { - return Domain{ID: domain.ID, Name: domain.Name, Alias: domain.Alias}, nil - } - return domain, nil -} - -func (svc service) RetrieveDomainPermissions(ctx context.Context, token, id string) (policies.Permissions, error) { - res, err := svc.Identify(ctx, token) - if err != nil { - return []string{}, err - } - domainUserSubject := EncodeDomainUserID(id, res.User) - if err := svc.Authorize(ctx, policies.Policy{ - Subject: domainUserSubject, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }); err != nil { - return []string{}, err - } - - lp, err := svc.policysvc.ListPermissions(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: domainUserSubject, - Object: id, - ObjectType: policies.DomainType, - }, []string{policies.AdminPermission, policies.EditPermission, policies.ViewPermission, policies.MembershipPermission, policies.CreatePermission}) - if err != nil { - return []string{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - return lp, nil -} - -func (svc service) UpdateDomain(ctx context.Context, token, id string, d DomainReq) (Domain, error) { - key, err := svc.Identify(ctx, token) - if err != nil { - return Domain{}, err - } - if err := svc.Authorize(ctx, policies.Policy{ - Subject: EncodeDomainUserID(id, key.User), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.EditPermission, - }); err != nil { - return Domain{}, err - } - - dom, err := svc.domains.Update(ctx, id, key.User, d) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return dom, nil -} - -func (svc service) ChangeDomainStatus(ctx context.Context, token, id string, d DomainReq) (Domain, error) { - key, err := svc.Identify(ctx, token) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - if err := svc.Authorize(ctx, policies.Policy{ - Subject: EncodeDomainUserID(id, key.User), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }); err != nil { - return Domain{}, err - } - - dom, err := svc.domains.Update(ctx, id, key.User, d) - if err != nil { - return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err) - } - return dom, nil -} - -func (svc service) ListDomains(ctx context.Context, token string, p Page) (DomainsPage, error) { - key, err := svc.Identify(ctx, token) - if err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - p.SubjectID = key.User - if err := svc.Authorize(ctx, policies.Policy{ - Subject: key.User, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - ObjectType: policies.PlatformType, - Object: policies.MagistralaObject, - }); err == nil { - p.SubjectID = "" - } - dp, err := svc.domains.ListDomains(ctx, p) - if err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - if p.SubjectID == "" { - for i := range dp.Domains { - dp.Domains[i].Permission = policies.AdministratorRelation - } - } - return dp, nil -} - -func (svc service) AssignUsers(ctx context.Context, token, id string, userIds []string, relation string) error { - res, err := svc.Identify(ctx, token) - if err != nil { - return errors.Wrap(svcerr.ErrAuthentication, err) - } - - if err := svc.Authorize(ctx, policies.Policy{ - Subject: res.User, - 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: res.User, - 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) - } - - pr := policies.Policy{ - Subject: res.User, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: id, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - } - if err := svc.Authorize(ctx, pr); err != nil { - return err - } - - pr.Permission = policies.AdminPermission - if err := svc.Authorize(ctx, pr); err != nil { - pr.SubjectKind = policies.UsersKind - // User is not admin. - pr.Subject = userID - if err := svc.Authorize(ctx, pr); err == nil { - // Non admin attempts to remove admin. - return errors.Wrap(svcerr.ErrAuthorization, err) - } - } - - if err := svc.policysvc.DeletePolicyFilter(ctx, policies.Policy{ - Subject: EncodeDomainUserID(id, userID), - SubjectType: policies.UserType, - }); err != nil { - return errors.Wrap(errRemovePolicies, err) - } - - pc := Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - ObjectType: policies.DomainType, - ObjectID: id, - } - - if err := svc.domains.DeletePolicies(ctx, pc); err != nil { - return errors.Wrap(errRemovePolicies, err) - } - - return nil -} - -// IMPROVEMENT NOTE: Take decision: Only Patform admin or both Patform and domain admins can see others users domain. -func (svc service) ListUserDomains(ctx context.Context, token, userID string, p Page) (DomainsPage, error) { - res, err := svc.Identify(ctx, token) - if err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrAuthentication, err) - } - if err := svc.Authorize(ctx, policies.Policy{ - Subject: res.User, - SubjectType: policies.UserType, - Permission: policies.AdminPermission, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - }); err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrAuthorization, err) - } - if userID != "" && res.User != userID { - p.SubjectID = userID - } else { - p.SubjectID = res.User - } - dp, err := svc.domains.ListDomains(ctx, p) - if err != nil { - return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - return dp, nil -} - -func (svc service) addDomainPolicies(ctx context.Context, domainID, relation string, userIDs ...string) (err error) { - var prs []policies.Policy - var pcs []Policy - - for _, userID := range userIDs { - prs = append(prs, policies.Policy{ - Subject: EncodeDomainUserID(domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Relation: relation, - Object: domainID, - ObjectType: policies.DomainType, - }) - pcs = append(pcs, Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - Relation: relation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }) - } - if err := svc.policysvc.AddPolicies(ctx, prs); err != nil { - return errors.Wrap(errAddPolicies, err) - } - defer func() { - if err != nil { - if errDel := svc.policysvc.DeletePolicies(ctx, prs); errDel != nil { - err = errors.Wrap(err, errors.Wrap(errRollbackPolicy, errDel)) - } - } - }() - - if err = svc.domains.SavePolicies(ctx, pcs...); err != nil { - return errors.Wrap(errAddPolicies, err) - } - return nil -} - -func (svc service) createDomainPolicy(ctx context.Context, userID, domainID, relation string) (err error) { - prs := []policies.Policy{ - { - Subject: EncodeDomainUserID(domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Relation: relation, - Object: domainID, - ObjectType: policies.DomainType, - }, - { - Subject: policies.MagistralaObject, - SubjectType: policies.PlatformType, - Relation: policies.PlatformRelation, - Object: domainID, - ObjectType: policies.DomainType, - }, - } - if err := svc.policysvc.AddPolicies(ctx, prs); err != nil { - return err - } - defer func() { - if err != nil { - if errDel := svc.policysvc.DeletePolicies(ctx, prs); errDel != nil { - err = errors.Wrap(err, errors.Wrap(errRollbackPolicy, errDel)) - } - } - }() - err = svc.domains.SavePolicies(ctx, Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - Relation: relation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }) - if err != nil { - return errors.Wrap(errCreateDomainPolicy, err) - } - return err -} - -func (svc service) createDomainPolicyRollback(ctx context.Context, userID, domainID, relation string) error { - var err error - prs := []policies.Policy{ - { - Subject: EncodeDomainUserID(domainID, userID), - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Relation: relation, - Object: domainID, - ObjectType: policies.DomainType, - }, - { - Subject: policies.MagistralaObject, - SubjectType: policies.PlatformType, - Relation: policies.PlatformRelation, - Object: domainID, - ObjectType: policies.DomainType, - }, - } - if errPolicy := svc.policysvc.DeletePolicies(ctx, prs); errPolicy != nil { - err = errors.Wrap(errRemovePolicyEngine, errPolicy) - } - errPolicyCopy := svc.domains.DeletePolicies(ctx, Policy{ - SubjectType: policies.UserType, - SubjectID: userID, - Relation: relation, - ObjectType: policies.DomainType, - ObjectID: domainID, - }) - if errPolicyCopy != nil { - err = errors.Wrap(err, errors.Wrap(errRemoveLocalPolicy, errPolicyCopy)) - } - return err -} - -func EncodeDomainUserID(domainID, userID string) string { - if domainID == "" || userID == "" { - return "" - } - 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 -} diff --git a/docker/addons/vault/scripts/auth/service_test.go b/docker/addons/vault/scripts/auth/service_test.go deleted file mode 100644 index 77baefce..00000000 --- a/docker/addons/vault/scripts/auth/service_test.go +++ /dev/null @@ -1,2427 +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/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, - } -) - -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: email, - 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 - checkPolicyErr error - err error - }{ - { - desc: "retrieve domain successfully", - token: accessToken, - domainID: validID, - err: nil, - }, - { - desc: "retrieve domain with invalid token", - token: inValidToken, - domainID: validID, - err: svcerr.ErrAuthentication, - }, - { - desc: "retrieve domain with empty domain id", - token: accessToken, - domainID: "", - err: svcerr.ErrViewEntity, - domainRepoErr1: repoerr.ErrNotFound, - }, - { - desc: "retrieve non-existing domain", - token: accessToken, - domainID: inValid, - domainRepoErr: repoerr.ErrNotFound, - err: svcerr.ErrViewEntity, - domainRepoErr1: repoerr.ErrNotFound, - }, - { - desc: "retrieve domain with failed to retrieve by id", - token: accessToken, - domainID: validID, - domainRepoErr1: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := drepo.On("RetrieveByID", mock.Anything, groupName).Return(auth.Domain{}, tc.domainRepoErr) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall2 := drepo.On("RetrieveByID", mock.Anything, tc.domainID).Return(auth.Domain{}, tc.domainRepoErr1) - _, err := svc.RetrieveDomain(context.Background(), tc.token, tc.domainID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - }) - } -} - -func TestRetrieveDomainPermissions(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - retreivePermissionsErr error - retreiveByIDErr error - checkPolicyErr error - err error - }{ - { - desc: "retrieve domain permissions successfully", - token: accessToken, - domainID: validID, - err: nil, - }, - { - desc: "retrieve domain permissions with invalid token", - token: inValidToken, - domainID: validID, - err: svcerr.ErrAuthentication, - }, - { - desc: "retrieve domain permissions with empty domainID", - token: accessToken, - domainID: "", - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "retrieve domain permissions with failed to retrieve permissions", - token: accessToken, - domainID: validID, - retreivePermissionsErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "retrieve domain permissions with failed to retrieve by id", - token: accessToken, - domainID: validID, - retreiveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pService.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything).Return(policies.Permissions{}, tc.retreivePermissionsErr) - repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retreiveByIDErr) - repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - _, err := svc.RetrieveDomainPermissions(context.Background(), tc.token, tc.domainID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - }) - } -} - -func TestUpdateDomain(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - domReq auth.DomainReq - checkPolicyErr error - retrieveByIDErr error - updateErr error - err error - }{ - { - desc: "update domain successfully", - token: accessToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - err: nil, - }, - { - desc: "update domain with invalid token", - token: inValidToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "update domain with empty domainID", - token: accessToken, - domainID: "", - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "update domain with failed to retrieve by id", - token: accessToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - retrieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "update domain with failed to update", - token: accessToken, - domainID: validID, - domReq: auth.DomainReq{ - Name: &valid, - Alias: &valid, - }, - updateErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall1 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retrieveByIDErr) - repoCall2 := drepo.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.updateErr) - _, err := svc.UpdateDomain(context.Background(), tc.token, tc.domainID, tc.domReq) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - }) - } -} - -func TestChangeDomainStatus(t *testing.T) { - svc, accessToken := newService() - - disabledStatus := auth.DisabledStatus - - cases := []struct { - desc string - token string - domainID string - domainReq auth.DomainReq - retreieveByIDErr error - checkPolicyErr error - updateErr error - err error - }{ - { - desc: "change domain status successfully", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - err: nil, - }, - { - desc: "change domain status with invalid token", - token: inValidToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "change domain status with empty domainID", - token: accessToken, - domainID: "", - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - retreieveByIDErr: repoerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "change domain status with unauthorized domain ID", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "change domain status with repository error on update", - token: accessToken, - domainID: validID, - domainReq: auth.DomainReq{ - Status: &disabledStatus, - }, - updateErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, tc.retreieveByIDErr) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall2 := drepo.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(auth.Domain{}, tc.updateErr) - _, err := svc.ChangeDomainStatus(context.Background(), tc.token, tc.domainID, tc.domainReq) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - }) - } -} - -func TestListDomains(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - authReq auth.Page - listDomainsRes auth.DomainsPage - retreiveByIDErr error - checkPolicyErr error - listDomainErr error - err error - }{ - { - desc: "list domains successfully", - token: accessToken, - domainID: validID, - authReq: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - Status: auth.EnabledStatus, - }, - listDomainsRes: auth.DomainsPage{ - Domains: []auth.Domain{domain}, - }, - err: nil, - }, - { - desc: "list domains with invalid token", - token: inValidToken, - domainID: validID, - authReq: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - Status: auth.EnabledStatus, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "list domains with repository error on list domains", - token: accessToken, - domainID: validID, - authReq: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - Status: auth.EnabledStatus, - }, - listDomainErr: errors.ErrMalformedEntity, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall1 := drepo.On("ListDomains", mock.Anything, mock.Anything).Return(tc.listDomainsRes, tc.listDomainErr) - _, err := svc.ListDomains(context.Background(), tc.token, auth.Page{}) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - }) - } -} - -func TestAssignUsers(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - userIDs []string - relation string - checkPolicyReq3 policies.Policy - checkAdminPolicyReq policies.Policy - checkDomainPolicyReq policies.Policy - checkPolicyReq33 policies.Policy - checkpolicyErr error - checkPolicyErr1 error - checkPolicyErr2 error - addPoliciesErr error - savePoliciesErr error - deletePoliciesErr error - err error - }{ - { - desc: "assign users successfully", - token: accessToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq3: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - 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, - }, - checkPolicyReq33: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - err: nil, - }, - { - desc: "assign users with invalid token", - token: inValidToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq3: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Domain: groupName, - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: validID, - SubjectType: policies.UserType, - Object: policies.MagistralaObject, - ObjectType: policies.PlatformType, - Permission: policies.MembershipPermission, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "assign users with invalid domainID", - token: accessToken, - domainID: inValid, - relation: policies.ContributorRelation, - checkPolicyReq3: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.ViewPermission, - }, - checkPolicyReq33: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr1: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "assign users with invalid userIDs", - token: accessToken, - userIDs: []string{inValid}, - domainID: validID, - relation: policies.ContributorRelation, - checkPolicyReq3: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: email, - 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, - }, - checkPolicyReq33: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr2: svcerr.ErrMalformedEntity, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "assign users with failed to add policies to agent", - token: accessToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq3: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - 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, - }, - checkPolicyReq33: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - addPoliciesErr: svcerr.ErrAuthorization, - err: errAddPolicies, - }, - { - desc: "assign users with failed to save policies to domain", - token: accessToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq3: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - 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, - }, - checkPolicyReq33: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - savePoliciesErr: repoerr.ErrCreateEntity, - err: errAddPolicies, - }, - { - desc: "assign users with failed to save policies to domain and failed to delete", - token: accessToken, - domainID: validID, - userIDs: []string{validID}, - relation: policies.ContributorRelation, - checkPolicyReq3: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - 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, - }, - checkPolicyReq33: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - savePoliciesErr: repoerr.ErrCreateEntity, - deletePoliciesErr: svcerr.ErrDomainAuthorization, - err: errAddPolicies, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, nil) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq3).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.checkPolicyReq33).Return(tc.checkPolicyErr2) - repoCall5 := pService.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesErr) - repoCall6 := drepo.On("SavePolicies", mock.Anything, mock.Anything, mock.Anything).Return(tc.savePoliciesErr) - repoCall7 := pService.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) - err := svc.AssignUsers(context.Background(), tc.token, tc.domainID, tc.userIDs, tc.relation) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - repoCall3.Unset() - repoCall4.Unset() - repoCall5.Unset() - repoCall6.Unset() - repoCall7.Unset() - }) - } -} - -func TestUnassignUser(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - domainID string - userID string - checkPolicyReq policies.Policy - checkAdminPolicyReq policies.Policy - checkDomainPolicyReq policies.Policy - checkPolicyErr error - checkPolicyErr1 error - deletePolicyFilterErr error - deletePoliciesErr error - err error - }{ - { - desc: "unassign user successfully", - token: accessToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - err: nil, - }, - { - desc: "unassign users with invalid token", - token: inValidToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "unassign users with invalid domainID", - token: accessToken, - domainID: inValid, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: inValid, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkPolicyErr1: svcerr.ErrAuthorization, - err: svcerr.ErrDomainAuthorization, - }, - { - desc: "unassign users with failed to delete policies from agent", - token: accessToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - deletePolicyFilterErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "unassign users with failed to delete policies from domain", - token: accessToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - deletePoliciesErr: errors.ErrMalformedEntity, - deletePolicyFilterErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - { - desc: "unassign user with failed to delete pService from domain", - token: accessToken, - domainID: validID, - userID: validID, - checkPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.MembershipPermission, - }, - checkAdminPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.AdminPermission, - }, - checkDomainPolicyReq: policies.Policy{ - Subject: email, - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Object: validID, - ObjectType: policies.DomainType, - Permission: policies.SharePermission, - }, - deletePoliciesErr: errors.ErrMalformedEntity, - err: errors.ErrMalformedEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(auth.Domain{}, nil) - repoCall1 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkPolicyReq).Return(tc.checkPolicyErr) - repoCall2 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkAdminPolicyReq).Return(tc.checkPolicyErr1) - repoCall3 := pEvaluator.On("CheckPolicy", mock.Anything, tc.checkDomainPolicyReq).Return(tc.checkPolicyErr1) - repoCall4 := pService.On("DeletePolicyFilter", mock.Anything, mock.Anything).Return(tc.deletePolicyFilterErr) - repoCall5 := drepo.On("DeletePolicies", mock.Anything, mock.Anything, mock.Anything).Return(tc.deletePoliciesErr) - err := svc.UnassignUser(context.Background(), tc.token, tc.domainID, tc.userID) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - repoCall3.Unset() - repoCall4.Unset() - repoCall5.Unset() - }) - } -} - -func TestListUsersDomains(t *testing.T) { - svc, accessToken := newService() - - cases := []struct { - desc string - token string - userID string - page auth.Page - retreiveByIDErr error - checkPolicyErr error - listDomainErr error - err error - }{ - { - desc: "list users domains successfully", - token: accessToken, - userID: validID, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - err: nil, - }, - { - desc: "list users domains successfully was admin", - token: accessToken, - userID: email, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - err: nil, - }, - { - desc: "list users domains with invalid token", - token: inValidToken, - userID: validID, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - err: svcerr.ErrAuthentication, - }, - { - desc: "list users domains with invalid domainID", - token: accessToken, - userID: inValid, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - checkPolicyErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, - }, - { - desc: "list users domains with repository error on list domains", - token: accessToken, - userID: validID, - page: auth.Page{ - Offset: 0, - Limit: 10, - Permission: policies.AdminPermission, - }, - listDomainErr: repoerr.ErrNotFound, - err: svcerr.ErrViewEntity, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := pEvaluator.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) - repoCall1 := drepo.On("ListDomains", mock.Anything, mock.Anything).Return(auth.DomainsPage{}, tc.listDomainErr) - _, err := svc.ListUserDomains(context.Background(), tc.token, tc.userID, tc.page) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - repoCall1.Unset() - }) - } -} - -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/docker/addons/vault/scripts/auth/tokenizer.go b/docker/addons/vault/scripts/auth/tokenizer.go deleted file mode 100644 index 1aaed7df..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/tracing/doc.go b/docker/addons/vault/scripts/auth/tracing/doc.go deleted file mode 100644 index 5aa1b44b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth/tracing/tracing.go b/docker/addons/vault/scripts/auth/tracing/tracing.go deleted file mode 100644 index 97b5f179..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/auth_grpc.pb.go b/docker/addons/vault/scripts/auth_grpc.pb.go deleted file mode 100644 index a9bb42dd..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/bootstrap/README.md b/docker/addons/vault/scripts/bootstrap/README.md deleted file mode 100644 index 9fb05388..00000000 --- a/docker/addons/vault/scripts/bootstrap/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# BOOTSTRAP SERVICE - -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 - -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. - -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. - -## Thing 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: - -1. corresponding Magistrala Thing ID -2. corresponding Magistrala Thing key -3. list of the Magistrala channels the Thing 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. - -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: - -| State | What it means | -| -------- | --------------------------------------------- | -| Inactive | Thing is created, but isn't enabled | -| Active | Thing is able to communicate using Magistrala | - -Switching between states `Active` and `Inactive` enables and disables Thing, 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. - -## 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 | <nats://localhost:4222> | -| MG_AUTH_GRPC_URL | Auth service Auth gRPC URL | <localhost:8181> | -| 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 | <http://localhost:9000> | -| MG_JAEGER_URL | Jaeger server URL | <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_BOOTSTRAP_INSTANCE_ID | Bootstrap service instance ID | "" | - -## Deployment - -The service itself is distributed as Docker container. Check the [`bootstrap`](https://github.com/absmach/magistrala/blob/main/docker/addons/bootstrap/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 servic e -make bootstrap - -# copy binary to bin -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="" \ -$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 `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 [API documentation](https://docs.api.magistrala.abstractmachines.fr/?urls.primaryName=bootstrap.yml). diff --git a/docker/addons/vault/scripts/bootstrap/api/doc.go b/docker/addons/vault/scripts/bootstrap/api/doc.go deleted file mode 100644 index 1e8268ee..00000000 --- a/docker/addons/vault/scripts/bootstrap/api/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package api contains implementation of bootstrap service HTTP API. -package api diff --git a/docker/addons/vault/scripts/bootstrap/api/endpoint.go b/docker/addons/vault/scripts/bootstrap/api/endpoint.go deleted file mode 100644 index 1bf7cf97..00000000 --- a/docker/addons/vault/scripts/bootstrap/api/endpoint.go +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -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" - "github.com/go-kit/kit/endpoint" -) - -func addEndpoint(svc bootstrap.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(addReq) - 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 - } - - channels := []bootstrap.Channel{} - for _, c := range req.Channels { - channels = append(channels, bootstrap.Channel{ID: c}) - } - - config := bootstrap.Config{ - ThingID: req.ThingID, - ExternalID: req.ExternalID, - ExternalKey: req.ExternalKey, - Channels: channels, - Name: req.Name, - ClientCert: req.ClientCert, - ClientKey: req.ClientKey, - CACert: req.CACert, - Content: req.Content, - } - - saved, err := svc.Add(ctx, session, req.token, config) - if err != nil { - return nil, err - } - - res := configRes{ - id: saved.ThingID, - created: true, - } - - return res, nil - } -} - -func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateCertReq) - 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 - } - - cfg, err := svc.UpdateCert(ctx, session, req.thingID, req.ClientCert, req.ClientKey, req.CACert) - if err != nil { - return nil, err - } - - res := updateConfigRes{ - ThingID: cfg.ThingID, - ClientCert: cfg.ClientCert, - CACert: cfg.CACert, - ClientKey: cfg.ClientKey, - } - - return res, nil - } -} - -func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(entityReq) - 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 - } - - config, err := svc.View(ctx, session, req.id) - if err != nil { - return nil, err - } - - var channels []channelRes - for _, ch := range config.Channels { - channels = append(channels, channelRes{ - ID: ch.ID, - Name: ch.Name, - Metadata: ch.Metadata, - }) - } - - 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, - } - - return res, nil - } -} - -func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateReq) - 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 - } - - config := bootstrap.Config{ - ThingID: req.id, - Name: req.Name, - Content: req.Content, - } - - if err := svc.Update(ctx, session, config); err != nil { - return nil, err - } - - res := configRes{ - id: config.ThingID, - created: false, - } - - return res, nil - } -} - -func updateConnEndpoint(svc bootstrap.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(updateConnReq) - 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.UpdateConnections(ctx, session, req.token, req.id, req.Channels); err != nil { - return nil, err - } - - res := configRes{ - id: req.id, - created: false, - } - - return res, nil - } -} - -func listEndpoint(svc bootstrap.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) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - page, err := svc.List(ctx, session, req.filter, req.offset, req.limit) - if err != nil { - return nil, err - } - res := listRes{ - Total: page.Total, - Offset: page.Offset, - Limit: page.Limit, - Configs: []viewRes{}, - } - - for _, cfg := range page.Configs { - var channels []channelRes - for _, ch := range cfg.Channels { - channels = append(channels, channelRes{ - ID: ch.ID, - Name: ch.Name, - Metadata: ch.Metadata, - }) - } - - 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, - } - res.Configs = append(res.Configs, view) - } - - return res, nil - } -} - -func removeEndpoint(svc bootstrap.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(entityReq) - if err := req.validate(); err != nil { - return removeRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - - session, ok := ctx.Value(api.SessionKey).(authn.Session) - if !ok { - return nil, svcerr.ErrAuthorization - } - - if err := svc.Remove(ctx, session, req.id); err != nil { - return nil, err - } - - return removeRes{}, nil - } -} - -func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader, secure bool) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(bootstrapReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - cfg, err := svc.Bootstrap(ctx, req.key, req.id, secure) - if err != nil { - return nil, err - } - - return reader.ReadConfig(cfg, secure) - } -} - -func stateEndpoint(svc bootstrap.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(changeStateReq) - 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.ChangeState(ctx, session, req.token, req.id, req.State); err != nil { - return nil, err - } - - return stateRes{}, nil - } -} diff --git a/docker/addons/vault/scripts/bootstrap/api/endpoint_test.go b/docker/addons/vault/scripts/bootstrap/api/endpoint_test.go deleted file mode 100644 index 02a0d746..00000000 --- a/docker/addons/vault/scripts/bootstrap/api/endpoint_test.go +++ /dev/null @@ -1,1418 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "context" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strconv" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const ( - validToken = "validToken" - domainID = "b4d7d79e-fd99-4c2b-ac09-524e43df6888" - invalidToken = "invalid" - email = "test@example.com" - unknown = "unknown" - channelsNum = 3 - contentType = "application/json" - wrongID = "wrong_id" - - addName = "name" - addContent = "config" - instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" -) - -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"` - }{ - ThingID: addThingID, - ThingKey: addThingKey, - 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{"1"}, - Content: "config update", - State: 1, - ClientCert: "newcert", - ClientKey: "newkey", - CACert: "newca", - } - - missingIDRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrMissingID.Error(), Msg: apiutil.ErrValidation.Error()}) - missingKeyRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrBearerKey.Error(), Msg: apiutil.ErrValidation.Error()}) - bsErrorRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrBootstrap.Error()}) - extKeyRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrExternalKey.Error()}) - extSecKeyRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrExternalKeySecure.Error()}) -) - -type testRequest struct { - client *http.Client - method string - url string - contentType string - token string - key string - body io.Reader -} - -func newConfig() bootstrap.Config { - return bootstrap.Config{ - ThingID: addThingID, - ThingKey: addThingKey, - ExternalID: addExternalID, - ExternalKey: addExternalKey, - Channels: []bootstrap.Channel{ - { - ID: "1", - Metadata: metadata, - }, - }, - Name: addName, - Content: addContent, - ClientCert: "newcert", - ClientKey: "newkey", - CACert: "newca", - } -} - -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.key != "" { - req.Header.Set("Authorization", apiutil.ThingPrefix+tr.key) - } - - if tr.contentType != "" { - req.Header.Set("Content-Type", tr.contentType) - } - - return tr.client.Do(req) -} - -func enc(in []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 -} - -func dec(in []byte) ([]byte, error) { - block, err := aes.NewCipher(encKey) - if err != nil { - return nil, err - } - if len(in) < aes.BlockSize { - return nil, errors.ErrMalformedEntity - } - iv := in[:aes.BlockSize] - in = in[aes.BlockSize:] - stream := cipher.NewCFBDecrypter(block, iv) - stream.XORKeyStream(in, in) - return in, nil -} - -func newBootstrapServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) { - logger := mglog.NewMock() - svc := new(mocks.Service) - authn := new(authnmocks.Authentication) - mux := bsapi.MakeHandler(svc, authn, bootstrap.NewConfigReader(encKey), logger, instanceID) - return httptest.NewServer(mux), svc, authn -} - -func toJSON(data interface{}) string { - jsonData, err := json.Marshal(data) - if err != nil { - return "" - } - return string(jsonData) -} - -func TestAdd(t *testing.T) { - bs, svc, auth := newBootstrapServer() - defer bs.Close() - c := newConfig() - - data := toJSON(addReq) - - neID := addReq - neID.ThingID = testsutil.GenerateUUID(t) - neData := toJSON(neID) - - invalidChannels := addReq - invalidChannels.Channels = []string{wrongID} - wrongData := toJSON(invalidChannels) - - cases := []struct { - desc string - req string - domainID string - token string - session mgauthn.Session - contentType string - status int - location string - authenticateErr error - err error - }{ - { - desc: "add a config with invalid token", - req: data, - domainID: domainID, - token: invalidToken, - contentType: contentType, - status: http.StatusUnauthorized, - location: "", - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "add a valid config", - req: data, - domainID: domainID, - token: validToken, - contentType: contentType, - status: http.StatusCreated, - location: "/things/configs/" + c.ThingID, - err: nil, - }, - { - desc: "add a config with wrong content type", - req: data, - domainID: domainID, - token: validToken, - contentType: "", - status: http.StatusUnsupportedMediaType, - location: "", - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "add an existing config", - req: data, - domainID: domainID, - token: validToken, - contentType: contentType, - status: http.StatusConflict, - location: "", - err: svcerr.ErrConflict, - }, - { - desc: "add a config with non-existent ID", - req: neData, - domainID: domainID, - token: validToken, - contentType: contentType, - status: http.StatusConflict, - location: "", - err: svcerr.ErrConflict, - }, - { - desc: "add a config with invalid channels", - req: wrongData, - domainID: domainID, - token: validToken, - contentType: contentType, - status: http.StatusConflict, - location: "", - err: svcerr.ErrConflict, - }, - { - desc: "add a config with wrong JSON", - req: "{\"external_id\": 5}", - domainID: domainID, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - { - desc: "add a config with invalid request format", - req: "}", - domainID: domainID, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - location: "", - err: svcerr.ErrMalformedEntity, - }, - { - desc: "add a config with empty JSON", - req: "{}", - domainID: domainID, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - location: "", - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "add a config with an empty request", - req: "", - domainID: domainID, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - location: "", - err: svcerr.ErrMalformedEntity, - }, - } - - 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("Add", mock.Anything, tc.session, tc.token, mock.Anything).Return(c, tc.err) - req := testRequest{ - client: bs.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/%s/things/configs", bs.URL, tc.domainID), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.req), - } - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - location := res.Header.Get("Location") - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - assert.Equal(t, tc.location, location, fmt.Sprintf("%s: expected location '%s' got '%s'", tc.desc, tc.location, location)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestView(t *testing.T) { - bs, svc, auth := newBootstrapServer() - defer bs.Close() - c := newConfig() - - var channels []channel - for _, ch := range c.Channels { - channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata}) - } - - 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, - } - - cases := []struct { - desc string - token string - session mgauthn.Session - id string - status int - res config - authenticateErr error - err error - }{ - { - desc: "view a config with invalid token", - token: invalidToken, - id: c.ThingID, - status: http.StatusUnauthorized, - res: config{}, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "view a config", - token: validToken, - id: c.ThingID, - status: http.StatusOK, - res: data, - err: nil, - }, - { - desc: "view a non-existing config", - token: validToken, - id: wrongID, - status: http.StatusNotFound, - res: config{}, - err: svcerr.ErrNotFound, - }, - { - desc: "view a config with an empty token", - token: "", - id: c.ThingID, - status: http.StatusUnauthorized, - res: config{}, - err: apiutil.ErrBearerToken, - }, - { - desc: "view config without authorization", - token: validToken, - id: c.ThingID, - status: http.StatusForbidden, - res: config{}, - err: svcerr.ErrAuthorization, - }, - } - - 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("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), - token: tc.token, - } - 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)) - var view config - if err := json.NewDecoder(res.Body).Decode(&view); err != io.EOF { - assert.Nil(t, err, fmt.Sprintf("Decoding expected to succeed %s: %s", tc.desc, err)) - } - - assert.ElementsMatch(t, tc.res.Channels, view.Channels, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res.Channels, view.Channels)) - // Empty channels to prevent order mismatch. - tc.res.Channels = []channel{} - view.Channels = []channel{} - assert.Equal(t, tc.res, view, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, view)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestUpdate(t *testing.T) { - bs, svc, auth := newBootstrapServer() - defer bs.Close() - c := newConfig() - - data := toJSON(updateReq) - - cases := []struct { - desc string - req string - id string - token string - session mgauthn.Session - contentType string - status int - authenticateErr error - err error - }{ - { - desc: "update with invalid token", - req: data, - id: c.ThingID, - token: invalidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update with an empty token", - req: data, - id: c.ThingID, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "update a valid config", - req: data, - id: c.ThingID, - token: validToken, - contentType: contentType, - status: http.StatusOK, - err: nil, - }, - { - desc: "update a config with wrong content type", - req: data, - id: c.ThingID, - token: validToken, - contentType: "", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "update a non-existing config", - req: data, - id: wrongID, - token: validToken, - contentType: contentType, - status: http.StatusNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "update a config with invalid request format", - req: "}", - id: c.ThingID, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - { - desc: "update a config with an empty request", - id: c.ThingID, - req: "", - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - } - - 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("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), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.req), - } - 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 TestUpdateCert(t *testing.T) { - bs, svc, auth := newBootstrapServer() - defer bs.Close() - c := newConfig() - - data := toJSON(updateReq) - - cases := []struct { - desc string - req string - id string - token string - session mgauthn.Session - contentType string - status int - authenticateErr error - err error - }{ - { - desc: "update with invalid token", - req: data, - id: c.ThingID, - token: invalidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update with an empty token", - req: data, - id: c.ThingID, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "update a valid config", - req: data, - id: c.ThingID, - token: validToken, - contentType: contentType, - status: http.StatusOK, - err: nil, - }, - { - desc: "update a config with wrong content type", - req: data, - id: c.ThingID, - token: validToken, - contentType: "", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "update a non-existing config", - req: data, - id: wrongID, - token: validToken, - contentType: contentType, - status: http.StatusNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "update a config with invalid request format", - req: "}", - id: c.ThingKey, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - { - desc: "update a config with an empty request", - id: c.ThingID, - req: "", - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - } - - 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("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), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.req), - } - 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 TestUpdateConnections(t *testing.T) { - bs, svc, auth := newBootstrapServer() - defer bs.Close() - c := newConfig() - data := toJSON(updateReq) - - invalidChannels := updateReq - invalidChannels.Channels = []string{wrongID} - - wrongData := toJSON(invalidChannels) - - cases := []struct { - desc string - req string - id string - token string - session mgauthn.Session - contentType string - status int - authenticateErr error - err error - }{ - { - desc: "update connections with invalid token", - req: data, - id: c.ThingID, - token: invalidToken, - contentType: contentType, - status: http.StatusUnauthorized, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "update connections with an empty token", - req: data, - id: c.ThingID, - token: "", - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "update connections valid config", - req: data, - id: c.ThingID, - token: validToken, - contentType: contentType, - status: http.StatusOK, - err: nil, - }, - { - desc: "update connections with wrong content type", - req: data, - id: c.ThingID, - token: validToken, - contentType: "", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "update connections for a non-existing config", - req: data, - id: wrongID, - token: validToken, - contentType: contentType, - status: http.StatusNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "update connections with invalid channels", - req: wrongData, - id: c.ThingID, - token: validToken, - contentType: contentType, - status: http.StatusNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "update a config with invalid request format", - req: "}", - id: c.ThingID, - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - { - desc: "update a config with an empty request", - id: c.ThingID, - req: "", - token: validToken, - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - } - - 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) - 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), - contentType: tc.contentType, - token: tc.token, - body: strings.NewReader(tc.req), - } - 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() - authCall.Unset() - }) - } -} - -func TestList(t *testing.T) { - configNum := 101 - changedStateNum := 20 - var active, inactive []config - list := make([]config, configNum) - - bs, svc, auth := newBootstrapServer() - defer bs.Close() - path := fmt.Sprintf("%s/%s/%s", bs.URL, domainID, "things/configs") - - c := newConfig() - - for i := 0; i < configNum; i++ { - c.ExternalID = strconv.Itoa(i) - c.ThingKey = c.ExternalID - c.Name = fmt.Sprintf("%s-%d", addName, i) - c.ExternalKey = fmt.Sprintf("%s%s", addExternalKey, strconv.Itoa(i)) - - var channels []channel - for _, ch := range c.Channels { - 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, - } - list[i] = s - } - // Change state of first 20 elements for filtering tests. - for i := 0; i < changedStateNum; i++ { - state := bootstrap.Active - if i%2 == 0 { - 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) - assert.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err)) - - svcCall.Unset() - - list[i].State = state - if state == bootstrap.Inactive { - inactive = append(inactive, list[i]) - continue - } - active = append(active, list[i]) - } - - cases := []struct { - desc string - token string - session mgauthn.Session - url string - status int - res configPage - authenticateErr error - err error - }{ - { - desc: "view list with invalid token", - token: invalidToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 0, 10), - status: http.StatusUnauthorized, - res: configPage{}, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "view list with an empty token", - token: "", - url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 0, 10), - status: http.StatusUnauthorized, - res: configPage{}, - err: apiutil.ErrBearerToken, - }, - { - desc: "view list", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 0, 1), - status: http.StatusOK, - res: configPage{ - Total: uint64(len(list)), - Offset: 0, - Limit: 1, - Configs: list[0:1], - }, - err: nil, - }, - { - desc: "view list searching by name", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", path, 0, 100, "95"), - status: http.StatusOK, - res: configPage{ - Total: 1, - Offset: 0, - Limit: 100, - Configs: list[95:96], - }, - err: nil, - }, - { - desc: "view last page", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 100, 10), - status: http.StatusOK, - res: configPage{ - Total: uint64(len(list)), - Offset: 100, - Limit: 10, - Configs: list[100:], - }, - err: nil, - }, - { - desc: "view with limit greater than allowed", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 0, 1000), - status: http.StatusBadRequest, - res: configPage{}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "view list with no specified limit and offset", - token: validToken, - url: path, - status: http.StatusOK, - res: configPage{ - Total: uint64(len(list)), - Offset: 0, - Limit: 10, - Configs: list[0:10], - }, - err: nil, - }, - { - desc: "view list with no specified limit", - token: validToken, - url: fmt.Sprintf("%s?offset=%d", path, 10), - status: http.StatusOK, - res: configPage{ - Total: uint64(len(list)), - Offset: 10, - Limit: 10, - Configs: list[10:20], - }, - err: nil, - }, - { - desc: "view list with no specified offset", - token: validToken, - url: fmt.Sprintf("%s?limit=%d", path, 10), - status: http.StatusOK, - res: configPage{ - Total: uint64(len(list)), - Offset: 0, - Limit: 10, - Configs: list[0:10], - }, - err: nil, - }, - { - desc: "view list with limit < 0", - token: validToken, - url: fmt.Sprintf("%s?limit=%d", path, -10), - status: http.StatusBadRequest, - res: configPage{}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "view list with offset < 0", - token: validToken, - url: fmt.Sprintf("%s?offset=%d", path, -10), - status: http.StatusBadRequest, - res: configPage{}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "view list with invalid query parameters", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d&key=%%", path, 10, 10, bootstrap.Inactive), - status: http.StatusBadRequest, - res: configPage{}, - err: apiutil.ErrInvalidQueryParams, - }, - { - desc: "view first 10 active", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d", path, 0, 20, bootstrap.Active), - status: http.StatusOK, - res: configPage{ - Total: uint64(len(active)), - Offset: 0, - Limit: 20, - Configs: active, - }, - err: nil, - }, - { - desc: "view first 10 inactive", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d", path, 0, 20, bootstrap.Inactive), - status: http.StatusOK, - res: configPage{ - Total: uint64(len(list) - len(inactive)), - Offset: 0, - Limit: 20, - Configs: inactive, - }, - err: nil, - }, - { - desc: "view first 5 active", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d", path, 0, 10, bootstrap.Active), - status: http.StatusOK, - res: configPage{ - Total: uint64(len(active)), - Offset: 0, - Limit: 10, - Configs: active[:5], - }, - err: nil, - }, - { - desc: "view last 5 inactive", - token: validToken, - url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d", path, 10, 10, bootstrap.Inactive), - status: http.StatusOK, - res: configPage{ - Total: uint64(len(list) - len(active)), - Offset: 10, - Limit: 10, - Configs: inactive[5:], - }, - 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.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) - req := testRequest{ - client: bs.Client(), - method: http.MethodGet, - url: tc.url, - token: tc.token, - } - - 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)) - var body configPage - - err = json.NewDecoder(res.Body).Decode(&body) - 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.res.Total, body.Total, fmt.Sprintf("%s: expected response total '%d' got '%d'", tc.desc, tc.res.Total, body.Total)) - svcCall.Unset() - authCall.Unset() - }) - } -} - -func TestRemove(t *testing.T) { - bs, svc, auth := newBootstrapServer() - defer bs.Close() - c := newConfig() - - cases := []struct { - desc string - id string - token string - session mgauthn.Session - status int - authenticateErr error - err error - }{ - { - desc: "remove with invalid token", - id: c.ThingID, - token: invalidToken, - status: http.StatusUnauthorized, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "remove with an empty token", - id: c.ThingID, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "remove non-existing config", - id: "non-existing", - token: validToken, - status: http.StatusNoContent, - err: nil, - }, - { - desc: "remove config", - id: c.ThingID, - token: validToken, - status: http.StatusNoContent, - err: nil, - }, - { - desc: "remove removed config", - id: wrongID, - token: validToken, - status: http.StatusNoContent, - 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.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), - token: tc.token, - } - 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 TestBootstrap(t *testing.T) { - bs, svc, _ := newBootstrapServer() - defer bs.Close() - c := newConfig() - - encExternKey, err := enc([]byte(c.ExternalKey)) - assert.Nil(t, err, fmt.Sprintf("Encrypting config expected to succeed: %s.\n", err)) - - var channels []channel - for _, ch := range c.Channels { - channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata}) - } - - 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"` - }{ - ThingID: c.ThingID, - ThingKey: c.ThingKey, - Channels: channels, - Content: c.Content, - ClientCert: c.ClientCert, - ClientKey: c.ClientKey, - CACert: c.CACert, - } - - data := toJSON(s) - - cases := []struct { - desc string - externalID string - externalKey string - status int - res string - secure bool - err error - }{ - { - desc: "bootstrap a Thing with unknown ID", - externalID: unknown, - externalKey: c.ExternalKey, - status: http.StatusNotFound, - res: bsErrorRes, - secure: false, - err: bootstrap.ErrBootstrap, - }, - { - desc: "bootstrap a Thing with an empty ID", - externalID: "", - externalKey: c.ExternalKey, - status: http.StatusBadRequest, - res: missingIDRes, - secure: false, - err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrMalformedEntity), - }, - { - desc: "bootstrap a Thing with unknown key", - externalID: c.ExternalID, - externalKey: unknown, - status: http.StatusForbidden, - res: extKeyRes, - secure: false, - err: errors.Wrap(bootstrap.ErrExternalKey, errors.New("")), - }, - { - desc: "bootstrap a Thing with an empty key", - externalID: c.ExternalID, - externalKey: "", - status: http.StatusBadRequest, - res: missingKeyRes, - secure: false, - err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrAuthentication), - }, - { - desc: "bootstrap known Thing", - externalID: c.ExternalID, - externalKey: c.ExternalKey, - status: http.StatusOK, - res: data, - secure: false, - err: nil, - }, - { - desc: "bootstrap secure", - externalID: fmt.Sprintf("secure/%s", c.ExternalID), - externalKey: hex.EncodeToString(encExternKey), - status: http.StatusOK, - res: data, - secure: true, - err: nil, - }, - { - desc: "bootstrap secure with unencrypted key", - externalID: fmt.Sprintf("secure/%s", c.ExternalID), - externalKey: c.ExternalKey, - status: http.StatusForbidden, - res: extSecKeyRes, - secure: true, - err: bootstrap.ErrExternalKeySecure, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - svcCall := svc.On("Bootstrap", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(c, tc.err) - req := testRequest{ - client: bs.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/things/bootstrap/%s", bs.URL, tc.externalID), - key: tc.externalKey, - } - 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)) - body, err := io.ReadAll(res.Body) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - if tc.secure && tc.status == http.StatusOK { - body, err = dec(body) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding body: %s", tc.desc, err)) - } - data := strings.Trim(string(body), "\n") - assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, data)) - svcCall.Unset() - }) - } -} - -func TestChangeState(t *testing.T) { - bs, svc, auth := newBootstrapServer() - defer bs.Close() - c := newConfig() - - inactive := fmt.Sprintf("{\"state\": %d}", bootstrap.Inactive) - active := fmt.Sprintf("{\"state\": %d}", bootstrap.Active) - - cases := []struct { - desc string - id string - token string - session mgauthn.Session - state string - contentType string - status int - authenticateErr error - err error - }{ - { - desc: "change state with invalid token", - id: c.ThingID, - token: invalidToken, - state: active, - contentType: contentType, - status: http.StatusUnauthorized, - authenticateErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "change state with an empty token", - id: c.ThingID, - token: "", - state: active, - contentType: contentType, - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, - }, - { - desc: "change state with invalid content type", - id: c.ThingID, - token: validToken, - state: active, - contentType: "", - status: http.StatusUnsupportedMediaType, - err: apiutil.ErrUnsupportedContentType, - }, - { - desc: "change state to active", - id: c.ThingID, - token: validToken, - state: active, - contentType: contentType, - status: http.StatusOK, - err: nil, - }, - { - desc: "change state to inactive", - id: c.ThingID, - token: validToken, - state: inactive, - contentType: contentType, - status: http.StatusOK, - err: nil, - }, - { - desc: "change state of non-existing config", - id: wrongID, - token: validToken, - state: active, - contentType: contentType, - status: http.StatusNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "change state to invalid value", - id: c.ThingID, - token: validToken, - state: fmt.Sprintf("{\"state\": %d}", -3), - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - { - desc: "change state with invalid data", - id: c.ThingID, - token: validToken, - state: "", - contentType: contentType, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - } - - 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("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), - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(tc.state), - } - 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 channel struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Metadata interface{} `json:"metadata,omitempty"` -} - -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"` -} - -type configPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Configs []config `json:"configs"` -} diff --git a/docker/addons/vault/scripts/bootstrap/api/requests.go b/docker/addons/vault/scripts/bootstrap/api/requests.go deleted file mode 100644 index f1279b44..00000000 --- a/docker/addons/vault/scripts/bootstrap/api/requests.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/pkg/apiutil" -) - -const maxLimitSize = 100 - -type addReq struct { - token string - ThingID string `json:"thing_id"` - ExternalID string `json:"external_id"` - ExternalKey string `json:"external_key"` - Channels []string `json:"channels"` - Name string `json:"name"` - Content string `json:"content"` - ClientCert string `json:"client_cert"` - ClientKey string `json:"client_key"` - CACert string `json:"ca_cert"` -} - -func (req addReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.ExternalID == "" { - return apiutil.ErrMissingID - } - - if req.ExternalKey == "" { - return apiutil.ErrBearerKey - } - - if len(req.Channels) == 0 { - return apiutil.ErrEmptyList - } - - for _, channel := range req.Channels { - if channel == "" { - return apiutil.ErrMissingID - } - } - - return nil -} - -type entityReq struct { - id string -} - -func (req entityReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateReq struct { - id string - Name string `json:"name"` - Content string `json:"content"` -} - -func (req updateReq) validate() error { - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateCertReq struct { - thingID string - ClientCert string `json:"client_cert"` - ClientKey string `json:"client_key"` - CACert string `json:"ca_cert"` -} - -func (req updateCertReq) validate() error { - if req.thingID == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type updateConnReq struct { - token string - id string - Channels []string `json:"channels"` -} - -func (req updateConnReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type listReq struct { - filter bootstrap.Filter - offset uint64 - limit uint64 -} - -func (req listReq) validate() error { - if req.limit > maxLimitSize { - return apiutil.ErrLimitSize - } - - return nil -} - -type bootstrapReq struct { - key string - id string -} - -func (req bootstrapReq) validate() error { - if req.key == "" { - return apiutil.ErrBearerKey - } - - if req.id == "" { - return apiutil.ErrMissingID - } - - return nil -} - -type changeStateReq struct { - token string - id string - State bootstrap.State `json:"state"` -} - -func (req changeStateReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.id == "" { - return apiutil.ErrMissingID - } - - if req.State != bootstrap.Inactive && - req.State != bootstrap.Active { - return apiutil.ErrBootstrapState - } - - return nil -} diff --git a/docker/addons/vault/scripts/bootstrap/api/requests_test.go b/docker/addons/vault/scripts/bootstrap/api/requests_test.go deleted file mode 100644 index 73ac1df9..00000000 --- a/docker/addons/vault/scripts/bootstrap/api/requests_test.go +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "testing" - - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/stretchr/testify/assert" -) - -var ( - channel1 = testsutil.GenerateUUID(&testing.T{}) - channel2 = testsutil.GenerateUUID(&testing.T{}) -) - -func TestAddReqValidation(t *testing.T) { - cases := []struct { - desc string - token string - externalID string - externalKey string - channels []string - err error - }{ - { - desc: "valid request", - token: "token", - externalID: "external-id", - externalKey: "external-key", - channels: []string{channel1, channel2}, - err: nil, - }, - { - desc: "empty token", - token: "", - externalID: "external-id", - externalKey: "external-key", - channels: []string{channel1, channel2}, - err: apiutil.ErrBearerToken, - }, - { - desc: "empty external ID", - token: "token", - externalID: "", - externalKey: "external-key", - channels: []string{channel1, channel2}, - err: apiutil.ErrMissingID, - }, - { - desc: "empty external key", - token: "token", - externalID: "external-id", - externalKey: "", - channels: []string{channel1, channel2}, - err: apiutil.ErrBearerKey, - }, - { - desc: "empty external key and external ID", - token: "token", - externalID: "", - externalKey: "", - channels: []string{channel1, channel2}, - err: apiutil.ErrMissingID, - }, - { - desc: "empty channels", - token: "token", - externalID: "external-id", - externalKey: "external-key", - channels: []string{}, - err: apiutil.ErrEmptyList, - }, - { - desc: "empty channel value", - token: "token", - externalID: "external-id", - externalKey: "external-key", - channels: []string{channel1, ""}, - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - req := addReq{ - token: tc.token, - ExternalID: tc.externalID, - ExternalKey: tc.externalKey, - Channels: tc.channels, - } - - err := req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestEntityReqValidation(t *testing.T) { - cases := []struct { - desc string - id string - err error - }{ - { - desc: "empty id", - id: "", - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - req := entityReq{ - id: tc.id, - } - - err := req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestUpdateReqValidation(t *testing.T) { - cases := []struct { - desc string - id string - err error - }{ - { - desc: "valid request", - id: "id", - err: nil, - }, - { - desc: "empty id", - id: "", - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - req := updateReq{ - id: tc.id, - } - - err := req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestUpdateCertReqValidation(t *testing.T) { - cases := []struct { - desc string - thingID string - err error - }{ - { - desc: "empty thing id", - thingID: "", - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - req := updateCertReq{ - thingID: tc.thingID, - } - - err := req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestUpdateConnReqValidation(t *testing.T) { - cases := []struct { - desc string - id string - token string - - err error - }{ - { - desc: "empty token", - token: "", - id: "id", - err: apiutil.ErrBearerToken, - }, - { - desc: "empty id", - token: "token", - id: "", - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - req := updateConnReq{ - token: tc.token, - id: tc.id, - } - - err := req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestListReqValidation(t *testing.T) { - cases := []struct { - desc string - offset uint64 - limit uint64 - err error - }{ - { - desc: "too large limit", - offset: 0, - limit: maxLimitSize + 1, - err: apiutil.ErrLimitSize, - }, - { - desc: "default limit", - offset: 0, - limit: defLimit, - err: nil, - }, - } - - for _, tc := range cases { - req := listReq{ - offset: tc.offset, - limit: tc.limit, - } - - err := req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestBootstrapReqValidation(t *testing.T) { - cases := []struct { - desc string - externKey string - externID string - err error - }{ - { - desc: "empty external key", - externKey: "", - externID: "id", - err: apiutil.ErrBearerKey, - }, - { - desc: "empty external id", - externKey: "key", - externID: "", - err: apiutil.ErrMissingID, - }, - } - - for _, tc := range cases { - req := bootstrapReq{ - id: tc.externID, - key: tc.externKey, - } - - err := req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestChangeStateReqValidation(t *testing.T) { - cases := []struct { - desc string - token string - id string - state bootstrap.State - err error - }{ - { - desc: "empty token", - token: "", - id: "id", - state: bootstrap.State(1), - err: apiutil.ErrBearerToken, - }, - { - desc: "empty id", - token: "token", - id: "", - state: bootstrap.State(0), - err: apiutil.ErrMissingID, - }, - { - desc: "invalid state", - token: "token", - id: "id", - state: bootstrap.State(14), - err: apiutil.ErrBootstrapState, - }, - } - - for _, tc := range cases { - req := changeStateReq{ - token: tc.token, - id: tc.id, - State: tc.state, - } - - err := req.validate() - assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} diff --git a/docker/addons/vault/scripts/bootstrap/api/responses.go b/docker/addons/vault/scripts/bootstrap/api/responses.go deleted file mode 100644 index 59d166f7..00000000 --- a/docker/addons/vault/scripts/bootstrap/api/responses.go +++ /dev/null @@ -1,144 +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/bootstrap" -) - -var ( - _ magistrala.Response = (*removeRes)(nil) - _ magistrala.Response = (*configRes)(nil) - _ magistrala.Response = (*stateRes)(nil) - _ magistrala.Response = (*viewRes)(nil) - _ magistrala.Response = (*listRes)(nil) -) - -type removeRes struct{} - -func (res removeRes) Code() int { - return http.StatusNoContent -} - -func (res removeRes) Headers() map[string]string { - return map[string]string{} -} - -func (res removeRes) Empty() bool { - return true -} - -type configRes struct { - id string - created bool -} - -func (res configRes) Code() int { - if res.created { - return http.StatusCreated - } - - return http.StatusOK -} - -func (res configRes) Headers() map[string]string { - if res.created { - return map[string]string{ - "Location": fmt.Sprintf("/things/configs/%s", res.id), - } - } - - return map[string]string{} -} - -func (res configRes) Empty() bool { - return true -} - -type channelRes struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Metadata interface{} `json:"metadata,omitempty"` -} - -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"` -} - -func (res viewRes) Code() int { - return http.StatusOK -} - -func (res viewRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewRes) Empty() bool { - return false -} - -type listRes struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Configs []viewRes `json:"configs"` -} - -func (res listRes) Code() int { - return http.StatusOK -} - -func (res listRes) Headers() map[string]string { - return map[string]string{} -} - -func (res listRes) Empty() bool { - return false -} - -type stateRes struct{} - -func (res stateRes) Code() int { - return http.StatusOK -} - -func (res stateRes) Headers() map[string]string { - return map[string]string{} -} - -func (res stateRes) Empty() bool { - return true -} - -type updateConfigRes struct { - ThingID string `json:"thing_id,omitempty"` - ClientCert string `json:"client_cert,omitempty"` - CACert string `json:"ca_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` -} - -func (res updateConfigRes) Code() int { - return http.StatusOK -} - -func (res updateConfigRes) Headers() map[string]string { - return map[string]string{} -} - -func (res updateConfigRes) Empty() bool { - return false -} diff --git a/docker/addons/vault/scripts/bootstrap/api/transport.go b/docker/addons/vault/scripts/bootstrap/api/transport.go deleted file mode 100644 index 742ba51e..00000000 --- a/docker/addons/vault/scripts/bootstrap/api/transport.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - "encoding/json" - "log/slog" - "net/http" - "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/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" - byteContentType = "application/octet-stream" - offsetKey = "offset" - limitKey = "limit" - defOffset = 0 - defLimit = 10 -) - -var ( - fullMatch = []string{"state", "external_id", "thing_id", "thing_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 { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - r := chi.NewRouter() - - r.Route("/{domainID}/things", func(r chi.Router) { - r.Group(func(r chi.Router) { - r.Use(api.AuthenticateMiddleware(authn, true)) - - r.Route("/configs", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - addEndpoint(svc), - decodeAddRequest, - api.EncodeResponse, - opts...), "add").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - listEndpoint(svc), - decodeListRequest, - api.EncodeResponse, - opts...), "list").ServeHTTP) - - r.Get("/{configID}", otelhttp.NewHandler(kithttp.NewServer( - viewEndpoint(svc), - decodeEntityRequest, - api.EncodeResponse, - opts...), "view").ServeHTTP) - - r.Put("/{configID}", otelhttp.NewHandler(kithttp.NewServer( - updateEndpoint(svc), - decodeUpdateRequest, - api.EncodeResponse, - opts...), "update").ServeHTTP) - - r.Delete("/{configID}", otelhttp.NewHandler(kithttp.NewServer( - removeEndpoint(svc), - decodeEntityRequest, - api.EncodeResponse, - opts...), "remove").ServeHTTP) - - r.Patch("/certs/{certID}", otelhttp.NewHandler(kithttp.NewServer( - updateCertEndpoint(svc), - decodeUpdateCertRequest, - api.EncodeResponse, - opts...), "update_cert").ServeHTTP) - - r.Put("/connections/{connID}", otelhttp.NewHandler(kithttp.NewServer( - updateConnEndpoint(svc), - decodeUpdateConnRequest, - api.EncodeResponse, - opts...), "update_connections").ServeHTTP) - }) - }) - - r.With(api.AuthenticateMiddleware(authn, true)).Put("/state/{thingID}", otelhttp.NewHandler(kithttp.NewServer( - stateEndpoint(svc), - decodeStateRequest, - api.EncodeResponse, - opts...), "update_state").ServeHTTP) - }) - - r.Route("/things/bootstrap", func(r chi.Router) { - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - bootstrapEndpoint(svc, reader, false), - decodeBootstrapRequest, - api.EncodeResponse, - opts...), "bootstrap").ServeHTTP) - r.Get("/{externalID}", otelhttp.NewHandler(kithttp.NewServer( - bootstrapEndpoint(svc, reader, false), - decodeBootstrapRequest, - api.EncodeResponse, - opts...), "bootstrap").ServeHTTP) - r.Get("/secure/{externalID}", otelhttp.NewHandler(kithttp.NewServer( - bootstrapEndpoint(svc, reader, true), - decodeBootstrapRequest, - encodeSecureRes, - opts...), "bootstrap_secure").ServeHTTP) - }) - - r.Get("/health", magistrala.Health("bootstrap", instanceID)) - r.Handle("/metrics", promhttp.Handler()) - - return r -} - -func decodeAddRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := addReq{ - 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 decodeUpdateRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := updateReq{ - id: chi.URLParam(r, "configID"), - } - 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 decodeUpdateCertRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := updateCertReq{ - thingID: 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)) - } - - return req, nil -} - -func decodeUpdateConnRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := updateConnReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "connID"), - } - 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 decodeListRequest(_ context.Context, r *http.Request) (interface{}, error) { - o, err := apiutil.ReadNumQuery[uint64](r, offsetKey, defOffset) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - l, err := apiutil.ReadNumQuery[uint64](r, limitKey, defLimit) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - q, err := url.ParseQuery(r.URL.RawQuery) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidQueryParams) - } - - req := listReq{ - filter: parseFilter(q), - offset: o, - limit: l, - } - - return req, nil -} - -func decodeBootstrapRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := bootstrapReq{ - id: chi.URLParam(r, "externalID"), - key: apiutil.ExtractThingKey(r), - } - - return req, nil -} - -func decodeStateRequest(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := changeStateReq{ - token: apiutil.ExtractBearerToken(r), - id: chi.URLParam(r, "thingID"), - } - 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 decodeEntityRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := entityReq{ - id: chi.URLParam(r, "configID"), - } - - return req, nil -} - -func encodeSecureRes(_ context.Context, w http.ResponseWriter, response interface{}) error { - w.Header().Set("Content-Type", byteContentType) - w.WriteHeader(http.StatusOK) - if b, ok := response.([]byte); ok { - if _, err := w.Write(b); err != nil { - return err - } - } - return nil -} - -func parseFilter(values url.Values) bootstrap.Filter { - ret := bootstrap.Filter{ - FullMatch: make(map[string]string), - PartialMatch: make(map[string]string), - } - for k := range values { - if contains(fullMatch, k) { - ret.FullMatch[k] = values.Get(k) - } - if contains(partialMatch, k) { - ret.PartialMatch[k] = strings.ToLower(values.Get(k)) - } - } - - return ret -} - -func contains(l []string, s string) bool { - for _, v := range l { - if v == s { - return true - } - } - return false -} diff --git a/docker/addons/vault/scripts/bootstrap/configs.go b/docker/addons/vault/scripts/bootstrap/configs.go deleted file mode 100644 index 24c8ecde..00000000 --- a/docker/addons/vault/scripts/bootstrap/configs.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bootstrap - -import ( - "context" - "time" - - "github.com/absmach/magistrala/things" -) - -// 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. -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"` -} - -// Channel represents Magistrala channel corresponding Magistrala Thing is connected to. -type Channel struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Metadata map[string]interface{} `json:"metadata,omitempty"` - DomainID string `json:"domain_id"` - Parent string `json:"parent_id,omitempty"` - Description string `json:"description,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - Status things.Status `json:"status"` -} - -// Filter is used for the search filters. -type Filter struct { - FullMatch map[string]string - PartialMatch map[string]string -} - -// ConfigsPage contains page related metadata as well as list of Configs that -// belong to this page. -type ConfigsPage struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Configs []Config `json:"configs"` -} - -// ConfigRepository specifies a Config persistence API. -// -//go:generate mockery --name ConfigRepository --output=./mocks --filename configs.go --quiet --note "Copyright (c) Abstract Machines" -type ConfigRepository interface { - // Save persists the Config. Successful operation is indicated by non-nil - // error response. - Save(ctx context.Context, cfg Config, chsConnIDs []string) (string, error) - - // RetrieveByID retrieves the Config having the provided identifier, that is owned - // by the specified user. - RetrieveByID(ctx context.Context, domainID, id string) (Config, error) - - // 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 - - // RetrieveByExternalID returns Config for given external ID. - RetrieveByExternalID(ctx context.Context, externalID string) (Config, error) - - // Update updates an existing Config. A non-nil error is returned - // to indicate operation failure. - Update(ctx context.Context, cfg Config) error - - // 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) - - // UpdateConnections updates a list of Channels the Config is connected to - // adding new Channels if needed. - UpdateConnections(ctx context.Context, domainID, id string, channels []Channel, connections []string) error - - // Remove removes the Config having the provided identifier, that is owned - // by the specified user. - Remove(ctx context.Context, domainID, id string) error - - // ChangeState changes of the Config, that is owned by the specific user. - ChangeState(ctx context.Context, domainID, id string, state State) error - - // 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 - // 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 - - // UpdateChannel updates channel with the given ID. - UpdateChannel(ctx context.Context, c Channel) error - - // 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 - - // DisconnectThing changes state of the Config when the corresponding Thing is disconnected from the Channel. - DisconnectThing(ctx context.Context, channelID, thingID string) error -} diff --git a/docker/addons/vault/scripts/bootstrap/doc.go b/docker/addons/vault/scripts/bootstrap/doc.go deleted file mode 100644 index 606c44a9..00000000 --- a/docker/addons/vault/scripts/bootstrap/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package bootstrap contains the domain concept definitions needed to support -// Magistrala bootstrap service functionality. -package bootstrap diff --git a/docker/addons/vault/scripts/bootstrap/events/consumer/doc.go b/docker/addons/vault/scripts/bootstrap/events/consumer/doc.go deleted file mode 100644 index f3fea76f..00000000 --- a/docker/addons/vault/scripts/bootstrap/events/consumer/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package consumer contains events consumer for events -// published by Bootstrap service. -package consumer diff --git a/docker/addons/vault/scripts/bootstrap/events/consumer/events.go b/docker/addons/vault/scripts/bootstrap/events/consumer/events.go deleted file mode 100644 index a3a05996..00000000 --- a/docker/addons/vault/scripts/bootstrap/events/consumer/events.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package consumer - -import "time" - -type removeEvent struct { - id string -} - -type updateChannelEvent struct { - id string - name string - metadata map[string]interface{} - updatedAt time.Time - updatedBy string -} - -// Connection event is either connect or disconnect event. -type connectionEvent struct { - thingIDs []string - channelID string -} diff --git a/docker/addons/vault/scripts/bootstrap/events/consumer/streams.go b/docker/addons/vault/scripts/bootstrap/events/consumer/streams.go deleted file mode 100644 index 7c0d5bcb..00000000 --- a/docker/addons/vault/scripts/bootstrap/events/consumer/streams.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package consumer - -import ( - "context" - "time" - - "github.com/absmach/magistrala/bootstrap" - svcerr "github.com/absmach/magistrala/pkg/errors/service" - "github.com/absmach/magistrala/pkg/events" -) - -const ( - thingRemove = "thing.remove" - thingConnect = "group.assign" - thingDisconnect = "group.unassign" - - channelPrefix = "group." - channelUpdate = channelPrefix + "update" - channelRemove = channelPrefix + "remove" - - memberKind = "things" - relation = "group" -) - -type eventHandler struct { - svc bootstrap.Service -} - -// NewEventHandler returns new event store handler. -func NewEventHandler(svc bootstrap.Service) events.EventHandler { - return &eventHandler{ - svc: svc, - } -} - -func (es *eventHandler) Handle(ctx context.Context, event events.Event) error { - msg, err := event.Encode() - if err != nil { - return err - } - - switch msg["operation"] { - case thingRemove: - rte := decodeRemoveThing(msg) - err = es.svc.RemoveConfigHandler(ctx, rte.id) - case thingConnect: - cte := decodeConnectThing(msg) - if cte.channelID == "" || len(cte.thingIDs) == 0 { - return svcerr.ErrMalformedEntity - } - for _, thingID := range cte.thingIDs { - if thingID == "" { - return svcerr.ErrMalformedEntity - } - if err := es.svc.ConnectThingHandler(ctx, cte.channelID, thingID); err != nil { - return err - } - } - case thingDisconnect: - dte := decodeDisconnectThing(msg) - if dte.channelID == "" || len(dte.thingIDs) == 0 { - return svcerr.ErrMalformedEntity - } - for _, thingID := range dte.thingIDs { - if thingID == "" { - return svcerr.ErrMalformedEntity - } - } - - for _, thingID := range dte.thingIDs { - if err = es.svc.DisconnectThingHandler(ctx, dte.channelID, thingID); err != nil { - return err - } - } - case channelUpdate: - uce := decodeUpdateChannel(msg) - err = es.handleUpdateChannel(ctx, uce) - case channelRemove: - rce := decodeRemoveChannel(msg) - err = es.svc.RemoveChannelHandler(ctx, rce.id) - } - if err != nil { - return err - } - - return nil -} - -func decodeRemoveThing(event map[string]interface{}) removeEvent { - return removeEvent{ - id: events.Read(event, "id", ""), - } -} - -func decodeUpdateChannel(event map[string]interface{}) updateChannelEvent { - metadata := events.Read(event, "metadata", map[string]interface{}{}) - - return updateChannelEvent{ - id: events.Read(event, "id", ""), - name: events.Read(event, "name", ""), - metadata: metadata, - updatedAt: events.Read(event, "updated_at", time.Now()), - updatedBy: events.Read(event, "updated_by", ""), - } -} - -func decodeRemoveChannel(event map[string]interface{}) removeEvent { - return removeEvent{ - id: events.Read(event, "id", ""), - } -} - -func decodeConnectThing(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"), - } -} - -func decodeDisconnectThing(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"), - } -} - -func (es *eventHandler) handleUpdateChannel(ctx context.Context, uce updateChannelEvent) error { - channel := bootstrap.Channel{ - ID: uce.id, - Name: uce.name, - Metadata: uce.metadata, - UpdatedAt: uce.updatedAt, - UpdatedBy: uce.updatedBy, - } - - return es.svc.UpdateChannelHandler(ctx, channel) -} diff --git a/docker/addons/vault/scripts/bootstrap/events/doc.go b/docker/addons/vault/scripts/bootstrap/events/doc.go deleted file mode 100644 index fa65f5af..00000000 --- a/docker/addons/vault/scripts/bootstrap/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 -// bootstrap events functionality. -package events diff --git a/docker/addons/vault/scripts/bootstrap/events/producer/doc.go b/docker/addons/vault/scripts/bootstrap/events/producer/doc.go deleted file mode 100644 index ab153751..00000000 --- a/docker/addons/vault/scripts/bootstrap/events/producer/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package producer contains the domain events needed to support -// event sourcing of Bootstrap service actions. -package producer diff --git a/docker/addons/vault/scripts/bootstrap/events/producer/events.go b/docker/addons/vault/scripts/bootstrap/events/producer/events.go deleted file mode 100644 index 86f5c430..00000000 --- a/docker/addons/vault/scripts/bootstrap/events/producer/events.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package producer - -import ( - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/pkg/events" -) - -const ( - configPrefix = "bootstrap.config." - configCreate = configPrefix + "create" - configUpdate = configPrefix + "update" - configRemove = configPrefix + "remove" - configView = configPrefix + "view" - 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" - - channelPrefix = "bootstrap.channel." - channelHandlerRemove = channelPrefix + "remove_handler" - channelUpdateHandler = channelPrefix + "update_handler" - - certUpdate = "bootstrap.cert.update" -) - -var ( - _ events.Event = (*configEvent)(nil) - _ events.Event = (*removeConfigEvent)(nil) - _ events.Event = (*bootstrapEvent)(nil) - _ events.Event = (*changeStateEvent)(nil) - _ events.Event = (*updateConnectionsEvent)(nil) - _ events.Event = (*updateCertEvent)(nil) - _ events.Event = (*listConfigsEvent)(nil) - _ events.Event = (*removeHandlerEvent)(nil) -) - -type configEvent struct { - bootstrap.Config - operation string -} - -func (ce configEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "state": ce.State.String(), - "operation": ce.operation, - } - if ce.ThingID != "" { - val["thing_id"] = ce.ThingID - } - if ce.Content != "" { - val["content"] = ce.Content - } - if ce.DomainID != "" { - val["domain_id "] = ce.DomainID - } - if ce.Name != "" { - val["name"] = ce.Name - } - if ce.ExternalID != "" { - val["external_id"] = ce.ExternalID - } - if len(ce.Channels) > 0 { - channels := make([]string, len(ce.Channels)) - for i, ch := range ce.Channels { - channels[i] = ch.ID - } - val["channels"] = channels - } - if ce.ClientCert != "" { - val["client_cert"] = ce.ClientCert - } - if ce.ClientKey != "" { - val["client_key"] = ce.ClientKey - } - if ce.CACert != "" { - val["ca_cert"] = ce.CACert - } - if ce.Content != "" { - val["content"] = ce.Content - } - - return val, nil -} - -type removeConfigEvent struct { - mgThing string -} - -func (rce removeConfigEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "thing_id": rce.mgThing, - "operation": configRemove, - }, nil -} - -type listConfigsEvent struct { - offset uint64 - limit uint64 - fullMatch map[string]string - partialMatch map[string]string -} - -func (rce listConfigsEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "offset": rce.offset, - "limit": rce.limit, - "operation": configList, - } - if len(rce.fullMatch) > 0 { - val["full_match"] = rce.fullMatch - } - - if len(rce.partialMatch) > 0 { - val["full_match"] = rce.partialMatch - } - return val, nil -} - -type bootstrapEvent struct { - bootstrap.Config - externalID string - success bool -} - -func (be bootstrapEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "external_id": be.externalID, - "success": be.success, - "operation": thingBootstrap, - } - - if be.ThingID != "" { - val["thing_id"] = be.ThingID - } - if be.Content != "" { - val["content"] = be.Content - } - if be.DomainID != "" { - val["domain_id "] = be.DomainID - } - if be.Name != "" { - val["name"] = be.Name - } - if be.ExternalID != "" { - val["external_id"] = be.ExternalID - } - if len(be.Channels) > 0 { - channels := make([]string, len(be.Channels)) - for i, ch := range be.Channels { - channels[i] = ch.ID - } - val["channels"] = channels - } - if be.ClientCert != "" { - val["client_cert"] = be.ClientCert - } - if be.ClientKey != "" { - val["client_key"] = be.ClientKey - } - if be.CACert != "" { - val["ca_cert"] = be.CACert - } - if be.Content != "" { - val["content"] = be.Content - } - return val, nil -} - -type changeStateEvent struct { - mgThing string - state bootstrap.State -} - -func (cse changeStateEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "thing_id": cse.mgThing, - "state": cse.state.String(), - "operation": thingStateChange, - }, nil -} - -type updateConnectionsEvent struct { - mgThing string - mgChannels []string -} - -func (uce updateConnectionsEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "thing_id": uce.mgThing, - "channels": uce.mgChannels, - "operation": thingUpdateConnections, - }, nil -} - -type updateCertEvent struct { - thingKey, clientCert, clientKey, caCert string -} - -func (uce updateCertEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "thing_key": uce.thingKey, - "client_cert": uce.clientCert, - "client_key": uce.clientKey, - "ca_cert": uce.caCert, - "operation": certUpdate, - }, nil -} - -type removeHandlerEvent struct { - id string - operation string -} - -func (rhe removeHandlerEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "config_id": rhe.id, - "operation": rhe.operation, - }, nil -} - -type updateChannelHandlerEvent struct { - bootstrap.Channel -} - -func (uche updateChannelHandlerEvent) Encode() (map[string]interface{}, error) { - val := map[string]interface{}{ - "operation": channelUpdateHandler, - } - - if uche.ID != "" { - val["channel_id"] = uche.ID - } - if uche.Name != "" { - val["name"] = uche.Name - } - if uche.Metadata != nil { - val["metadata"] = uche.Metadata - } - return val, nil -} - -type connectThingEvent struct { - thingID string - channelID string -} - -func (cte connectThingEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "thing_id": cte.thingID, - "channel_id": cte.channelID, - "operation": thingConnect, - }, nil -} - -type disconnectThingEvent struct { - thingID string - channelID string -} - -func (dte disconnectThingEvent) Encode() (map[string]interface{}, error) { - return map[string]interface{}{ - "thing_id": dte.thingID, - "channel_id": dte.channelID, - "operation": thingDisconnect, - }, nil -} diff --git a/docker/addons/vault/scripts/bootstrap/events/producer/setup_test.go b/docker/addons/vault/scripts/bootstrap/events/producer/setup_test.go deleted file mode 100644 index 517cd652..00000000 --- a/docker/addons/vault/scripts/bootstrap/events/producer/setup_test.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package producer_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/docker/addons/vault/scripts/bootstrap/events/producer/streams.go b/docker/addons/vault/scripts/bootstrap/events/producer/streams.go deleted file mode 100644 index 6202c168..00000000 --- a/docker/addons/vault/scripts/bootstrap/events/producer/streams.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package producer - -import ( - "context" - - "github.com/absmach/magistrala/bootstrap" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/absmach/magistrala/pkg/events" -) - -var _ bootstrap.Service = (*eventStore)(nil) - -type eventStore struct { - events.Publisher - svc bootstrap.Service -} - -// NewEventStoreMiddleware returns wrapper around bootstrap service that sends -// events to event store. -func NewEventStoreMiddleware(svc bootstrap.Service, publisher events.Publisher) bootstrap.Service { - return &eventStore{ - svc: svc, - Publisher: publisher, - } -} - -func (es *eventStore) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) { - saved, err := es.svc.Add(ctx, session, token, cfg) - if err != nil { - return saved, err - } - - ev := configEvent{ - saved, configCreate, - } - - if err := es.Publish(ctx, ev); err != nil { - return saved, err - } - - return saved, err -} - -func (es *eventStore) View(ctx context.Context, session mgauthn.Session, id string) (bootstrap.Config, error) { - cfg, err := es.svc.View(ctx, session, id) - if err != nil { - return cfg, err - } - ev := configEvent{ - cfg, configView, - } - - if err := es.Publish(ctx, ev); err != nil { - return cfg, err - } - - return cfg, err -} - -func (es *eventStore) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) error { - if err := es.svc.Update(ctx, session, cfg); err != nil { - return err - } - - ev := configEvent{ - cfg, configUpdate, - } - - 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) - if err != nil { - return cfg, err - } - - ev := updateCertEvent{ - thingKey: thingKey, - clientCert: clientCert, - clientKey: clientKey, - caCert: caCert, - } - - if err := es.Publish(ctx, ev); err != nil { - return cfg, err - } - - return cfg, nil -} - -func (es *eventStore) UpdateConnections(ctx context.Context, session mgauthn.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, - 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) { - bp, err := es.svc.List(ctx, session, filter, offset, limit) - if err != nil { - return bp, err - } - - ev := listConfigsEvent{ - offset: offset, - limit: limit, - fullMatch: filter.FullMatch, - partialMatch: filter.PartialMatch, - } - - if err := es.Publish(ctx, ev); err != nil { - return bp, err - } - - return bp, nil -} - -func (es *eventStore) Remove(ctx context.Context, session mgauthn.Session, id string) error { - if err := es.svc.Remove(ctx, session, id); err != nil { - return err - } - - ev := removeConfigEvent{ - mgThing: id, - } - - return es.Publish(ctx, ev) -} - -func (es *eventStore) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (bootstrap.Config, error) { - cfg, err := es.svc.Bootstrap(ctx, externalKey, externalID, secure) - - ev := bootstrapEvent{ - cfg, - externalID, - true, - } - - if err != nil { - ev.success = false - } - - if err := es.Publish(ctx, ev); err != nil { - return cfg, err - } - - return cfg, err -} - -func (es *eventStore) ChangeState(ctx context.Context, session mgauthn.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, - } - - return es.Publish(ctx, ev) -} - -func (es *eventStore) RemoveConfigHandler(ctx context.Context, id string) error { - if err := es.svc.RemoveConfigHandler(ctx, id); err != nil { - return err - } - - ev := removeHandlerEvent{ - id: id, - operation: configHandlerRemove, - } - - return es.Publish(ctx, ev) -} - -func (es *eventStore) RemoveChannelHandler(ctx context.Context, id string) error { - if err := es.svc.RemoveChannelHandler(ctx, id); err != nil { - return err - } - - ev := removeHandlerEvent{ - id: id, - operation: channelHandlerRemove, - } - - return es.Publish(ctx, ev) -} - -func (es *eventStore) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) error { - if err := es.svc.UpdateChannelHandler(ctx, channel); err != nil { - return err - } - - ev := updateChannelHandlerEvent{ - channel, - } - - 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 { - return err - } - - ev := connectThingEvent{ - thingID: thingID, - 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 { - return err - } - - ev := disconnectThingEvent{ - thingID: thingID, - channelID: channelID, - } - - return es.Publish(ctx, ev) -} diff --git a/docker/addons/vault/scripts/bootstrap/events/producer/streams_test.go b/docker/addons/vault/scripts/bootstrap/events/producer/streams_test.go deleted file mode 100644 index aa5f1de8..00000000 --- a/docker/addons/vault/scripts/bootstrap/events/producer/streams_test.go +++ /dev/null @@ -1,1482 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package producer_test - -import ( - "context" - "fmt" - "strconv" - "strings" - "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/redis/go-redis/v9" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -const ( - streamID = "magistrala.bootstrap" - email = "user@example.com" - validToken = "validToken" - invalidToken = "invalid" - unknownThingID = "unknown" - channelsNum = 3 - defaultTimout = 5 - - configPrefix = "config." - configCreate = configPrefix + "create" - configView = configPrefix + "view" - configUpdate = configPrefix + "update" - configRemove = configPrefix + "remove" - 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" - - channelPrefix = "group." - channelHandlerRemove = channelPrefix + "remove_handler" - channelUpdateHandler = channelPrefix + "update_handler" - - certUpdate = "cert.update" - instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" -) - -var ( - encKey = []byte("1234567891011121") - - domainID = testsutil.GenerateUUID(&testing.T{}) - validID = testsutil.GenerateUUID(&testing.T{}) - - channel = bootstrap.Channel{ - ID: testsutil.GenerateUUID(&testing.T{}), - Name: "name", - Metadata: map[string]interface{}{"name": "value"}, - } - - 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", - } -) - -type testVariable struct { - svc bootstrap.Service - boot *mocks.ConfigRepository - policies *policymocks.Service - sdk *sdkmocks.SDK -} - -func newTestVariable(t *testing.T, redisURL string) testVariable { - boot := new(mocks.ConfigRepository) - policies := new(policymocks.Service) - sdk := new(sdkmocks.SDK) - idp := uuid.NewMock() - svc := bootstrap.New(policies, boot, sdk, encKey, idp) - publisher, err := store.NewPublisher(context.Background(), redisURL, streamID) - require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - svc = producer.NewEventStoreMiddleware(svc, publisher) - return testVariable{ - svc: svc, - boot: boot, - policies: policies, - sdk: sdk, - } -} - -func TestAdd(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - var channels []string - for _, ch := range config.Channels { - channels = append(channels, ch.ID) - } - - invalidConfig := config - invalidConfig.Channels = []bootstrap.Channel{{ID: "empty"}} - 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: "create config successfully", - config: config, - token: validToken, - id: validID, - domainID: domainID, - channel: config.Channels, - event: map[string]interface{}{ - "thing_id": "1", - "domain_id": domainID, - "name": config.Name, - "channels": channels, - "external_id": config.ExternalID, - "content": config.Content, - "timestamp": time.Now().Unix(), - "operation": configCreate, - }, - 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 list existing", - config: config, - token: validToken, - id: validID, - domainID: domainID, - event: nil, - listErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "create invalid config", - config: invalidConfig, - token: validToken, - id: validID, - domainID: domainID, - event: nil, - listErr: svcerr.ErrMalformedEntity, - err: svcerr.ErrMalformedEntity, - }, - } - - 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)) - 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) - - _, err := tv.svc.Add(context.Background(), tc.session, tc.token, tc.config) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - event := streams[0].Messages - lastID = event[0].ID - } - - test(t, tc.event, event, tc.desc) - - sdkCall.Unset() - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestView(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - nonExisting := config - nonExisting.ThingID = unknownThingID - - cases := []struct { - desc string - config bootstrap.Config - token string - session mgauthn.Session - id string - domainID string - retrieveErr error - err error - event map[string]interface{} - }{ - { - desc: "view successfully", - config: config, - token: validToken, - id: validID, - domainID: domainID, - err: nil, - event: map[string]interface{}{ - "thing_id": config.ThingID, - "domain_id": config.DomainID, - "name": config.Name, - "channels": config.Channels, - "external_id": config.ExternalID, - "content": config.Content, - "timestamp": time.Now().Unix(), - "operation": configView, - }, - }, - { - desc: "view with failed retrieve", - config: nonExisting, - token: validToken, - id: validID, - domainID: domainID, - retrieveErr: svcerr.ErrViewEntity, - err: svcerr.ErrViewEntity, - event: nil, - }, - } - - 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) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - msg := streams[0].Messages[0] - event = msg.Values - event["timestamp"] = msg.ID - lastID = msg.ID - } - - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func TestUpdate(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - c := config - - ch1 := channel - ch1.ID = testsutil.GenerateUUID(t) - - ch2 := channel - ch2.ID = testsutil.GenerateUUID(t) - - c.Channels = append(c.Channels, ch1, ch2) - - modified := c - modified.Content = "new-config" - modified.Name = "new name" - - nonExisting := config - nonExisting.ThingID = unknownThingID - - channels := []string{modified.Channels[0].ID, modified.Channels[1].ID} - - cases := []struct { - desc string - config bootstrap.Config - token string - session mgauthn.Session - id string - domainID string - updateErr error - err error - event map[string]interface{} - }{ - { - desc: "update config successfully", - config: modified, - token: validToken, - id: validID, - domainID: domainID, - err: nil, - event: map[string]interface{}{ - "name": modified.Name, - "content": modified.Content, - "timestamp": time.Now().UnixNano(), - "operation": configUpdate, - "channels": channels, - "external_id": modified.ExternalID, - "thing_id": modified.ThingID, - "domain_id": domainID, - "state": "0", - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "update with failed update", - config: nonExisting, - token: validToken, - id: validID, - domainID: domainID, - updateErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - event: nil, - }, - } - - lastID := "0" - for _, tc := range cases { - tc.session = mgauthn.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)) - - streams := redisClient.XRead(context.Background(), &redis.XReadArgs{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - msg := streams[0].Messages[0] - event = msg.Values - event["timestamp"] = msg.ID - lastID = msg.ID - } - - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func TestUpdateConnections(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - configID string - id string - domainID string - token string - session mgauthn.Session - connections []string - thingErr error - channelErr error - retrieveErr error - listErr error - updateErr error - err error - event map[string]interface{} - }{ - { - desc: "update connections successfully", - configID: config.ThingID, - token: validToken, - id: validID, - domainID: domainID, - connections: []string{config.Channels[0].ID}, - err: nil, - event: map[string]interface{}{ - "thing_id": config.ThingID, - "channels": "2", - "timestamp": time.Now().Unix(), - "operation": thingUpdateConnections, - }, - }, - { - desc: "update connections with failed channel fetch", - configID: config.ThingID, - token: validToken, - id: validID, - domainID: domainID, - connections: []string{"256"}, - channelErr: errors.NewSDKError(svcerr.ErrNotFound), - err: svcerr.ErrNotFound, - event: nil, - }, - { - desc: "update connections with failed RetrieveByID", - configID: config.ThingID, - token: validToken, - id: validID, - domainID: domainID, - connections: []string{config.Channels[0].ID}, - retrieveErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - event: nil, - }, - { - desc: "update connections with failed ListExisting", - configID: config.ThingID, - token: validToken, - id: validID, - domainID: domainID, - connections: []string{config.Channels[0].ID}, - listErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - event: nil, - }, - { - desc: "update connections with failed UpdateConnections", - configID: config.ThingID, - token: validToken, - id: validID, - domainID: domainID, - connections: []string{config.Channels[0].ID}, - updateErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, - event: nil, - }, - } - - lastID := "0" - for _, tc := range cases { - tc.session = mgauthn.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) - repoCall2 := tv.boot.On("UpdateConnections", context.Background(), tc.domainID, tc.configID, mock.Anything, tc.connections).Return(tc.updateErr) - err := tv.svc.UpdateConnections(context.Background(), tc.session, tc.token, tc.configID, tc.connections) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - event := streams[0].Messages - lastID = event[0].ID - } - - test(t, tc.event, event, tc.desc) - sdkCall.Unset() - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - } -} - -func TestUpdateCert(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - configID string - userID string - domainID string - token string - session mgauthn.Session - clientCert string - clientKey string - caCert string - updateErr error - err error - event map[string]interface{} - }{ - { - desc: "update cert successfully", - configID: config.ThingID, - userID: validID, - domainID: domainID, - token: validToken, - clientCert: "clientCert", - clientKey: "clientKey", - caCert: "caCert", - err: nil, - event: map[string]interface{}{ - "thing_key": config.ThingKey, - "client_cert": "clientCert", - "client_key": "clientKey", - "ca_cert": "caCert", - "operation": certUpdate, - }, - }, - { - desc: "update cert with failed update", - configID: "invalidThingID", - token: validToken, - userID: validID, - domainID: domainID, - clientCert: "clientCert", - clientKey: "clientKey", - caCert: "caCert", - updateErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - event: nil, - }, - { - desc: "update cert with empty client certificate", - configID: config.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - clientCert: "", - clientKey: "clientKey", - caCert: "caCert", - err: nil, - event: nil, - }, - { - desc: "update cert with empty client key", - configID: config.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - clientCert: "clientCert", - clientKey: "", - caCert: "caCert", - err: nil, - event: nil, - }, - { - desc: "update cert with empty CA certificate", - configID: config.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - clientCert: "clientCert", - clientKey: "clientKey", - caCert: "", - err: nil, - event: nil, - }, - { - desc: "successful update without CA certificate", - configID: config.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - clientCert: "clientCert", - clientKey: "clientKey", - 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(), - }, - }, - } - - lastID := "0" - for _, tc := range cases { - tc.session = mgauthn.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) - - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - event := streams[0].Messages - lastID = event[0].ID - } - - test(t, tc.event, event, tc.desc) - - repoCall.Unset() - } -} - -func TestList(t *testing.T) { - tv := newTestVariable(t, redisURL) - - numThings := 101 - var c bootstrap.Config - saved := make([]bootstrap.Config, 0) - for i := 0; i < numThings; i++ { - c := config - c.ExternalID = testsutil.GenerateUUID(t) - c.ExternalKey = testsutil.GenerateUUID(t) - c.Name = fmt.Sprintf("%s-%d", config.Name, i) - if i == 41 { - c.State = bootstrap.Active - } - saved = append(saved, c) - } - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - domainID string - config bootstrap.ConfigsPage - filter bootstrap.Filter - offset uint64 - limit uint64 - listObjectsResponse policysvc.PolicyPage - listObjectsErr error - retrieveErr error - err error - event map[string]interface{} - }{ - { - desc: "list successfully as super admin", - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 0, - Limit: 10, - Configs: saved[0:10], - }, - filter: bootstrap.Filter{}, - offset: 0, - limit: 10, - listObjectsResponse: policysvc.PolicyPage{}, - err: nil, - event: map[string]interface{}{ - "thing_id": c.ThingID, - "domain_id": c.DomainID, - "name": c.Name, - "channels": c.Channels, - "external_id": c.ExternalID, - "content": c.Content, - "timestamp": time.Now().Unix(), - "operation": configList, - }, - }, - { - desc: "list successfully as domain admin", - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 0, - Limit: 10, - Configs: saved[0:10], - }, - filter: bootstrap.Filter{}, - offset: 0, - limit: 10, - listObjectsResponse: policysvc.PolicyPage{}, - err: nil, - event: map[string]interface{}{ - "thing_id": c.ThingID, - "domain_id": c.DomainID, - "name": c.Name, - "channels": c.Channels, - "external_id": c.ExternalID, - "content": c.Content, - "timestamp": time.Now().Unix(), - "operation": configList, - }, - }, - { - desc: "list successfully as non admin", - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 0, - Limit: 10, - Configs: saved[0:10], - }, - filter: bootstrap.Filter{}, - offset: 0, - limit: 10, - listObjectsResponse: policysvc.PolicyPage{}, - err: nil, - event: map[string]interface{}{ - "thing_id": c.ThingID, - "domain_id": c.DomainID, - "name": c.Name, - "channels": c.Channels, - "external_id": c.ExternalID, - "content": c.Content, - "timestamp": time.Now().Unix(), - "operation": configList, - }, - }, - { - desc: "list as non admin with failed list all objects", - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - filter: bootstrap.Filter{}, - offset: 0, - limit: 10, - listObjectsResponse: policysvc.PolicyPage{}, - listObjectsErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - event: nil, - }, - - { - desc: "list as super admin with failed retrieve all", - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - filter: bootstrap.Filter{}, - offset: 0, - limit: 10, - listObjectsResponse: policysvc.PolicyPage{}, - retrieveErr: nil, - err: nil, - event: nil, - }, - { - desc: "list as domain admin with failed retrieve all", - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - filter: bootstrap.Filter{}, - offset: 0, - limit: 10, - listObjectsResponse: policysvc.PolicyPage{}, - retrieveErr: nil, - err: nil, - event: nil, - }, - { - desc: "list as non admin with failed retrieve all", - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - filter: bootstrap.Filter{}, - offset: 0, - limit: 10, - listObjectsResponse: policysvc.PolicyPage{}, - retrieveErr: nil, - err: nil, - event: nil, - }, - } - - lastID := "0" - for _, tc := range cases { - policyCall := tv.policies.On("ListAllObjects", mock.Anything, policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: tc.userID, - Permission: policysvc.ViewPermission, - ObjectType: policysvc.ThingType, - }).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) - - _, err := tv.svc.List(context.Background(), tc.session, tc.filter, tc.offset, tc.limit) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - event := streams[0].Messages - lastID = event[0].ID - } - - test(t, tc.event, event, tc.desc) - - policyCall.Unset() - repoCall.Unset() - } -} - -func TestRemove(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - nonExisting := config - nonExisting.ThingID = unknownThingID - - cases := []struct { - desc string - configID string - userID string - domainID string - token string - session mgauthn.Session - removeErr error - err error - event map[string]interface{} - }{ - { - desc: "remove config successfully", - configID: config.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - err: nil, - event: map[string]interface{}{ - "thing_id": config.ThingID, - "timestamp": time.Now().Unix(), - "operation": configRemove, - }, - }, - { - desc: "remove config with failed removal", - configID: nonExisting.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - removeErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - event: nil, - }, - } - - lastID := "0" - for _, tc := range cases { - tc.session = mgauthn.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)) - - streams := redisClient.XRead(context.Background(), &redis.XReadArgs{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - event := streams[0].Messages - lastID = event[0].ID - } - - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func TestBootstrap(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - externalID string - externalKey string - err error - retrieveErr error - event map[string]interface{} - }{ - { - desc: "bootstrap successfully", - externalID: config.ExternalID, - externalKey: config.ExternalKey, - err: nil, - event: map[string]interface{}{ - "external_id": config.ExternalID, - "success": "1", - "timestamp": time.Now().Unix(), - "operation": thingBootstrap, - }, - }, - { - desc: "bootstrap with an error", - externalID: "external_id1", - externalKey: "external_id", - retrieveErr: bootstrap.ErrBootstrap, - err: bootstrap.ErrBootstrap, - event: map[string]interface{}{ - "external_id": "external_id", - "success": "0", - "timestamp": time.Now().Unix(), - "operation": thingBootstrap, - }, - }, - } - - lastID := "0" - for _, tc := range cases { - repoCall := tv.boot.On("RetrieveByExternalID", context.Background(), mock.Anything).Return(config, tc.retrieveErr) - _, err = tv.svc.Bootstrap(context.Background(), tc.externalKey, tc.externalID, false) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - event := streams[0].Messages - lastID = event[0].ID - } - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func TestChangeState(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - id string - userID string - domainID string - token string - session mgauthn.Session - state bootstrap.State - authResponse *magistrala.AuthZRes - authorizeErr error - connectErr error - retrieveErr error - stateErr error - authenticateErr error - err error - event map[string]interface{} - }{ - { - desc: "change state to active", - id: config.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - state: bootstrap.Active, - authResponse: &magistrala.AuthZRes{Authorized: true}, - err: nil, - event: map[string]interface{}{ - "thing_id": config.ThingID, - "state": bootstrap.Active.String(), - "timestamp": time.Now().Unix(), - "operation": thingStateChange, - }, - }, - { - desc: "change state with failed retrieve by ID", - id: "", - token: validToken, - userID: validID, - domainID: domainID, - state: bootstrap.Active, - retrieveErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - event: nil, - }, - { - desc: "change state with failed connect", - id: config.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - state: bootstrap.Active, - connectErr: bootstrap.ErrThings, - err: bootstrap.ErrThings, - event: nil, - }, - { - desc: "change state unsuccessfully", - id: config.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - state: bootstrap.Active, - stateErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, - event: nil, - }, - } - - 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.id).Return(config, tc.retrieveErr) - sdkCall1 := tv.sdk.On("Connect", 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)) - - streams := redisClient.XRead(context.Background(), &redis.XReadArgs{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - event := streams[0].Messages - lastID = event[0].ID - } - - test(t, tc.event, event, tc.desc) - sdkCall1.Unset() - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestUpdateChannelHandler(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - channel bootstrap.Channel - err error - event map[string]interface{} - }{ - { - desc: "update channel handler successfully", - channel: channel, - err: nil, - event: map[string]interface{}{ - "channel_id": channel.ID, - "metadata": "{\"name\":\"value\"}", - "name": channel.Name, - "operation": channelUpdateHandler, - "timestamp": time.Now().UnixNano(), - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "update non-existing channel handler", - channel: bootstrap.Channel{ID: "unknown", Name: "NonExistingChannel"}, - err: nil, - event: nil, - }, - { - desc: "update channel handler with empty ID", - channel: bootstrap.Channel{Name: "ChannelWithEmptyID"}, - err: nil, - event: nil, - }, - { - desc: "update channel handler with empty name", - channel: bootstrap.Channel{ID: "3"}, - err: nil, - event: nil, - }, - { - desc: "update channel handler successfully with modified fields", - channel: channel, - err: nil, - event: map[string]interface{}{ - "channel_id": channel.ID, - "metadata": "{\"name\":\"value\"}", - "name": channel.Name, - "operation": channelUpdateHandler, - "timestamp": time.Now().UnixNano(), - "occurred_at": time.Now().UnixNano(), - }, - }, - } - - lastID := "0" - for _, tc := range cases { - repoCall := tv.boot.On("UpdateChannel", context.Background(), mock.Anything).Return(tc.err) - err := tv.svc.UpdateChannelHandler(context.Background(), tc.channel) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - msg := streams[0].Messages[0] - event = msg.Values - event["timestamp"] = msg.ID - lastID = msg.ID - } - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func TestRemoveChannelHandler(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - channelID string - err error - event map[string]interface{} - }{ - { - desc: "remove channel handler successfully", - channelID: channel.ID, - err: nil, - event: map[string]interface{}{ - "config_id": channel.ID, - "operation": channelHandlerRemove, - "timestamp": time.Now().UnixNano(), - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "remove non-existing channel handler", - channelID: "unknown", - err: nil, - event: nil, - }, - { - desc: "remove channel handler with empty ID", - channelID: "", - err: nil, - event: nil, - }, - } - - lastID := "0" - for _, tc := range cases { - repoCall := tv.boot.On("RemoveChannel", context.Background(), mock.Anything).Return(tc.err) - err := tv.svc.RemoveChannelHandler(context.Background(), tc.channelID) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - msg := streams[0].Messages[0] - event = msg.Values - event["timestamp"] = msg.ID - lastID = msg.ID - } - - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func TestRemoveConfigHandler(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - configID string - err error - event map[string]interface{} - }{ - { - desc: "remove config handler successfully", - configID: channel.ID, - err: nil, - event: map[string]interface{}{ - "config_id": channel.ID, - "operation": configHandlerRemove, - "timestamp": time.Now().UnixNano(), - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "remove non-existing config handler", - configID: "unknown", - err: nil, - event: nil, - }, - { - desc: "remove config handler with empty ID", - configID: "", - err: nil, - event: nil, - }, - } - - lastID := "0" - for _, tc := range cases { - repoCall := tv.boot.On("RemoveThing", 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)) - - streams := redisClient.XRead(context.Background(), &redis.XReadArgs{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - msg := streams[0].Messages[0] - event = msg.Values - event["timestamp"] = msg.ID - lastID = msg.ID - } - - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func TestConnectThingHandler(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - channelID string - thingID string - err error - event map[string]interface{} - }{ - { - desc: "connect thing handler successfully", - channelID: channel.ID, - thingID: "1", - err: nil, - event: map[string]interface{}{ - "channel_id": channel.ID, - "thing_id": "1", - "operation": thingConnect, - "timestamp": time.Now().UnixNano(), - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "connect non-existing thing handler", - channelID: channel.ID, - thingID: "unknown", - err: nil, - event: nil, - }, - { - desc: "connect thing handler with empty thing ID", - channelID: channel.ID, - thingID: "", - err: nil, - event: nil, - }, - { - desc: "connect thing handler with empty channel ID", - channelID: "", - thingID: "1", - err: nil, - event: nil, - }, - } - - 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) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - msg := streams[0].Messages[0] - event = msg.Values - event["timestamp"] = msg.ID - lastID = msg.ID - } - - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func TestDisconnectThingHandler(t *testing.T) { - err := redisClient.FlushAll(context.Background()).Err() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - tv := newTestVariable(t, redisURL) - - cases := []struct { - desc string - channelID string - thingID string - err error - event map[string]interface{} - }{ - { - desc: "disconnect thing handler successfully", - channelID: channel.ID, - thingID: "1", - err: nil, - event: map[string]interface{}{ - "channel_id": channel.ID, - "thing_id": "1", - "operation": thingDisconnect, - "timestamp": time.Now().UnixNano(), - "occurred_at": time.Now().UnixNano(), - }, - }, - { - desc: "remove non-existing thing handler", - channelID: "unknown", - err: nil, - }, - { - desc: "remove thing handler with empty thing ID", - channelID: channel.ID, - thingID: "", - err: nil, - event: nil, - }, - { - desc: "remove thing handler with empty channel ID", - channelID: "", - err: nil, - event: nil, - }, - { - desc: "remove thing handler successfully", - channelID: channel.ID, - thingID: "1", - err: nil, - event: map[string]interface{}{ - "channel_id": channel.ID, - "thing_id": "1", - "operation": thingDisconnect, - "timestamp": time.Now().UnixNano(), - "occurred_at": time.Now().UnixNano(), - }, - }, - } - - 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) - 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{ - Streams: []string{streamID, lastID}, - Count: 1, - Block: time.Second, - }).Val() - - var event map[string]interface{} - if len(streams) > 0 && len(streams[0].Messages) > 0 { - msg := streams[0].Messages[0] - event = msg.Values - event["timestamp"] = msg.ID - lastID = msg.ID - } - - test(t, tc.event, event, tc.desc) - repoCall.Unset() - } -} - -func test(t *testing.T, expected, actual map[string]interface{}, description string) { - if expected != nil && actual != nil { - ts1 := expected["timestamp"].(int64) - ats := actual["timestamp"].(string) - ts2, err := strconv.ParseInt(strings.Split(ats, "-")[0], 10, 64) - require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid timestamp, got %s", description, err)) - ts1 = ts1 / 1e9 - ts2 = ts2 / 1e3 - if assert.WithinDuration(t, time.Unix(ts1, 0), time.Unix(ts2, 0), time.Second, fmt.Sprintf("%s: timestamp is not in valid range of 1 second", description)) { - delete(expected, "timestamp") - delete(actual, "timestamp") - } - - oa1 := expected["occurred_at"].(int64) - aoa := actual["occurred_at"].(string) - oa2, err := strconv.ParseInt(aoa, 10, 64) - require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid occurred_at, got %s", description, err)) - oa1 = oa1 / 1e9 - oa2 = oa2 / 1e9 - if assert.WithinDuration(t, time.Unix(oa1, 0), time.Unix(oa2, 0), time.Second, fmt.Sprintf("%s: occurred_at is not in valid range of 1 second", description)) { - delete(expected, "occurred_at") - delete(actual, "occurred_at") - } - - exchs := expected["channels"].([]interface{}) - achs := actual["channels"].([]interface{}) - - if exchs != nil && achs != nil { - if assert.Len(t, exchs, len(achs), fmt.Sprintf("%s: got incorrect number of channels\n", description)) { - for _, exch := range exchs { - assert.Contains(t, achs, exch, fmt.Sprintf("%s: got incorrect channel\n", description)) - } - } - } - - assert.Equal(t, expected, actual, fmt.Sprintf("%s: got incorrect event\n", description)) - } -} diff --git a/docker/addons/vault/scripts/bootstrap/middleware/authorization.go b/docker/addons/vault/scripts/bootstrap/middleware/authorization.go deleted file mode 100644 index cc14e55a..00000000 --- a/docker/addons/vault/scripts/bootstrap/middleware/authorization.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -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" -) - -var _ bootstrap.Service = (*authorizationMiddleware)(nil) - -type authorizationMiddleware struct { - svc bootstrap.Service - authz mgauthz.Authorization -} - -// AuthorizationMiddleware adds authorization to the clients service. -func AuthorizationMiddleware(svc bootstrap.Service, authz mgauthz.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) { - if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID); err != nil { - return bootstrap.Config{}, err - } - - 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 { - 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 { - 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 { - return bootstrap.Config{}, err - } - - return am.svc.UpdateCert(ctx, session, thingID, 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 { - 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) { - if err := am.checkSuperAdmin(ctx, session.DomainUserID); err == nil { - session.SuperAdmin = true - } - if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.AdminPermission, policies.DomainType, session.DomainID); err == nil { - session.SuperAdmin = true - } - - 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 { - return err - } - - return am.svc.Remove(ctx, session, id) -} - -func (am *authorizationMiddleware) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (bootstrap.Config, error) { - 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 { - return am.svc.ChangeState(ctx, session, token, id, state) -} - -func (am *authorizationMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) error { - return am.svc.UpdateChannelHandler(ctx, channel) -} - -func (am *authorizationMiddleware) RemoveConfigHandler(ctx context.Context, id string) error { - return am.svc.RemoveConfigHandler(ctx, id) -} - -func (am *authorizationMiddleware) RemoveChannelHandler(ctx context.Context, id string) error { - 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) DisconnectThingHandler(ctx context.Context, channelID, ThingID string) error { - return am.svc.DisconnectThingHandler(ctx, channelID, ThingID) -} - -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/docker/addons/vault/scripts/bootstrap/middleware/logging.go b/docker/addons/vault/scripts/bootstrap/middleware/logging.go deleted file mode 100644 index 362920d8..00000000 --- a/docker/addons/vault/scripts/bootstrap/middleware/logging.go +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package middleware - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/bootstrap" - mgauthn "github.com/absmach/magistrala/pkg/authn" -) - -var _ bootstrap.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc bootstrap.Service -} - -// LoggingMiddleware adds logging facilities to the bootstrap service. -func LoggingMiddleware(svc bootstrap.Service, logger *slog.Logger) bootstrap.Service { - return &loggingMiddleware{logger, svc} -} - -// Add logs the add 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) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (saved bootstrap.Config, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", saved.ThingID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Add new bootstrap failed", args...) - return - } - lm.logger.Info("Add new bootstrap completed successfully", args...) - }(time.Now()) - - 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. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) View(ctx context.Context, session mgauthn.Session, id string) (saved bootstrap.Config, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View thing config failed", args...) - return - } - lm.logger.Info("View thing 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. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("config", - slog.String("thing_id", cfg.ThingID), - slog.String("name", cfg.Name), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update bootstrap config failed", args...) - return - } - lm.logger.Info("Update bootstrap config completed successfully", args...) - }(time.Now()) - - 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. -// 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", cfg.ThingID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update bootstrap config certificate failed", args...) - return - } - lm.logger.Info("Update bootstrap config certificate completed successfully", args...) - }(time.Now()) - - return lm.svc.UpdateCert(ctx, session, thingID, 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("thing_id", id), - slog.Any("connections", connections), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update config connections failed", args...) - return - } - lm.logger.Info("Update config connections completed successfully", args...) - }(time.Now()) - - return lm.svc.UpdateConnections(ctx, session, token, id, connections) -} - -// 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("page", - slog.Any("filter", filter), - slog.Uint64("offset", offset), - slog.Uint64("limit", limit), - slog.Uint64("total", res.Total), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List configs failed", args...) - return - } - lm.logger.Info("List configs completed successfully", args...) - }(time.Now()) - - return lm.svc.List(ctx, session, filter, offset, limit) -} - -// 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) { - 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("Remove bootstrap config failed", args...) - return - } - lm.logger.Info("Remove bootstrap config completed successfully", args...) - }(time.Now()) - - return lm.svc.Remove(ctx, session, id) -} - -func (lm *loggingMiddleware) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (cfg bootstrap.Config, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("external_id", externalID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View bootstrap config failed", args...) - return - } - lm.logger.Info("View bootstrap completed successfully", args...) - }(time.Now()) - - 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) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("id", id), - slog.Any("state", state), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Change thing state failed", args...) - return - } - lm.logger.Info("Change thing state completed successfully", args...) - }(time.Now()) - - return lm.svc.ChangeState(ctx, session, token, id, state) -} - -func (lm *loggingMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("channel", - slog.String("id", channel.ID), - slog.String("name", channel.Name), - slog.Any("metadata", channel.Metadata), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Update channel handler failed", args...) - return - } - lm.logger.Info("Update channel handler completed successfully", args...) - }(time.Now()) - - return lm.svc.UpdateChannelHandler(ctx, channel) -} - -func (lm *loggingMiddleware) RemoveConfigHandler(ctx context.Context, id string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("config_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Remove config handler failed", args...) - return - } - lm.logger.Info("Remove config handler completed successfully", args...) - }(time.Now()) - - return lm.svc.RemoveConfigHandler(ctx, id) -} - -func (lm *loggingMiddleware) RemoveChannelHandler(ctx context.Context, id string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("channel_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Remove channel handler failed", args...) - return - } - lm.logger.Info("Remove channel handler completed successfully", args...) - }(time.Now()) - - return lm.svc.RemoveChannelHandler(ctx, id) -} - -func (lm *loggingMiddleware) ConnectThingHandler(ctx context.Context, channelID, thingID 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), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Connect thing handler failed", args...) - return - } - lm.logger.Info("Connect thing handler completed successfully", args...) - }(time.Now()) - - return lm.svc.ConnectThingHandler(ctx, channelID, thingID) -} - -func (lm *loggingMiddleware) DisconnectThingHandler(ctx context.Context, channelID, thingID 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), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Disconnect thing handler failed", args...) - return - } - lm.logger.Info("Disconnect thing handler completed successfully", args...) - }(time.Now()) - - return lm.svc.DisconnectThingHandler(ctx, channelID, thingID) -} diff --git a/docker/addons/vault/scripts/bootstrap/middleware/metrics.go b/docker/addons/vault/scripts/bootstrap/middleware/metrics.go deleted file mode 100644 index cd95e4e6..00000000 --- a/docker/addons/vault/scripts/bootstrap/middleware/metrics.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package middleware - -import ( - "context" - "time" - - "github.com/absmach/magistrala/bootstrap" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "github.com/go-kit/kit/metrics" -) - -var _ bootstrap.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc bootstrap.Service -} - -// MetricsMiddleware instruments core service by tracking request count and latency. -func MetricsMiddleware(svc bootstrap.Service, counter metrics.Counter, latency metrics.Histogram) bootstrap.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -// 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) { - defer func(begin time.Time) { - mm.counter.With("method", "add").Add(1) - mm.latency.With("method", "add").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.Add(ctx, session, token, cfg) -} - -// View instruments View method with metrics. -func (mm *metricsMiddleware) View(ctx context.Context, session mgauthn.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()) - }(time.Now()) - - return mm.svc.View(ctx, session, id) -} - -// Update instruments Update method with metrics. -func (mm *metricsMiddleware) Update(ctx context.Context, session mgauthn.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()) - }(time.Now()) - - return mm.svc.Update(ctx, session, cfg) -} - -// 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) { - 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) -} - -// UpdateConnections instruments UpdateConnections method with metrics. -func (mm *metricsMiddleware) UpdateConnections(ctx context.Context, session mgauthn.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()) - }(time.Now()) - - return mm.svc.UpdateConnections(ctx, session, token, id, connections) -} - -// 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) { - defer func(begin time.Time) { - mm.counter.With("method", "list").Add(1) - mm.latency.With("method", "list").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.List(ctx, session, filter, offset, limit) -} - -// Remove instruments Remove method with metrics. -func (mm *metricsMiddleware) Remove(ctx context.Context, session mgauthn.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()) - }(time.Now()) - - return mm.svc.Remove(ctx, session, id) -} - -// Bootstrap instruments Bootstrap method with metrics. -func (mm *metricsMiddleware) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (cfg bootstrap.Config, err error) { - defer func(begin time.Time) { - mm.counter.With("method", "bootstrap").Add(1) - mm.latency.With("method", "bootstrap").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.Bootstrap(ctx, externalKey, externalID, secure) -} - -// ChangeState instruments ChangeState method with metrics. -func (mm *metricsMiddleware) ChangeState(ctx context.Context, session mgauthn.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()) - }(time.Now()) - - return mm.svc.ChangeState(ctx, session, token, id, state) -} - -// UpdateChannelHandler instruments UpdateChannelHandler method with metrics. -func (mm *metricsMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) (err error) { - defer func(begin time.Time) { - mm.counter.With("method", "update_channel").Add(1) - mm.latency.With("method", "update_channel").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.UpdateChannelHandler(ctx, channel) -} - -// RemoveConfigHandler instruments RemoveConfigHandler method with metrics. -func (mm *metricsMiddleware) RemoveConfigHandler(ctx context.Context, id string) (err error) { - defer func(begin time.Time) { - mm.counter.With("method", "remove_config").Add(1) - mm.latency.With("method", "remove_config").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.RemoveConfigHandler(ctx, id) -} - -// RemoveChannelHandler instruments RemoveChannelHandler method with metrics. -func (mm *metricsMiddleware) RemoveChannelHandler(ctx context.Context, id string) (err error) { - defer func(begin time.Time) { - mm.counter.With("method", "remove_channel").Add(1) - mm.latency.With("method", "remove_channel").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.RemoveChannelHandler(ctx, id) -} - -// ConnectThingHandler instruments ConnectThingHandler method with metrics. -func (mm *metricsMiddleware) ConnectThingHandler(ctx context.Context, channelID, thingID 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()) - }(time.Now()) - - return mm.svc.ConnectThingHandler(ctx, channelID, thingID) -} - -// DisconnectThingHandler instruments DisconnectThingHandler method with metrics. -func (mm *metricsMiddleware) DisconnectThingHandler(ctx context.Context, channelID, thingID 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()) - }(time.Now()) - - return mm.svc.DisconnectThingHandler(ctx, channelID, thingID) -} diff --git a/docker/addons/vault/scripts/bootstrap/mocks/config_reader.go b/docker/addons/vault/scripts/bootstrap/mocks/config_reader.go deleted file mode 100644 index 5a3361bd..00000000 --- a/docker/addons/vault/scripts/bootstrap/mocks/config_reader.go +++ /dev/null @@ -1,59 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - bootstrap "github.com/absmach/magistrala/bootstrap" - mock "github.com/stretchr/testify/mock" -) - -// ConfigReader is an autogenerated mock type for the ConfigReader type -type ConfigReader struct { - mock.Mock -} - -// ReadConfig provides a mock function with given fields: _a0, _a1 -func (_m *ConfigReader) ReadConfig(_a0 bootstrap.Config, _a1 bool) (interface{}, error) { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for ReadConfig") - } - - var r0 interface{} - var r1 error - if rf, ok := ret.Get(0).(func(bootstrap.Config, bool) (interface{}, error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(bootstrap.Config, bool) interface{}); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) - } - } - - if rf, ok := ret.Get(1).(func(bootstrap.Config, bool) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewConfigReader creates a new instance of ConfigReader. 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 NewConfigReader(t interface { - mock.TestingT - Cleanup(func()) -}) *ConfigReader { - mock := &ConfigReader{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/docker/addons/vault/scripts/bootstrap/mocks/configs.go b/docker/addons/vault/scripts/bootstrap/mocks/configs.go deleted file mode 100644 index d088cb13..00000000 --- a/docker/addons/vault/scripts/bootstrap/mocks/configs.go +++ /dev/null @@ -1,354 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - bootstrap "github.com/absmach/magistrala/bootstrap" - - mock "github.com/stretchr/testify/mock" -) - -// ConfigRepository is an autogenerated mock type for the ConfigRepository type -type ConfigRepository struct { - mock.Mock -} - -// ChangeState provides a mock function with given fields: ctx, domainID, id, state -func (_m *ConfigRepository) ChangeState(ctx context.Context, domainID string, id string, state bootstrap.State) error { - ret := _m.Called(ctx, domainID, id, state) - - if len(ret) == 0 { - panic("no return value specified for ChangeState") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, bootstrap.State) error); ok { - r0 = rf(ctx, domainID, id, state) - } else { - r0 = ret.Error(0) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for ConnectThing") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, channelID, thingID) - } else { - r0 = ret.Error(0) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for DisconnectThing") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, channelID, thingID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ListExisting provides a mock function with given fields: ctx, domainID, ids -func (_m *ConfigRepository) ListExisting(ctx context.Context, domainID string, ids []string) ([]bootstrap.Channel, error) { - ret := _m.Called(ctx, domainID, ids) - - if len(ret) == 0 { - panic("no return value specified for ListExisting") - } - - var r0 []bootstrap.Channel - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, []string) ([]bootstrap.Channel, error)); ok { - return rf(ctx, domainID, ids) - } - if rf, ok := ret.Get(0).(func(context.Context, string, []string) []bootstrap.Channel); ok { - r0 = rf(ctx, domainID, ids) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]bootstrap.Channel) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok { - r1 = rf(ctx, domainID, ids) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Remove provides a mock function with given fields: ctx, domainID, id -func (_m *ConfigRepository) Remove(ctx context.Context, domainID string, id string) error { - ret := _m.Called(ctx, domainID, 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, domainID, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// RemoveChannel provides a mock function with given fields: ctx, id -func (_m *ConfigRepository) RemoveChannel(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for RemoveChannel") - } - - 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 -} - -// RemoveThing provides a mock function with given fields: ctx, id -func (_m *ConfigRepository) RemoveThing(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for RemoveThing") - } - - 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, 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) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAll") - } - - 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) - } else { - r0 = ret.Get(0).(bootstrap.ConfigsPage) - } - - return r0 -} - -// RetrieveByExternalID provides a mock function with given fields: ctx, externalID -func (_m *ConfigRepository) RetrieveByExternalID(ctx context.Context, externalID string) (bootstrap.Config, error) { - ret := _m.Called(ctx, externalID) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByExternalID") - } - - var r0 bootstrap.Config - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (bootstrap.Config, error)); ok { - return rf(ctx, externalID) - } - if rf, ok := ret.Get(0).(func(context.Context, string) bootstrap.Config); ok { - r0 = rf(ctx, externalID) - } else { - r0 = ret.Get(0).(bootstrap.Config) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, externalID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveByID provides a mock function with given fields: ctx, domainID, id -func (_m *ConfigRepository) RetrieveByID(ctx context.Context, domainID string, id string) (bootstrap.Config, error) { - ret := _m.Called(ctx, domainID, id) - - if len(ret) == 0 { - panic("no return value specified for RetrieveByID") - } - - var r0 bootstrap.Config - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (bootstrap.Config, error)); ok { - return rf(ctx, domainID, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) bootstrap.Config); ok { - r0 = rf(ctx, domainID, id) - } else { - r0 = ret.Get(0).(bootstrap.Config) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, domainID, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, cfg, chsConnIDs -func (_m *ConfigRepository) Save(ctx context.Context, cfg bootstrap.Config, chsConnIDs []string) (string, error) { - ret := _m.Called(ctx, cfg, chsConnIDs) - - 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, bootstrap.Config, []string) (string, error)); ok { - return rf(ctx, cfg, chsConnIDs) - } - if rf, ok := ret.Get(0).(func(context.Context, bootstrap.Config, []string) string); ok { - r0 = rf(ctx, cfg, chsConnIDs) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, bootstrap.Config, []string) error); ok { - r1 = rf(ctx, cfg, chsConnIDs) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Update provides a mock function with given fields: ctx, cfg -func (_m *ConfigRepository) Update(ctx context.Context, cfg bootstrap.Config) error { - ret := _m.Called(ctx, cfg) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, bootstrap.Config) error); ok { - r0 = rf(ctx, cfg) - } else { - r0 = ret.Error(0) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdateCert") - } - - 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) - } - 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) - } 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) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateChannel provides a mock function with given fields: ctx, c -func (_m *ConfigRepository) UpdateChannel(ctx context.Context, c bootstrap.Channel) error { - ret := _m.Called(ctx, c) - - if len(ret) == 0 { - panic("no return value specified for UpdateChannel") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, bootstrap.Channel) error); ok { - r0 = rf(ctx, c) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UpdateConnections provides a mock function with given fields: ctx, domainID, id, channels, connections -func (_m *ConfigRepository) UpdateConnections(ctx context.Context, domainID string, id string, channels []bootstrap.Channel, connections []string) error { - ret := _m.Called(ctx, domainID, id, channels, connections) - - if len(ret) == 0 { - panic("no return value specified for UpdateConnections") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, []bootstrap.Channel, []string) error); ok { - r0 = rf(ctx, domainID, id, channels, connections) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewConfigRepository creates a new instance of ConfigRepository. 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 NewConfigRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *ConfigRepository { - mock := &ConfigRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/docker/addons/vault/scripts/bootstrap/mocks/doc.go b/docker/addons/vault/scripts/bootstrap/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/bootstrap/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/docker/addons/vault/scripts/bootstrap/mocks/service.go b/docker/addons/vault/scripts/bootstrap/mocks/service.go deleted file mode 100644 index 851e6ef1..00000000 --- a/docker/addons/vault/scripts/bootstrap/mocks/service.go +++ /dev/null @@ -1,335 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - bootstrap "github.com/absmach/magistrala/bootstrap" - authn "github.com/absmach/magistrala/pkg/authn" - - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// Add provides a mock function with given fields: ctx, session, token, cfg -func (_m *Service) Add(ctx context.Context, session authn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) { - ret := _m.Called(ctx, session, token, cfg) - - if len(ret) == 0 { - panic("no return value specified for Add") - } - - var r0 bootstrap.Config - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, bootstrap.Config) (bootstrap.Config, error)); ok { - return rf(ctx, session, token, cfg) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, bootstrap.Config) bootstrap.Config); ok { - r0 = rf(ctx, session, token, cfg) - } else { - r0 = ret.Get(0).(bootstrap.Config) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, string, bootstrap.Config) error); ok { - r1 = rf(ctx, session, token, cfg) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Bootstrap provides a mock function with given fields: ctx, externalKey, externalID, secure -func (_m *Service) Bootstrap(ctx context.Context, externalKey string, externalID string, secure bool) (bootstrap.Config, error) { - ret := _m.Called(ctx, externalKey, externalID, secure) - - if len(ret) == 0 { - panic("no return value specified for Bootstrap") - } - - var r0 bootstrap.Config - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) (bootstrap.Config, error)); ok { - return rf(ctx, externalKey, externalID, secure) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) bootstrap.Config); ok { - r0 = rf(ctx, externalKey, externalID, secure) - } else { - r0 = ret.Get(0).(bootstrap.Config) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string, bool) error); ok { - r1 = rf(ctx, externalKey, externalID, secure) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ChangeState provides a mock function with given fields: ctx, session, token, id, state -func (_m *Service) ChangeState(ctx context.Context, session authn.Session, token string, id string, state bootstrap.State) error { - ret := _m.Called(ctx, session, token, id, state) - - if len(ret) == 0 { - panic("no return value specified for ChangeState") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, bootstrap.State) error); ok { - r0 = rf(ctx, session, token, id, state) - } else { - r0 = ret.Error(0) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for ConnectThingHandler") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, channelID, ThingID) - } else { - r0 = ret.Error(0) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for DisconnectThingHandler") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, channelID, ThingID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// List provides a mock function with given fields: ctx, session, filter, offset, limit -func (_m *Service) List(ctx context.Context, session authn.Session, filter bootstrap.Filter, offset uint64, limit uint64) (bootstrap.ConfigsPage, error) { - ret := _m.Called(ctx, session, filter, offset, limit) - - if len(ret) == 0 { - panic("no return value specified for List") - } - - var r0 bootstrap.ConfigsPage - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, bootstrap.Filter, uint64, uint64) (bootstrap.ConfigsPage, error)); ok { - return rf(ctx, session, filter, offset, limit) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, bootstrap.Filter, uint64, uint64) bootstrap.ConfigsPage); ok { - r0 = rf(ctx, session, filter, offset, limit) - } else { - r0 = ret.Get(0).(bootstrap.ConfigsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, authn.Session, bootstrap.Filter, uint64, uint64) error); ok { - r1 = rf(ctx, session, filter, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Remove provides a mock function with given fields: ctx, session, id -func (_m *Service) Remove(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 Remove") - } - - 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 -} - -// RemoveChannelHandler provides a mock function with given fields: ctx, id -func (_m *Service) RemoveChannelHandler(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for RemoveChannelHandler") - } - - 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 -} - -// RemoveConfigHandler provides a mock function with given fields: ctx, id -func (_m *Service) RemoveConfigHandler(ctx context.Context, id string) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for RemoveConfigHandler") - } - - 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 -} - -// Update provides a mock function with given fields: ctx, session, cfg -func (_m *Service) Update(ctx context.Context, session authn.Session, cfg bootstrap.Config) error { - ret := _m.Called(ctx, session, cfg) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, bootstrap.Config) error); ok { - r0 = rf(ctx, session, cfg) - } else { - r0 = ret.Error(0) - } - - 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) - - if len(ret) == 0 { - panic("no return value specified for UpdateCert") - } - - 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) - } - 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) - } 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) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateChannelHandler provides a mock function with given fields: ctx, channel -func (_m *Service) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) error { - ret := _m.Called(ctx, channel) - - if len(ret) == 0 { - panic("no return value specified for UpdateChannelHandler") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, bootstrap.Channel) error); ok { - r0 = rf(ctx, channel) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UpdateConnections provides a mock function with given fields: ctx, session, token, id, connections -func (_m *Service) UpdateConnections(ctx context.Context, session authn.Session, token string, id string, connections []string) error { - ret := _m.Called(ctx, session, token, id, connections) - - if len(ret) == 0 { - panic("no return value specified for UpdateConnections") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string, string, []string) error); ok { - r0 = rf(ctx, session, token, id, connections) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// View provides a mock function with given fields: ctx, session, id -func (_m *Service) View(ctx context.Context, session authn.Session, id string) (bootstrap.Config, error) { - ret := _m.Called(ctx, session, id) - - if len(ret) == 0 { - panic("no return value specified for View") - } - - var r0 bootstrap.Config - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) (bootstrap.Config, error)); ok { - return rf(ctx, session, id) - } - if rf, ok := ret.Get(0).(func(context.Context, authn.Session, string) bootstrap.Config); ok { - r0 = rf(ctx, session, id) - } else { - r0 = ret.Get(0).(bootstrap.Config) - } - - 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/docker/addons/vault/scripts/bootstrap/postgres/configs.go b/docker/addons/vault/scripts/bootstrap/postgres/configs.go deleted file mode 100644 index 6c46a3fe..00000000 --- a/docker/addons/vault/scripts/bootstrap/postgres/configs.go +++ /dev/null @@ -1,778 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "log/slog" - "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/jackc/pgerrcode" - "github.com/jackc/pgtype" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jmoiron/sqlx" -) - -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") -) - -const cleanupQuery = `DELETE FROM channels ch WHERE NOT EXISTS ( - SELECT channel_id FROM connections c WHERE ch.magistrala_channel = c.channel_id);` - -var _ bootstrap.ConfigRepository = (*configRepository)(nil) - -type configRepository struct { - db postgres.Database - log *slog.Logger -} - -// NewConfigRepository instantiates a PostgreSQL implementation of config -// repository. -func NewConfigRepository(db postgres.Database, log *slog.Logger) bootstrap.ConfigRepository { - 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)` - - tx, err := cr.db.BeginTxx(ctx, nil) - if err != nil { - return "", errors.Wrap(repoerr.ErrCreateEntity, err) - } - dbcfg := toDBConfig(cfg) - - defer func() { - if err != nil { - err = cr.rollback("Save method", err, tx) - } - }() - - if _, err := tx.NamedExec(q, dbcfg); err != nil { - switch pgErr := err.(type) { - case *pgconn.PgError: - if pgErr.Code == pgerrcode.UniqueViolation { - err = repoerr.ErrConflict - } - } - return "", err - } - - if err := insertChannels(cfg.DomainID, cfg.Channels, tx); err != nil { - return "", errors.Wrap(errSaveChannels, err) - } - - if err := insertConnections(ctx, cfg, chsConnIDs, tx); err != nil { - return "", errors.Wrap(errSaveConnections, err) - } - - if commitErr := tx.Commit(); commitErr != nil { - return "", commitErr - } - - return cfg.ThingID, 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 - FROM configs - WHERE magistrala_thing = :magistrala_thing AND domain_id = :domain_id` - - dbcfg := dbConfig{ - ThingID: id, - DomainID: domainID, - } - row, err := cr.db.NamedQueryContext(ctx, q, dbcfg) - if err != nil { - if err == sql.ErrNoRows { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, err) - } - - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - if ok := row.Next(); !ok { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) - } - - if err := row.StructScan(&dbcfg); err != nil { - return bootstrap.Config{}, err - } - - 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` - - rows, err := cr.db.NamedQueryContext(ctx, q, dbcfg) - if err != nil { - cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err)) - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - chans := []bootstrap.Channel{} - 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)) - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - dbch.DomainID = nullString(dbcfg.DomainID) - - ch, err := toChannel(dbch) - if err != nil { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - chans = append(chans, ch) - } - - cfg := toConfig(dbcfg) - cfg.Channels = chans - - 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) - 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 = fmt.Sprintf(q, search, n+1, n+2) - - rows, err := cr.db.QueryContext(ctx, q, append(params, limit, offset)...) - if err != nil { - cr.log.Error(fmt.Sprintf("Failed to retrieve configs due to %s", err)) - return bootstrap.ConfigsPage{} - } - defer rows.Close() - - var name, content sql.NullString - configs := []bootstrap.Config{} - - 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 { - cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err)) - return bootstrap.ConfigsPage{} - } - - c.Name = name.String - c.Content = content.String - configs = append(configs, c) - } - - q = fmt.Sprintf(`SELECT COUNT(*) FROM configs %s`, search) - - var total uint64 - if err := cr.db.QueryRowxContext(ctx, q, params...).Scan(&total); err != nil { - cr.log.Error(fmt.Sprintf("Failed to count configs due to %s", err)) - return bootstrap.ConfigsPage{} - } - - return bootstrap.ConfigsPage{ - Total: total, - Limit: limit, - Offset: offset, - Configs: configs, - } -} - -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 - FROM configs - WHERE external_id = :external_id` - dbcfg := dbConfig{ - ExternalID: externalID, - } - - row, err := cr.db.NamedQueryContext(ctx, q, dbcfg) - if err != nil { - if err == sql.ErrNoRows { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, err) - } - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - if ok := row.Next(); !ok { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) - } - - if err := row.StructScan(&dbcfg); err != nil { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - 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` - - rows, err := cr.db.NamedQueryContext(ctx, q, dbcfg) - if err != nil { - cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err)) - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - channels := []bootstrap.Channel{} - 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)) - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - ch, err := toChannel(dbch) - if err != nil { - cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err)) - return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - channels = append(channels, ch) - } - - cfg := toConfig(dbcfg) - cfg.Channels = channels - - return cfg, nil -} - -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 ` - - dbcfg := dbConfig{ - Name: nullString(cfg.Name), - Content: nullString(cfg.Content), - ThingID: cfg.ThingID, - DomainID: cfg.DomainID, - } - - res, err := cr.db.NamedExecContext(ctx, q, dbcfg) - if err != nil { - return errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - cnt, err := res.RowsAffected() - if err != nil { - return errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - if cnt == 0 { - return repoerr.ErrNotFound - } - - 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` - - dbcfg := dbConfig{ - ThingID: thingID, - ClientCert: nullString(clientCert), - DomainID: domainID, - ClientKey: nullString(clientKey), - CaCert: nullString(caCert), - } - - row, err := cr.db.NamedQueryContext(ctx, q, dbcfg) - if err != nil { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrUpdateEntity, err) - } - defer row.Close() - - if ok := row.Next(); !ok { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, row.Err()) - } - - if err := row.StructScan(&dbcfg); err != nil { - return bootstrap.Config{}, err - } - - return toConfig(dbcfg), nil -} - -func (cr configRepository) UpdateConnections(ctx context.Context, domainID, id string, channels []bootstrap.Channel, connections []string) (err error) { - tx, err := cr.db.BeginTxx(ctx, nil) - if err != nil { - return errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - defer func() { - if err != nil { - err = cr.rollback("UpdateConnections method", err, tx) - } else { - if commitErr := tx.Commit(); commitErr != nil { - err = commitErr - } - } - }() - - if err = insertChannels(domainID, channels, tx); err != nil { - err = errors.Wrap(repoerr.ErrUpdateEntity, err) - return err - } - - if err = updateConnections(domainID, id, connections, tx); err != nil { - if e, ok := err.(*pgconn.PgError); ok { - if e.Code == pgerrcode.ForeignKeyViolation { - err = repoerr.ErrNotFound - } - } - err = errors.Wrap(repoerr.ErrUpdateEntity, err) - return err - } - - return nil -} - -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` - dbcfg := dbConfig{ - ThingID: id, - DomainID: domainID, - } - - if _, err := cr.db.NamedExecContext(ctx, q, dbcfg); err != nil { - return errors.Wrap(repoerr.ErrRemoveEntity, err) - } - - if _, err := cr.db.ExecContext(ctx, cleanupQuery); err != nil { - cr.log.Warn("Failed to clean dangling channels after removal") - } - - return nil -} - -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;` - - dbcfg := dbConfig{ - ThingID: id, - State: state, - DomainID: domainID, - } - - res, err := cr.db.NamedExecContext(ctx, q, dbcfg) - if err != nil { - return errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - cnt, err := res.RowsAffected() - if err != nil { - return errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - if cnt == 0 { - return repoerr.ErrNotFound - } - - return nil -} - -func (cr configRepository) ListExisting(ctx context.Context, domainID string, ids []string) ([]bootstrap.Channel, error) { - var channels []bootstrap.Channel - if len(ids) == 0 { - return channels, nil - } - - var chans pgtype.TextArray - if err := chans.Set(ids); err != nil { - return []bootstrap.Channel{}, err - } - - q := "SELECT magistrala_channel, name, metadata FROM channels WHERE domain_id = $1 AND magistrala_channel = ANY ($2)" - rows, err := cr.db.QueryxContext(ctx, q, domainID, chans) - if err != nil { - return []bootstrap.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - for rows.Next() { - var dbch dbChannel - if err := rows.StructScan(&dbch); err != nil { - cr.log.Error(fmt.Sprintf("Failed to read retrieved channels due to %s", err)) - return []bootstrap.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - ch, err := toChannel(dbch) - if err != nil { - cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err)) - return []bootstrap.Channel{}, err - } - - channels = append(channels, ch) - } - - return channels, nil -} - -func (cr configRepository) RemoveThing(ctx context.Context, id string) error { - q := `DELETE FROM configs WHERE magistrala_thing = $1` - _, err := cr.db.ExecContext(ctx, q, id) - - if _, err := cr.db.ExecContext(ctx, cleanupQuery); err != nil { - cr.log.Warn("Failed to clean dangling channels after removal") - } - if err != nil { - return errors.Wrap(repoerr.ErrRemoveEntity, err) - } - return nil -} - -func (cr configRepository) UpdateChannel(ctx context.Context, c bootstrap.Channel) error { - dbch, err := toDBChannel("", c) - if err != nil { - return errors.Wrap(repoerr.ErrUpdateEntity, err) - } - - q := `UPDATE channels SET name = :name, metadata = :metadata, updated_at = :updated_at, updated_by = :updated_by - WHERE magistrala_channel = :magistrala_channel` - if _, err = cr.db.NamedExecContext(ctx, q, dbch); err != nil { - return errors.Wrap(errUpdateChannels, err) - } - return nil -} - -func (cr configRepository) RemoveChannel(ctx context.Context, id string) error { - q := `DELETE FROM channels WHERE magistrala_channel = $1` - if _, err := cr.db.ExecContext(ctx, q, id); err != nil { - return errors.Wrap(errRemoveChannels, err) - } - return nil -} - -func (cr configRepository) ConnectThing(ctx context.Context, channelID, thingID string) error { - q := `UPDATE configs SET state = $1 - WHERE magistrala_thing = $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) - if err != nil { - return errors.Wrap(errConnectThing, err) - } - if rows, _ := result.RowsAffected(); rows == 0 { - return repoerr.ErrNotFound - } - return nil -} - -func (cr configRepository) DisconnectThing(ctx context.Context, channelID, thingID string) error { - q := `UPDATE configs SET state = $1 - WHERE magistrala_thing = $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) - if err != nil { - return errors.Wrap(errDisconnectThing, err) - } - return nil -} - -func buildRetrieveQueryParams(domainID string, thingIDs []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, "','"))) - } else if domainID != "" { - params = append(params, domainID) - queries = append(queries, fmt.Sprintf("domain_id = $%d", len(params))) - } - - // Adjust the starting point for placeholders based on the current length of params - counter := len(params) + 1 - for k, v := range filter.FullMatch { - params = append(params, v) - queries = append(queries, fmt.Sprintf("%s = $%d", k, counter)) - counter++ - } - for k, v := range filter.PartialMatch { - params = append(params, v) - queries = append(queries, fmt.Sprintf("LOWER(%s) LIKE '%%' || $%d || '%%'", k, counter)) - counter++ - } - - if len(queries) > 0 { - return "WHERE " + strings.Join(queries, " AND "), params - } - return "", params -} - -func (cr configRepository) rollback(content string, defErr error, tx *sqlx.Tx) error { - if err := tx.Rollback(); err != nil { - return errors.Wrap(defErr, errors.Wrap(errors.New("failed to rollback at "+content), err)) - } - - return defErr -} - -func insertChannels(domainID string, channels []bootstrap.Channel, tx *sqlx.Tx) error { - if len(channels) == 0 { - return nil - } - - var chans []dbChannel - for _, ch := range channels { - dbch, err := toDBChannel(domainID, ch) - if err != nil { - return err - } - chans = append(chans, dbch) - } - q := `INSERT INTO channels (magistrala_channel, domain_id, name, metadata, parent_id, description, created_at, updated_at, updated_by, status) - VALUES (:magistrala_channel, :domain_id, :name, :metadata, :parent_id, :description, :created_at, :updated_at, :updated_by, :status)` - if _, err := tx.NamedExec(q, chans); err != nil { - e := err - if pqErr, ok := err.(*pgconn.PgError); ok && pqErr.Code == pgerrcode.UniqueViolation { - e = repoerr.ErrConflict - } - return e - } - - return nil -} - -func insertConnections(_ context.Context, cfg bootstrap.Config, connections []string, tx *sqlx.Tx) error { - if len(connections) == 0 { - return nil - } - - q := `INSERT INTO connections (config_id, channel_id, domain_id) - VALUES (:config_id, :channel_id, :domain_id)` - - conns := []dbConnection{} - for _, conn := range connections { - dbconn := dbConnection{ - Config: cfg.ThingID, - Channel: conn, - DomainID: cfg.DomainID, - } - conns = append(conns, dbconn) - } - _, err := tx.NamedExec(q, conns) - - return err -} - -func updateConnections(domainID, id string, connections []string, tx *sqlx.Tx) error { - if len(connections) == 0 { - return nil - } - - q := `DELETE FROM connections - WHERE config_id = $1 AND domain_id = $2 - AND channel_id NOT IN ($3)` - - var conn pgtype.TextArray - if err := conn.Set(connections); err != nil { - return err - } - - res, err := tx.Exec(q, id, domainID, conn) - if err != nil { - return err - } - - cnt, err := res.RowsAffected() - if err != nil { - return err - } - - q = `INSERT INTO connections (config_id, channel_id, domain_id) - VALUES (:config_id, :channel_id, :domain_id)` - - conns := []dbConnection{} - for _, conn := range connections { - dbconn := dbConnection{ - Config: id, - Channel: conn, - DomainID: domainID, - } - conns = append(conns, dbconn) - } - - if _, err := tx.NamedExec(q, conns); err != nil { - return err - } - - if cnt == 0 { - return nil - } - - _, err = tx.Exec(cleanupQuery) - - return err -} - -func nullString(s string) sql.NullString { - if s == "" { - return sql.NullString{} - } - - return sql.NullString{ - String: s, - Valid: true, - } -} - -func nullTime(t time.Time) sql.NullTime { - if t.IsZero() { - return sql.NullTime{} - } - - return sql.NullTime{ - Time: t, - Valid: true, - } -} - -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"` -} - -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, - } -} - -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, - } - - if dbcfg.Name.Valid { - cfg.Name = dbcfg.Name.String - } - - if dbcfg.Content.Valid { - cfg.Content = dbcfg.Content.String - } - - if dbcfg.ClientCert.Valid { - cfg.ClientCert = dbcfg.ClientCert.String - } - - if dbcfg.ClientKey.Valid { - cfg.ClientKey = dbcfg.ClientKey.String - } - - if dbcfg.CaCert.Valid { - cfg.CACert = dbcfg.CaCert.String - } - return cfg -} - -type dbChannel struct { - ID string `db:"magistrala_channel"` - Name sql.NullString `db:"name"` - DomainID sql.NullString `db:"domain_id"` - Metadata string `db:"metadata"` - Parent sql.NullString `db:"parent_id,omitempty"` - Description string `db:"description,omitempty"` - 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"` -} - -func toDBChannel(domainID string, ch bootstrap.Channel) (dbChannel, error) { - dbch := dbChannel{ - ID: ch.ID, - Name: nullString(ch.Name), - DomainID: nullString(domainID), - Parent: nullString(ch.Parent), - Description: ch.Description, - CreatedAt: ch.CreatedAt, - UpdatedAt: nullTime(ch.UpdatedAt), - UpdatedBy: nullString(ch.UpdatedBy), - Status: ch.Status, - } - - metadata, err := json.Marshal(ch.Metadata) - if err != nil { - return dbChannel{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - - dbch.Metadata = string(metadata) - return dbch, nil -} - -func toChannel(dbch dbChannel) (bootstrap.Channel, error) { - ch := bootstrap.Channel{ - ID: dbch.ID, - Description: dbch.Description, - CreatedAt: dbch.CreatedAt, - Status: dbch.Status, - } - - if dbch.Name.Valid { - ch.Name = dbch.Name.String - } - if dbch.DomainID.Valid { - ch.DomainID = dbch.DomainID.String - } - if dbch.Parent.Valid { - ch.Parent = dbch.Parent.String - } - if dbch.UpdatedBy.Valid { - ch.UpdatedBy = dbch.UpdatedBy.String - } - if dbch.UpdatedAt.Valid { - ch.UpdatedAt = dbch.UpdatedAt.Time - } - - if err := json.Unmarshal([]byte(dbch.Metadata), &ch.Metadata); err != nil { - return bootstrap.Channel{}, errors.Wrap(errors.ErrMalformedEntity, err) - } - - return ch, nil -} - -type dbConnection struct { - Config string `db:"config_id"` - Channel string `db:"channel_id"` - DomainID string `db:"domain_id"` -} diff --git a/docker/addons/vault/scripts/bootstrap/postgres/configs_test.go b/docker/addons/vault/scripts/bootstrap/postgres/configs_test.go deleted file mode 100644 index 584ddd42..00000000 --- a/docker/addons/vault/scripts/bootstrap/postgres/configs_test.go +++ /dev/null @@ -1,913 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "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/gofrs/uuid/v5" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const numConfigs = 10 - -var ( - config = bootstrap.Config{ - ThingID: "mg-thing", - ThingKey: "mg-key", - ExternalID: "external-id", - ExternalKey: "external-key", - DomainID: testsutil.GenerateUUID(&testing.T{}), - Channels: []bootstrap.Channel{ - {ID: "1", Name: "name 1", Metadata: map[string]interface{}{"meta": 1.0}}, - {ID: "2", Name: "name 2", Metadata: map[string]interface{}{"meta": 2.0}}, - }, - Content: "content", - State: bootstrap.Inactive, - } - - channels = []string{"1", "2"} -) - -func TestSave(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - diff := "different" - - duplicateThing := config - duplicateThing.ExternalID = diff - duplicateThing.ThingKey = diff - duplicateThing.Channels = []bootstrap.Channel{} - - duplicateExternal := config - duplicateExternal.ThingID = diff - duplicateExternal.ThingKey = diff - duplicateExternal.Channels = []bootstrap.Channel{} - - duplicateChannels := config - duplicateChannels.ExternalID = diff - duplicateChannels.ThingKey = diff - duplicateChannels.ThingID = diff - - cases := []struct { - desc string - config bootstrap.Config - connections []string - err error - }{ - { - desc: "save a config", - config: config, - connections: channels, - err: nil, - }, - { - desc: "save config with same Thing ID", - config: duplicateThing, - connections: nil, - err: repoerr.ErrConflict, - }, - { - desc: "save config with same external ID", - config: duplicateExternal, - connections: nil, - err: repoerr.ErrConflict, - }, - { - desc: "save config with same Channels", - config: duplicateChannels, - connections: channels, - err: repoerr.ErrConflict, - }, - } - for _, tc := range cases { - 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)) - } - } -} - -func TestRetrieveByID(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - id, err := repo.Save(context.Background(), c, channels) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - nonexistentConfID, err := uuid.NewV4() - require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - - cases := []struct { - desc string - domainID string - id string - err error - }{ - { - desc: "retrieve config", - domainID: c.DomainID, - id: id, - err: nil, - }, - { - desc: "retrieve config with wrong domain ID ", - domainID: "2", - id: id, - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve a non-existing config", - domainID: c.DomainID, - id: nonexistentConfID.String(), - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve a config with invalid ID", - domainID: c.DomainID, - id: "invalid", - err: repoerr.ErrNotFound, - }, - } - for _, tc := range cases { - _, err := repo.RetrieveByID(context.Background(), tc.domainID, tc.id) - 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) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - thingIDs := make([]string, numConfigs) - - for i := 0; i < numConfigs; i++ { - c := config - - // Use UUID to prevent conflict errors. - uid, err := uuid.NewV4() - 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() - - thingIDs[i] = c.ThingID - - if i%2 == 0 { - c.State = bootstrap.Active - } - - if i > 0 { - c.Channels = nil - } - - _, err = repo.Save(context.Background(), c, channels) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - } - cases := []struct { - desc string - domainID string - thingID []string - offset uint64 - limit uint64 - filter bootstrap.Filter - size int - }{ - { - desc: "retrieve all configs", - domainID: config.DomainID, - thingID: []string{}, - offset: 0, - limit: uint64(numConfigs), - size: numConfigs, - }, - { - desc: "retrieve a subset of configs", - domainID: config.DomainID, - thingID: []string{}, - offset: 5, - limit: uint64(numConfigs - 5), - size: numConfigs - 5, - }, - { - desc: "retrieve with wrong domain ID ", - domainID: "2", - thingID: []string{}, - offset: 0, - limit: uint64(numConfigs), - size: 0, - }, - { - desc: "retrieve all active configs ", - domainID: config.DomainID, - thingID: []string{}, - offset: 0, - limit: uint64(numConfigs), - filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}}, - size: numConfigs / 2, - }, - { - desc: "retrieve all with partial match filter", - domainID: config.DomainID, - thingID: []string{}, - offset: 0, - limit: uint64(numConfigs), - filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}}, - size: 1, - }, - { - desc: "retrieve search by name", - domainID: config.DomainID, - thingID: []string{}, - offset: 0, - limit: uint64(numConfigs), - filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}}, - size: 1, - }, - { - desc: "retrieve by valid thingIDs", - domainID: config.DomainID, - thingID: thingIDs, - offset: 0, - limit: uint64(numConfigs), - size: 10, - }, - { - desc: "retrieve by non-existing thingID", - domainID: config.DomainID, - thingID: []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) - size := len(ret.Configs) - assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.size, size)) - } -} - -func TestRetrieveByExternalID(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - _, err = repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - cases := []struct { - desc string - externalID string - err error - }{ - { - desc: "retrieve with invalid external ID", - externalID: strconv.Itoa(numConfigs + 1), - err: repoerr.ErrNotFound, - }, - { - desc: "retrieve with external key", - externalID: c.ExternalID, - err: nil, - }, - } - for _, tc := range cases { - _, err := repo.RetrieveByExternalID(context.Background(), tc.externalID) - 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) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - _, err = repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - c.Content = "new content" - c.Name = "new name" - - wrongDomainID := c - wrongDomainID.DomainID = "3" - - cases := []struct { - desc string - id string - config bootstrap.Config - err error - }{ - { - desc: "update with wrong domainID ", - config: wrongDomainID, - err: repoerr.ErrNotFound, - }, - { - desc: "update a config", - config: c, - err: nil, - }, - } - for _, tc := range cases { - err := repo.Update(context.Background(), tc.config) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - } -} - -func TestUpdateCert(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - _, err = repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - c.Content = "new content" - c.Name = "new name" - - wrongDomainID := c - wrongDomainID.DomainID = "3" - - cases := []struct { - desc string - thingID string - domainID string - cert string - certKey string - ca string - expectedConfig bootstrap.Config - err error - }{ - { - desc: "update with wrong domain ID ", - thingID: "", - cert: "cert", - certKey: "certKey", - ca: "", - domainID: wrongDomainID.DomainID, - expectedConfig: bootstrap.Config{}, - err: repoerr.ErrNotFound, - }, - { - desc: "update a config", - thingID: c.ThingID, - cert: "cert", - certKey: "certKey", - ca: "ca", - domainID: c.DomainID, - expectedConfig: bootstrap.Config{ - ThingID: c.ThingID, - ClientCert: "cert", - CACert: "ca", - ClientKey: "certKey", - DomainID: c.DomainID, - }, - err: nil, - }, - } - for _, tc := range cases { - cfg, err := repo.UpdateCert(context.Background(), tc.domainID, tc.thingID, 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)) - } -} - -func TestUpdateConnections(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - _, err = repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - c.Channels = []bootstrap.Channel{} - c2, err := repo.Save(context.Background(), c, []string{channels[0]}) - assert.Nil(t, err, fmt.Sprintf("Saving a config expected to succeed: %s.\n", err)) - - cases := []struct { - desc string - domainID string - id string - channels []bootstrap.Channel - connections []string - err error - }{ - { - desc: "update connections of non-existing config", - domainID: config.DomainID, - id: "unknown", - channels: nil, - connections: []string{channels[1]}, - err: repoerr.ErrNotFound, - }, - { - desc: "update connections", - domainID: config.DomainID, - id: c.ThingID, - channels: nil, - connections: []string{channels[1]}, - err: nil, - }, - { - desc: "update connections with existing channels", - domainID: config.DomainID, - id: c2, - channels: nil, - connections: channels, - err: nil, - }, - { - desc: "update connections no channels", - domainID: config.DomainID, - id: c.ThingID, - channels: nil, - connections: nil, - err: nil, - }, - } - for _, tc := range cases { - err := repo.UpdateConnections(context.Background(), tc.domainID, tc.id, tc.channels, tc.connections) - 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) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - id, err := repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - // Removal works the same for both existing and non-existing - // (removed) config - for i := 0; i < 2; i++ { - err := repo.Remove(context.Background(), c.DomainID, id) - assert.Nil(t, err, fmt.Sprintf("%d: failed to remove config due to: %s", i, err)) - - _, err = repo.RetrieveByID(context.Background(), c.DomainID, id) - assert.True(t, errors.Contains(err, repoerr.ErrNotFound), fmt.Sprintf("%d: expected %s got %s", i, repoerr.ErrNotFound, err)) - } -} - -func TestChangeState(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.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)) - - cases := []struct { - desc string - domainID string - id string - state bootstrap.State - err error - }{ - { - desc: "change state with wrong domain ID ", - id: saved, - domainID: "2", - err: repoerr.ErrNotFound, - }, - { - desc: "change state with wrong id", - id: "wrong", - domainID: c.DomainID, - err: repoerr.ErrNotFound, - }, - { - desc: "change state to Active", - id: saved, - domainID: c.DomainID, - state: bootstrap.Active, - err: nil, - }, - { - desc: "change state to Inactive", - id: saved, - domainID: c.DomainID, - state: bootstrap.Inactive, - err: nil, - }, - } - for _, tc := range cases { - err := repo.ChangeState(context.Background(), tc.domainID, 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)) - } -} - -func TestListExisting(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - _, err = repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - var chs []bootstrap.Channel - chs = append(chs, config.Channels...) - - cases := []struct { - desc string - domainID string - connections []string - existing []bootstrap.Channel - }{ - { - desc: "list all existing channels", - domainID: c.DomainID, - connections: channels, - existing: chs, - }, - { - desc: "list a subset of existing channels", - domainID: c.DomainID, - connections: []string{channels[0], "5"}, - existing: []bootstrap.Channel{chs[0]}, - }, - { - desc: "list a subset of existing channels empty", - domainID: c.DomainID, - connections: []string{"5", "6"}, - existing: []bootstrap.Channel{}, - }, - } - for _, tc := range cases { - existing, err := repo.ListExisting(context.Background(), tc.domainID, tc.connections) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error: %s", tc.desc, err)) - assert.ElementsMatch(t, tc.existing, existing, fmt.Sprintf("%s: Got non-matching elements.", tc.desc)) - } -} - -func TestRemoveThing(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.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) - assert.Nil(t, err, fmt.Sprintf("an unexpected error occurred: %s\n", err)) - } -} - -func TestUpdateChannel(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - _, err = repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - id := c.Channels[0].ID - update := bootstrap.Channel{ - ID: id, - Name: "update name", - Metadata: map[string]interface{}{"update": "metadata update"}, - } - 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) - assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err)) - var retreved bootstrap.Channel - for _, c := range cfg.Channels { - if c.ID == id { - retreved = c - break - } - } - update.DomainID = retreved.DomainID - assert.Equal(t, update, retreved, fmt.Sprintf("expected %s, go %s", update, retreved)) -} - -func TestRemoveChannel(t *testing.T) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - _, err = repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - 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) - 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) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - c.State = bootstrap.Inactive - saved, err := repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - wrongID := testsutil.GenerateUUID(&testing.T{}) - - connectedThing := c - - randomThing := c - randomThingID, _ := uuid.NewV4() - randomThing.ThingID = randomThingID.String() - - emptyThing := c - emptyThing.ThingID = "" - - cases := []struct { - desc string - domainID string - id string - state bootstrap.State - channels []bootstrap.Channel - connections []string - err error - }{ - { - desc: "connect disconnected thing", - domainID: c.DomainID, - id: saved, - state: bootstrap.Inactive, - channels: c.Channels, - connections: channels, - err: nil, - }, - { - desc: "connect already connected thing", - domainID: c.DomainID, - id: connectedThing.ThingID, - state: connectedThing.State, - channels: c.Channels, - connections: channels, - err: nil, - }, - { - desc: "connect non-existent thing", - domainID: c.DomainID, - id: wrongID, - channels: c.Channels, - connections: channels, - err: repoerr.ErrNotFound, - }, - { - desc: "connect random thing", - domainID: c.DomainID, - id: randomThing.ThingID, - channels: c.Channels, - connections: channels, - err: repoerr.ErrNotFound, - }, - { - desc: "connect empty thing", - domainID: c.DomainID, - id: emptyThing.ThingID, - channels: c.Channels, - connections: channels, - err: repoerr.ErrNotFound, - }, - } - for _, tc := range cases { - for i, ch := range tc.channels { - if i == 0 { - err = repo.ConnectThing(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) - 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) - } - } - - cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ThingID) - 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) { - repo := postgres.NewConfigRepository(db, testLog) - err := deleteChannels(context.Background(), repo) - require.Nil(t, err, "Channels cleanup expected to succeed.") - - c := config - // 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.ExternalID = uid.String() - c.ExternalKey = uid.String() - c.State = bootstrap.Inactive - saved, err := repo.Save(context.Background(), c, channels) - assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - wrongID := testsutil.GenerateUUID(&testing.T{}) - - connectedThing := c - - randomThing := c - randomThingID, _ := uuid.NewV4() - randomThing.ThingID = randomThingID.String() - - emptyThing := c - emptyThing.ThingID = "" - - cases := []struct { - desc string - domainID string - id string - state bootstrap.State - channels []bootstrap.Channel - connections []string - err error - }{ - { - desc: "disconnect connected thing", - domainID: c.DomainID, - id: connectedThing.ThingID, - state: connectedThing.State, - channels: c.Channels, - connections: channels, - err: nil, - }, - { - desc: "disconnect already disconnected thing", - domainID: c.DomainID, - id: saved, - state: bootstrap.Inactive, - channels: c.Channels, - connections: channels, - err: nil, - }, - { - desc: "disconnect invalid thing", - domainID: c.DomainID, - id: wrongID, - channels: c.Channels, - connections: channels, - err: nil, - }, - { - desc: "disconnect random thing", - domainID: c.DomainID, - id: randomThing.ThingID, - channels: c.Channels, - connections: channels, - err: nil, - }, - { - desc: "disconnect empty thing", - domainID: c.DomainID, - id: emptyThing.ThingID, - channels: c.Channels, - connections: channels, - err: nil, - }, - } - - for _, tc := range cases { - for _, ch := range tc.channels { - err = repo.DisconnectThing(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) - 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)) - } -} - -func deleteChannels(ctx context.Context, repo bootstrap.ConfigRepository) error { - for _, ch := range channels { - if err := repo.RemoveChannel(ctx, ch); err != nil { - return err - } - } - - return nil -} diff --git a/docker/addons/vault/scripts/bootstrap/postgres/doc.go b/docker/addons/vault/scripts/bootstrap/postgres/doc.go deleted file mode 100644 index 73a67847..00000000 --- a/docker/addons/vault/scripts/bootstrap/postgres/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres contains repository implementations using PostgreSQL as -// the underlying database. -package postgres diff --git a/docker/addons/vault/scripts/bootstrap/postgres/init.go b/docker/addons/vault/scripts/bootstrap/postgres/init.go deleted file mode 100644 index f562551c..00000000 --- a/docker/addons/vault/scripts/bootstrap/postgres/init.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import migrate "github.com/rubenv/sql-migrate" - -// Migration of bootstrap service. -func Migration() *migrate.MemoryMigrationSource { - return &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "configs_1", - Up: []string{ - `CREATE TABLE IF NOT EXISTS configs ( - mainflux_thing TEXT UNIQUE NOT NULL, - owner VARCHAR(254), - name TEXT, - mainflux_key CHAR(36) UNIQUE NOT NULL, - external_id TEXT UNIQUE NOT NULL, - external_key TEXT NOT NULL, - content TEXT, - client_cert TEXT, - client_key TEXT, - ca_cert TEXT, - state BIGINT NOT NULL, - PRIMARY KEY (mainflux_thing, owner) - )`, - `CREATE TABLE IF NOT EXISTS unknown_configs ( - external_id TEXT UNIQUE NOT NULL, - external_key TEXT NOT NULL, - PRIMARY KEY (external_id, external_key) - )`, - `CREATE TABLE IF NOT EXISTS channels ( - mainflux_channel TEXT UNIQUE NOT NULL, - owner VARCHAR(254), - name TEXT, - metadata JSON, - PRIMARY KEY (mainflux_channel, owner) - )`, - `CREATE TABLE IF NOT EXISTS connections ( - channel_id TEXT, - channel_owner VARCHAR(256), - 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, - PRIMARY KEY (channel_id, channel_owner, config_id, config_owner) - )`, - }, - Down: []string{ - "DROP TABLE connections", - "DROP TABLE configs", - "DROP TABLE channels", - "DROP TABLE unknown_configs", - }, - }, - { - Id: "configs_2", - Up: []string{ - "DROP TABLE IF EXISTS unknown_configs", - }, - Down: []string{ - "CREATE TABLE IF NOT EXISTS unknown_configs", - }, - }, - { - Id: "configs_3", - Up: []string{ - `ALTER TABLE IF EXISTS channels ADD COLUMN IF NOT EXISTS parent_id VARCHAR(36)`, - `ALTER TABLE IF EXISTS channels ADD COLUMN IF NOT EXISTS description VARCHAR(1024)`, - `ALTER TABLE IF EXISTS channels ADD COLUMN IF NOT EXISTS created_at TIMESTAMP`, - `ALTER TABLE IF EXISTS channels ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP`, - `ALTER TABLE IF EXISTS channels ADD COLUMN IF NOT EXISTS updated_by VARCHAR(254)`, - `ALTER TABLE IF EXISTS channels ADD COLUMN IF NOT EXISTS status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0)`, - }, - }, - { - 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 channels RENAME COLUMN mainflux_channel TO magistrala_channel`, - }, - }, - { - Id: "configs_5", - Up: []string{ - `ALTER TABLE IF EXISTS configs RENAME COLUMN owner TO domain_id`, - `ALTER TABLE IF EXISTS channels RENAME COLUMN owner TO domain_id`, - `ALTER TABLE IF EXISTS configs ADD CONSTRAINT configs_name_domain_id_key UNIQUE (name, domain_id)`, - }, - }, - { - Id: "configs_6", - Up: []string{ - `ALTER TABLE IF EXISTS connections DROP CONSTRAINT IF EXISTS connections_pkey`, - `ALTER TABLE IF EXISTS connections DROP COLUMN IF EXISTS channel_owner`, - `ALTER TABLE IF EXISTS connections DROP COLUMN IF EXISTS config_owner`, - `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`, - }, - }, - }, - } -} diff --git a/docker/addons/vault/scripts/bootstrap/postgres/setup_test.go b/docker/addons/vault/scripts/bootstrap/postgres/setup_test.go deleted file mode 100644 index 3848cd49..00000000 --- a/docker/addons/vault/scripts/bootstrap/postgres/setup_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "fmt" - "log" - "os" - "testing" - - "github.com/absmach/magistrala/bootstrap/postgres" - mglog "github.com/absmach/magistrala/logger" - pgclient "github.com/absmach/magistrala/pkg/postgres" - "github.com/jmoiron/sqlx" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" -) - -var ( - testLog, _ = mglog.New(os.Stdout, "info") - db *sqlx.DB -) - -func TestMain(m *testing.M) { - pool, err := dockertest.NewPool("") - if err != nil { - testLog.Error(fmt.Sprintf("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") - - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err = sqlx.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - testLog.Error(fmt.Sprintf("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, *postgres.Migration()); err != nil { - testLog.Error(fmt.Sprintf("Could not setup test DB connection: %s", err)) - } - - code := m.Run() - - // Defers will not be run when using os.Exit - db.Close() - if err := pool.Purge(container); err != nil { - testLog.Error(fmt.Sprintf("Could not purge container: %s", err)) - } - - os.Exit(code) -} diff --git a/docker/addons/vault/scripts/bootstrap/reader.go b/docker/addons/vault/scripts/bootstrap/reader.go deleted file mode 100644 index dd435808..00000000 --- a/docker/addons/vault/scripts/bootstrap/reader.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bootstrap - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/json" - "io" - "net/http" -) - -// bootstrapRes represent Magistrala 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"` -} - -type channelRes struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Metadata interface{} `json:"metadata,omitempty"` -} - -func (res bootstrapRes) Code() int { - return http.StatusOK -} - -func (res bootstrapRes) Headers() map[string]string { - return map[string]string{} -} - -func (res bootstrapRes) Empty() bool { - return false -} - -type reader struct { - encKey []byte -} - -// NewConfigReader return new reader which is used to generate response -// from the config. -func NewConfigReader(encKey []byte) ConfigReader { - return reader{encKey: encKey} -} - -func (r reader) ReadConfig(cfg Config, secure bool) (interface{}, error) { - var channels []channelRes - for _, ch := range cfg.Channels { - channels = append(channels, channelRes{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata}) - } - - res := bootstrapRes{ - ThingKey: cfg.ThingKey, - ThingID: cfg.ThingID, - Channels: channels, - Content: cfg.Content, - ClientCert: cfg.ClientCert, - ClientKey: cfg.ClientKey, - CACert: cfg.CACert, - } - if secure { - b, err := json.Marshal(res) - if err != nil { - return nil, err - } - return r.encrypt(b) - } - - return res, nil -} - -func (r reader) encrypt(in []byte) ([]byte, error) { - block, err := aes.NewCipher(r.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/docker/addons/vault/scripts/bootstrap/reader_test.go b/docker/addons/vault/scripts/bootstrap/reader_test.go deleted file mode 100644 index c283f336..00000000 --- a/docker/addons/vault/scripts/bootstrap/reader_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bootstrap_test - -import ( - "crypto/aes" - "crypto/cipher" - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/absmach/magistrala" - "github.com/absmach/magistrala/bootstrap" - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" -) - -type readChan struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Metadata interface{} `json:"metadata,omitempty"` -} - -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"` -} - -func dec(in []byte) ([]byte, error) { - block, err := aes.NewCipher(encKey) - if err != nil { - return nil, err - } - if len(in) < aes.BlockSize { - return nil, errors.ErrMalformedEntity - } - iv := in[:aes.BlockSize] - in = in[aes.BlockSize:] - stream := cipher.NewCFBDecrypter(block, iv) - stream.XORKeyStream(in, in) - return in, nil -} - -func TestReadConfig(t *testing.T) { - cfg := bootstrap.Config{ - ThingID: "mg_id", - ClientCert: "client_cert", - ClientKey: "client_key", - CACert: "ca_cert", - ThingKey: "mg_key", - Channels: []bootstrap.Channel{ - { - ID: "mg_id", - Name: "mg_name", - Metadata: map[string]interface{}{"key": "value}"}, - }, - }, - Content: "content", - } - ret := readResp{ - ThingID: "mg_id", - ThingKey: "mg_key", - Channels: []readChan{ - { - ID: "mg_id", - Name: "mg_name", - Metadata: map[string]interface{}{"key": "value}"}, - }, - }, - Content: "content", - ClientCert: "client_cert", - ClientKey: "client_key", - CACert: "ca_cert", - } - - bin, err := json.Marshal(ret) - assert.Nil(t, err, fmt.Sprintf("Marshalling expected to succeed: %s.\n", err)) - - reader := bootstrap.NewConfigReader(encKey) - cases := []struct { - desc string - config bootstrap.Config - enc []byte - secret bool - err error - }{ - { - desc: "read a config", - config: cfg, - enc: bin, - secret: false, - }, - { - desc: "read encrypted config", - config: cfg, - enc: bin, - secret: true, - }, - } - - for _, tc := range cases { - res, err := reader.ReadConfig(tc.config, tc.secret) - assert.Nil(t, err, fmt.Sprintf("Reading config to succeed: %s.\n", err)) - - if tc.secret { - d, err := dec(res.([]byte)) - assert.Nil(t, err, fmt.Sprintf("Decrypting expected to succeed: %s.\n", err)) - assert.Equal(t, tc.enc, d, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.enc, d)) - continue - } - 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) - 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/docker/addons/vault/scripts/bootstrap/service.go b/docker/addons/vault/scripts/bootstrap/service.go deleted file mode 100644 index 91976bd5..00000000 --- a/docker/addons/vault/scripts/bootstrap/service.go +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bootstrap - -import ( - "context" - "crypto/aes" - "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" -) - -var ( - // ErrThings indicates failure to communicate with Magistrala Things service. - // It can be due to networking error or invalid/unauthenticated request. - ErrThings = errors.New("failed to receive response from Things service") - - // ErrExternalKey indicates a non-existent bootstrap configuration for given external key. - ErrExternalKey = errors.New("failed to get bootstrap configuration for given external key") - - // ErrExternalKeySecure indicates error in getting bootstrap configuration for given encrypted external key. - ErrExternalKeySecure = errors.New("failed to get bootstrap configuration for given encrypted external key") - - // ErrBootstrap indicates error in getting bootstrap configuration. - ErrBootstrap = errors.New("failed to read bootstrap configuration") - - // ErrAddBootstrap indicates error in adding bootstrap configuration. - ErrAddBootstrap = errors.New("failed to add bootstrap configuration") - - // ErrNotInSameDomain indicates entities are not in the same domain. - errNotInSameDomain = errors.New("entities are not in the same domain") - - errUpdateConnections = errors.New("failed to update connections") - errRemoveBootstrap = errors.New("failed to remove bootstrap configuration") - errChangeState = errors.New("failed to change state of bootstrap configuration") - 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") - errCheckChannels = errors.New("failed to check if channels exists") - errConnectionChannels = errors.New("failed to check channels connections") - errThingNotFound = errors.New("failed to find thing") - errUpdateCert = errors.New("failed to update cert") -) - -var _ Service = (*bootstrapService)(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 { - // 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) - - // 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) - - // Update updates editable fields of the provided Config. - Update(ctx context.Context, session mgauthn.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) - - // UpdateConnections updates list of Channels related to given Config. - UpdateConnections(ctx context.Context, session mgauthn.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) - - // Remove removes Config with specified token that belongs to the user identified by the given token. - Remove(ctx context.Context, session mgauthn.Session, id string) error - - // Bootstrap returns Config to the Thing with provided external ID using external key. - Bootstrap(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 - - // Methods RemoveConfig, UpdateChannel, and RemoveChannel are used as - // handlers for events. That's why these methods surpass ownership check. - - // UpdateChannelHandler updates Channel with data received from an event. - UpdateChannelHandler(ctx context.Context, channel Channel) error - - // RemoveConfigHandler removes Configuration with id received from an event. - RemoveConfigHandler(ctx context.Context, id string) error - - // 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 - - // DisconnectThingHandler changes state of the Config to inactive when disconnect event occurs. - DisconnectThingHandler(ctx context.Context, channelID, ThingID string) error -} - -// ConfigReader is used to parse Config into format which will be encoded -// as a JSON and consumed from the client side. The purpose of this interface -// is to provide convenient way to generate custom configuration response -// based on the specific Config which will be consumed by the client. -// -//go:generate mockery --name ConfigReader --output=./mocks --filename config_reader.go --quiet --note "Copyright (c) Abstract Machines" -type ConfigReader interface { - ReadConfig(Config, bool) (interface{}, error) -} - -type bootstrapService struct { - policies policies.Service - configs ConfigRepository - sdk mgsdk.SDK - encKey []byte - idProvider magistrala.IDProvider -} - -// New returns new Bootstrap service. -func New(policyService policies.Service, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte, idp magistrala.IDProvider) Service { - return &bootstrapService{ - configs: configs, - sdk: sdk, - policies: policyService, - encKey: encKey, - idProvider: idp, - } -} - -func (bs bootstrapService) Add(ctx context.Context, session mgauthn.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. - existing, err := bs.configs.ListExisting(ctx, session.DomainID, toConnect) - if err != nil { - return Config{}, errors.Wrap(errCheckChannels, err) - } - - cfg.Channels, err = bs.connectionChannels(toConnect, bs.toIDList(existing), session.DomainID, token) - if err != nil { - return Config{}, errors.Wrap(errConnectionChannels, err) - } - - id := cfg.ThingID - mgThing, err := bs.thing(session.DomainID, id, token) - if err != nil { - return Config{}, errors.Wrap(errThingNotFound, err) - } - - for _, channel := range cfg.Channels { - if channel.DomainID != mgThing.DomainID { - return Config{}, errors.Wrap(svcerr.ErrMalformedEntity, errNotInSameDomain) - } - } - - cfg.ThingID = mgThing.ID - cfg.DomainID = session.DomainID - cfg.State = Inactive - cfg.ThingKey = mgThing.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 == "" { - if errT := bs.sdk.DeleteThing(cfg.ThingID, cfg.DomainID, token); errT != nil { - err = errors.Wrap(err, errT) - } - } - return Config{}, errors.Wrap(ErrAddBootstrap, err) - } - - cfg.ThingID = saved - cfg.Channels = append(cfg.Channels, existing...) - - return cfg, nil -} - -func (bs bootstrapService) View(ctx context.Context, session mgauthn.Session, id string) (Config, error) { - cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id) - if err != nil { - return Config{}, errors.Wrap(svcerr.ErrViewEntity, err) - } - return cfg, nil -} - -func (bs bootstrapService) Update(ctx context.Context, session mgauthn.Session, cfg Config) error { - cfg.DomainID = session.DomainID - if err := bs.configs.Update(ctx, cfg); err != nil { - return errors.Wrap(errUpdateConnections, err) - } - 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) - 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 { - cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id) - if err != nil { - return errors.Wrap(errUpdateConnections, err) - } - - add, remove := bs.updateList(cfg, connections) - - // Check if channels exist. This is the way to prevent fetching channels that already exist. - existing, err := bs.configs.ListExisting(ctx, session.DomainID, connections) - if err != nil { - return errors.Wrap(errUpdateConnections, err) - } - - channels, err := bs.connectionChannels(connections, bs.toIDList(existing), session.DomainID, token) - if err != nil { - return errors.Wrap(errUpdateConnections, err) - } - - cfg.Channels = channels - var connect, disconnect []string - - if cfg.State == Active { - connect = add - disconnect = remove - } - - for _, c := range disconnect { - if err := bs.sdk.DisconnectThing(id, c, session.DomainID, token); err != nil { - if errors.Contains(err, repoerr.ErrNotFound) { - continue - } - return ErrThings - } - } - - for _, c := range connect { - conIDs := mgsdk.Connection{ - ChannelID: c, - ThingID: id, - } - if err := bs.sdk.Connect(conIDs, session.DomainID, token); err != nil { - return ErrThings - } - } - if err := bs.configs.UpdateConnections(ctx, session.DomainID, id, channels, connections); err != nil { - return errors.Wrap(errUpdateConnections, err) - } - return nil -} - -func (bs bootstrapService) listClientIDs(ctx context.Context, userID string) ([]string, error) { - tids, err := bs.policies.ListAllObjects(ctx, policies.Policy{ - SubjectType: policies.UserType, - Subject: userID, - Permission: policies.ViewPermission, - ObjectType: policies.ThingType, - }) - if err != nil { - return nil, errors.Wrap(svcerr.ErrNotFound, err) - } - return tids.Policies, nil -} - -func (bs bootstrapService) List(ctx context.Context, session mgauthn.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) - if err != nil { - return ConfigsPage{}, errors.Wrap(svcerr.ErrNotFound, err) - } - - if len(thingIDs) == 0 { - return ConfigsPage{ - Total: 0, - Offset: offset, - Limit: limit, - Configs: []Config{}, - }, nil - } - - return bs.configs.RetrieveAll(ctx, session.DomainID, thingIDs, filter, offset, limit), nil -} - -func (bs bootstrapService) Remove(ctx context.Context, session mgauthn.Session, id string) error { - if err := bs.configs.Remove(ctx, session.DomainID, id); err != nil { - return errors.Wrap(errRemoveBootstrap, err) - } - return nil -} - -func (bs bootstrapService) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (Config, error) { - cfg, err := bs.configs.RetrieveByExternalID(ctx, externalID) - if err != nil { - return cfg, errors.Wrap(ErrBootstrap, err) - } - if secure { - dec, err := bs.dec(externalKey) - if err != nil { - return Config{}, errors.Wrap(ErrExternalKeySecure, err) - } - externalKey = dec - } - if cfg.ExternalKey != externalKey { - return Config{}, ErrExternalKey - } - - return cfg, nil -} - -func (bs bootstrapService) ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state State) error { - cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id) - if err != nil { - return errors.Wrap(errChangeState, err) - } - - if cfg.State == state { - return nil - } - - 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 { - // Ignore conflict errors as they indicate the connection already exists. - if errors.Contains(err, svcerr.ErrConflict) { - continue - } - return ErrThings - } - } - case Inactive: - for _, c := range cfg.Channels { - if err := bs.sdk.DisconnectThing(cfg.ThingID, c.ID, session.DomainID, token); err != nil { - if errors.Contains(err, repoerr.ErrNotFound) { - continue - } - return ErrThings - } - } - } - if err := bs.configs.ChangeState(ctx, session.DomainID, id, state); err != nil { - return errors.Wrap(errChangeState, err) - } - return nil -} - -func (bs bootstrapService) UpdateChannelHandler(ctx context.Context, channel Channel) error { - if err := bs.configs.UpdateChannel(ctx, channel); err != nil { - return errors.Wrap(errUpdateChannel, err) - } - return nil -} - -func (bs bootstrapService) RemoveConfigHandler(ctx context.Context, id string) error { - if err := bs.configs.RemoveThing(ctx, id); err != nil { - return errors.Wrap(errRemoveConfig, err) - } - return nil -} - -func (bs bootstrapService) RemoveChannelHandler(ctx context.Context, id string) error { - if err := bs.configs.RemoveChannel(ctx, id); err != nil { - return errors.Wrap(errRemoveChannel, err) - } - 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) - } - 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) - } - 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. - if id == "" { - id, err := bs.idProvider.ID() - if err != nil { - return mgsdk.Thing{}, errors.Wrap(errCreateThing, err) - } - thing, sdkErr := bs.sdk.CreateThing(mgsdk.Thing{ID: id, Name: "Bootstrapped Thing " + id}, domainID, token) - if sdkErr != nil { - return mgsdk.Thing{}, errors.Wrap(errCreateThing, sdkErr) - } - return thing, nil - } - - // If Thing ID is provided, then retrieve thing - thing, sdkErr := bs.sdk.Thing(id, domainID, token) - if sdkErr != nil { - return mgsdk.Thing{}, errors.Wrap(ErrThings, sdkErr) - } - return thing, nil -} - -func (bs bootstrapService) connectionChannels(channels, existing []string, domainID, token string) ([]Channel, error) { - add := make(map[string]bool, len(channels)) - for _, ch := range channels { - add[ch] = true - } - - for _, ch := range existing { - if add[ch] { - delete(add, ch) - } - } - - var ret []Channel - for id := range add { - ch, err := bs.sdk.Channel(id, domainID, token) - if err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) - } - - ret = append(ret, Channel{ - ID: ch.ID, - Name: ch.Name, - Metadata: ch.Metadata, - DomainID: ch.DomainID, - }) - } - - return ret, nil -} - -// Method updateList accepts config and channel IDs and returns three lists: -// 1) IDs of Channels to be added -// 2) IDs of Channels to be removed -// 3) IDs of common Channels for these two configs. -func (bs bootstrapService) updateList(cfg Config, connections []string) (add, remove []string) { - disconnect := make(map[string]bool, len(cfg.Channels)) - for _, c := range cfg.Channels { - disconnect[c.ID] = true - } - - for _, c := range connections { - if disconnect[c] { - // Don't disconnect common elements. - delete(disconnect, c) - continue - } - // Connect new elements. - add = append(add, c) - } - - for v := range disconnect { - remove = append(remove, v) - } - - return -} - -func (bs bootstrapService) toIDList(channels []Channel) []string { - var ret []string - for _, ch := range channels { - ret = append(ret, ch.ID) - } - - return ret -} - -func (bs bootstrapService) dec(in string) (string, error) { - ciphertext, err := hex.DecodeString(in) - if err != nil { - return "", err - } - block, err := aes.NewCipher(bs.encKey) - if err != nil { - return "", err - } - if len(ciphertext) < aes.BlockSize { - return "", err - } - iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] - stream := cipher.NewCFBDecrypter(block, iv) - stream.XORKeyStream(ciphertext, ciphertext) - return string(ciphertext), nil -} diff --git a/docker/addons/vault/scripts/bootstrap/service_test.go b/docker/addons/vault/scripts/bootstrap/service_test.go deleted file mode 100644 index f2918f2e..00000000 --- a/docker/addons/vault/scripts/bootstrap/service_test.go +++ /dev/null @@ -1,1113 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bootstrap_test - -import ( - "context" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/hex" - "fmt" - "io" - "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/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const ( - validToken = "validToken" - invalidToken = "invalid" - invalidDomainID = "invalid" - email = "test@example.com" - unknown = "unknown" - channelsNum = 3 - instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" -) - -var ( - encKey = []byte("1234567891011121") - domainID = testsutil.GenerateUUID(&testing.T{}) - channel = bootstrap.Channel{ - ID: testsutil.GenerateUUID(&testing.T{}), - Name: "name", - Metadata: map[string]interface{}{"name": "value"}, - } - - 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", - } -) - -var ( - boot *mocks.ConfigRepository - policies *policymocks.Service - sdk *sdkmocks.SDK -) - -func newService() bootstrap.Service { - boot = new(mocks.ConfigRepository) - policies = new(policymocks.Service) - sdk = new(sdkmocks.SDK) - idp := uuid.NewMock() - return bootstrap.New(policies, boot, sdk, encKey, idp) -} - -func enc(in []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 -} - -func TestAdd(t *testing.T) { - svc := newService() - - neID := config - neID.ThingID = "non-existent" - - wrongChannels := config - ch := channel - ch.ID = "invalid" - wrongChannels.Channels = append(wrongChannels.Channels, ch) - - cases := []struct { - desc string - config bootstrap.Config - token string - session mgauthn.Session - userID string - domainID string - thingErr error - createThingErr error - channelErr error - listExistingErr error - saveErr error - deleteThingErr error - err error - }{ - { - desc: "add a new config", - config: config, - token: validToken, - userID: validID, - domainID: domainID, - 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 invalid list of channels", - config: wrongChannels, - token: validToken, - userID: validID, - domainID: domainID, - listExistingErr: svcerr.ErrMalformedEntity, - err: svcerr.ErrMalformedEntity, - }, - { - desc: "add empty config", - config: bootstrap.Config{}, - token: validToken, - userID: validID, - domainID: domainID, - }, - } - - 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) - 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) - 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 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: "view an existing config", - configID: config.ThingID, - userID: validID, - thingDomain: 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 config with invalid domain", - configID: config.ThingID, - userID: validID, - thingDomain: 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) - _, 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() - }) - } -} - -func TestUpdate(t *testing.T) { - svc := newService() - - c := config - ch := channel - ch.ID = "2" - c.Channels = append(c.Channels, ch) - - modifiedCreated := c - modifiedCreated.Content = "new-config" - modifiedCreated.Name = "new name" - - nonExisting := c - nonExisting.ThingID = unknown - - cases := []struct { - desc string - config bootstrap.Config - token string - session mgauthn.Session - userID string - domainID string - updateErr error - err error - }{ - { - desc: "update a config with state Created", - config: modifiedCreated, - token: validToken, - userID: validID, - domainID: domainID, - err: nil, - }, - { - desc: "update a non-existing config", - config: nonExisting, - token: validToken, - userID: validID, - domainID: domainID, - updateErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "update a config with update error", - config: c, - token: validToken, - userID: validID, - domainID: domainID, - updateErr: svcerr.ErrUpdateEntity, - err: svcerr.ErrUpdateEntity, - }, - } - - 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 := 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)) - repoCall.Unset() - }) - } -} - -func TestUpdateCert(t *testing.T) { - svc := newService() - - c := config - ch := channel - ch.ID = "2" - c.Channels = append(c.Channels, ch) - - cases := []struct { - desc string - token string - session mgauthn.Session - userID string - domainID string - thingID string - clientCert string - clientKey string - caCert string - expectedConfig bootstrap.Config - authorizeErr error - authenticateErr error - updateErr error - err error - }{ - { - desc: "update certs for the valid config", - userID: validID, - domainID: domainID, - thingID: c.ThingID, - 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", - }, - err: nil, - }, - { - desc: "update cert for a non-existing config", - userID: validID, - domainID: domainID, - thingID: "empty", - clientCert: "newCert", - clientKey: "newKey", - caCert: "newCert", - token: validToken, - expectedConfig: bootstrap.Config{}, - updateErr: 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.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) - 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 - }) - sort.Slice(tc.expectedConfig.Channels, func(i, j int) bool { - return tc.expectedConfig.Channels[i].ID < tc.expectedConfig.Channels[j].ID - }) - assert.Equal(t, tc.expectedConfig, cfg, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.expectedConfig, cfg)) - repoCall.Unset() - }) - } -} - -func TestUpdateConnections(t *testing.T) { - svc := newService() - - c := config - c.State = bootstrap.Inactive - - activeConf := config - activeConf.State = bootstrap.Active - - ch := channel - - cases := []struct { - desc string - token string - session mgauthn.Session - id string - state bootstrap.State - userID string - domainID string - connections []string - updateErr error - thingErr error - channelErr error - retrieveErr error - listErr error - err error - }{ - { - desc: "update connections for config with state Inactive", - token: validToken, - userID: validID, - domainID: domainID, - id: c.ThingID, - state: c.State, - connections: []string{ch.ID}, - err: nil, - }, - { - desc: "update connections for config with state Active", - token: validToken, - userID: validID, - domainID: domainID, - id: activeConf.ThingID, - state: activeConf.State, - connections: []string{ch.ID}, - err: nil, - }, - { - desc: "update connections with invalid channels", - token: validToken, - userID: validID, - domainID: domainID, - id: c.ThingID, - connections: []string{"wrong"}, - channelErr: errors.NewSDKError(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.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) - repoCall2 := boot.On("UpdateConnections", context.Background(), mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.updateErr) - err := svc.UpdateConnections(context.Background(), tc.session, tc.token, tc.id, tc.connections) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - sdkCall.Unset() - repoCall.Unset() - repoCall1.Unset() - repoCall2.Unset() - }) - } -} - -func TestList(t *testing.T) { - svc := newService() - - numThings := 101 - var saved []bootstrap.Config - for i := 0; i < numThings; i++ { - c := config - c.ExternalID = testsutil.GenerateUUID(t) - c.ExternalKey = testsutil.GenerateUUID(t) - c.Name = fmt.Sprintf("%s-%d", config.Name, i) - if i == 41 { - c.State = bootstrap.Active - } - saved = append(saved, c) - } - cases := []struct { - desc string - config bootstrap.ConfigsPage - filter bootstrap.Filter - offset uint64 - limit uint64 - token string - session mgauthn.Session - userID string - domainID string - listObjectsResponse policysvc.PolicyPage - listObjectsErr error - retrieveErr error - err error - }{ - { - desc: "list configs successfully as super admin", - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 0, - Limit: 10, - Configs: saved[0:10], - }, - filter: bootstrap.Filter{}, - token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - userID: validID, - domainID: domainID, - offset: 0, - limit: 10, - err: nil, - }, - { - desc: "list configs with failed super admin check", - config: bootstrap.ConfigsPage{}, - filter: bootstrap.Filter{}, - token: validID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - userID: validID, - domainID: domainID, - listObjectsResponse: policysvc.PolicyPage{}, - offset: 0, - limit: 10, - err: nil, - }, - { - desc: "list configs successfully as domain admin", - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 0, - Limit: 10, - Configs: saved[0:10], - }, - filter: bootstrap.Filter{}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - listObjectsResponse: policysvc.PolicyPage{}, - offset: 0, - limit: 10, - err: nil, - }, - { - desc: "list configs successfully as non admin", - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 0, - Limit: 10, - Configs: saved[0:10], - }, - filter: bootstrap.Filter{}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - listObjectsResponse: policysvc.PolicyPage{Policies: []string{"test", "test"}}, - offset: 0, - limit: 10, - err: nil, - }, - { - desc: "list configs with specified name as super admin", - config: bootstrap.ConfigsPage{ - Total: 1, - Offset: 0, - Limit: 100, - Configs: saved[95:96], - }, - filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "95"}}, - token: validToken, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - userID: validID, - domainID: domainID, - offset: 0, - limit: 100, - err: nil, - }, - { - desc: "list configs with specified name as domain admin", - config: bootstrap.ConfigsPage{ - Total: 1, - Offset: 0, - Limit: 100, - Configs: saved[95:96], - }, - filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "95"}}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - offset: 0, - limit: 100, - err: nil, - }, - { - desc: "list configs with specified name as non admin", - config: bootstrap.ConfigsPage{ - Total: 1, - Offset: 0, - Limit: 100, - Configs: saved[95:96], - }, - filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "95"}}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - listObjectsResponse: policysvc.PolicyPage{Policies: []string{"test", "test"}}, - offset: 0, - limit: 100, - err: nil, - }, - { - desc: "list last page as super admin", - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 95, - Limit: 10, - Configs: saved[95:], - }, - filter: bootstrap.Filter{}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - offset: 95, - limit: 10, - err: nil, - }, - { - desc: "list last page as domain admin", - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 95, - Limit: 10, - Configs: saved[95:], - }, - filter: bootstrap.Filter{}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - offset: 95, - limit: 10, - err: nil, - }, - { - desc: "list last page as non admin", - config: bootstrap.ConfigsPage{ - Total: uint64(len(saved)), - Offset: 95, - Limit: 10, - Configs: saved[95:], - }, - filter: bootstrap.Filter{}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - listObjectsResponse: policysvc.PolicyPage{Policies: []string{"test", "test"}}, - offset: 95, - limit: 10, - err: nil, - }, - { - desc: "list configs with Active state as super admin", - config: bootstrap.ConfigsPage{ - Total: 1, - Offset: 35, - Limit: 20, - Configs: []bootstrap.Config{saved[41]}, - }, - filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - offset: 35, - limit: 20, - err: nil, - }, - { - desc: "list configs with Active state as domain admin", - config: bootstrap.ConfigsPage{ - Total: 1, - Offset: 35, - Limit: 20, - Configs: []bootstrap.Config{saved[41]}, - }, - filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true}, - offset: 35, - limit: 20, - err: nil, - }, - { - desc: "list configs with Active state as non admin", - config: bootstrap.ConfigsPage{ - Total: 1, - Offset: 35, - Limit: 20, - Configs: []bootstrap.Config{saved[41]}, - }, - filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}}, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - listObjectsResponse: policysvc.PolicyPage{Policies: []string{"test", "test"}}, - offset: 35, - limit: 20, - err: nil, - }, - { - desc: "list configs with failed to list objects", - config: bootstrap.ConfigsPage{}, - filter: bootstrap.Filter{}, - offset: 0, - limit: 10, - token: validToken, - userID: validID, - domainID: domainID, - session: mgauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID}, - listObjectsResponse: policysvc.PolicyPage{}, - listObjectsErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - policyCall := policies.On("ListAllObjects", mock.Anything, policysvc.Policy{ - SubjectType: policysvc.UserType, - Subject: tc.userID, - Permission: policysvc.ViewPermission, - ObjectType: policysvc.ThingType, - }).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) - - result, err := svc.List(context.Background(), tc.session, tc.filter, tc.offset, tc.limit) - assert.ElementsMatch(t, tc.config.Configs, result.Configs, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Configs, result.Configs)) - assert.Equal(t, tc.config.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Total, result.Total)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - policyCall.Unset() - repoCall.Unset() - }) - } -} - -func TestRemove(t *testing.T) { - svc := newService() - - c := config - cases := []struct { - desc string - id string - token string - session mgauthn.Session - userID string - domainID string - removeErr error - err error - }{ - { - desc: "remove an existing config", - id: c.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - err: nil, - }, - { - desc: "remove removed config", - id: c.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - err: nil, - }, - { - desc: "remove a config with failed remove", - id: c.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - removeErr: svcerr.ErrRemoveEntity, - err: svcerr.ErrRemoveEntity, - }, - } - - 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 := 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)) - repoCall.Unset() - }) - } -} - -func TestBootstrap(t *testing.T) { - svc := newService() - - c := config - e, err := enc([]byte(c.ExternalKey)) - assert.Nil(t, err, fmt.Sprintf("Encrypting external key expected to succeed: %s.\n", err)) - - cases := []struct { - desc string - config bootstrap.Config - externalKey string - externalID string - userID string - domainID string - err error - encrypted bool - }{ - { - desc: "bootstrap using invalid external id", - config: bootstrap.Config{}, - externalID: "invalid", - externalKey: c.ExternalKey, - userID: validID, - domainID: invalidDomainID, - err: svcerr.ErrNotFound, - encrypted: false, - }, - { - desc: "bootstrap using invalid external key", - config: bootstrap.Config{}, - externalID: c.ExternalID, - externalKey: "invalid", - userID: validID, - domainID: domainID, - err: bootstrap.ErrExternalKey, - encrypted: false, - }, - { - desc: "bootstrap an existing config", - config: c, - externalID: c.ExternalID, - externalKey: c.ExternalKey, - userID: validID, - domainID: domainID, - err: nil, - encrypted: false, - }, - { - desc: "bootstrap encrypted", - config: c, - externalID: c.ExternalID, - externalKey: hex.EncodeToString(e), - userID: validID, - domainID: domainID, - err: nil, - encrypted: true, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := boot.On("RetrieveByExternalID", context.Background(), mock.Anything).Return(tc.config, tc.err) - config, err := svc.Bootstrap(context.Background(), tc.externalKey, tc.externalID, tc.encrypted) - assert.Equal(t, tc.config, config, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.config, config)) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - }) - } -} - -func TestChangeState(t *testing.T) { - svc := newService() - - c := config - cases := []struct { - desc string - state bootstrap.State - id string - token string - session mgauthn.Session - userID string - domainID string - retrieveErr error - connectErr errors.SDKError - disconenctErr error - stateErr error - err error - }{ - { - desc: "change state of non-existing config", - state: bootstrap.Active, - id: unknown, - token: validToken, - userID: validID, - domainID: domainID, - retrieveErr: svcerr.ErrNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "change state to Active", - state: bootstrap.Active, - id: c.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - err: nil, - }, - { - desc: "change state to current state", - state: bootstrap.Active, - id: c.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - err: nil, - }, - { - desc: "change state to Inactive", - state: bootstrap.Inactive, - id: c.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - err: nil, - }, - { - desc: "change state with failed Connect", - state: bootstrap.Active, - id: c.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - connectErr: errors.NewSDKError(bootstrap.ErrThings), - err: bootstrap.ErrThings, - }, - { - desc: "change state with invalid state", - state: bootstrap.State(2), - id: c.ThingID, - token: validToken, - userID: validID, - domainID: domainID, - stateErr: svcerr.ErrMalformedEntity, - err: svcerr.ErrMalformedEntity, - }, - } - - 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 := 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) - 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)) - sdkCall.Unset() - repoCall.Unset() - repoCall1.Unset() - }) - } -} - -func TestUpdateChannelHandler(t *testing.T) { - svc := newService() - - ch := bootstrap.Channel{ - ID: channel.ID, - Name: "new name", - Metadata: map[string]interface{}{"meta": "new"}, - } - - cases := []struct { - desc string - channel bootstrap.Channel - err error - }{ - { - desc: "update an existing channel", - channel: ch, - err: nil, - }, - { - desc: "update a non-existing channel", - channel: bootstrap.Channel{ID: ""}, - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := boot.On("UpdateChannel", context.Background(), mock.Anything).Return(tc.err) - err := svc.UpdateChannelHandler(context.Background(), tc.channel) - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - repoCall.Unset() - }) - } -} - -func TestRemoveChannelHandler(t *testing.T) { - svc := newService() - - cases := []struct { - desc string - id string - err error - }{ - { - desc: "remove an existing channel", - id: config.Channels[0].ID, - err: nil, - }, - { - desc: "remove a non-existing channel", - id: "unknown", - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := boot.On("RemoveChannel", context.Background(), mock.Anything).Return(tc.err) - err := svc.RemoveChannelHandler(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() - }) - } -} - -func TestRemoveConfigHandler(t *testing.T) { - svc := newService() - - cases := []struct { - desc string - id string - err error - }{ - { - desc: "remove an existing config", - id: config.ThingID, - err: nil, - }, - { - desc: "remove a non-existing channel", - id: "unknown", - err: nil, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repoCall := boot.On("RemoveThing", 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() - }) - } -} - -func TestConnectThingsHandler(t *testing.T) { - svc := newService() - - cases := []struct { - desc string - thingID string - channelID string - err error - }{ - { - desc: "connect", - channelID: channel.ID, - thingID: config.ThingID, - err: nil, - }, - { - desc: "connect connected", - channelID: channel.ID, - thingID: config.ThingID, - 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) - 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) { - svc := newService() - - cases := []struct { - desc string - thingID string - channelID string - err error - }{ - { - desc: "disconnect", - channelID: channel.ID, - thingID: config.ThingID, - err: nil, - }, - { - desc: "disconnect disconnected", - channelID: channel.ID, - thingID: config.ThingID, - 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) - 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/docker/addons/vault/scripts/bootstrap/state.go b/docker/addons/vault/scripts/bootstrap/state.go deleted file mode 100644 index da8acccb..00000000 --- a/docker/addons/vault/scripts/bootstrap/state.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bootstrap - -import "strconv" - -const ( - // Inactive Thing is created, but not able to exchange messages using Magistrala. - Inactive State = iota - // Active Thing is created, configured, and whitelisted. - Active -) - -// State represents corresponding Magistrala Thing 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 |. -type State int - -// String returns string representation of State. -func (s State) String() string { - return strconv.Itoa(int(s)) -} diff --git a/docker/addons/vault/scripts/bootstrap/tracing/doc.go b/docker/addons/vault/scripts/bootstrap/tracing/doc.go deleted file mode 100644 index 5aa1b44b..00000000 --- a/docker/addons/vault/scripts/bootstrap/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/docker/addons/vault/scripts/bootstrap/tracing/tracing.go b/docker/addons/vault/scripts/bootstrap/tracing/tracing.go deleted file mode 100644 index fee7e354..00000000 --- a/docker/addons/vault/scripts/bootstrap/tracing/tracing.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/bootstrap" - mgauthn "github.com/absmach/magistrala/pkg/authn" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ bootstrap.Service = (*tracingMiddleware)(nil) - -type tracingMiddleware struct { - tracer trace.Tracer - svc bootstrap.Service -} - -// New returns a new bootstrap service with tracing capabilities. -func New(svc bootstrap.Service, tracer trace.Tracer) bootstrap.Service { - return &tracingMiddleware{tracer, svc} -} - -// Add traces the "Add" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) { - ctx, span := tm.tracer.Start(ctx, "svc_register_user", trace.WithAttributes( - attribute.String("thing_id", cfg.ThingID), - attribute.String("domain_id ", cfg.DomainID), - attribute.String("name", cfg.Name), - attribute.String("external_id", cfg.ExternalID), - attribute.String("content", cfg.Content), - attribute.String("state", cfg.State.String()), - )) - defer span.End() - - return tm.svc.Add(ctx, session, token, cfg) -} - -// 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) { - 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) -} - -// Update traces the "Update" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) Update(ctx context.Context, session mgauthn.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("domain_id ", cfg.DomainID), - )) - defer span.End() - - return tm.svc.Update(ctx, session, cfg) -} - -// 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) { - ctx, span := tm.tracer.Start(ctx, "svc_update_cert", trace.WithAttributes( - attribute.String("thing_id", thingID), - )) - defer span.End() - - return tm.svc.UpdateCert(ctx, session, thingID, 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 { - ctx, span := tm.tracer.Start(ctx, "svc_update_connections", trace.WithAttributes( - attribute.String("id", id), - attribute.StringSlice("connections", connections), - )) - defer span.End() - - return tm.svc.UpdateConnections(ctx, session, token, id, connections) -} - -// 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) { - ctx, span := tm.tracer.Start(ctx, "svc_list_users", trace.WithAttributes( - attribute.Int64("offset", int64(offset)), - attribute.Int64("limit", int64(limit)), - )) - defer span.End() - - return tm.svc.List(ctx, session, filter, offset, limit) -} - -// Remove traces the "Remove" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) Remove(ctx context.Context, session mgauthn.Session, id string) error { - ctx, span := tm.tracer.Start(ctx, "svc_remove_user", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - - return tm.svc.Remove(ctx, session, id) -} - -// Bootstrap traces the "Bootstrap" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (bootstrap.Config, error) { - ctx, span := tm.tracer.Start(ctx, "svc_bootstrap_user", trace.WithAttributes( - attribute.String("external_key", externalKey), - attribute.String("external_id", externalID), - attribute.Bool("secure", secure), - )) - defer span.End() - - return tm.svc.Bootstrap(ctx, externalKey, externalID, secure) -} - -// 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 { - ctx, span := tm.tracer.Start(ctx, "svc_change_state", trace.WithAttributes( - attribute.String("id", id), - attribute.String("state", state.String()), - )) - defer span.End() - - return tm.svc.ChangeState(ctx, session, token, id, state) -} - -// UpdateChannelHandler traces the "UpdateChannelHandler" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) error { - ctx, span := tm.tracer.Start(ctx, "svc_update_channel_handler", trace.WithAttributes( - attribute.String("id", channel.ID), - attribute.String("name", channel.Name), - attribute.String("description", channel.Description), - )) - defer span.End() - - return tm.svc.UpdateChannelHandler(ctx, channel) -} - -// RemoveConfigHandler traces the "RemoveConfigHandler" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) RemoveConfigHandler(ctx context.Context, id string) error { - ctx, span := tm.tracer.Start(ctx, "svc_remove_config_handler", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - - return tm.svc.RemoveConfigHandler(ctx, id) -} - -// RemoveChannelHandler traces the "RemoveChannelHandler" operation of the wrapped bootstrap.Service. -func (tm *tracingMiddleware) RemoveChannelHandler(ctx context.Context, id string) error { - ctx, span := tm.tracer.Start(ctx, "svc_remove_channel_handler", trace.WithAttributes( - attribute.String("id", id), - )) - defer span.End() - - 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( - attribute.String("channel_id", channelID), - attribute.String("thing_id", thingID), - )) - defer span.End() - - return tm.svc.ConnectThingHandler(ctx, channelID, thingID) -} - -// 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( - attribute.String("channel_id", channelID), - attribute.String("thing_id", thingID), - )) - defer span.End() - - return tm.svc.DisconnectThingHandler(ctx, channelID, thingID) -} diff --git a/docker/addons/vault/scripts/certs/README.md b/docker/addons/vault/scripts/certs/README.md deleted file mode 100644 index b7f2b3cf..00000000 --- a/docker/addons/vault/scripts/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=<https://vault-domain:8200> -MG_CERTS_VAULT_NAMESPACE=<vault_namespace> -MG_CERTS_VAULT_APPROLE_ROLEID=<vault_approle_roleid> -MG_CERTS_VAULT_APPROLE_SECRET=<vault_approle_sceret> -MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH=<vault_things_certs_pki_path> -MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME=<vault_things_certs_issue_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/docker/addons/vault/scripts/certs/api/doc.go b/docker/addons/vault/scripts/certs/api/doc.go deleted file mode 100644 index 943cf198..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/api/endpoint.go b/docker/addons/vault/scripts/certs/api/endpoint.go deleted file mode 100644 index 8e03f472..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/api/endpoint_test.go b/docker/addons/vault/scripts/certs/api/endpoint_test.go deleted file mode 100644 index 6cc2c143..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/api/logging.go b/docker/addons/vault/scripts/certs/api/logging.go deleted file mode 100644 index 7a8c3b7d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/api/metrics.go b/docker/addons/vault/scripts/certs/api/metrics.go deleted file mode 100644 index 9f78fd01..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/api/requests.go b/docker/addons/vault/scripts/certs/api/requests.go deleted file mode 100644 index 54bea166..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/api/responses.go b/docker/addons/vault/scripts/certs/api/responses.go deleted file mode 100644 index 4b5f15d4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/api/transport.go b/docker/addons/vault/scripts/certs/api/transport.go deleted file mode 100644 index 4d71d1aa..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/certs.go b/docker/addons/vault/scripts/certs/certs.go deleted file mode 100644 index f1d4f1bb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/certs_test.go b/docker/addons/vault/scripts/certs/certs_test.go deleted file mode 100644 index 3ee7dc74..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/doc.go b/docker/addons/vault/scripts/certs/doc.go deleted file mode 100644 index 24a19874..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/mocks/doc.go b/docker/addons/vault/scripts/certs/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/mocks/pki.go b/docker/addons/vault/scripts/certs/mocks/pki.go deleted file mode 100644 index 3daf9318..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/mocks/service.go b/docker/addons/vault/scripts/certs/mocks/service.go deleted file mode 100644 index 864f3e28..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/pki/amcerts/am_certs.go b/docker/addons/vault/scripts/certs/pki/amcerts/am_certs.go deleted file mode 100644 index b5247aec..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/pki/amcerts/doc.go b/docker/addons/vault/scripts/certs/pki/amcerts/doc.go deleted file mode 100644 index cedf1854..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/pki/vault/doc.go b/docker/addons/vault/scripts/certs/pki/vault/doc.go deleted file mode 100644 index cbd2d979..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/pki/vault/vault.go b/docker/addons/vault/scripts/certs/pki/vault/vault.go deleted file mode 100644 index 2bde972a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/service.go b/docker/addons/vault/scripts/certs/service.go deleted file mode 100644 index d5e39805..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/service_test.go b/docker/addons/vault/scripts/certs/service_test.go deleted file mode 100644 index 54088587..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/tracing/doc.go b/docker/addons/vault/scripts/certs/tracing/doc.go deleted file mode 100644 index 6a419f3b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/certs/tracing/tracing.go b/docker/addons/vault/scripts/certs/tracing/tracing.go deleted file mode 100644 index 48a0173d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/README.md b/docker/addons/vault/scripts/cli/README.md deleted file mode 100644 index 58800b7a..00000000 --- a/docker/addons/vault/scripts/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 <service> -``` - -### Users management - -#### Create User - -```bash -magistrala-cli users create <user_name> <user_email> <user_password> - -magistrala-cli users create <user_name> <user_email> <user_password> <user_token> -``` - -#### Login User - -```bash -magistrala-cli users token <user_email> <user_password> -``` - -#### Get User - -```bash -magistrala-cli users get <user_id> <user_token> -``` - -#### Get Users - -```bash -magistrala-cli users get all <user_token> -``` - -#### Update User Metadata - -```bash -magistrala-cli users update <user_id> '{"name":"value1", "metadata":{"value2": "value3"}}' <user_token> -``` - -#### Update User Password - -```bash -magistrala-cli users password <old_password> <password> <user_token> -``` - -#### Enable User - -```bash -magistrala-cli users enable <user_id> <user_token> -``` - -#### Disable User - -```bash -magistrala-cli users disable <user_id> <user_token> -``` - -### System Provisioning - -#### Create Thing - -```bash -magistrala-cli things create '{"name":"myThing"}' <user_token> -``` - -#### Create Thing with metadata - -```bash -magistrala-cli things create '{"name":"myThing", "metadata": {"key1":"value1"}}' <user_token> -``` - -#### Bulk Provision Things - -```bash -magistrala-cli provision things <file> <user_token> -``` - -- `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": "<thing1_name>", - "status": "enabled" - }, - { - "name": "<thing2_name>", - "status": "disabled" - }, - { - "name": "<thing3_name>", - "status": "enabled", - "credentials": { - "identity": "<thing3_identity>", - "secret": "<thing3_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 <thing_id> '{"name":"value1", "metadata":{"key1": "value2"}}' <user_token> -``` - -#### Identify Thing - -```bash -magistrala-cli things identify <thing_key> -``` - -#### Enable Thing - -```bash -magistrala-cli things enable <thing_id> <user_token> -``` - -#### Disable Thing - -```bash -magistrala-cli things disable <thing_id> <user_token> -``` - -#### Get Thing - -```bash -magistrala-cli things get <thing_id> <user_token> -``` - -#### Get Things - -```bash -magistrala-cli things get all <user_token> -``` - -#### Get a subset list of provisioned Things - -```bash -magistrala-cli things get all --offset=1 --limit=5 <user_token> -``` - -#### Create Channel - -```bash -magistrala-cli channels create '{"name":"myChannel"}' <user_token> -``` - -#### Bulk Provision Channels - -```bash -magistrala-cli provision channels <file> <user_token> -``` - -- `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 -<channel1_name>, -<channel2_name>, -<channel3_name>, -``` - -in which the first column is channel names. - -A comparable JSON file would be - -```json -[ - { - "name": "<channel1_name>", - "description": "<channel1_description>", - "status": "enabled" - }, - { - "name": "<channel2_name>", - "description": "<channel2_description>", - "status": "disabled" - }, - { - "name": "<channel3_name>", - "description": "<channel3_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":"<channel_id>","name":"myNewName"}' <user_token> -``` - -#### Enable Channel - -```bash -magistrala-cli channels enable <channel_id> <user_token> -``` - -#### Disable Channel - -```bash -magistrala-cli channels disable <channel_id> <user_token> -``` - -#### Get Channel - -```bash -magistrala-cli channels get <channel_id> <user_token> -``` - -#### Get Channels - -```bash -magistrala-cli channels get all <user_token> -``` - -#### Get a subset list of provisioned Channels - -```bash -magistrala-cli channels get all --offset=1 --limit=5 <user_token> -``` - -### Access control - -#### Connect Thing to Channel - -```bash -magistrala-cli things connect <thing_id> <channel_id> <user_token> -``` - -#### Bulk Connect Things to Channels - -```bash -magistrala-cli provision connect <file> <user_token> -``` - -- `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 -<thing_id1>,<channel_id1> -<thing_id2>,<channel_id2> -``` - -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": ["<thing_id1>", "<thing_id2>"], - "group_ids": ["<channel_id1>", "<channel_id2>"] -} -``` - -#### Disconnect Thing from Channel - -```bash -magistrala-cli things disconnect <thing_id> <channel_id> <user_token> -``` - -#### Get a subset list of Channels connected to Thing - -```bash -magistrala-cli things connections <thing_id> <user_token> -``` - -#### Get a subset list of Things connected to Channel - -```bash -magistrala-cli channels connections <channel_id> <user_token> -``` - -### Messaging - -#### Send a message over HTTP - -```bash -magistrala-cli messages send <channel_id> '[{"bn":"Dev1","n":"temp","v":20}, {"n":"hum","v":40}, {"bn":"Dev2", "n":"temp","v":20}, {"n":"hum","v":40}]' <thing_secret> -``` - -#### Read messages over HTTP - -```bash -magistrala-cli messages read <channel_id> <user_token> -R <reader_url> -``` - -### Bootstrap - -#### Add configuration - -```bash -magistrala-cli bootstrap create '{"external_id": "myExtID", "external_key": "myExtKey", "name": "myName", "content": "myContent"}' <user_token> -b <bootstrap-url> -``` - -#### View configuration - -```bash -magistrala-cli bootstrap get <thing_id> <user_token> -b <bootstrap-url> -``` - -#### Update configuration - -```bash -magistrala-cli bootstrap update '{"thing_id":"<thing_id>", "name": "newName", "content": "newContent"}' <user_token> -b <bootstrap-url> -``` - -#### Remove configuration - -```bash -magistrala-cli bootstrap remove <thing_id> <user_token> -b <bootstrap-url> -``` - -#### Bootstrap configuration - -```bash -magistrala-cli bootstrap bootstrap <external_id> <external_key> -b <bootstrap-url> -``` - -### Groups - -#### Create Group - -```bash -magistrala-cli groups create '{"name":"<group_name>","description":"<description>","parentID":"<parent_id>","metadata":"<metadata>"}' <user_token> -``` - -#### Get Group - -```bash -magistrala-cli groups get <group_id> <user_token> -``` - -#### Get Groups - -```bash -magistrala-cli groups get all <user_token> -``` - -#### Get Group Members - -```bash -magistrala-cli groups members <group_id> <user_token> -``` - -#### Get Memberships - -```bash -magistrala-cli groups membership <member_id> <user_token> -``` - -#### Assign Members to Group - -```bash -magistrala-cli groups assign <member_ids> <member_type> <group_id> <user_token> -``` - -#### Unassign Members to Group - -```bash -magistrala-cli groups unassign <member_ids> <group_id> <user_token> -``` - -#### Enable Group - -```bash -magistrala-cli groups enable <group_id> <user_token> -``` - -#### Disable Group - -```bash -magistrala-cli groups disable <group_id> <user_token> -``` diff --git a/docker/addons/vault/scripts/cli/bootstrap.go b/docker/addons/vault/scripts/cli/bootstrap.go deleted file mode 100644 index dde560fa..00000000 --- a/docker/addons/vault/scripts/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 <JSON_config> <domain_id> <user_auth_token>", - 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 | <thing_id>] <domain_id> <user_auth_token>", - Short: "Get config", - Long: `Get Thing Config with given ID belonging to the user identified by the given key. - all - lists all config - <thing_id> - view config of <thing_id>`, - 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 <JSON_config> | connection <id> <channel_ids> | certs <id> <client_cert> <client_key> <ca> ] <domain_id> <user_auth_token>", - Short: "Update config", - Long: `Updates editable fields of the provided Config. - config <JSON_config> - Updates editable fields of the provided Config. - connection <id> <channel_ids> - Updates connections performs update of the channel list corresponding Thing is connected to. - channel_ids - '["channel_id1", ...]' - certs <id> <client_cert> <client_key> <ca> - 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 <thing_id> <domain_id> <user_auth_token>", - 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 [<external_id> <external_key> | secure <external_id> <external_key> <crypto_key> ]", - 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 <JSON_config> <domain_id> <user_auth_token>", - 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/docker/addons/vault/scripts/cli/bootstrap_test.go b/docker/addons/vault/scripts/cli/bootstrap_test.go deleted file mode 100644 index 3fdacb65..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/certs.go b/docker/addons/vault/scripts/cli/certs.go deleted file mode 100644 index 988e0c20..00000000 --- a/docker/addons/vault/scripts/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 [<cert_serial> | thing <thing_id> ] <domain_id> <user_auth_token>", - 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 <thing_id> <domain_id> <user_auth_token>", - 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 <thing_id> <domain_id> <user_auth_token> [--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/docker/addons/vault/scripts/cli/certs_test.go b/docker/addons/vault/scripts/cli/certs_test.go deleted file mode 100644 index efc057c1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/channels.go b/docker/addons/vault/scripts/cli/channels.go deleted file mode 100644 index a033f1aa..00000000 --- a/docker/addons/vault/scripts/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 <JSON_channel> <domain_id> <user_auth_token>", - 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 | <channel_id>] <domain_id> <user_auth_token>", - Short: "Get channel", - Long: `Get all channels or get channel by id. Channels can be filtered by name or metadata. - all - lists all channels - <channel_id> - shows thing with provided <channel_id>`, - - 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 <channel_id> <domain_id> <user_auth_token>", - Short: "Delete channel", - Long: "Delete channel by id.\n" + - "Usage:\n" + - "\tmagistrala-cli channels delete <channel_id> $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 <channel_id> <JSON_string> <domain_id> <user_auth_token>", - 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 <channel_id> <domain_id> <user_auth_token>", - 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 <channel_id> <domain_id> <user_auth_token>", - 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 <channel_id> <domain_id> <user_auth_token>", - 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 <channel_id> <domain_id> <user_auth_token>", - Short: "List users", - Long: "List users of a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels users <channel_id> $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 <channel_id> <domain_id> <user_auth_token>", - Short: "List groups", - Long: "List groups of a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels groups <channel_id> $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 <relation> <user_ids> <channel_id> <domain_id> <user_auth_token>", - Short: "Assign users", - Long: "Assign users to a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels assign users <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <channel_id> $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 <group_ids> <channel_id> <domain_id> <user_auth_token>", - Short: "Assign groups", - Long: "Assign groups to a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels assign groups '[\"<group_id_1>\", \"<group_id_2>\"]' <channel_id> $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 <group_ids> <channel_id> <domain_id> <user_auth_token>", - Short: "Unassign groups", - Long: "Unassign groups from a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels unassign groups '[\"<group_id_1>\", \"<group_id_2>\"]' <channel_id> $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 <relation> <user_ids> <channel_id> <domain_id> <user_auth_token>", - Short: "Unassign users", - Long: "Unassign users from a channel\n" + - "Usage:\n" + - "\tmagistrala-cli channels unassign users <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <channel_id> $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/docker/addons/vault/scripts/cli/channels_test.go b/docker/addons/vault/scripts/cli/channels_test.go deleted file mode 100644 index 428144fe..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/commands_test.go b/docker/addons/vault/scripts/cli/commands_test.go deleted file mode 100644 index 3e432f2f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/config.go b/docker/addons/vault/scripts/cli/config.go deleted file mode 100644 index e3910aaa..00000000 --- a/docker/addons/vault/scripts/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 <key> <value>", - 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/docker/addons/vault/scripts/cli/consumers.go b/docker/addons/vault/scripts/cli/consumers.go deleted file mode 100644 index d6b363e3..00000000 --- a/docker/addons/vault/scripts/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 <topic> <contact> <user_auth_token>", - 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 | <sub_id>] <user_auth_token>", - Short: "Get subscription", - Long: `Get subscription. - all - lists all subscriptions - <sub_id> - view subscription of <sub_id>`, - 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 <sub_id> <user_auth_token>", - 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/docker/addons/vault/scripts/cli/consumers_test.go b/docker/addons/vault/scripts/cli/consumers_test.go deleted file mode 100644 index 41f30b4b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/doc.go b/docker/addons/vault/scripts/cli/doc.go deleted file mode 100644 index 4045431e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/domains.go b/docker/addons/vault/scripts/cli/domains.go deleted file mode 100644 index 5d66d25d..00000000 --- a/docker/addons/vault/scripts/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 <name> <alias> <token>", - 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 | <domain_id> ] <token>", - 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 <domain_id> <token>", - 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 <domain_id> <JSON_string> <user_auth_token>", - Short: "Update domains", - Long: "Updates domains name, alias and metadata \n" + - "Usage:\n" + - "\tmagistrala-cli domains update <domain_id> '{\"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 <domain_id> <token>", - Short: "Change domain status to enabled", - Long: "Change domain status to enabled\n" + - "Usage:\n" + - "\tmagistrala-cli domains enable <domain_id> <token>\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 <domain_id> <token>", - Short: "Change domain status to disabled", - Long: "Change domain status to disabled\n" + - "Usage:\n" + - "\tmagistrala-cli domains disable <domain_id> <token>\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 <relation> <user_ids> <domain_id> <token>", - Short: "Assign users", - Long: "Assign users to a domain\n" + - "Usage:\n" + - "\tmagistrala-cli domains assign users <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <domain_id> $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 <user_id> <domain_id> <token>", - Short: "Unassign users", - Long: "Unassign users from a domain\n" + - "Usage:\n" + - "\tmagistrala-cli domains unassign users <user_id> <domain_id> $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/docker/addons/vault/scripts/cli/domains_test.go b/docker/addons/vault/scripts/cli/domains_test.go deleted file mode 100644 index 3a486900..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/groups.go b/docker/addons/vault/scripts/cli/groups.go deleted file mode 100644 index 867d1ec6..00000000 --- a/docker/addons/vault/scripts/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 <JSON_group> <domain_id> <user_auth_token>", - 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 <JSON_group> <domain_id> <user_auth_token>", - Short: "Update group", - Long: "Updates group\n" + - "Usage:\n" + - "\tmagistrala-cli groups update '{\"id\":\"<group_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 <group_id> | parents <group_id> | members <group_id> | <group_id>] <domain_id> <user_auth_token>", - 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 <group_id> $DOMAINID $USERTOKEN - lists all children groups of <group_id>\n" + - "\tmagistrala-cli groups get parents <group_id> $DOMAINID $USERTOKEN - lists all parent groups of <group_id>\n" + - "\tmagistrala-cli groups get <group_id> $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 <group_id> <domain_id> <user_auth_token>", - Short: "Delete group", - Long: "Delete group by id.\n" + - "Usage:\n" + - "\tmagistrala-cli groups delete <group_id> $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 <group_id> <domain_id> <user_auth_token>", - Short: "List users", - Long: "List users in a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups users <group_id> $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 <group_id> <domain_id> <user_auth_token>", - Short: "List channels", - Long: "List channels in a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups channels <group_id> $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 <group_id> <domain_id> <user_auth_token>", - Short: "Change group status to enabled", - Long: "Change group status to enabled\n" + - "Usage:\n" + - "\tmagistrala-cli groups enable <group_id> $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 <group_id> <domain_id> <user_auth_token>", - Short: "Change group status to disabled", - Long: "Change group status to disabled\n" + - "Usage:\n" + - "\tmagistrala-cli groups disable <group_id> $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 <relation> <user_ids> <group_id> <domain_id> <user_auth_token>", - Short: "Assign users", - Long: "Assign users to a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups assign users <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <group_id> $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 <relation> <user_ids> <group_id> <domain_id> <user_auth_token>", - Short: "Unassign users", - Long: "Unassign users from a group\n" + - "Usage:\n" + - "\tmagistrala-cli groups unassign users <relation> '[\"<user_id_1>\", \"<user_id_2>\"]' <group_id> $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/docker/addons/vault/scripts/cli/groups_test.go b/docker/addons/vault/scripts/cli/groups_test.go deleted file mode 100644 index 5f3daed8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/health.go b/docker/addons/vault/scripts/cli/health.go deleted file mode 100644 index b66d8be3..00000000 --- a/docker/addons/vault/scripts/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 <service>", - Short: "Health Check", - Long: "Magistrala service Health Check\n" + - "usage:\n" + - "\tmagistrala-cli health <service>", - 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/docker/addons/vault/scripts/cli/health_test.go b/docker/addons/vault/scripts/cli/health_test.go deleted file mode 100644 index 16273256..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/invitations.go b/docker/addons/vault/scripts/cli/invitations.go deleted file mode 100644 index 379187c8..00000000 --- a/docker/addons/vault/scripts/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 <user_id> <domain_id> <relation> <user_auth_token>", - 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 | <user_id> <domain_id> ] <user_auth_token>", - Short: "Get invitations", - Long: "Get invitations\n" + - "Usage:\n" + - "\tmagistrala-cli invitations get all <user_auth_token> - lists all invitations\n" + - "\tmagistrala-cli invitations get all <user_auth_token> --offset <offset> --limit <limit> - lists all invitations with provided offset and limit\n" + - "\tmagistrala-cli invitations get <user_id> <domain_id> <user_auth_token> - 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 <domain_id> <user_auth_token>", - 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 <domain_id> <user_auth_token>", - 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 <user_id> <domain_id> <user_auth_token>", - 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/docker/addons/vault/scripts/cli/invitations_test.go b/docker/addons/vault/scripts/cli/invitations_test.go deleted file mode 100644 index 43b9bb86..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/journal.go b/docker/addons/vault/scripts/cli/journal.go deleted file mode 100644 index 1b7ca147..00000000 --- a/docker/addons/vault/scripts/cli/journal.go +++ /dev/null @@ -1,50 +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 <entity_type> <entity_id> <user_auth_token>", - Short: "Get journal", - Long: "Get journal\n" + - "Usage:\n" + - "\tmagistrala-cli journal get <entity_type> <entity_id> <user_auth_token> - lists journal logs\n" + - "\tmagistrala-cli journal get <entity_type> <entity_id> <user_auth_token> --offset <offset> --limit <limit> - lists journal logs with provided offset and limit\n", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 3 { - logUsageCmd(*cmd, cmd.Use) - return - } - - pageMetadata := mgxsdk.PageMetadata{ - Offset: Offset, - Limit: Limit, - } - - journal, err := sdk.Journal(args[0], args[1], pageMetadata, args[2]) - 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/docker/addons/vault/scripts/cli/journal_test.go b/docker/addons/vault/scripts/cli/journal_test.go deleted file mode 100644 index 50bec552..00000000 --- a/docker/addons/vault/scripts/cli/journal_test.go +++ /dev/null @@ -1,102 +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 := "entity_type" - entityId := journal.ID - - cases := []struct { - desc string - args []string - sdkErr errors.SDKError - page mgsdk.JournalsPage - logType outputLog - errLogMessage string - }{ - { - desc: "get journal with journal id", - args: []string{ - entityType, - entityId, - 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, - extraArg, - }, - logType: usageLog, - }, - { - desc: "get journal with invalid token", - args: []string{ - entityType, - entityId, - 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) - - 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/docker/addons/vault/scripts/cli/message.go b/docker/addons/vault/scripts/cli/message.go deleted file mode 100644 index e4cfc0b2..00000000 --- a/docker/addons/vault/scripts/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 <channel_id.subtopic> <JSON_string> <thing_secret>", - 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 <channel_id.subtopic> <domain_id> <user_token>", - Short: "Read messages", - Long: "Reads all channel messages\n" + - "Usage:\n" + - "\tmagistrala-cli messages read <channel_id.subtopic> <domain_id> <user_token> --offset <offset> --limit <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/docker/addons/vault/scripts/cli/message_test.go b/docker/addons/vault/scripts/cli/message_test.go deleted file mode 100644 index a145fe60..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/provision.go b/docker/addons/vault/scripts/cli/provision.go deleted file mode 100644 index 6811a290..00000000 --- a/docker/addons/vault/scripts/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 <things_file> <domain_id> <user_token>", - 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 <channels_file> <domain_id> <user_token>", - 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 <connections_file> <domain_id> <user_token>", - 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/docker/addons/vault/scripts/cli/sdk.go b/docker/addons/vault/scripts/cli/sdk.go deleted file mode 100644 index 9f7e273c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/setup_test.go b/docker/addons/vault/scripts/cli/setup_test.go deleted file mode 100644 index 71099fdf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/things.go b/docker/addons/vault/scripts/cli/things.go deleted file mode 100644 index b5ec1ad4..00000000 --- a/docker/addons/vault/scripts/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 <JSON_thing> <domain_id> <user_auth_token>", - 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 | <thing_id>] <domain_id> <user_auth_token>", - 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 <thing_id> $DOMAINID $USERTOKEN - shows thing with provided <thing_id>\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 <thing_id> <domain_id> <user_auth_token>", - Short: "Delete thing", - Long: "Delete thing by id\n" + - "Usage:\n" + - "\tmagistrala-cli things delete <thing_id> $DOMAINID $USERTOKEN - delete thing with <thing_id>\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 [<thing_id> <JSON_string> | tags <thing_id> <tags> | secret <thing_id> <secret> ] <domain_id> <user_auth_token>", - Short: "Update thing", - Long: "Updates thing with provided id, name and metadata, or updates thing tags, secret\n" + - "Usage:\n" + - "\tmagistrala-cli things update <thing_id> '{\"name\":\"new name\", \"metadata\":{\"key\": \"value\"}}' $DOMAINID $USERTOKEN\n" + - "\tmagistrala-cli things update tags <thing_id> '{\"tag1\":\"value1\", \"tag2\":\"value2\"}' $DOMAINID $USERTOKEN\n" + - "\tmagistrala-cli things update secret <thing_id> <newsecret> $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 <thing_id> <domain_id> <user_auth_token>", - Short: "Change thing status to enabled", - Long: "Change thing status to enabled\n" + - "Usage:\n" + - "\tmagistrala-cli things enable <thing_id> $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 <thing_id> <domain_id> <user_auth_token>", - Short: "Change thing status to disabled", - Long: "Change thing status to disabled\n" + - "Usage:\n" + - "\tmagistrala-cli things disable <thing_id> $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 <thing_id> <user_id> <relation> <domain_id> <user_auth_token>", - Short: "Share thing with a user", - Long: "Share thing with a user\n" + - "Usage:\n" + - "\tmagistrala-cli things share <thing_id> <user_id> <relation> $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 <thing_id> <user_id> <relation> <domain_id> <user_auth_token>", - Short: "Unshare thing with a user", - Long: "Unshare thing with a user\n" + - "Usage:\n" + - "\tmagistrala-cli things share <thing_id> <user_id> <relation> $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 <thing_id> <channel_id> <domain_id> <user_auth_token>", - Short: "Connect thing", - Long: "Connect thing to the channel\n" + - "Usage:\n" + - "\tmagistrala-cli things connect <thing_id> <channel_id> $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 <thing_id> <channel_id> <domain_id> <user_auth_token>", - Short: "Disconnect thing", - Long: "Disconnect thing to the channel\n" + - "Usage:\n" + - "\tmagistrala-cli things disconnect <thing_id> <channel_id> $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 <thing_id> <domain_id> <user_auth_token>", - Short: "Connected list", - Long: "List of Channels connected to Thing\n" + - "Usage:\n" + - "\tmagistrala-cli connections <thing_id> $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 <thing_id> <domain_id> <user_auth_token>", - Short: "List users", - Long: "List users of a thing\n" + - "Usage:\n" + - "\tmagistrala-cli things users <thing_id> $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/docker/addons/vault/scripts/cli/things_test.go b/docker/addons/vault/scripts/cli/things_test.go deleted file mode 100644 index f9b403d9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/users.go b/docker/addons/vault/scripts/cli/users.go deleted file mode 100644 index 54b41585..00000000 --- a/docker/addons/vault/scripts/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 <first_name> <last_name> <email> <username> <password> <user_auth_token>", - 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 | <user_id> ] <user_auth_token>", - 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 <user_auth_token> - lists all users\n" + - "\tmagistrala-cli users get all <user_auth_token> --offset <offset> --limit <limit> - lists all users with provided offset and limit\n" + - "\tmagistrala-cli users get <user_id> <user_auth_token> - shows user with provided <user_id>\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 <username> <password>", - 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 <token>", - Short: "Get token", - Long: "Generate new token from refresh token\n" + - "For example:\n" + - "\tmagistrala-cli users refreshtoken <refresh_token>\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 [<user_id> <JSON_string> | tags <user_id> <tags> | username <user_id> <username> | email <user_id> <email>] <user_auth_token>", - Short: "Update user", - Long: "Updates either user name and metadata or user tags or user email\n" + - "Usage:\n" + - "\tmagistrala-cli users update <user_id> '{\"first_name\":\"new first_name\", \"metadata\":{\"key\": \"value\"}}' $USERTOKEN - updates user first and lastname and metadata\n" + - "\tmagistrala-cli users update tags <user_id> '[\"tag1\", \"tag2\"]' $USERTOKEN - updates user tags\n" + - "\tmagistrala-cli users update username <user_id> newusername $USERTOKEN - updates user name\n" + - "\tmagistrala-cli users update email <user_id> 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 <user_auth_token>", - 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 <email>", - 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 <password> <confpass> <password_request_token>", - 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 <old_password> <password> <user_auth_token>", - 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 <user_id> <user_auth_token>", - Short: "Change user status to enabled", - Long: "Change user status to enabled\n" + - "Usage:\n" + - "\tmagistrala-cli users enable <user_id> <user_auth_token>\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 <user_id> <user_auth_token>", - Short: "Change user status to disabled", - Long: "Change user status to disabled\n" + - "Usage:\n" + - "\tmagistrala-cli users disable <user_id> <user_auth_token>\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 <user_id> <user_auth_token>", - Short: "Delete user", - Long: "Delete user by id\n" + - "Usage:\n" + - "\tmagistrala-cli users delete <user_id> $USERTOKEN - delete user with <user_id>\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 <user_id> <user_auth_token>", - Short: "List channels", - Long: "List channels of user\n" + - "Usage:\n" + - "\tmagistrala-cli users channels <user_id> <user_auth_token>\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 <user_id> <user_auth_token>", - Short: "List things", - Long: "List things of user\n" + - "Usage:\n" + - "\tmagistrala-cli users things <user_id> <user_auth_token>\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 <user_id> <user_auth_token>", - Short: "List domains", - Long: "List user's domains\n" + - "Usage:\n" + - "\tmagistrala-cli users domains <user_id> <user_auth_token>\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 <user_id> <user_auth_token>", - Short: "List groups", - Long: "List groups of user\n" + - "Usage:\n" + - "\tmagistrala-cli users groups <user_id> <user_auth_token>\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 <query> <user_auth_token>", - Short: "Search users", - Long: "Search users by query\n" + - "Usage:\n" + - "\tmagistrala-cli users search <query> <user_auth_token>\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/docker/addons/vault/scripts/cli/users_test.go b/docker/addons/vault/scripts/cli/users_test.go deleted file mode 100644 index b78a89fd..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cli/utils.go b/docker/addons/vault/scripts/cli/utils.go deleted file mode 100644 index 0809f69a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/auth/main.go b/docker/addons/vault/scripts/cmd/auth/main.go deleted file mode 100644 index a2947783..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/bootstrap/main.go b/docker/addons/vault/scripts/cmd/bootstrap/main.go deleted file mode 100644 index cfe998b4..00000000 --- a/docker/addons/vault/scripts/cmd/bootstrap/main.go +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains bootstrap main function to start the bootstrap 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/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/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" -) - -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" -) - -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"` -} - -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 - } - } - - // Create new postgres client - 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, *bootstrappg.Migration()) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer db.Close() - - policySvc, err := newPolicyService(cfg, logger) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - logger.Info("Policy client successfully connected to spicedb gRPC server") - - 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) - - 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 - } - logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure()) - defer authnClient.Close() - - authz, authzClient, err := authsvcAuthz.NewAuthorization(ctx, grpcCfg) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer authzClient.Close() - logger.Info("AuthZ successfully connected to auth gRPC server " + authzClient.Secure()) - - // Create new service - svc, err := newService(ctx, authz, policySvc, db, tracer, logger, cfg, dbConfig) - if err != nil { - logger.Error(fmt.Sprintf("failed to create %s service: %s", svcName, err)) - exitCode = 1 - return - } - - if err = subscribeToThingsES(ctx, svc, cfg, logger); err != nil { - logger.Error(fmt.Sprintf("failed to subscribe to things event store: %s", err)) - exitCode = 1 - return - } - - logger.Info("Subscribed to Event Store") - - 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, bootstrap.NewConfigReader([]byte(cfg.EncKey)), logger, cfg.InstanceID), logger) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, magistrala.Version, logger, cancel) - go chc.CallHome(ctx) - } - - // Start servers - 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("Bootstrap service terminated: %s", err)) - } -} - -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) { - database := pgclient.NewDatabase(db, dbConfig, tracer) - - repoConfig := bootstrappg.NewConfigRepository(database, logger) - - config := mgsdk.Config{ - ThingsURL: cfg.ThingsURL, - } - - sdk := mgsdk.NewSDK(config) - idp := uuid.New() - - svc := bootstrap.New(policySvc, repoConfig, sdk, []byte(cfg.EncKey), idp) - - publisher, err := store.NewPublisher(ctx, cfg.ESURL, streamID) - if err != nil { - return nil, err - } - - svc = middleware.AuthorizationMiddleware(svc, authz) - svc = producer.NewEventStoreMiddleware(svc, publisher) - svc = middleware.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics(svcName, "api") - svc = middleware.MetricsMiddleware(svc, counter, latency) - svc = tracing.New(svc, tracer) - - return svc, nil -} - -func subscribeToThingsES(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, - Consumer: cfg.ESConsumerName, - Handler: consumer.NewEventHandler(svc), - } - return subscriber.Subscribe(ctx, subConfig) -} - -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/docker/addons/vault/scripts/cmd/certs/main.go b/docker/addons/vault/scripts/cmd/certs/main.go deleted file mode 100644 index 00c7ac32..00000000 --- a/docker/addons/vault/scripts/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("AutN 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/docker/addons/vault/scripts/cmd/cli/main.go b/docker/addons/vault/scripts/cmd/cli/main.go deleted file mode 100644 index 7ed42dfb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/coap/main.go b/docker/addons/vault/scripts/cmd/coap/main.go deleted file mode 100644 index ad16e992..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/http/main.go b/docker/addons/vault/scripts/cmd/http/main.go deleted file mode 100644 index 4bf25efa..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/invitations/main.go b/docker/addons/vault/scripts/cmd/invitations/main.go deleted file mode 100644 index 8f79da39..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/journal/main.go b/docker/addons/vault/scripts/cmd/journal/main.go deleted file mode 100644 index 3df9c5cd..00000000 --- a/docker/addons/vault/scripts/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" - mgauthn "github.com/absmach/magistrala/pkg/authn" - 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, authn, 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, 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, authn mgauthn.Authentication, 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(authn, authz, idp, repo) - 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/docker/addons/vault/scripts/cmd/mqtt/main.go b/docker/addons/vault/scripts/cmd/mqtt/main.go deleted file mode 100644 index 1d226543..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/postgres-reader/main.go b/docker/addons/vault/scripts/cmd/postgres-reader/main.go deleted file mode 100644 index 5354061b..00000000 --- a/docker/addons/vault/scripts/cmd/postgres-reader/main.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains postgres-reader main function to start the postgres-reader service. -package main - -import ( - "context" - "fmt" - "log" - "log/slog" - "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/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" -) - -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:""` -} - -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 := pgclient.Config{} - if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - db, err := pgclient.Connect(dbConfig) - if err != nil { - logger.Error(fmt.Sprintf("failed to setup postgres database : %s", err)) - exitCode = 1 - return - } - 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)) - exitCode = 1 - return - } - - authz, authzHandler, err := authsvc.NewAuthorization(ctx, clientCfg) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer authzHandler.Close() - logger.Info("Authz successfully connected to auth gRPC server " + authzHandler.Secure()) - - authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, clientCfg) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer authnHandler.Close() - logger.Info("Authn successfully connected to auth gRPC server " + authnHandler.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)) - 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()) - - repo := newService(db, logger) - - httpServerConfig := server.Config{Port: defSvcHTTPPort} - if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err)) - exitCode = 1 - return - } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authn, authz, thingsClient, 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("Postgres reader service terminated: %s", err)) - } -} - -func newService(db *sqlx.DB, logger *slog.Logger) readers.MessageRepository { - svc := postgres.New(db) - svc = api.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics("postgres", "message_reader") - svc = api.MetricsMiddleware(svc, counter, latency) - - return svc -} diff --git a/docker/addons/vault/scripts/cmd/postgres-writer/main.go b/docker/addons/vault/scripts/cmd/postgres-writer/main.go deleted file mode 100644 index d5b258e0..00000000 --- a/docker/addons/vault/scripts/cmd/postgres-writer/main.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains postgres-writer main function to start the postgres-writer 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/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/caarlos0/env/v11" - "github.com/jmoiron/sqlx" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "postgres-writer" - envPrefixDB = "MG_POSTGRES_" - envPrefixHTTP = "MG_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"` -} - -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 - } - - dbConfig := pgclient.Config{Name: defDB} - if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s Postgres configuration : %s", svcName, err)) - exitCode = 1 - return - } - db, err := pgclient.Setup(dbConfig, *writerpg.Migration()) - if err != nil { - logger.Error(err.Error()) - } - 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) - - pubSub, 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 pubSub.Close() - pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub) - - repo := newService(db, logger) - repo = consumertracing.NewBlocking(tracer, repo, httpServerConfig) - - if err = consumers.Start(ctx, svcName, pubSub, repo, cfg.ConfigPath, logger); err != nil { - logger.Error(fmt.Sprintf("failed to create Postgres writer: %s", err)) - exitCode = 1 - return - } - - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(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("Postgres writer service terminated: %s", err)) - } -} - -func newService(db *sqlx.DB, logger *slog.Logger) consumers.BlockingConsumer { - svc := writerpg.New(db) - svc = api.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics("postgres", "message_writer") - svc = api.MetricsMiddleware(svc, counter, latency) - return svc -} diff --git a/docker/addons/vault/scripts/cmd/provision/main.go b/docker/addons/vault/scripts/cmd/provision/main.go deleted file mode 100644 index 986f7acf..00000000 --- a/docker/addons/vault/scripts/cmd/provision/main.go +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains provision main function to start the provision service. -package main - -import ( - "context" - "encoding/json" - "fmt" - "log" - "os" - "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" - "github.com/caarlos0/env/v11" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "provision" - contentType = "application/json" -) - -var ( - errMissingConfigFile = errors.New("missing config file setting") - errFailLoadingConfigFile = errors.New("failed to load config from file") - errFailedToReadBootstrapContent = errors.New("failed to read bootstrap content from envs") -) - -func main() { - ctx, cancel := context.WithCancel(context.Background()) - g, ctx := errgroup.WithContext(ctx) - - cfg, err := loadConfig() - if err != nil { - log.Fatalf("failed to load %s configuration : %s", svcName, err) - } - - logger, err := mglog.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) - - 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 cfgFromFile, err := loadConfigFromFile(cfg.File); err != nil { - logger.Warn(fmt.Sprintf("Continue with settings from env, failed to load from: %s: %s", cfg.File, err)) - } else { - // Merge environment variables and file settings. - mergeConfigs(&cfgFromFile, &cfg) - cfg = cfgFromFile - logger.Info("Continue with settings from file: " + cfg.File) - } - - SDKCfg := mgsdk.Config{ - UsersURL: cfg.Server.UsersURL, - ThingsURL: cfg.Server.ThingsURL, - BootstrapURL: cfg.Server.MgBSURL, - CertsURL: cfg.Server.MgCertsURL, - MsgContentType: contentType, - TLSVerification: cfg.Server.TLS, - } - SDK := mgsdk.NewSDK(SDKCfg) - - svc := provision.New(cfg, SDK, logger) - svc = api.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) - - 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("Provision service terminated: %s", err)) - } -} - -func loadConfigFromFile(file string) (provision.Config, error) { - _, err := os.Stat(file) - if os.IsNotExist(err) { - return provision.Config{}, errors.Wrap(errMissingConfigFile, err) - } - c, err := provision.Read(file) - if err != nil { - return provision.Config{}, errors.Wrap(errFailLoadingConfigFile, err) - } - return c, nil -} - -func loadConfig() (provision.Config, error) { - cfg := provision.Config{} - if err := env.Parse(&cfg); err != nil { - return provision.Config{}, err - } - - if cfg.Bootstrap.AutoWhiteList && !cfg.Bootstrap.Provision { - return provision.Config{}, errors.New("Can't auto whitelist if auto config save is off") - } - - var content map[string]interface{} - if cfg.BSContent != "" { - if err := json.Unmarshal([]byte(cfg.BSContent), &content); err != nil { - return provision.Config{}, errFailedToReadBootstrapContent - } - } - - cfg.Bootstrap.Content = content - // This is default conf for provision if there is no config file - cfg.Channels = []mggroups.Group{ - { - Name: "control-channel", - Metadata: map[string]interface{}{"type": "control"}, - }, { - Name: "data-channel", - Metadata: map[string]interface{}{"type": "data"}, - }, - } - cfg.Things = []things.Client{ - { - Name: "thing", - Metadata: map[string]interface{}{"external_id": "xxxxxx"}, - }, - } - - return cfg, nil -} - -func mergeConfigs(dst, src interface{}) interface{} { - d := reflect.ValueOf(dst).Elem() - s := reflect.ValueOf(src).Elem() - - for i := 0; i < d.NumField(); i++ { - dField := d.Field(i) - sField := s.Field(i) - switch dField.Kind() { - case reflect.Struct: - dst := dField.Addr().Interface() - src := sField.Addr().Interface() - m := mergeConfigs(dst, src) - val := reflect.ValueOf(m).Elem().Interface() - dField.Set(reflect.ValueOf(val)) - case reflect.Slice: - case reflect.Bool: - if dField.Interface() == false { - dField.Set(reflect.ValueOf(sField.Interface())) - } - case reflect.Int: - if dField.Interface() == 0 { - dField.Set(reflect.ValueOf(sField.Interface())) - } - case reflect.String: - if dField.Interface() == "" { - dField.Set(reflect.ValueOf(sField.Interface())) - } - } - } - return dst -} diff --git a/docker/addons/vault/scripts/cmd/things/main.go b/docker/addons/vault/scripts/cmd/things/main.go deleted file mode 100644 index f29f05c4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/timescale-reader/main.go b/docker/addons/vault/scripts/cmd/timescale-reader/main.go deleted file mode 100644 index 2d7a5e05..00000000 --- a/docker/addons/vault/scripts/cmd/timescale-reader/main.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains timescale-reader main function to start the timescale-reader service. -package main - -import ( - "context" - "fmt" - "log" - "log/slog" - "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/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" -) - -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:""` -} - -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 := 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.Connect(dbConfig) - if err != nil { - logger.Error(err.Error()) - } - defer db.Close() - - 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)) - exitCode = 1 - return - } - - authz, authzHandler, err := authsvc.NewAuthorization(ctx, clientCfg) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer authzHandler.Close() - logger.Info("Authz successfully connected to auth gRPC server " + authzHandler.Secure()) - - authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, clientCfg) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer authnHandler.Close() - logger.Info("Authn successfully connected to auth gRPC server " + authnHandler.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)) - 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("ThingsService gRPC client successfully connected to things gRPC server " + thingsHandler.Secure()) - - 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(repo, authn, authz, thingsClient, 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("Timescale reader service terminated: %s", err)) - } -} - -func newService(db *sqlx.DB, logger *slog.Logger) readers.MessageRepository { - svc := timescale.New(db) - svc = api.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics("timescale", "message_reader") - svc = api.MetricsMiddleware(svc, counter, latency) - - return svc -} diff --git a/docker/addons/vault/scripts/cmd/timescale-writer/main.go b/docker/addons/vault/scripts/cmd/timescale-writer/main.go deleted file mode 100644 index 1b26fcda..00000000 --- a/docker/addons/vault/scripts/cmd/timescale-writer/main.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains timescale-writer main function to start the timescale-writer 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/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/caarlos0/env/v11" - "github.com/jmoiron/sqlx" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "timescaledb-writer" - envPrefixDB = "MG_TIMESCALE_" - envPrefixHTTP = "MG_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"` -} - -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 service 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 - } - - dbConfig := pgclient.Config{Name: defDB} - if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s Postgres configuration : %s", svcName, err)) - exitCode = 1 - return - } - db, err := pgclient.Setup(dbConfig, *timescale.Migration()) - 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) - - repo := newService(db, logger) - repo = consumertracing.NewBlocking(tracer, repo, httpServerConfig) - - pubSub, 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 pubSub.Close() - pubSub = brokerstracing.NewPubSub(httpServerConfig, tracer, pubSub) - - if err = consumers.Start(ctx, svcName, pubSub, repo, cfg.ConfigPath, logger); err != nil { - logger.Error(fmt.Sprintf("failed to create Timescale writer: %s", err)) - exitCode = 1 - return - } - - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(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("Timescale writer service terminated: %s", err)) - } -} - -func newService(db *sqlx.DB, logger *slog.Logger) consumers.BlockingConsumer { - svc := timescale.New(db) - svc = api.LoggingMiddleware(svc, logger) - counter, latency := prometheus.MakeMetrics("timescale", "message_writer") - svc = api.MetricsMiddleware(svc, counter, latency) - return svc -} diff --git a/docker/addons/vault/scripts/cmd/users/main.go b/docker/addons/vault/scripts/cmd/users/main.go deleted file mode 100644 index a7e43212..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/cmd/ws/main.go b/docker/addons/vault/scripts/cmd/ws/main.go deleted file mode 100644 index a2f1e57d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/coap/README.md b/docker/addons/vault/scripts/coap/README.md deleted file mode 100644 index 373bd866..00000000 --- a/docker/addons/vault/scripts/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 | <localhost:7000> | -| 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 | <nats://localhost:4222> | -| MG_JAEGER_URL | Jaeger server URL | <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_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/<channel_id>/messages?auth=<thing_auth_key>`. -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/docker/addons/vault/scripts/coap/adapter.go b/docker/addons/vault/scripts/coap/adapter.go deleted file mode 100644 index 2d25b3c0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/coap/api/doc.go b/docker/addons/vault/scripts/coap/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/coap/api/logging.go b/docker/addons/vault/scripts/coap/api/logging.go deleted file mode 100644 index 2f81f77f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/coap/api/metrics.go b/docker/addons/vault/scripts/coap/api/metrics.go deleted file mode 100644 index e6bca329..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/coap/api/transport.go b/docker/addons/vault/scripts/coap/api/transport.go deleted file mode 100644 index a2bbc8d1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/coap/client.go b/docker/addons/vault/scripts/coap/client.go deleted file mode 100644 index 6b278ce0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/coap/tracing/adapter.go b/docker/addons/vault/scripts/coap/tracing/adapter.go deleted file mode 100644 index f2d3e92a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/coap/tracing/doc.go b/docker/addons/vault/scripts/coap/tracing/doc.go deleted file mode 100644 index 2d65dbe4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/config.toml b/docker/addons/vault/scripts/config.toml deleted file mode 100644 index 07458473..00000000 --- a/docker/addons/vault/scripts/config.toml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -raw_output = "false" -user_token = "" - -[filter] - limit = "10" - offset = "0" - topic = "" - -[remotes] - journal_url = "http://localhost:9021" - bootstrap_url = "http://localhost:9013" - certs_url = "http://localhost:9019" - domains_url = "http://localhost:8189" - host_url = "http://localhost" - http_adapter_url = "http://localhost:8008" - invitations_url = "http://localhost:9020" - reader_url = "http://localhost:9011" - things_url = "http://localhost:9000" - tls_verification = false - users_url = "http://localhost:9002" diff --git a/docker/addons/vault/scripts/consumers/README.md b/docker/addons/vault/scripts/consumers/README.md deleted file mode 100644 index f4e2f28b..00000000 --- a/docker/addons/vault/scripts/consumers/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# 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 -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. - -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]. - -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). - -[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/docker/addons/vault/scripts/consumers/consumer.go b/docker/addons/vault/scripts/consumers/consumer.go deleted file mode 100644 index 403f9a3f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/consumers/doc.go b/docker/addons/vault/scripts/consumers/doc.go deleted file mode 100644 index 6280125e..00000000 --- a/docker/addons/vault/scripts/consumers/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package consumers contain the domain concept definitions needed to -// support Magistrala consumer services functionality. -package consumers diff --git a/docker/addons/vault/scripts/consumers/messages.go b/docker/addons/vault/scripts/consumers/messages.go deleted file mode 100644 index 0d25edf6..00000000 --- a/docker/addons/vault/scripts/consumers/messages.go +++ /dev/null @@ -1,159 +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{ - Format: defFormat, - ContentType: defContentType, - }, - } - - 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", cfg.Format)) - os.Exit(1) - return nil - } -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/README.md b/docker/addons/vault/scripts/consumers/notifiers/README.md deleted file mode 100644 index 18667196..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Notifiers service - -Notifiers service provides a service for sending notifications using Notifiers. -Notifiers service can be configured to use different types of Notifiers to send -different types of notifications such as SMS messages, emails, or push notifications. -Service is extensible so that new implementations of Notifiers can be easily added. -Notifiers **are not standalone services** but rather dependencies used by Notifiers service -for sending notifications over specific protocols. - -## Configuration - -The service is configured using the environment variables. -The environment variables needed for service configuration depend on the underlying Notifier. -An example of the service configuration for SMTP Notifier can be found [in SMTP Notifier documentation](smtp/README.md). -Note that any unset variables will be replaced with their -default values. - - -## Usage - -Subscriptions service will start consuming messages and sending notifications when a message is received. - -[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/docker/addons/vault/scripts/consumers/notifiers/api/doc.go b/docker/addons/vault/scripts/consumers/notifiers/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/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/docker/addons/vault/scripts/consumers/notifiers/api/endpoint.go b/docker/addons/vault/scripts/consumers/notifiers/api/endpoint.go deleted file mode 100644 index 4b411eaf..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/api/endpoint.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - - notifiers "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/go-kit/kit/endpoint" -) - -func createSubscriptionEndpoint(svc notifiers.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(createSubReq) - if err := req.validate(); err != nil { - return createSubRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - sub := notifiers.Subscription{ - Contact: req.Contact, - Topic: req.Topic, - } - id, err := svc.CreateSubscription(ctx, req.token, sub) - if err != nil { - return createSubRes{}, err - } - ucr := createSubRes{ - ID: id, - } - - return ucr, nil - } -} - -func viewSubscriptionEndpint(svc notifiers.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(subReq) - if err := req.validate(); err != nil { - return viewSubRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - sub, err := svc.ViewSubscription(ctx, req.token, req.id) - if err != nil { - return viewSubRes{}, err - } - res := viewSubRes{ - ID: sub.ID, - OwnerID: sub.OwnerID, - Contact: sub.Contact, - Topic: sub.Topic, - } - return res, nil - } -} - -func listSubscriptionsEndpoint(svc notifiers.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listSubsReq) - if err := req.validate(); err != nil { - return listSubsRes{}, errors.Wrap(apiutil.ErrValidation, err) - } - pm := notifiers.PageMetadata{ - Topic: req.topic, - Contact: req.contact, - Offset: req.offset, - Limit: int(req.limit), - } - page, err := svc.ListSubscriptions(ctx, req.token, pm) - if err != nil { - return listSubsRes{}, err - } - res := listSubsRes{ - Offset: page.Offset, - Limit: page.Limit, - Total: page.Total, - } - for _, sub := range page.Subscriptions { - r := viewSubRes{ - ID: sub.ID, - OwnerID: sub.OwnerID, - Contact: sub.Contact, - Topic: sub.Topic, - } - res.Subscriptions = append(res.Subscriptions, r) - } - - return res, nil - } -} - -func deleteSubscriptionEndpint(svc notifiers.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(subReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if err := svc.RemoveSubscription(ctx, req.token, req.id); err != nil { - return nil, err - } - return removeSubRes{}, nil - } -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/api/endpoint_test.go b/docker/addons/vault/scripts/consumers/notifiers/api/endpoint_test.go deleted file mode 100644 index ec9e7842..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/api/endpoint_test.go +++ /dev/null @@ -1,548 +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" - "path" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const ( - contentType = "application/json" - email = "user@example.com" - contact1 = "email1@example.com" - contact2 = "email2@example.com" - token = "token" - invalidToken = "invalid" - topic = "topic" - instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" -) - -var ( - notFoundRes = toJSON(apiutil.ErrorRes{Msg: svcerr.ErrNotFound.Error()}) - unauthRes = toJSON(apiutil.ErrorRes{Msg: svcerr.ErrAuthentication.Error()}) - invalidRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrInvalidQueryParams.Error(), Msg: apiutil.ErrValidation.Error()}) - missingTokRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrBearerToken.Error(), Msg: apiutil.ErrValidation.Error()}) -) - -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 newServer() (*httptest.Server, *mocks.Service) { - logger := mglog.NewMock() - svc := new(mocks.Service) - mux := httpapi.MakeHandler(svc, logger, instanceID) - return httptest.NewServer(mux), svc -} - -func toJSON(data interface{}) string { - jsonData, err := json.Marshal(data) - if err != nil { - return "" - } - return string(jsonData) -} - -func TestCreate(t *testing.T) { - ss, svc := newServer() - defer ss.Close() - - sub := notifiers.Subscription{ - Topic: topic, - Contact: contact1, - } - - data := toJSON(sub) - - emptyTopic := toJSON(notifiers.Subscription{Contact: contact1}) - emptyContact := toJSON(notifiers.Subscription{Topic: "topic123"}) - - cases := []struct { - desc string - req string - contentType string - auth string - status int - location string - err error - }{ - { - desc: "add successfully", - req: data, - contentType: contentType, - auth: token, - status: http.StatusCreated, - location: fmt.Sprintf("/subscriptions/%s%012d", uuid.Prefix, 1), - err: nil, - }, - { - desc: "add an existing subscription", - req: data, - contentType: contentType, - auth: token, - status: http.StatusConflict, - location: "", - err: svcerr.ErrConflict, - }, - { - desc: "add with empty topic", - req: emptyTopic, - contentType: contentType, - auth: token, - status: http.StatusBadRequest, - location: "", - err: svcerr.ErrMalformedEntity, - }, - { - desc: "add with empty contact", - req: emptyContact, - contentType: contentType, - auth: token, - status: http.StatusBadRequest, - location: "", - err: svcerr.ErrMalformedEntity, - }, - { - desc: "add with invalid auth token", - req: data, - contentType: contentType, - auth: invalidToken, - status: http.StatusUnauthorized, - location: "", - err: svcerr.ErrAuthentication, - }, - { - desc: "add with empty auth token", - req: data, - contentType: contentType, - auth: "", - status: http.StatusUnauthorized, - location: "", - err: svcerr.ErrAuthentication, - }, - { - desc: "add with invalid request format", - req: "}", - contentType: contentType, - auth: token, - status: http.StatusBadRequest, - location: "", - err: svcerr.ErrMalformedEntity, - }, - { - desc: "add without content type", - req: data, - contentType: "", - auth: token, - status: http.StatusUnsupportedMediaType, - location: "", - err: apiutil.ErrUnsupportedContentType, - }, - } - - for _, tc := range cases { - svcCall := svc.On("CreateSubscription", mock.Anything, tc.auth, sub).Return(path.Base(tc.location), tc.err) - - req := testRequest{ - client: ss.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/subscriptions", ss.URL), - contentType: tc.contentType, - token: tc.auth, - body: strings.NewReader(tc.req), - } - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - - location := res.Header.Get("Location") - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - assert.Equal(t, tc.location, location, fmt.Sprintf("%s: expected location %s got %s", tc.desc, tc.location, location)) - - svcCall.Unset() - } -} - -func TestView(t *testing.T) { - ss, svc := newServer() - defer ss.Close() - - sub := notifiers.Subscription{ - Topic: topic, - Contact: contact1, - ID: testsutil.GenerateUUID(t), - OwnerID: validID, - } - - sr := subRes{ - ID: sub.ID, - OwnerID: validID, - Contact: sub.Contact, - Topic: sub.Topic, - } - data := toJSON(sr) - - cases := []struct { - desc string - id string - auth string - status int - res string - err error - Sub notifiers.Subscription - }{ - { - desc: "view successfully", - id: sub.ID, - auth: token, - status: http.StatusOK, - res: data, - err: nil, - Sub: sub, - }, - { - desc: "view not existing", - id: "not existing", - auth: token, - status: http.StatusNotFound, - res: notFoundRes, - err: svcerr.ErrNotFound, - }, - { - desc: "view with invalid auth token", - id: sub.ID, - auth: invalidToken, - status: http.StatusUnauthorized, - res: unauthRes, - err: svcerr.ErrAuthentication, - }, - { - desc: "view with empty auth token", - id: sub.ID, - auth: "", - status: http.StatusUnauthorized, - res: missingTokRes, - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - svcCall := svc.On("ViewSubscription", mock.Anything, tc.auth, tc.id).Return(tc.Sub, tc.err) - - req := testRequest{ - client: ss.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/subscriptions/%s", ss.URL, tc.id), - token: tc.auth, - } - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected request error %s", tc.desc, err)) - body, err := io.ReadAll(res.Body) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected read error %s", tc.desc, err)) - data := strings.Trim(string(body), "\n") - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected body %s got %s", tc.desc, tc.res, data)) - - svcCall.Unset() - } -} - -func TestList(t *testing.T) { - ss, svc := newServer() - defer ss.Close() - - const numSubs = 100 - var subs []subRes - var sub notifiers.Subscription - - for i := 0; i < numSubs; i++ { - sub = notifiers.Subscription{ - Topic: fmt.Sprintf("topic.subtopic.%d", i), - Contact: contact1, - ID: testsutil.GenerateUUID(t), - } - if i%2 == 0 { - sub.Contact = contact2 - } - sr := subRes{ - ID: sub.ID, - OwnerID: validID, - Contact: sub.Contact, - Topic: sub.Topic, - } - subs = append(subs, sr) - } - noLimit := toJSON(page{Offset: 5, Limit: 20, Total: numSubs, Subscriptions: subs[5:25]}) - one := toJSON(page{Offset: 0, Limit: 20, Total: 1, Subscriptions: subs[10:11]}) - - var contact2Subs []subRes - for i := 20; i < 40; i += 2 { - contact2Subs = append(contact2Subs, subs[i]) - } - contactList := toJSON(page{Offset: 10, Limit: 10, Total: 50, Subscriptions: contact2Subs}) - - cases := []struct { - desc string - query map[string]string - auth string - status int - res string - err error - page notifiers.Page - }{ - { - desc: "list default limit", - query: map[string]string{ - "offset": "5", - }, - auth: token, - status: http.StatusOK, - res: noLimit, - err: nil, - page: notifiers.Page{ - PageMetadata: notifiers.PageMetadata{ - Offset: 5, - Limit: 20, - }, - Total: numSubs, - Subscriptions: subscriptionsSlice(subs, 5, 25), - }, - }, - { - desc: "list not existing", - query: map[string]string{ - "topic": "not-found-topic", - }, - auth: token, - status: http.StatusNotFound, - res: notFoundRes, - err: svcerr.ErrNotFound, - }, - { - desc: "list one with topic", - query: map[string]string{ - "topic": "topic.subtopic.10", - }, - auth: token, - status: http.StatusOK, - res: one, - err: nil, - page: notifiers.Page{ - PageMetadata: notifiers.PageMetadata{ - Offset: 0, - Limit: 20, - }, - Total: 1, - Subscriptions: subscriptionsSlice(subs, 10, 11), - }, - }, - { - desc: "list with contact", - query: map[string]string{ - "contact": contact2, - "offset": "10", - "limit": "10", - }, - auth: token, - status: http.StatusOK, - res: contactList, - err: nil, - page: notifiers.Page{ - PageMetadata: notifiers.PageMetadata{ - Offset: 10, - Limit: 10, - }, - Total: 50, - Subscriptions: subscriptionsSlice(contact2Subs, 0, 10), - }, - }, - { - desc: "list with invalid query", - query: map[string]string{ - "offset": "two", - }, - auth: token, - status: http.StatusBadRequest, - res: invalidRes, - err: svcerr.ErrMalformedEntity, - }, - { - desc: "list with invalid auth token", - auth: invalidToken, - status: http.StatusUnauthorized, - res: unauthRes, - err: svcerr.ErrAuthentication, - }, - { - desc: "list with empty auth token", - auth: "", - status: http.StatusUnauthorized, - res: missingTokRes, - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - svcCall := svc.On("ListSubscriptions", mock.Anything, tc.auth, mock.Anything).Return(tc.page, tc.err) - req := testRequest{ - client: ss.Client(), - method: http.MethodGet, - url: fmt.Sprintf("%s/subscriptions%s", ss.URL, makeQuery(tc.query)), - token: tc.auth, - } - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - body, err := io.ReadAll(res.Body) - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - data := strings.Trim(string(body), "\n") - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - assert.Equal(t, tc.res, data, fmt.Sprintf("%s: got unexpected body\n", tc.desc)) - - svcCall.Unset() - } -} - -func TestRemove(t *testing.T) { - ss, svc := newServer() - defer ss.Close() - id := testsutil.GenerateUUID(t) - - cases := []struct { - desc string - id string - auth string - status int - res string - err error - }{ - { - desc: "remove successfully", - id: id, - auth: token, - status: http.StatusNoContent, - err: nil, - }, - { - desc: "remove not existing", - id: "not existing", - auth: token, - status: http.StatusNotFound, - err: svcerr.ErrNotFound, - }, - { - desc: "remove empty id", - id: "", - auth: token, - status: http.StatusBadRequest, - err: svcerr.ErrMalformedEntity, - }, - { - desc: "view with invalid auth token", - id: id, - auth: invalidToken, - status: http.StatusUnauthorized, - res: unauthRes, - err: svcerr.ErrAuthentication, - }, - { - desc: "view with empty auth token", - id: id, - auth: "", - status: http.StatusUnauthorized, - res: missingTokRes, - err: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - svcCall := svc.On("RemoveSubscription", mock.Anything, tc.auth, tc.id).Return(tc.err) - - req := testRequest{ - client: ss.Client(), - method: http.MethodDelete, - url: fmt.Sprintf("%s/subscriptions/%s", ss.URL, tc.id), - token: tc.auth, - } - 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 makeQuery(m map[string]string) string { - var ret string - for k, v := range m { - ret += fmt.Sprintf("&%s=%s", k, v) - } - if ret != "" { - return fmt.Sprintf("?%s", ret[1:]) - } - return "" -} - -type subRes struct { - ID string `json:"id"` - OwnerID string `json:"owner_id"` - Contact string `json:"contact"` - Topic string `json:"topic"` -} -type page struct { - Offset uint `json:"offset"` - Limit int `json:"limit"` - Total uint `json:"total,omitempty"` - Subscriptions []subRes `json:"subscriptions,omitempty"` -} - -func subscriptionsSlice(subs []subRes, start, end int) []notifiers.Subscription { - var res []notifiers.Subscription - for i := start; i < end; i++ { - sub := subs[i] - res = append(res, notifiers.Subscription{ - ID: sub.ID, - OwnerID: sub.OwnerID, - Contact: sub.Contact, - Topic: sub.Topic, - }) - } - return res -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/api/logging.go b/docker/addons/vault/scripts/consumers/notifiers/api/logging.go deleted file mode 100644 index e327d922..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/api/logging.go +++ /dev/null @@ -1,131 +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/consumers/notifiers" -) - -var _ notifiers.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc notifiers.Service -} - -// LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc notifiers.Service, logger *slog.Logger) notifiers.Service { - return &loggingMiddleware{logger, svc} -} - -// CreateSubscription logs the create_subscription request. It logs subscription ID and topic and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) CreateSubscription(ctx context.Context, token string, sub notifiers.Subscription) (id string, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("subscription", - slog.String("topic", sub.Topic), - slog.String("id", id), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Create subscription failed", args...) - return - } - lm.logger.Info("Create subscription completed successfully", args...) - }(time.Now()) - - return lm.svc.CreateSubscription(ctx, token, sub) -} - -// ViewSubscription logs the view_subscription request. It logs subscription topic and id and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ViewSubscription(ctx context.Context, token, topic string) (sub notifiers.Subscription, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("subscription", - slog.String("topic", topic), - slog.String("id", sub.ID), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("View subscription failed", args...) - return - } - lm.logger.Info("View subscription completed successfully", args...) - }(time.Now()) - - return lm.svc.ViewSubscription(ctx, token, topic) -} - -// ListSubscriptions logs the list_subscriptions request. It logs page metadata and subscription topic and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ListSubscriptions(ctx context.Context, token string, pm notifiers.PageMetadata) (res notifiers.Page, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.Group("page", - slog.String("topic", pm.Topic), - slog.Int("limit", pm.Limit), - slog.Uint64("offset", uint64(pm.Offset)), - slog.Uint64("total", uint64(res.Total)), - ), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("List subscriptions failed", args...) - return - } - lm.logger.Info("List subscriptions completed successfully", args...) - }(time.Now()) - - return lm.svc.ListSubscriptions(ctx, token, pm) -} - -// RemoveSubscription logs the remove_subscription request. It logs subscription ID and the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) RemoveSubscription(ctx context.Context, token, id string) (err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("subscription_id", id), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Remove subscription failed", args...) - return - } - lm.logger.Info("Remove subscription completed successfully", args...) - }(time.Now()) - - return lm.svc.RemoveSubscription(ctx, token, id) -} - -// ConsumeBlocking logs the consume_blocking request. It logs the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msg interface{}) (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("Blocking consumer failed to consume messages successfully", args...) - return - } - lm.logger.Info("Blocking consumer consumed messages successfully", args...) - }(time.Now()) - - return lm.svc.ConsumeBlocking(ctx, msg) -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/api/metrics.go b/docker/addons/vault/scripts/consumers/notifiers/api/metrics.go deleted file mode 100644 index 20973028..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/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/consumers/notifiers" - "github.com/go-kit/kit/metrics" -) - -var _ notifiers.Service = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc notifiers.Service -} - -// MetricsMiddleware instruments core service by tracking request count and latency. -func MetricsMiddleware(svc notifiers.Service, counter metrics.Counter, latency metrics.Histogram) notifiers.Service { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -// CreateSubscription instruments CreateSubscription method with metrics. -func (ms *metricsMiddleware) CreateSubscription(ctx context.Context, token string, sub notifiers.Subscription) (string, error) { - defer func(begin time.Time) { - ms.counter.With("method", "create_subscription").Add(1) - ms.latency.With("method", "create_subscription").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.CreateSubscription(ctx, token, sub) -} - -// ViewSubscription instruments ViewSubscription method with metrics. -func (ms *metricsMiddleware) ViewSubscription(ctx context.Context, token, topic string) (notifiers.Subscription, error) { - defer func(begin time.Time) { - ms.counter.With("method", "view_subscription").Add(1) - ms.latency.With("method", "view_subscription").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.ViewSubscription(ctx, token, topic) -} - -// ListSubscriptions instruments ListSubscriptions method with metrics. -func (ms *metricsMiddleware) ListSubscriptions(ctx context.Context, token string, pm notifiers.PageMetadata) (notifiers.Page, error) { - defer func(begin time.Time) { - ms.counter.With("method", "list_subscriptions").Add(1) - ms.latency.With("method", "list_subscriptions").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.ListSubscriptions(ctx, token, pm) -} - -// RemoveSubscription instruments RemoveSubscription method with metrics. -func (ms *metricsMiddleware) RemoveSubscription(ctx context.Context, token, id string) error { - defer func(begin time.Time) { - ms.counter.With("method", "remove_subscription").Add(1) - ms.latency.With("method", "remove_subscription").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.RemoveSubscription(ctx, token, id) -} - -// ConsumeBlocking instruments ConsumeBlocking method with metrics. -func (ms *metricsMiddleware) ConsumeBlocking(ctx context.Context, msg interface{}) error { - defer func(begin time.Time) { - ms.counter.With("method", "consume").Add(1) - ms.latency.With("method", "consume").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return ms.svc.ConsumeBlocking(ctx, msg) -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/api/requests.go b/docker/addons/vault/scripts/consumers/notifiers/api/requests.go deleted file mode 100644 index 9285f4d7..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/api/requests.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import "github.com/absmach/magistrala/pkg/apiutil" - -type createSubReq struct { - token string - Topic string `json:"topic,omitempty"` - Contact string `json:"contact,omitempty"` -} - -func (req createSubReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.Topic == "" { - return apiutil.ErrInvalidTopic - } - if req.Contact == "" { - return apiutil.ErrInvalidContact - } - return nil -} - -type subReq struct { - token string - id string -} - -func (req subReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.id == "" { - return apiutil.ErrMissingID - } - return nil -} - -type listSubsReq struct { - token string - topic string - contact string - offset uint - limit uint -} - -func (req listSubsReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - return nil -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/api/responses.go b/docker/addons/vault/scripts/consumers/notifiers/api/responses.go deleted file mode 100644 index 7d310062..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/api/responses.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "net/http" - - "github.com/absmach/magistrala" -) - -var ( - _ magistrala.Response = (*createSubRes)(nil) - _ magistrala.Response = (*viewSubRes)(nil) - _ magistrala.Response = (*listSubsRes)(nil) - _ magistrala.Response = (*removeSubRes)(nil) -) - -type createSubRes struct { - ID string -} - -func (res createSubRes) Code() int { - return http.StatusCreated -} - -func (res createSubRes) Headers() map[string]string { - return map[string]string{ - "Location": fmt.Sprintf("/subscriptions/%s", res.ID), - } -} - -func (res createSubRes) Empty() bool { - return true -} - -type viewSubRes struct { - ID string `json:"id"` - OwnerID string `json:"owner_id"` - Contact string `json:"contact"` - Topic string `json:"topic"` -} - -func (res viewSubRes) Code() int { - return http.StatusOK -} - -func (res viewSubRes) Headers() map[string]string { - return map[string]string{} -} - -func (res viewSubRes) Empty() bool { - return false -} - -type listSubsRes struct { - Offset uint `json:"offset"` - Limit int `json:"limit"` - Total uint `json:"total,omitempty"` - Subscriptions []viewSubRes `json:"subscriptions,omitempty"` -} - -func (res listSubsRes) Code() int { - return http.StatusOK -} - -func (res listSubsRes) Headers() map[string]string { - return map[string]string{} -} - -func (res listSubsRes) Empty() bool { - return false -} - -type removeSubRes struct{} - -func (res removeSubRes) Code() int { - return http.StatusNoContent -} - -func (res removeSubRes) Headers() map[string]string { - return map[string]string{} -} - -func (res removeSubRes) Empty() bool { - return true -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/api/transport.go b/docker/addons/vault/scripts/consumers/notifiers/api/transport.go deleted file mode 100644 index 2f6e258b..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/api/transport.go +++ /dev/null @@ -1,131 +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/consumers/notifiers" - "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" - "github.com/prometheus/client_golang/prometheus/promhttp" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -const ( - contentType = "application/json" - offsetKey = "offset" - limitKey = "limit" - topicKey = "topic" - contactKey = "contact" - defOffset = 0 - defLimit = 20 -) - -// MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc notifiers.Service, logger *slog.Logger, instanceID string) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - mux := chi.NewRouter() - - mux.Route("/subscriptions", func(r chi.Router) { - r.Post("/", otelhttp.NewHandler(kithttp.NewServer( - createSubscriptionEndpoint(svc), - decodeCreate, - api.EncodeResponse, - opts..., - ), "create").ServeHTTP) - - r.Get("/", otelhttp.NewHandler(kithttp.NewServer( - listSubscriptionsEndpoint(svc), - decodeList, - api.EncodeResponse, - opts..., - ), "list").ServeHTTP) - - r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( - deleteSubscriptionEndpint(svc), - decodeSubscription, - api.EncodeResponse, - opts..., - ), "delete").ServeHTTP) - - r.Get("/{subID}", otelhttp.NewHandler(kithttp.NewServer( - viewSubscriptionEndpint(svc), - decodeSubscription, - api.EncodeResponse, - opts..., - ), "view").ServeHTTP) - - r.Delete("/{subID}", otelhttp.NewHandler(kithttp.NewServer( - deleteSubscriptionEndpint(svc), - decodeSubscription, - api.EncodeResponse, - opts..., - ), "delete").ServeHTTP) - }) - mux.Get("/health", magistrala.Health("notifier", instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} - -func decodeCreate(_ context.Context, r *http.Request) (interface{}, error) { - if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := createSubReq{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 decodeSubscription(_ context.Context, r *http.Request) (interface{}, error) { - req := subReq{ - id: chi.URLParam(r, "subID"), - token: apiutil.ExtractBearerToken(r), - } - - return req, nil -} - -func decodeList(_ context.Context, r *http.Request) (interface{}, error) { - req := listSubsReq{token: apiutil.ExtractBearerToken(r)} - vals := r.URL.Query()[topicKey] - if len(vals) > 0 { - req.topic = vals[0] - } - - vals = r.URL.Query()[contactKey] - if len(vals) > 0 { - req.contact = vals[0] - } - - offset, err := apiutil.ReadNumQuery[uint64](r, offsetKey, defOffset) - if err != nil { - return listSubsReq{}, errors.Wrap(apiutil.ErrValidation, err) - } - req.offset = uint(offset) - - limit, err := apiutil.ReadNumQuery[uint64](r, limitKey, defLimit) - if err != nil { - return listSubsReq{}, errors.Wrap(apiutil.ErrValidation, err) - } - req.limit = uint(limit) - - return req, nil -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/doc.go b/docker/addons/vault/scripts/consumers/notifiers/doc.go deleted file mode 100644 index e90c58c1..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package notifiers contain the domain concept definitions needed to -// support Magistrala notifications functionality. -package notifiers diff --git a/docker/addons/vault/scripts/consumers/notifiers/mocks/doc.go b/docker/addons/vault/scripts/consumers/notifiers/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/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/docker/addons/vault/scripts/consumers/notifiers/mocks/notifier.go b/docker/addons/vault/scripts/consumers/notifiers/mocks/notifier.go deleted file mode 100644 index a3dcc56f..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/mocks/notifier.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - messaging "github.com/absmach/magistrala/pkg/messaging" - mock "github.com/stretchr/testify/mock" -) - -// Notifier is an autogenerated mock type for the Notifier type -type Notifier struct { - mock.Mock -} - -// Notify provides a mock function with given fields: from, to, msg -func (_m *Notifier) Notify(from string, to []string, msg *messaging.Message) error { - ret := _m.Called(from, to, msg) - - if len(ret) == 0 { - panic("no return value specified for Notify") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string, []string, *messaging.Message) error); ok { - r0 = rf(from, to, msg) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewNotifier creates a new instance of Notifier. 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 NewNotifier(t interface { - mock.TestingT - Cleanup(func()) -}) *Notifier { - mock := &Notifier{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/mocks/repository.go b/docker/addons/vault/scripts/consumers/notifiers/mocks/repository.go deleted file mode 100644 index 49e57276..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/mocks/repository.go +++ /dev/null @@ -1,133 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - notifiers "github.com/absmach/magistrala/consumers/notifiers" - mock "github.com/stretchr/testify/mock" -) - -// SubscriptionsRepository is an autogenerated mock type for the SubscriptionsRepository type -type SubscriptionsRepository struct { - mock.Mock -} - -// Remove provides a mock function with given fields: ctx, id -func (_m *SubscriptionsRepository) Remove(ctx context.Context, id string) error { - ret := _m.Called(ctx, 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) error); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Retrieve provides a mock function with given fields: ctx, id -func (_m *SubscriptionsRepository) Retrieve(ctx context.Context, id string) (notifiers.Subscription, error) { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for Retrieve") - } - - var r0 notifiers.Subscription - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (notifiers.Subscription, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string) notifiers.Subscription); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(notifiers.Subscription) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RetrieveAll provides a mock function with given fields: ctx, pm -func (_m *SubscriptionsRepository) RetrieveAll(ctx context.Context, pm notifiers.PageMetadata) (notifiers.Page, error) { - ret := _m.Called(ctx, pm) - - if len(ret) == 0 { - panic("no return value specified for RetrieveAll") - } - - var r0 notifiers.Page - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, notifiers.PageMetadata) (notifiers.Page, error)); ok { - return rf(ctx, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, notifiers.PageMetadata) notifiers.Page); ok { - r0 = rf(ctx, pm) - } else { - r0 = ret.Get(0).(notifiers.Page) - } - - if rf, ok := ret.Get(1).(func(context.Context, notifiers.PageMetadata) error); ok { - r1 = rf(ctx, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Save provides a mock function with given fields: ctx, sub -func (_m *SubscriptionsRepository) Save(ctx context.Context, sub notifiers.Subscription) (string, error) { - ret := _m.Called(ctx, sub) - - 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, notifiers.Subscription) (string, error)); ok { - return rf(ctx, sub) - } - if rf, ok := ret.Get(0).(func(context.Context, notifiers.Subscription) string); ok { - r0 = rf(ctx, sub) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, notifiers.Subscription) error); ok { - r1 = rf(ctx, sub) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewSubscriptionsRepository creates a new instance of SubscriptionsRepository. 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 NewSubscriptionsRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *SubscriptionsRepository { - mock := &SubscriptionsRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/mocks/service.go b/docker/addons/vault/scripts/consumers/notifiers/mocks/service.go deleted file mode 100644 index 9fe9494f..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/mocks/service.go +++ /dev/null @@ -1,151 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - context "context" - - notifiers "github.com/absmach/magistrala/consumers/notifiers" - mock "github.com/stretchr/testify/mock" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// ConsumeBlocking provides a mock function with given fields: ctx, messages -func (_m *Service) ConsumeBlocking(ctx context.Context, messages interface{}) error { - ret := _m.Called(ctx, messages) - - if len(ret) == 0 { - panic("no return value specified for ConsumeBlocking") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, interface{}) error); ok { - r0 = rf(ctx, messages) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// CreateSubscription provides a mock function with given fields: ctx, token, sub -func (_m *Service) CreateSubscription(ctx context.Context, token string, sub notifiers.Subscription) (string, error) { - ret := _m.Called(ctx, token, sub) - - if len(ret) == 0 { - panic("no return value specified for CreateSubscription") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, notifiers.Subscription) (string, error)); ok { - return rf(ctx, token, sub) - } - if rf, ok := ret.Get(0).(func(context.Context, string, notifiers.Subscription) string); ok { - r0 = rf(ctx, token, sub) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, notifiers.Subscription) error); ok { - r1 = rf(ctx, token, sub) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ListSubscriptions provides a mock function with given fields: ctx, token, pm -func (_m *Service) ListSubscriptions(ctx context.Context, token string, pm notifiers.PageMetadata) (notifiers.Page, error) { - ret := _m.Called(ctx, token, pm) - - if len(ret) == 0 { - panic("no return value specified for ListSubscriptions") - } - - var r0 notifiers.Page - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, notifiers.PageMetadata) (notifiers.Page, error)); ok { - return rf(ctx, token, pm) - } - if rf, ok := ret.Get(0).(func(context.Context, string, notifiers.PageMetadata) notifiers.Page); ok { - r0 = rf(ctx, token, pm) - } else { - r0 = ret.Get(0).(notifiers.Page) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, notifiers.PageMetadata) error); ok { - r1 = rf(ctx, token, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RemoveSubscription provides a mock function with given fields: ctx, token, id -func (_m *Service) RemoveSubscription(ctx context.Context, token string, id string) error { - ret := _m.Called(ctx, token, id) - - if len(ret) == 0 { - panic("no return value specified for RemoveSubscription") - } - - 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 -} - -// ViewSubscription provides a mock function with given fields: ctx, token, id -func (_m *Service) ViewSubscription(ctx context.Context, token string, id string) (notifiers.Subscription, error) { - ret := _m.Called(ctx, token, id) - - if len(ret) == 0 { - panic("no return value specified for ViewSubscription") - } - - var r0 notifiers.Subscription - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (notifiers.Subscription, error)); ok { - return rf(ctx, token, id) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) notifiers.Subscription); ok { - r0 = rf(ctx, token, id) - } else { - r0 = ret.Get(0).(notifiers.Subscription) - } - - 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 -} - -// 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/docker/addons/vault/scripts/consumers/notifiers/notifier.go b/docker/addons/vault/scripts/consumers/notifiers/notifier.go deleted file mode 100644 index 2c23bc9e..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/notifier.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package notifiers - -import ( - "errors" - - "github.com/absmach/magistrala/pkg/messaging" -) - -// ErrNotify wraps sending notification errors. -var ErrNotify = errors.New("error sending notification") - -// Notifier represents an API for sending notification. -// -//go:generate mockery --name Notifier --output=./mocks --filename notifier.go --quiet --note "Copyright (c) Abstract Machines" -type Notifier interface { - // Notify method is used to send notification for the - // received message to the provided list of receivers. - Notify(from string, to []string, msg *messaging.Message) error -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/postgres/database.go b/docker/addons/vault/scripts/consumers/notifiers/postgres/database.go deleted file mode 100644 index 2e7ee740..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/postgres/database.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "database/sql" - "fmt" - - "github.com/jmoiron/sqlx" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var _ Database = (*database)(nil) - -type database struct { - db *sqlx.DB - tracer trace.Tracer -} - -// Database provides a database interface. -type Database interface { - NamedExecContext(context.Context, string, interface{}) (sql.Result, error) - QueryRowxContext(context.Context, string, ...interface{}) *sqlx.Row - NamedQueryContext(context.Context, string, interface{}) (*sqlx.Rows, error) - GetContext(context.Context, interface{}, string, ...interface{}) error -} - -// NewDatabase creates a SubscriptionsDatabase instance. -func NewDatabase(db *sqlx.DB, tracer trace.Tracer) Database { - return &database{ - db: db, - tracer: tracer, - } -} - -func (dm database) NamedExecContext(ctx context.Context, query string, args interface{}) (sql.Result, error) { - ctx, span := dm.addSpanTags(ctx, "NamedExecContext", query) - defer span.End() - return dm.db.NamedExecContext(ctx, query, args) -} - -func (dm database) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row { - ctx, span := dm.addSpanTags(ctx, "QueryRowxContext", query) - defer span.End() - return dm.db.QueryRowxContext(ctx, query, args...) -} - -func (dm database) NamedQueryContext(ctx context.Context, query string, args interface{}) (*sqlx.Rows, error) { - ctx, span := dm.addSpanTags(ctx, "NamedQueryContext", query) - defer span.End() - return dm.db.NamedQueryContext(ctx, query, args) -} - -func (dm database) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { - ctx, span := dm.addSpanTags(ctx, "GetContext", query) - defer span.End() - return dm.db.GetContext(ctx, dest, query, args...) -} - -func (dm database) addSpanTags(ctx context.Context, method, query string) (context.Context, trace.Span) { - ctx, span := dm.tracer.Start(ctx, - fmt.Sprintf("sql_%s", method), - trace.WithAttributes( - attribute.String("sql.statement", query), - attribute.String("span.kind", "client"), - attribute.String("peer.service", "postgres"), - attribute.String("db.type", "sql"), - ), - ) - return ctx, span -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/postgres/doc.go b/docker/addons/vault/scripts/consumers/notifiers/postgres/doc.go deleted file mode 100644 index 73a67847..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/postgres/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres contains repository implementations using PostgreSQL as -// the underlying database. -package postgres diff --git a/docker/addons/vault/scripts/consumers/notifiers/postgres/init.go b/docker/addons/vault/scripts/consumers/notifiers/postgres/init.go deleted file mode 100644 index ac74c3c0..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/postgres/init.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import migrate "github.com/rubenv/sql-migrate" - -func Migration() *migrate.MemoryMigrationSource { - return &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "subscriptions_1", - Up: []string{ - `CREATE TABLE IF NOT EXISTS subscriptions ( - id VARCHAR(254) PRIMARY KEY, - owner_id VARCHAR(254) NOT NULL, - contact VARCHAR(254), - topic TEXT, - UNIQUE(topic, contact) - )`, - }, - Down: []string{ - "DROP TABLE IF EXISTS subscriptions", - }, - }, - }, - } -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/postgres/setup_test.go b/docker/addons/vault/scripts/consumers/notifiers/postgres/setup_test.go deleted file mode 100644 index b6033780..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/postgres/setup_test.go +++ /dev/null @@ -1,89 +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 ( - "fmt" - "log" - "os" - "testing" - - "github.com/absmach/magistrala/consumers/notifiers/postgres" - pgclient "github.com/absmach/magistrala/pkg/postgres" - "github.com/absmach/magistrala/pkg/ulid" - _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access - "github.com/jmoiron/sqlx" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" -) - -var ( - idProvider = ulid.New() - db *sqlx.DB -) - -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") - - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - if err := pool.Retry(func() error { - db, err = sqlx.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, *postgres.Migration()); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - 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/docker/addons/vault/scripts/consumers/notifiers/postgres/subscriptions.go b/docker/addons/vault/scripts/consumers/notifiers/postgres/subscriptions.go deleted file mode 100644 index 1d445d93..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/postgres/subscriptions.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "database/sql" - "fmt" - "strings" - - "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/pkg/errors" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" - "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" -) - -var _ notifiers.SubscriptionsRepository = (*subscriptionsRepo)(nil) - -type subscriptionsRepo struct { - db Database -} - -// New instantiates a PostgreSQL implementation of Subscriptions repository. -func New(db Database) notifiers.SubscriptionsRepository { - return &subscriptionsRepo{ - db: db, - } -} - -func (repo subscriptionsRepo) Save(ctx context.Context, sub notifiers.Subscription) (string, error) { - q := `INSERT INTO subscriptions (id, owner_id, contact, topic) VALUES (:id, :owner_id, :contact, :topic) RETURNING id` - - dbSub := dbSubscription{ - ID: sub.ID, - OwnerID: sub.OwnerID, - Contact: sub.Contact, - Topic: sub.Topic, - } - - row, err := repo.db.NamedQueryContext(ctx, q, dbSub) - if err != nil { - if pqErr, ok := err.(*pgconn.PgError); ok && pqErr.Code == pgerrcode.UniqueViolation { - return "", errors.Wrap(repoerr.ErrConflict, err) - } - return "", errors.Wrap(repoerr.ErrCreateEntity, err) - } - defer row.Close() - - return sub.ID, nil -} - -func (repo subscriptionsRepo) Retrieve(ctx context.Context, id string) (notifiers.Subscription, error) { - q := `SELECT id, owner_id, contact, topic FROM subscriptions WHERE id = $1` - sub := dbSubscription{} - if err := repo.db.QueryRowxContext(ctx, q, id).StructScan(&sub); err != nil { - if err == sql.ErrNoRows { - return notifiers.Subscription{}, errors.Wrap(repoerr.ErrNotFound, err) - } - return notifiers.Subscription{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - return fromDBSub(sub), nil -} - -func (repo subscriptionsRepo) RetrieveAll(ctx context.Context, pm notifiers.PageMetadata) (notifiers.Page, error) { - q := `SELECT id, owner_id, contact, topic FROM subscriptions` - args := make(map[string]interface{}) - if pm.Topic != "" { - args["topic"] = pm.Topic - } - if pm.Contact != "" { - args["contact"] = pm.Contact - } - var condition string - if len(args) > 0 { - var cond []string - for k := range args { - cond = append(cond, fmt.Sprintf("%s = :%s", k, k)) - } - condition = fmt.Sprintf(" WHERE %s", strings.Join(cond, " AND ")) - q = fmt.Sprintf("%s%s", q, condition) - } - args["offset"] = pm.Offset - q = fmt.Sprintf("%s OFFSET :offset", q) - if pm.Limit > 0 { - q = fmt.Sprintf("%s LIMIT :limit", q) - args["limit"] = pm.Limit - } - - rows, err := repo.db.NamedQueryContext(ctx, q, args) - if err != nil { - return notifiers.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - defer rows.Close() - - var subs []notifiers.Subscription - for rows.Next() { - sub := dbSubscription{} - if err := rows.StructScan(&sub); err != nil { - return notifiers.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - subs = append(subs, fromDBSub(sub)) - } - - if len(subs) == 0 { - return notifiers.Page{}, repoerr.ErrNotFound - } - - cq := fmt.Sprintf(`SELECT COUNT(*) FROM subscriptions %s`, condition) - total, err := total(ctx, repo.db, cq, args) - if err != nil { - return notifiers.Page{}, errors.Wrap(repoerr.ErrViewEntity, err) - } - - ret := notifiers.Page{ - PageMetadata: pm, - Total: total, - Subscriptions: subs, - } - - return ret, nil -} - -func (repo subscriptionsRepo) Remove(ctx context.Context, id string) error { - q := `DELETE from subscriptions WHERE id = $1` - - if r := repo.db.QueryRowxContext(ctx, q, id); r.Err() != nil { - return errors.Wrap(repoerr.ErrRemoveEntity, r.Err()) - } - return nil -} - -func total(ctx context.Context, db Database, query string, params interface{}) (uint, error) { - rows, err := db.NamedQueryContext(ctx, query, params) - if err != nil { - return 0, err - } - defer rows.Close() - var total uint - if rows.Next() { - if err := rows.Scan(&total); err != nil { - return 0, err - } - } - return total, nil -} - -type dbSubscription struct { - ID string `db:"id"` - OwnerID string `db:"owner_id"` - Contact string `db:"contact"` - Topic string `db:"topic"` -} - -func fromDBSub(sub dbSubscription) notifiers.Subscription { - return notifiers.Subscription{ - ID: sub.ID, - OwnerID: sub.OwnerID, - Contact: sub.Contact, - Topic: sub.Topic, - } -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/postgres/subscriptions_test.go b/docker/addons/vault/scripts/consumers/notifiers/postgres/subscriptions_test.go deleted file mode 100644 index 507de040..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/postgres/subscriptions_test.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "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/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel" -) - -const ( - owner = "owner@example.com" - numSubs = 100 -) - -var tracer = otel.Tracer("tests") - -func TestSave(t *testing.T) { - dbMiddleware := postgres.NewDatabase(db, tracer) - repo := postgres.New(dbMiddleware) - - id1, err := idProvider.ID() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - id2, err := idProvider.ID() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - sub1 := notifiers.Subscription{ - OwnerID: id1, - ID: id1, - Contact: owner, - Topic: "topic.subtopic", - } - - sub2 := sub1 - sub2.ID = id2 - - cases := []struct { - desc string - sub notifiers.Subscription - id string - err error - }{ - { - desc: "save successfully", - sub: sub1, - id: id1, - err: nil, - }, - { - desc: "save duplicate", - sub: sub2, - id: "", - err: repoerr.ErrConflict, - }, - } - - for _, tc := range cases { - id, err := repo.Save(context.Background(), tc.sub) - assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected id %s got %s\n", 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 TestView(t *testing.T) { - dbMiddleware := postgres.NewDatabase(db, tracer) - repo := postgres.New(dbMiddleware) - - id, err := idProvider.ID() - require.Nil(t, err, fmt.Sprintf("got an error creating id: %s", err)) - - sub := notifiers.Subscription{ - OwnerID: id, - ID: id, - Contact: owner, - Topic: "view.subtopic", - } - - ret, err := repo.Save(context.Background(), sub) - require.Nil(t, err, fmt.Sprintf("creating subscription must not fail: %s", err)) - require.Equal(t, id, ret, fmt.Sprintf("provided id %s must be the same as the returned id %s", id, ret)) - - cases := []struct { - desc string - sub notifiers.Subscription - id string - err error - }{ - { - desc: "retrieve successfully", - sub: sub, - id: id, - err: nil, - }, - { - desc: "retrieve not existing", - sub: notifiers.Subscription{}, - id: "non-existing", - err: repoerr.ErrNotFound, - }, - } - - for _, tc := range cases { - sub, err := repo.Retrieve(context.Background(), tc.id) - assert.Equal(t, tc.sub, sub, fmt.Sprintf("%s: expected sub %v got %v\n", tc.desc, tc.sub, sub)) - 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) { - _, err := db.Exec("DELETE FROM subscriptions") - require.Nil(t, err, fmt.Sprintf("cleanup must not fail: %s", err)) - - dbMiddleware := postgres.NewDatabase(db, tracer) - repo := postgres.New(dbMiddleware) - - var subs []notifiers.Subscription - - for i := 0; i < numSubs; i++ { - id, err := idProvider.ID() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - sub := notifiers.Subscription{ - OwnerID: "owner", - ID: id, - Contact: owner, - Topic: fmt.Sprintf("list.subtopic.%d", i), - } - - ret, err := repo.Save(context.Background(), sub) - require.Nil(t, err, fmt.Sprintf("creating subscription must not fail: %s", err)) - require.Equal(t, id, ret, fmt.Sprintf("provided id %s must be the same as the returned id %s", id, ret)) - subs = append(subs, sub) - } - - cases := []struct { - desc string - pageMeta notifiers.PageMetadata - page notifiers.Page - err error - }{ - { - desc: "retrieve successfully", - pageMeta: notifiers.PageMetadata{ - Offset: 10, - Limit: 2, - }, - page: notifiers.Page{ - Total: numSubs, - PageMetadata: notifiers.PageMetadata{ - Offset: 10, - Limit: 2, - }, - Subscriptions: subs[10:12], - }, - err: nil, - }, - { - desc: "retrieve with contact", - pageMeta: notifiers.PageMetadata{ - Offset: 10, - Limit: 2, - Contact: owner, - }, - page: notifiers.Page{ - Total: numSubs, - PageMetadata: notifiers.PageMetadata{ - Offset: 10, - Limit: 2, - Contact: owner, - }, - Subscriptions: subs[10:12], - }, - err: nil, - }, - { - desc: "retrieve with topic", - pageMeta: notifiers.PageMetadata{ - Offset: 0, - Limit: 2, - Topic: "list.subtopic.11", - }, - page: notifiers.Page{ - Total: 1, - PageMetadata: notifiers.PageMetadata{ - Offset: 0, - Limit: 2, - Topic: "list.subtopic.11", - }, - Subscriptions: subs[11:12], - }, - err: nil, - }, - { - desc: "retrieve with no limit", - pageMeta: notifiers.PageMetadata{ - Offset: 0, - Limit: -1, - }, - page: notifiers.Page{ - Total: numSubs, - PageMetadata: notifiers.PageMetadata{ - Limit: -1, - }, - Subscriptions: subs, - }, - err: nil, - }, - } - - for _, tc := range cases { - page, err := repo.RetrieveAll(context.Background(), tc.pageMeta) - assert.Equal(t, tc.page, page, fmt.Sprintf("%s: got unexpected page\n", tc.desc)) - 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) { - dbMiddleware := postgres.NewDatabase(db, tracer) - repo := postgres.New(dbMiddleware) - id, err := idProvider.ID() - require.Nil(t, err, fmt.Sprintf("got an error creating id: %s", err)) - sub := notifiers.Subscription{ - OwnerID: id, - ID: id, - Contact: owner, - Topic: "remove.subtopic.%d", - } - - ret, err := repo.Save(context.Background(), sub) - require.Nil(t, err, fmt.Sprintf("creating subscription must not fail: %s", err)) - require.Equal(t, id, ret, fmt.Sprintf("provided id %s must be the same as the returned id %s", id, ret)) - - cases := []struct { - desc string - id string - err error - }{ - { - desc: "remove successfully", - id: id, - err: nil, - }, - { - desc: "remove not existing", - id: "empty", - err: nil, - }, - } - - for _, tc := range cases { - err := repo.Remove(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)) - } -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/service.go b/docker/addons/vault/scripts/consumers/notifiers/service.go deleted file mode 100644 index 1207a011..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/service.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package notifiers - -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" -) - -// ErrMessage indicates an error converting a message to Magistrala message. -var ErrMessage = errors.New("failed to convert to Magistrala message") - -var _ consumers.AsyncConsumer = (*notifierService)(nil) - -// Service reprents a notification service. -// -//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" -type Service interface { - // CreateSubscription persists a subscription. - // Successful operation is indicated by non-nil error response. - CreateSubscription(ctx context.Context, token string, sub Subscription) (string, error) - - // ViewSubscription retrieves the subscription for the given user and id. - ViewSubscription(ctx context.Context, token, id string) (Subscription, error) - - // ListSubscriptions lists subscriptions having the provided user token and search params. - ListSubscriptions(ctx context.Context, token string, pm PageMetadata) (Page, error) - - // RemoveSubscription removes the subscription having the provided identifier. - RemoveSubscription(ctx context.Context, token, id string) error - - consumers.BlockingConsumer -} - -var _ Service = (*notifierService)(nil) - -type notifierService struct { - authn mgauthn.Authentication - subs SubscriptionsRepository - idp magistrala.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 { - return ¬ifierService{ - authn: authn, - subs: subs, - idp: idp, - notifier: notifier, - errCh: make(chan error, 1), - from: from, - } -} - -func (ns *notifierService) CreateSubscription(ctx context.Context, token string, sub Subscription) (string, error) { - session, err := ns.authn.Authenticate(ctx, token) - if err != nil { - return "", err - } - sub.ID, err = ns.idp.ID() - if err != nil { - return "", err - } - - sub.OwnerID = session.DomainUserID - id, err := ns.subs.Save(ctx, sub) - if err != nil { - return "", errors.Wrap(svcerr.ErrCreateEntity, err) - } - return id, nil -} - -func (ns *notifierService) ViewSubscription(ctx context.Context, token, id string) (Subscription, error) { - if _, err := ns.authn.Authenticate(ctx, token); err != nil { - return Subscription{}, err - } - - return ns.subs.Retrieve(ctx, id) -} - -func (ns *notifierService) ListSubscriptions(ctx context.Context, token string, pm PageMetadata) (Page, error) { - if _, err := ns.authn.Authenticate(ctx, token); err != nil { - return Page{}, err - } - - return ns.subs.RetrieveAll(ctx, pm) -} - -func (ns *notifierService) RemoveSubscription(ctx context.Context, token, id string) error { - if _, err := ns.authn.Authenticate(ctx, token); err != nil { - return err - } - - return ns.subs.Remove(ctx, id) -} - -func (ns *notifierService) ConsumeBlocking(ctx context.Context, message interface{}) error { - msg, ok := message.(*messaging.Message) - if !ok { - return ErrMessage - } - topic := msg.GetChannel() - if msg.GetSubtopic() != "" { - topic = fmt.Sprintf("%s.%s", msg.GetChannel(), msg.GetSubtopic()) - } - pm := PageMetadata{ - Topic: topic, - Offset: 0, - Limit: -1, - } - page, err := ns.subs.RetrieveAll(ctx, pm) - if err != nil { - return err - } - - var to []string - for _, sub := range page.Subscriptions { - to = append(to, sub.Contact) - } - if len(to) > 0 { - err := ns.notifier.Notify(ns.from, to, msg) - if err != nil { - return errors.Wrap(ErrNotify, err) - } - } - - return nil -} - -func (ns *notifierService) ConsumeAsync(ctx context.Context, message interface{}) { - msg, ok := message.(*messaging.Message) - if !ok { - ns.errCh <- ErrMessage - return - } - topic := msg.GetChannel() - if msg.GetSubtopic() != "" { - topic = fmt.Sprintf("%s.%s", msg.GetChannel(), msg.GetSubtopic()) - } - pm := PageMetadata{ - Topic: topic, - Offset: 0, - Limit: -1, - } - page, err := ns.subs.RetrieveAll(ctx, pm) - if err != nil { - ns.errCh <- err - return - } - - var to []string - for _, sub := range page.Subscriptions { - to = append(to, sub.Contact) - } - if len(to) > 0 { - if err := ns.notifier.Notify(ns.from, to, msg); err != nil { - ns.errCh <- errors.Wrap(ErrNotify, err) - } - } -} - -func (ns *notifierService) Errors() <-chan error { - return ns.errCh -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/service_test.go b/docker/addons/vault/scripts/consumers/notifiers/service_test.go deleted file mode 100644 index 28c0092b..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/service_test.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package notifiers_test - -import ( - "context" - "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/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const ( - total = 100 - exampleUser1 = "token1" - exampleUser2 = "token2" - validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" -) - -func newService() (notifiers.Service, *authnmocks.Authentication, *mocks.SubscriptionsRepository) { - repo := new(mocks.SubscriptionsRepository) - auth := new(authnmocks.Authentication) - notifier := new(mocks.Notifier) - idp := uuid.NewMock() - from := "exampleFrom" - return notifiers.New(auth, repo, idp, notifier, from), auth, repo -} - -func TestCreateSubscription(t *testing.T) { - svc, auth, repo := newService() - - cases := []struct { - desc string - token string - sub notifiers.Subscription - id string - err error - authenticateErr error - userID string - }{ - { - desc: "test success", - token: exampleUser1, - sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, - id: uuid.Prefix + fmt.Sprintf("%012d", 1), - err: nil, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test already existing", - token: exampleUser1, - sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, - id: "", - err: repoerr.ErrConflict, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test with empty token", - token: "", - sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, - id: "", - err: svcerr.ErrAuthentication, - authenticateErr: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(mgauthn.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)) - assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.id, id)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestViewSubscription(t *testing.T) { - svc, auth, repo := newService() - sub := notifiers.Subscription{ - Contact: exampleUser1, - Topic: "valid.topic", - ID: testsutil.GenerateUUID(t), - OwnerID: validID, - } - - cases := []struct { - desc string - token string - id string - sub notifiers.Subscription - err error - authenticateErr error - userID string - }{ - { - desc: "test success", - token: exampleUser1, - id: validID, - sub: sub, - err: nil, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test not existing", - token: exampleUser1, - id: "not_exist", - sub: notifiers.Subscription{}, - err: svcerr.ErrNotFound, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test with empty token", - token: "", - id: validID, - sub: notifiers.Subscription{}, - err: svcerr.ErrAuthentication, - authenticateErr: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(mgauthn.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)) - assert.Equal(t, tc.sub, sub, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.sub, sub)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestListSubscriptions(t *testing.T) { - svc, auth, repo := newService() - sub := notifiers.Subscription{Contact: exampleUser1, OwnerID: exampleUser1} - topic := "topic.subtopic" - var subs []notifiers.Subscription - for i := 0; i < total; i++ { - tmp := sub - if i%2 == 0 { - tmp.Contact = exampleUser2 - tmp.OwnerID = exampleUser2 - } - tmp.Topic = fmt.Sprintf("%s.%d", topic, i) - tmp.ID = testsutil.GenerateUUID(t) - tmp.OwnerID = validID - subs = append(subs, tmp) - } - - var offsetSubs []notifiers.Subscription - for i := 20; i < 40; i += 2 { - offsetSubs = append(offsetSubs, subs[i]) - } - - cases := []struct { - desc string - token string - pageMeta notifiers.PageMetadata - page notifiers.Page - err error - authenticateErr error - userID string - }{ - { - desc: "test success", - token: exampleUser1, - pageMeta: notifiers.PageMetadata{ - Offset: 0, - Limit: 3, - }, - err: nil, - page: notifiers.Page{ - PageMetadata: notifiers.PageMetadata{ - Offset: 0, - Limit: 3, - }, - Subscriptions: subs[:3], - Total: total, - }, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test not existing", - token: exampleUser1, - pageMeta: notifiers.PageMetadata{ - Limit: 10, - Contact: "empty@example.com", - }, - page: notifiers.Page{}, - err: svcerr.ErrNotFound, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test with empty token", - token: "", - pageMeta: notifiers.PageMetadata{ - Offset: 2, - Limit: 12, - Topic: "topic.subtopic.13", - }, - page: notifiers.Page{}, - err: svcerr.ErrAuthentication, - authenticateErr: svcerr.ErrAuthentication, - }, - { - desc: "test with topic", - token: exampleUser1, - pageMeta: notifiers.PageMetadata{ - Limit: 10, - Topic: fmt.Sprintf("%s.%d", topic, 4), - }, - page: notifiers.Page{ - PageMetadata: notifiers.PageMetadata{ - Limit: 10, - Topic: fmt.Sprintf("%s.%d", topic, 4), - }, - Subscriptions: subs[4:5], - Total: 1, - }, - err: nil, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test with contact and offset", - token: exampleUser1, - pageMeta: notifiers.PageMetadata{ - Offset: 10, - Limit: 10, - Contact: exampleUser2, - }, - page: notifiers.Page{ - PageMetadata: notifiers.PageMetadata{ - Offset: 10, - Limit: 10, - Contact: exampleUser2, - }, - Subscriptions: offsetSubs, - Total: uint(total / 2), - }, - err: nil, - authenticateErr: nil, - userID: validID, - }, - } - - for _, tc := range cases { - repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(mgauthn.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)) - assert.Equal(t, tc.page, page, fmt.Sprintf("%s: got unexpected page\n", tc.desc)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestRemoveSubscription(t *testing.T) { - svc, auth, repo := newService() - sub := notifiers.Subscription{ - ID: testsutil.GenerateUUID(t), - } - - cases := []struct { - desc string - token string - id string - err error - authenticateErr error - userID string - }{ - { - desc: "test success", - token: exampleUser1, - id: sub.ID, - err: nil, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test not existing", - token: exampleUser1, - id: "not_exist", - err: svcerr.ErrNotFound, - authenticateErr: nil, - userID: validID, - }, - { - desc: "test with empty token", - token: "", - id: sub.ID, - err: svcerr.ErrAuthentication, - authenticateErr: svcerr.ErrAuthentication, - }, - } - - for _, tc := range cases { - repoCall := auth.On("Authenticate", context.Background(), tc.token).Return(mgauthn.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)) - repoCall.Unset() - repoCall1.Unset() - } -} - -func TestConsume(t *testing.T) { - svc, _, repo := newService() - msg := messaging.Message{ - Channel: "topic", - Subtopic: "subtopic", - } - errMsg := messaging.Message{ - Channel: "topic", - Subtopic: "subtopic-2", - } - - cases := []struct { - desc string - msg *messaging.Message - err error - }{ - { - desc: "test success", - msg: &msg, - err: nil, - }, - { - desc: "test fail", - msg: &errMsg, - err: notifiers.ErrNotify, - }, - } - - for _, tc := range cases { - repoCall := repo.On("RetrieveAll", context.TODO(), mock.Anything).Return(notifiers.Page{}, tc.err) - err := svc.ConsumeBlocking(context.TODO(), tc.msg) - 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/docker/addons/vault/scripts/consumers/notifiers/smtp/notifier.go b/docker/addons/vault/scripts/consumers/notifiers/smtp/notifier.go deleted file mode 100644 index fb8d618e..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/smtp/notifier.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package smtp - -import ( - "fmt" - - "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/internal/email" - "github.com/absmach/magistrala/pkg/messaging" -) - -const ( - footer = "Sent by Magistrala SMTP Notification" - contentTemplate = "A publisher with an id %s sent the message over %s with the following values \n %s" -) - -var _ notifiers.Notifier = (*notifier)(nil) - -type notifier struct { - agent *email.Agent -} - -// New instantiates SMTP message notifier. -func New(agent *email.Agent) notifiers.Notifier { - return ¬ifier{agent: agent} -} - -func (n *notifier) Notify(from string, to []string, msg *messaging.Message) error { - subject := fmt.Sprintf(`Notification for Channel %s`, msg.GetChannel()) - if msg.GetSubtopic() != "" { - subject = fmt.Sprintf("%s and subtopic %s", subject, msg.GetSubtopic()) - } - - values := string(msg.GetPayload()) - content := fmt.Sprintf(contentTemplate, msg.GetPublisher(), msg.GetProtocol(), values) - - return n.agent.Send(to, from, subject, "", "", content, footer) -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/subscriptions.go b/docker/addons/vault/scripts/consumers/notifiers/subscriptions.go deleted file mode 100644 index dcaf4eb6..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/subscriptions.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package notifiers - -import "context" - -// Subscription represents a user Subscription. -type Subscription struct { - ID string - OwnerID string - Contact string - Topic string -} - -// Page represents page metadata with content. -type Page struct { - PageMetadata - Total uint - Subscriptions []Subscription -} - -// PageMetadata contains page metadata that helps navigation. -type PageMetadata struct { - Offset uint - // Limit values less than 0 indicate no limit. - Limit int - Topic string - Contact string -} - -// SubscriptionsRepository specifies a Subscription persistence API. -// -//go:generate mockery --name SubscriptionsRepository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" -type SubscriptionsRepository interface { - // Save persists a subscription. Successful operation is indicated by non-nil - // error response. - Save(ctx context.Context, sub Subscription) (string, error) - - // Retrieve retrieves the subscription for the given id. - Retrieve(ctx context.Context, id string) (Subscription, error) - - // RetrieveAll retrieves all the subscriptions for the given page metadata. - RetrieveAll(ctx context.Context, pm PageMetadata) (Page, error) - - // Remove removes the subscription for the given ID. - Remove(ctx context.Context, id string) error -} diff --git a/docker/addons/vault/scripts/consumers/notifiers/tracing/doc.go b/docker/addons/vault/scripts/consumers/notifiers/tracing/doc.go deleted file mode 100644 index 2d65dbe4..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/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/docker/addons/vault/scripts/consumers/notifiers/tracing/subscriptions.go b/docker/addons/vault/scripts/consumers/notifiers/tracing/subscriptions.go deleted file mode 100644 index c8c29201..00000000 --- a/docker/addons/vault/scripts/consumers/notifiers/tracing/subscriptions.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tracing contains middlewares that will add spans -// to existing traces. -package tracing - -import ( - "context" - - "github.com/absmach/magistrala/consumers/notifiers" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -const ( - saveOp = "save_op" - retrieveOp = "retrieve_op" - retrieveAllOp = "retrieve_all_op" - removeOp = "remove_op" -) - -var _ notifiers.SubscriptionsRepository = (*subRepositoryMiddleware)(nil) - -type subRepositoryMiddleware struct { - tracer trace.Tracer - repo notifiers.SubscriptionsRepository -} - -// New instantiates a new Subscriptions repository that -// tracks request and their latency, and adds spans to context. -func New(tracer trace.Tracer, repo notifiers.SubscriptionsRepository) notifiers.SubscriptionsRepository { - return subRepositoryMiddleware{ - tracer: tracer, - repo: repo, - } -} - -// Save traces the "Save" operation of the wrapped Subscriptions repository. -func (urm subRepositoryMiddleware) Save(ctx context.Context, sub notifiers.Subscription) (string, error) { - ctx, span := urm.tracer.Start(ctx, saveOp, trace.WithAttributes( - attribute.String("id", sub.ID), - attribute.String("contact", sub.Contact), - attribute.String("topic", sub.Topic), - )) - defer span.End() - - return urm.repo.Save(ctx, sub) -} - -// Retrieve traces the "Retrieve" operation of the wrapped Subscriptions repository. -func (urm subRepositoryMiddleware) Retrieve(ctx context.Context, id string) (notifiers.Subscription, error) { - ctx, span := urm.tracer.Start(ctx, retrieveOp, trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return urm.repo.Retrieve(ctx, id) -} - -// RetrieveAll traces the "RetrieveAll" operation of the wrapped Subscriptions repository. -func (urm subRepositoryMiddleware) RetrieveAll(ctx context.Context, pm notifiers.PageMetadata) (notifiers.Page, error) { - ctx, span := urm.tracer.Start(ctx, retrieveAllOp) - defer span.End() - - return urm.repo.RetrieveAll(ctx, pm) -} - -// Remove traces the "Remove" operation of the wrapped Subscriptions repository. -func (urm subRepositoryMiddleware) Remove(ctx context.Context, id string) error { - ctx, span := urm.tracer.Start(ctx, removeOp, trace.WithAttributes(attribute.String("id", id))) - defer span.End() - - return urm.repo.Remove(ctx, id) -} diff --git a/docker/addons/vault/scripts/consumers/tracing/consumers.go b/docker/addons/vault/scripts/consumers/tracing/consumers.go deleted file mode 100644 index c9cb362b..00000000 --- a/docker/addons/vault/scripts/consumers/tracing/consumers.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package tracing - -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" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -const ( - consumeBlockingOP = "retrieve_blocking" // This is not specified in the open telemetry spec. - consumeAsyncOP = "retrieve_async" // This is not specified in the open telemetry spec. -) - -var defaultAttributes = []attribute.KeyValue{ - attribute.String("messaging.system", "nats"), - 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", "nats"), - attribute.String("network.protocol.version", "2.2.4"), - attribute.String("network.transport", "tcp"), - attribute.String("network.type", "ipv4"), -} - -var ( - _ consumers.AsyncConsumer = (*tracingMiddlewareAsync)(nil) - _ consumers.BlockingConsumer = (*tracingMiddlewareBlock)(nil) -) - -type tracingMiddlewareAsync struct { - consumer consumers.AsyncConsumer - tracer trace.Tracer - host server.Config -} -type tracingMiddlewareBlock struct { - consumer consumers.BlockingConsumer - tracer trace.Tracer - host server.Config -} - -// NewAsync creates a new traced consumers.AsyncConsumer service. -func NewAsync(tracer trace.Tracer, consumerAsync consumers.AsyncConsumer, host server.Config) consumers.AsyncConsumer { - return &tracingMiddlewareAsync{ - consumer: consumerAsync, - tracer: tracer, - host: host, - } -} - -// NewBlocking creates a new traced consumers.BlockingConsumer service. -func NewBlocking(tracer trace.Tracer, consumerBlock consumers.BlockingConsumer, host server.Config) consumers.BlockingConsumer { - return &tracingMiddlewareBlock{ - consumer: consumerBlock, - tracer: tracer, - host: host, - } -} - -// ConsumeBlocking traces consume operations for message/s consumed. -func (tm *tracingMiddlewareBlock) ConsumeBlocking(ctx context.Context, messages interface{}) error { - var span trace.Span - switch m := messages.(type) { - case mgjson.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) - defer span.End() - } - case []senml.Message: - if len(m) > 0 { - firstMsg := m[0] - ctx, span = createSpan(ctx, consumeBlockingOP, firstMsg.Publisher, firstMsg.Channel, firstMsg.Subtopic, len(m), tm.host, trace.SpanKindConsumer, tm.tracer) - defer span.End() - } - } - return tm.consumer.ConsumeBlocking(ctx, messages) -} - -// ConsumeAsync traces consume operations for message/s consumed. -func (tm *tracingMiddlewareAsync) ConsumeAsync(ctx context.Context, messages interface{}) { - var span trace.Span - switch m := messages.(type) { - case mgjson.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) - defer span.End() - } - case []senml.Message: - if len(m) > 0 { - firstMsg := m[0] - ctx, span = createSpan(ctx, consumeAsyncOP, firstMsg.Publisher, firstMsg.Channel, firstMsg.Subtopic, len(m), tm.host, trace.SpanKindConsumer, tm.tracer) - defer span.End() - } - } - tm.consumer.ConsumeAsync(ctx, messages) -} - -// Errors traces async consume errors. -func (tm *tracingMiddlewareAsync) Errors() <-chan error { - return tm.consumer.Errors() -} - -func createSpan(ctx context.Context, operation, clientID, topic, subTopic string, noMessages 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), - attribute.Int("messaging.batch.message_count", noMessages), - } - - kvOpts = append(kvOpts, defaultAttributes...) - - return tracer.Start(ctx, spanName, trace.WithAttributes(kvOpts...), trace.WithSpanKind(spanKind)) -} diff --git a/docker/addons/vault/scripts/consumers/writers/README.md b/docker/addons/vault/scripts/consumers/writers/README.md deleted file mode 100644 index 3bfd0e6b..00000000 --- a/docker/addons/vault/scripts/consumers/writers/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Writers - -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. - -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 -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]. - -[doc]: https://docs.magistrala.abstractmachines.fr -[compose]: ../docker/docker-compose.yml diff --git a/docker/addons/vault/scripts/consumers/writers/api/doc.go b/docker/addons/vault/scripts/consumers/writers/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/consumers/writers/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/docker/addons/vault/scripts/consumers/writers/api/logging.go b/docker/addons/vault/scripts/consumers/writers/api/logging.go deleted file mode 100644 index 77e5f914..00000000 --- a/docker/addons/vault/scripts/consumers/writers/api/logging.go +++ /dev/null @@ -1,47 +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/consumers" -) - -var _ consumers.BlockingConsumer = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - consumer consumers.BlockingConsumer -} - -// LoggingMiddleware adds logging facilities to the adapter. -func LoggingMiddleware(consumer consumers.BlockingConsumer, logger *slog.Logger) consumers.BlockingConsumer { - return &loggingMiddleware{ - logger: logger, - consumer: consumer, - } -} - -// ConsumeBlocking logs the consume request. It logs the time it took to complete the request. -// If the request fails, it logs the error. -func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) (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("Blocking consumer failed to consume messages successfully", args...) - return - } - lm.logger.Info("Blocking consumer consumed messages successfully", args...) - }(time.Now()) - - return lm.consumer.ConsumeBlocking(ctx, msgs) -} diff --git a/docker/addons/vault/scripts/consumers/writers/api/metrics.go b/docker/addons/vault/scripts/consumers/writers/api/metrics.go deleted file mode 100644 index 29dfb2f4..00000000 --- a/docker/addons/vault/scripts/consumers/writers/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/consumers" - "github.com/go-kit/kit/metrics" -) - -var _ consumers.BlockingConsumer = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - consumer consumers.BlockingConsumer -} - -// MetricsMiddleware returns new message repository -// with Save method wrapped to expose metrics. -func MetricsMiddleware(consumer consumers.BlockingConsumer, counter metrics.Counter, latency metrics.Histogram) consumers.BlockingConsumer { - return &metricsMiddleware{ - counter: counter, - latency: latency, - consumer: consumer, - } -} - -// ConsumeBlocking instruments ConsumeBlocking method with metrics. -func (mm *metricsMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) error { - defer func(begin time.Time) { - mm.counter.With("method", "consume").Add(1) - mm.latency.With("method", "consume").Observe(time.Since(begin).Seconds()) - }(time.Now()) - return mm.consumer.ConsumeBlocking(ctx, msgs) -} diff --git a/docker/addons/vault/scripts/consumers/writers/api/transport.go b/docker/addons/vault/scripts/consumers/writers/api/transport.go deleted file mode 100644 index 3c2fa5d5..00000000 --- a/docker/addons/vault/scripts/consumers/writers/api/transport.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "net/http" - - "github.com/absmach/magistrala" - "github.com/go-chi/chi/v5" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// 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.Handle("/metrics", promhttp.Handler()) - - return r -} diff --git a/docker/addons/vault/scripts/consumers/writers/doc.go b/docker/addons/vault/scripts/consumers/writers/doc.go deleted file mode 100644 index 59e88b65..00000000 --- a/docker/addons/vault/scripts/consumers/writers/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package writers contain the domain concept definitions needed to -// support Magistrala writer services functionality. -package writers diff --git a/docker/addons/vault/scripts/consumers/writers/postgres/README.md b/docker/addons/vault/scripts/consumers/writers/postgres/README.md deleted file mode 100644 index 26898d4b..00000000 --- a/docker/addons/vault/scripts/consumers/writers/postgres/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# Postgres writer - -Postgres writer provides message repository implementation for Postgres. - -## 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_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 | "" | - -## 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. - -To start the service, execute the following shell script: - -```bash -# download the latest version of the service -git clone https://github.com/absmach/magistrala - -cd magistrala - -# compile the postgres writer -make postgres-writer - -# copy binary to bin -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] \ - -$GOBIN/magistrala-postgres-writer -``` - -## Usage - -Starting service will start consuming normalized messages in SenML format. diff --git a/docker/addons/vault/scripts/consumers/writers/postgres/consumer.go b/docker/addons/vault/scripts/consumers/writers/postgres/consumer.go deleted file mode 100644 index e78408e4..00000000 --- a/docker/addons/vault/scripts/consumers/writers/postgres/consumer.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "context" - "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/gofrs/uuid/v5" - "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jmoiron/sqlx" // required for DB access -) - -var ( - errInvalidMessage = errors.New("invalid message representation") - errSaveMessage = errors.New("failed to save message to postgres database") - errTransRollback = errors.New("failed to rollback transaction") - errNoTable = errors.New("relation does not exist") -) - -var _ consumers.BlockingConsumer = (*postgresRepo)(nil) - -type postgresRepo struct { - db *sqlx.DB -} - -// New returns new PostgreSQL writer. -func New(db *sqlx.DB) consumers.BlockingConsumer { - return &postgresRepo{db: db} -} - -func (pr postgresRepo) ConsumeBlocking(ctx context.Context, message interface{}) (err error) { - switch m := message.(type) { - case mgjson.Messages: - return pr.saveJSON(ctx, m) - default: - return pr.saveSenml(ctx, m) - } -} - -func (pr postgresRepo) saveSenml(ctx context.Context, messages interface{}) (err error) { - msgs, ok := messages.([]senml.Message) - if !ok { - return errSaveMessage - } - q := `INSERT INTO messages (id, channel, subtopic, publisher, protocol, - name, unit, value, string_value, bool_value, data_value, sum, - time, update_time) - VALUES (:id, :channel, :subtopic, :publisher, :protocol, :name, :unit, - :value, :string_value, :bool_value, :data_value, :sum, - :time, :update_time);` - - tx, err := pr.db.BeginTxx(ctx, nil) - if err != nil { - return errors.Wrap(errSaveMessage, err) - } - defer func() { - if err != nil { - if txErr := tx.Rollback(); txErr != nil { - err = errors.Wrap(err, errors.Wrap(errTransRollback, txErr)) - } - return - } - - if err = tx.Commit(); err != nil { - err = errors.Wrap(errSaveMessage, err) - } - }() - - for _, msg := range msgs { - id, err := uuid.NewV4() - if err != nil { - return err - } - m := senmlMessage{Message: msg, ID: id.String()} - if _, err := tx.NamedExec(q, m); err != nil { - pgErr, ok := err.(*pgconn.PgError) - if ok { - if pgErr.Code == pgerrcode.InvalidTextRepresentation { - return errors.Wrap(errSaveMessage, errInvalidMessage) - } - } - - return errors.Wrap(errSaveMessage, err) - } - } - return err -} - -func (pr postgresRepo) saveJSON(ctx context.Context, msgs mgjson.Messages) error { - if err := pr.insertJSON(ctx, msgs); err != nil { - if err == errNoTable { - if err := pr.createTable(msgs.Format); err != nil { - return err - } - return pr.insertJSON(ctx, msgs) - } - return err - } - return nil -} - -func (pr postgresRepo) insertJSON(ctx context.Context, msgs mgjson.Messages) error { - tx, err := pr.db.BeginTxx(ctx, nil) - if err != nil { - return errors.Wrap(errSaveMessage, err) - } - defer func() { - if err != nil { - if txErr := tx.Rollback(); txErr != nil { - err = errors.Wrap(err, errors.Wrap(errTransRollback, txErr)) - } - return - } - - if err = tx.Commit(); err != nil { - err = errors.Wrap(errSaveMessage, err) - } - }() - - q := `INSERT INTO %s (id, channel, created, subtopic, publisher, protocol, payload) - VALUES (:id, :channel, :created, :subtopic, :publisher, :protocol, :payload);` - q = fmt.Sprintf(q, msgs.Format) - - for _, m := range msgs.Data { - var dbmsg jsonMessage - dbmsg, err = toJSONMessage(m) - if err != nil { - return errors.Wrap(errSaveMessage, err) - } - - if _, err = tx.NamedExec(q, dbmsg); err != nil { - pgErr, ok := err.(*pgconn.PgError) - if ok { - switch pgErr.Code { - case pgerrcode.InvalidTextRepresentation: - return errors.Wrap(errSaveMessage, errInvalidMessage) - case pgerrcode.UndefinedTable: - return errNoTable - } - } - return err - } - } - return nil -} - -func (pr postgresRepo) createTable(name string) error { - q := `CREATE TABLE IF NOT EXISTS %s ( - id UUID, - created BIGINT, - channel VARCHAR(254), - subtopic VARCHAR(254), - publisher VARCHAR(254), - protocol TEXT, - payload JSONB, - PRIMARY KEY (id) - )` - q = fmt.Sprintf(q, name) - - _, err := pr.db.Exec(q) - return err -} - -type senmlMessage struct { - senml.Message - ID string `db:"id"` -} - -type jsonMessage struct { - ID string `db:"id"` - Channel string `db:"channel"` - Created int64 `db:"created"` - Subtopic string `db:"subtopic"` - Publisher string `db:"publisher"` - Protocol string `db:"protocol"` - Payload []byte `db:"payload"` -} - -func toJSONMessage(msg mgjson.Message) (jsonMessage, error) { - id, err := uuid.NewV4() - if err != nil { - return jsonMessage{}, err - } - - data := []byte("{}") - if msg.Payload != nil { - b, err := json.Marshal(msg.Payload) - if err != nil { - return jsonMessage{}, errors.Wrap(errSaveMessage, err) - } - data = b - } - - m := jsonMessage{ - ID: id.String(), - Channel: msg.Channel, - Created: msg.Created, - Subtopic: msg.Subtopic, - Publisher: msg.Publisher, - Protocol: msg.Protocol, - Payload: data, - } - - return m, nil -} diff --git a/docker/addons/vault/scripts/consumers/writers/postgres/consumer_test.go b/docker/addons/vault/scripts/consumers/writers/postgres/consumer_test.go deleted file mode 100644 index bbaee845..00000000 --- a/docker/addons/vault/scripts/consumers/writers/postgres/consumer_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "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/gofrs/uuid/v5" - "github.com/stretchr/testify/assert" -) - -const ( - msgsNum = 42 - valueFields = 5 - subtopic = "topic" -) - -var ( - v float64 = 5 - stringV = "value" - boolV = true - dataV = "base64" - sum float64 = 42 -) - -func TestSaveSenml(t *testing.T) { - repo := postgres.New(db) - - chid, err := uuid.NewV4() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - msg := senml.Message{} - msg.Channel = chid.String() - - pubid, err := uuid.NewV4() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - msg.Publisher = pubid.String() - - now := time.Now().Unix() - var msgs []senml.Message - - for i := 0; i < msgsNum; i++ { - // Mix possible values as well as value sum. - count := i % valueFields - switch count { - case 0: - msg.Subtopic = subtopic - msg.Value = &v - case 1: - msg.BoolValue = &boolV - case 2: - msg.StringValue = &stringV - case 3: - msg.DataValue = &dataV - case 4: - msg.Sum = &sum - } - - msg.Time = float64(now + int64(i)) - msgs = append(msgs, msg) - } - - err = repo.ConsumeBlocking(context.TODO(), msgs) - assert.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) -} - -func TestSaveJSON(t *testing.T) { - repo := postgres.New(db) - - chid, err := uuid.NewV4() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - pubid, err := uuid.NewV4() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - msg := json.Message{ - Channel: chid.String(), - Publisher: pubid.String(), - Created: time.Now().Unix(), - Subtopic: "subtopic/format/some_json", - Protocol: "mqtt", - Payload: map[string]interface{}{ - "field_1": 123, - "field_2": "value", - "field_3": false, - "field_4": 12.344, - "field_5": map[string]interface{}{ - "field_1": "value", - "field_2": 42, - }, - }, - } - - now := time.Now().Unix() - msgs := json.Messages{ - Format: "some_json", - } - - for i := 0; i < msgsNum; i++ { - msg.Created = now + int64(i) - msgs.Data = append(msgs.Data, msg) - } - - err = repo.ConsumeBlocking(context.TODO(), msgs) - assert.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) -} diff --git a/docker/addons/vault/scripts/consumers/writers/postgres/doc.go b/docker/addons/vault/scripts/consumers/writers/postgres/doc.go deleted file mode 100644 index a92d4f9b..00000000 --- a/docker/addons/vault/scripts/consumers/writers/postgres/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres contains repository implementations using Postgres as -// the underlying database. -package postgres diff --git a/docker/addons/vault/scripts/consumers/writers/postgres/init.go b/docker/addons/vault/scripts/consumers/writers/postgres/init.go deleted file mode 100644 index de140b25..00000000 --- a/docker/addons/vault/scripts/consumers/writers/postgres/init.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import migrate "github.com/rubenv/sql-migrate" - -// Migration of postgres-writer. -func Migration() *migrate.MemoryMigrationSource { - return &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "messages_1", - Up: []string{ - `CREATE TABLE IF NOT EXISTS messages ( - id UUID, - channel UUID, - subtopic VARCHAR(254), - publisher UUID, - protocol TEXT, - name TEXT, - unit TEXT, - value FLOAT, - string_value TEXT, - bool_value BOOL, - data_value BYTEA, - sum FLOAT, - time FLOAT, - update_time FLOAT, - PRIMARY KEY (id) - )`, - }, - Down: []string{ - "DROP TABLE messages", - }, - }, - { - Id: "messages_2", - Up: []string{ - `ALTER TABLE messages DROP CONSTRAINT messages_pkey`, - `ALTER TABLE messages ADD PRIMARY KEY (time, publisher, subtopic, name)`, - }, - }, - }, - } -} diff --git a/docker/addons/vault/scripts/consumers/writers/postgres/setup_test.go b/docker/addons/vault/scripts/consumers/writers/postgres/setup_test.go deleted file mode 100644 index a046f8df..00000000 --- a/docker/addons/vault/scripts/consumers/writers/postgres/setup_test.go +++ /dev/null @@ -1,85 +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 ( - "fmt" - "log" - "os" - "testing" - - "github.com/absmach/magistrala/consumers/writers/postgres" - pgclient "github.com/absmach/magistrala/pkg/postgres" - "github.com/jmoiron/sqlx" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" -) - -var db *sqlx.DB - -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") - - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err = sqlx.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: "", - } - - db, err = pgclient.Setup(dbConfig, *postgres.Migration()) - if err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - 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/docker/addons/vault/scripts/consumers/writers/timescale/README.md b/docker/addons/vault/scripts/consumers/writers/timescale/README.md deleted file mode 100644 index 5554d32f..00000000 --- a/docker/addons/vault/scripts/consumers/writers/timescale/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Timescale writer - -Timescale writer provides message repository implementation for Timescale. - -## 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_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 | "" | - -## 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. - -To start the service, execute the following shell script: - -```bash -# download the latest version of the service -git clone https://github.com/absmach/magistrala - -cd magistrala - -# compile the timescale writer -make timescale-writer - -# copy binary to bin -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 -``` - -## Usage - -Starting service will start consuming normalized messages in SenML format. diff --git a/docker/addons/vault/scripts/consumers/writers/timescale/consumer.go b/docker/addons/vault/scripts/consumers/writers/timescale/consumer.go deleted file mode 100644 index 070fe5d7..00000000 --- a/docker/addons/vault/scripts/consumers/writers/timescale/consumer.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package timescale - -import ( - "context" - "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/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jmoiron/sqlx" // required for DB access -) - -var ( - errInvalidMessage = errors.New("invalid message representation") - errSaveMessage = errors.New("failed to save message to timescale database") - errTransRollback = errors.New("failed to rollback transaction") - errNoTable = errors.New("relation does not exist") -) - -var _ consumers.BlockingConsumer = (*timescaleRepo)(nil) - -type timescaleRepo struct { - db *sqlx.DB -} - -// New returns new TimescaleSQL writer. -func New(db *sqlx.DB) consumers.BlockingConsumer { - return ×caleRepo{db: db} -} - -func (tr *timescaleRepo) ConsumeBlocking(ctx context.Context, message interface{}) (err error) { - switch m := message.(type) { - case mgjson.Messages: - return tr.saveJSON(ctx, m) - default: - return tr.saveSenml(ctx, m) - } -} - -func (tr timescaleRepo) saveSenml(ctx context.Context, messages interface{}) (err error) { - msgs, ok := messages.([]senml.Message) - if !ok { - return errSaveMessage - } - q := `INSERT INTO messages (channel, subtopic, publisher, protocol, - name, unit, value, string_value, bool_value, data_value, sum, - time, update_time) - VALUES (:channel, :subtopic, :publisher, :protocol, :name, :unit, - :value, :string_value, :bool_value, :data_value, :sum, - :time, :update_time);` - - tx, err := tr.db.BeginTxx(ctx, nil) - if err != nil { - return errors.Wrap(errSaveMessage, err) - } - defer func() { - if err != nil { - if txErr := tx.Rollback(); txErr != nil { - err = errors.Wrap(err, errors.Wrap(errTransRollback, txErr)) - } - return - } - - if err = tx.Commit(); err != nil { - err = errors.Wrap(errSaveMessage, err) - } - }() - - for _, msg := range msgs { - m := senmlMessage{Message: msg} - if _, err := tx.NamedExec(q, m); err != nil { - pgErr, ok := err.(*pgconn.PgError) - if ok { - if pgErr.Code == pgerrcode.InvalidTextRepresentation { - return errors.Wrap(errSaveMessage, errInvalidMessage) - } - } - - return errors.Wrap(errSaveMessage, err) - } - } - return err -} - -func (tr timescaleRepo) saveJSON(ctx context.Context, msgs mgjson.Messages) error { - if err := tr.insertJSON(ctx, msgs); err != nil { - if err == errNoTable { - if err := tr.createTable(msgs.Format); err != nil { - return err - } - return tr.insertJSON(ctx, msgs) - } - return err - } - return nil -} - -func (tr timescaleRepo) insertJSON(ctx context.Context, msgs mgjson.Messages) error { - tx, err := tr.db.BeginTxx(ctx, nil) - if err != nil { - return errors.Wrap(errSaveMessage, err) - } - defer func() { - if err != nil { - if txErr := tx.Rollback(); txErr != nil { - err = errors.Wrap(err, errors.Wrap(errTransRollback, txErr)) - } - return - } - - if err = tx.Commit(); err != nil { - err = errors.Wrap(errSaveMessage, err) - } - }() - - q := `INSERT INTO %s (channel, created, subtopic, publisher, protocol, payload) - VALUES (:channel, :created, :subtopic, :publisher, :protocol, :payload);` - q = fmt.Sprintf(q, msgs.Format) - - for _, m := range msgs.Data { - var dbmsg jsonMessage - dbmsg, err = toJSONMessage(m) - if err != nil { - return errors.Wrap(errSaveMessage, err) - } - if _, err = tx.NamedExec(q, dbmsg); err != nil { - pgErr, ok := err.(*pgconn.PgError) - if ok { - switch pgErr.Code { - case pgerrcode.InvalidTextRepresentation: - return errors.Wrap(errSaveMessage, errInvalidMessage) - case pgerrcode.UndefinedTable: - return errNoTable - } - } - return err - } - } - return nil -} - -func (tr timescaleRepo) createTable(name string) error { - q := `CREATE TABLE IF NOT EXISTS %s ( - created BIGINT NOT NULL, - channel VARCHAR(254), - subtopic VARCHAR(254), - publisher VARCHAR(254), - protocol TEXT, - payload JSONB, - PRIMARY KEY (created, publisher, subtopic) - );` - q = fmt.Sprintf(q, name) - - _, err := tr.db.Exec(q) - return err -} - -type senmlMessage struct { - senml.Message -} - -type jsonMessage struct { - Channel string `db:"channel"` - Created int64 `db:"created"` - Subtopic string `db:"subtopic"` - Publisher string `db:"publisher"` - Protocol string `db:"protocol"` - Payload []byte `db:"payload"` -} - -func toJSONMessage(msg mgjson.Message) (jsonMessage, error) { - data := []byte("{}") - if msg.Payload != nil { - b, err := json.Marshal(msg.Payload) - if err != nil { - return jsonMessage{}, errors.Wrap(errSaveMessage, err) - } - data = b - } - - m := jsonMessage{ - Channel: msg.Channel, - Created: msg.Created, - Subtopic: msg.Subtopic, - Publisher: msg.Publisher, - Protocol: msg.Protocol, - Payload: data, - } - - return m, nil -} diff --git a/docker/addons/vault/scripts/consumers/writers/timescale/consumer_test.go b/docker/addons/vault/scripts/consumers/writers/timescale/consumer_test.go deleted file mode 100644 index a8c36f1f..00000000 --- a/docker/addons/vault/scripts/consumers/writers/timescale/consumer_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package timescale_test - -import ( - "context" - "fmt" - "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/gofrs/uuid/v5" - "github.com/stretchr/testify/assert" -) - -const ( - msgsNum = 42 - valueFields = 5 - subtopic = "topic" -) - -var ( - v float64 = 5 - stringV = "value" - boolV = true - dataV = "base64" - sum float64 = 42 -) - -func TestSaveSenml(t *testing.T) { - repo := timescale.New(db) - - chid, err := uuid.NewV4() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - msg := senml.Message{} - msg.Channel = chid.String() - - pubid, err := uuid.NewV4() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - msg.Publisher = pubid.String() - - now := time.Now().Unix() - var msgs []senml.Message - - for i := 0; i < msgsNum; i++ { - // Mix possible values as well as value sum. - count := i % valueFields - switch count { - case 0: - msg.Subtopic = subtopic - msg.Value = &v - case 1: - msg.BoolValue = &boolV - case 2: - msg.StringValue = &stringV - case 3: - msg.DataValue = &dataV - case 4: - msg.Sum = &sum - } - - msg.Time = float64(now + int64(i)) - msgs = append(msgs, msg) - } - - err = repo.ConsumeBlocking(context.TODO(), msgs) - assert.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) -} - -func TestSaveJSON(t *testing.T) { - repo := timescale.New(db) - - chid, err := uuid.NewV4() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - pubid, err := uuid.NewV4() - assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - - msg := json.Message{ - Channel: chid.String(), - Publisher: pubid.String(), - Created: time.Now().Unix(), - Subtopic: "subtopic/format/some_json", - Protocol: "mqtt", - Payload: map[string]interface{}{ - "field_1": 123, - "field_2": "value", - "field_3": false, - "field_4": 12.344, - "field_5": map[string]interface{}{ - "field_1": "value", - "field_2": 42, - }, - }, - } - - now := time.Now().Unix() - msgs := json.Messages{ - Format: "some_json", - } - - for i := 0; i < msgsNum; i++ { - msg.Created = now + int64(i) - msgs.Data = append(msgs.Data, msg) - } - - err = repo.ConsumeBlocking(context.TODO(), msgs) - assert.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) -} diff --git a/docker/addons/vault/scripts/consumers/writers/timescale/doc.go b/docker/addons/vault/scripts/consumers/writers/timescale/doc.go deleted file mode 100644 index 302be6ea..00000000 --- a/docker/addons/vault/scripts/consumers/writers/timescale/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package timescale contains repository implementations using Timescale as -// the underlying database. -package timescale diff --git a/docker/addons/vault/scripts/consumers/writers/timescale/init.go b/docker/addons/vault/scripts/consumers/writers/timescale/init.go deleted file mode 100644 index cfd7156b..00000000 --- a/docker/addons/vault/scripts/consumers/writers/timescale/init.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package timescale - -import migrate "github.com/rubenv/sql-migrate" - -// Migration of timescale-writer. -func Migration() *migrate.MemoryMigrationSource { - return &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "messages_1", - Up: []string{ - `CREATE TABLE IF NOT EXISTS messages ( - time BIGINT NOT NULL, - channel UUID, - subtopic VARCHAR(254), - publisher UUID, - protocol TEXT, - name VARCHAR(254), - unit TEXT, - value FLOAT, - string_value TEXT, - bool_value BOOL, - data_value BYTEA, - sum FLOAT, - update_time FLOAT, - PRIMARY KEY (time, publisher, subtopic, name) - ); - SELECT create_hypertable('messages', 'time', create_default_indexes => FALSE, chunk_time_interval => 86400000, if_not_exists => TRUE);`, - }, - Down: []string{ - "DROP TABLE messages", - }, - }, - }, - } -} diff --git a/docker/addons/vault/scripts/consumers/writers/timescale/setup_test.go b/docker/addons/vault/scripts/consumers/writers/timescale/setup_test.go deleted file mode 100644 index d3d9064f..00000000 --- a/docker/addons/vault/scripts/consumers/writers/timescale/setup_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package timescale_test contains tests for TimescaleSQL repository -// implementations. -package timescale_test - -import ( - "fmt" - "log" - "os" - "testing" - - "github.com/absmach/magistrala/consumers/writers/timescale" - pgclient "github.com/absmach/magistrala/pkg/postgres" - "github.com/jmoiron/sqlx" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" -) - -var db *sqlx.DB - -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: "timescale/timescaledb", - Tag: "2.13.1-pg16", - 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") - - if err := pool.Retry(func() error { - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - db, err = sqlx.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: "", - } - - db, err = pgclient.Setup(dbConfig, *timescale.Migration()) - if err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - 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/docker/addons/vault/scripts/doc.go b/docker/addons/vault/scripts/doc.go deleted file mode 100644 index f286a114..00000000 --- a/docker/addons/vault/scripts/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// package magistrala acts as an umbrella package containing multiple different -// microservices and defines all shared domain concepts. -package magistrala diff --git a/docker/addons/vault/scripts/docker/.env b/docker/addons/vault/scripts/docker/.env deleted file mode 100644 index 305d2c06..00000000 --- a/docker/addons/vault/scripts/docker/.env +++ /dev/null @@ -1,481 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 -# 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 - -## Nats -MG_NATS_PORT=4222 -MG_NATS_HTTP_PORT=8222 -MG_NATS_JETSTREAM_KEY=u7wFoAPgXpDueXOFldBnXDh4xjnSOyEJ2Cb8Z5SZvGLzIZ3U4exWhhoIBZHzuNvh -MG_NATS_URL=nats://nats:${MG_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 - -## 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} - -## Message Broker -MG_MESSAGE_BROKER_TYPE=nats -MG_MESSAGE_BROKER_URL=${MG_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 - -## 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} - -## Redis -MG_REDIS_TCP_PORT=6379 -MG_REDIS_URL=redis://es-redis:${MG_REDIS_TCP_PORT}/0 - -## Event Store -MG_ES_TYPE=${MG_MESSAGE_BROKER_TYPE} -MG_ES_URL=${MG_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 - -## Call home -MG_SEND_TELEMETRY=true - -## Postgres -MG_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} - -#### Domains Client Config -MG_DOMAINS_URL=http://auth:8189 - -### SpiceDB Datastore config -MG_SPICEDB_DB_USER=magistrala -MG_SPICEDB_DB_PASS=magistrala -MG_SPICEDB_DB_NAME=spicedb -MG_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 - -### 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= - -### 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 - -### 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 - -### 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 - -### 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} - -### 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= - -### 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 - -### 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= - -### 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= - -## 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= - -### 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= - -### 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 - -# 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 - -### 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= - -### 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= - -### 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= - -### 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= - -### 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= - -### 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= - -### 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= - -### GRAFANA and PROMETHEUS -MG_PROMETHEUS_PORT=9090 -MG_GRAFANA_PORT=3000 -MG_GRAFANA_ADMIN_USER=magistrala -MG_GRAFANA_ADMIN_PASSWORD=magistrala - -# Docker image tag -MG_RELEASE_TAG=latest diff --git a/docker/addons/vault/scripts/docker/Dockerfile b/docker/addons/vault/scripts/docker/Dockerfile deleted file mode 100644 index 8996185a..00000000 --- a/docker/addons/vault/scripts/docker/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -FROM golang:1.23-alpine AS builder -ARG SVC -ARG GOARCH -ARG GOARM -ARG VERSION -ARG COMMIT -ARG TIME - -WORKDIR /go/src/github.com/absmach/magistrala -COPY . . -RUN apk update \ - && apk add make upx\ - && make $SVC \ - && upx build/$SVC \ - && mv build/$SVC /exe - -FROM scratch -# Certificates are needed so that mailing util can work. -COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -COPY --from=builder /exe / -ENTRYPOINT ["/exe"] diff --git a/docker/addons/vault/scripts/docker/Dockerfile.dev b/docker/addons/vault/scripts/docker/Dockerfile.dev deleted file mode 100644 index 7d55569c..00000000 --- a/docker/addons/vault/scripts/docker/Dockerfile.dev +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -FROM scratch -ARG SVC -COPY $SVC /exe -COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -ENTRYPOINT ["/exe"] diff --git a/docker/addons/vault/scripts/docker/README.md b/docker/addons/vault/scripts/docker/README.md deleted file mode 100644 index c21e20d4..00000000 --- a/docker/addons/vault/scripts/docker/README.md +++ /dev/null @@ -1,134 +0,0 @@ -# Docker Composition - -Configure environment variables and run Magistrala Docker Composition. - -\*Note\*\*: `docker-compose` uses `.env` file to set all environment variables. Ensure that you run the command from the same location as .env file. - -## Installation - -Follow the [official documentation](https://docs.docker.com/compose/install/). - -## Usage - -Run the following commands from the project root directory. - -```bash -docker compose -f docker/docker-compose.yml up -``` - -```bash -docker compose -f docker/addons/<path>/docker-compose.yml up -``` - -To pull docker images from a specific release you need to change the value of `MG_RELEASE_TAG` in `.env` before running these commands. - -## Broker Configuration - -Magistrala supports configurable MQTT broker and Message broker, which also acts as an events store. Magistrala uses two types of brokers: - -1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. This can either be 'VerneMQ' or 'NATS'. -2. MESSAGE_BROKER: Manages message exchange between Magistrala core, optional, and external services. This can either be 'NATS' or 'RabbitMQ'. This is used to store messages for distributed processing. - -Events store: This is used by Magistrala services to store events for distributed processing. Magistrala uses a single service to be the message broker and events store. This can either be 'NATS' or 'RabbitMQ'. Redis can also be used as an events store, but it requires a message broker to be deployed along with it for message exchange. - -This is the same as MESSAGE_BROKER. This can either be 'NATS' or 'RabbitMQ' or 'Redis'. If Redis is used as an events store, then RabbitMQ or NATS is used as a message broker. - -The current deployment strategy for Magistrala in `docker/docker-compose.yml` is to use VerneMQ as a MQTT_BROKER and NATS as a MESSAGE_BROKER and EVENTS_STORE. - -Therefore, the following combinations are possible: - -- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS -- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis -- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ -- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis -- MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ -- MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis -- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS -- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis - -For Message brokers other than NATS, you would need to build the docker images with RabbitMQ as the build tag and change the `docker/.env`. For example, to use RabbitMQ as a message broker: - -```bash -MG_MESSAGE_BROKER_TYPE=rabbitmq make dockers -``` - -```env -MG_MESSAGE_BROKER_TYPE=rabbitmq -MG_MESSAGE_BROKER_URL=${MG_RABBITMQ_URL} -``` - -For Redis as an events store, you would need to run RabbitMQ or NATS as a message broker. For example, to use Redis as an events store with rabbitmq as a message broker: - -```bash -MG_ES_TYPE=redis MG_MESSAGE_BROKER_TYPE=rabbitmq make dockers -``` - -```env -MG_MESSAGE_BROKER_TYPE=rabbitmq -MG_MESSAGE_BROKER_URL=${MG_RABBITMQ_URL} -MG_ES_TYPE=redis -MG_ES_URL=${MG_REDIS_URL} -``` - -For MQTT broker other than VerneMQ, you would need to change the `docker/.env`. For example, to use NATS as a MQTT broker: - -```env -MG_MQTT_BROKER_TYPE=nats -MG_MQTT_BROKER_HEALTH_CHECK=${MG_NATS_HEALTH_CHECK} -MG_MQTT_ADAPTER_MQTT_QOS=${MG_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} -``` - -### RabbitMQ configuration - -```yaml -services: - rabbitmq: - image: rabbitmq:3.12.12-management-alpine - container_name: magistrala-rabbitmq - restart: on-failure - environment: - RABBITMQ_ERLANG_COOKIE: ${MG_RABBITMQ_COOKIE} - RABBITMQ_DEFAULT_USER: ${MG_RABBITMQ_USER} - RABBITMQ_DEFAULT_PASS: ${MG_RABBITMQ_PASS} - RABBITMQ_DEFAULT_VHOST: ${MG_RABBITMQ_VHOST} - ports: - - ${MG_RABBITMQ_PORT}:${MG_RABBITMQ_PORT} - - ${MG_RABBITMQ_HTTP_PORT}:${MG_RABBITMQ_HTTP_PORT} - networks: - - magistrala-base-net -``` - -### Redis configuration - -```yaml -services: - redis: - image: redis:7.2.4-alpine - container_name: magistrala-es-redis - restart: on-failure - networks: - - magistrala-base-net - volumes: - - magistrala-broker-volume:/data -``` - -## Nginx Configuration - -Nginx is the entry point for all traffic to Magistrala. -By using environment variables file at `docker/.env` you can modify the below given Nginx directive. - -`MG_NGINX_SERVER_NAME` environmental variable is used to configure nginx directive `server_name`. If environmental variable `MG_NGINX_SERVER_NAME` is empty then default value `localhost` will set to `server_name`. - -`MG_NGINX_SERVER_CERT` environmental variable is used to configure nginx directive `ssl_certificate`. If environmental variable `MG_NGINX_SERVER_CERT` is empty then by default server certificate in the path `docker/ssl/certs/magistrala-server.crt` will be assigned. - -`MG_NGINX_SERVER_KEY` environmental variable is used to configure nginx directive `ssl_certificate_key`. If environmental variable `MG_NGINX_SERVER_KEY` is empty then by default server certificate key in the path `docker/ssl/certs/magistrala-server.key` will be assigned. - -`MG_NGINX_SERVER_CLIENT_CA` environmental variable is used to configure nginx directive `ssl_client_certificate`. If environmental variable `MG_NGINX_SERVER_CLIENT_CA` is empty then by default certificate in the path `docker/ssl/certs/ca.crt` will be assigned. - -`MG_NGINX_SERVER_DHPARAM` environmental variable is used to configure nginx directive `ssl_dhparam`. If environmental variable `MG_NGINX_SERVER_DHPARAM` is empty then by default file in the path `docker/ssl/dhparam.pem` will be assigned. diff --git a/docker/addons/vault/scripts/docker/addons/bootstrap/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/bootstrap/docker-compose.yml deleted file mode 100644 index d51df053..00000000 --- a/docker/addons/vault/scripts/docker/addons/bootstrap/docker-compose.yml +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional bootstrap services. Since it's optional, this file is -# dependent of docker-compose file from <project_root>/docker. In order to run this services, execute command: -# docker compose -f docker/docker-compose.yml -f docker/addons/bootstrap/docker-compose.yml up -# from project root. - -networks: - magistrala-base-net: - -volumes: - magistrala-bootstrap-db-volume: - -services: - bootstrap-db: - image: postgres:16.2-alpine - container_name: magistrala-bootstrap-db - restart: on-failure - environment: - POSTGRES_USER: ${MG_BOOTSTRAP_DB_USER} - POSTGRES_PASSWORD: ${MG_BOOTSTRAP_DB_PASS} - POSTGRES_DB: ${MG_BOOTSTRAP_DB_NAME} - networks: - - magistrala-base-net - volumes: - - magistrala-bootstrap-db-volume:/var/lib/postgresql/data - - bootstrap: - image: magistrala/bootstrap:${MG_RELEASE_TAG} - container_name: magistrala-bootstrap - depends_on: - - bootstrap-db - restart: on-failure - ports: - - ${MG_BOOTSTRAP_HTTP_PORT}:${MG_BOOTSTRAP_HTTP_PORT} - environment: - MG_BOOTSTRAP_LOG_LEVEL: ${MG_BOOTSTRAP_LOG_LEVEL} - MG_BOOTSTRAP_ENCRYPT_KEY: ${MG_BOOTSTRAP_ENCRYPT_KEY} - MG_BOOTSTRAP_EVENT_CONSUMER: ${MG_BOOTSTRAP_EVENT_CONSUMER} - MG_ES_URL: ${MG_ES_URL} - MG_BOOTSTRAP_HTTP_HOST: ${MG_BOOTSTRAP_HTTP_HOST} - MG_BOOTSTRAP_HTTP_PORT: ${MG_BOOTSTRAP_HTTP_PORT} - MG_BOOTSTRAP_HTTP_SERVER_CERT: ${MG_BOOTSTRAP_HTTP_SERVER_CERT} - MG_BOOTSTRAP_HTTP_SERVER_KEY: ${MG_BOOTSTRAP_HTTP_SERVER_KEY} - MG_BOOTSTRAP_DB_HOST: ${MG_BOOTSTRAP_DB_HOST} - MG_BOOTSTRAP_DB_PORT: ${MG_BOOTSTRAP_DB_PORT} - MG_BOOTSTRAP_DB_USER: ${MG_BOOTSTRAP_DB_USER} - MG_BOOTSTRAP_DB_PASS: ${MG_BOOTSTRAP_DB_PASS} - MG_BOOTSTRAP_DB_NAME: ${MG_BOOTSTRAP_DB_NAME} - MG_BOOTSTRAP_DB_SSL_MODE: ${MG_BOOTSTRAP_DB_SSL_MODE} - MG_BOOTSTRAP_DB_SSL_CERT: ${MG_BOOTSTRAP_DB_SSL_CERT} - MG_BOOTSTRAP_DB_SSL_KEY: ${MG_BOOTSTRAP_DB_SSL_KEY} - MG_BOOTSTRAP_DB_SSL_ROOT_CERT: ${MG_BOOTSTRAP_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_THINGS_URL: ${MG_THINGS_URL} - MG_JAEGER_URL: ${MG_JAEGER_URL} - MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_BOOTSTRAP_INSTANCE_ID: ${MG_BOOTSTRAP_INSTANCE_ID} - MG_SPICEDB_PRE_SHARED_KEY: ${MG_SPICEDB_PRE_SHARED_KEY} - MG_SPICEDB_HOST: ${MG_SPICEDB_HOST} - MG_SPICEDB_PORT: ${MG_SPICEDB_PORT} - networks: - - magistrala-base-net - volumes: - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca} - target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true diff --git a/docker/addons/vault/scripts/docker/addons/certs/config.yml b/docker/addons/vault/scripts/docker/addons/certs/config.yml deleted file mode 100644 index 2104ee64..00000000 --- a/docker/addons/vault/scripts/docker/addons/certs/config.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -common_name: "AbstractMachines_Selfsigned_ca" -organization: - - "AbstractMacines" -organizational_unit: - - "AbstractMachines_ca" -country: - - "France" -province: - - "Paris" -locality: - - "Quai de Valmy" -postal_code: - - "75010 Paris" -dns_names: - - "localhost" -ip_addresses: - - "localhost" diff --git a/docker/addons/vault/scripts/docker/addons/certs/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/certs/docker-compose.yml deleted file mode 100644 index 806ff033..00000000 --- a/docker/addons/vault/scripts/docker/addons/certs/docker-compose.yml +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional certs services. Since it's optional, this file is -# dependent of docker-compose file from <project_root>/docker. In order to run this services, execute command: -# docker compose -f docker/docker-compose.yml -f docker/addons/certs/docker-compose.yml up -# from project root. - -networks: - magistrala-base-net: - -volumes: - magistrala-certs-db-volume: - - -services: - certs: - image: magistrala/certs:${MG_RELEASE_TAG} - container_name: magistrala-certs - depends_on: - - am-certs - restart: on-failure - networks: - - magistrala-base-net - ports: - - ${MG_CERTS_HTTP_PORT}:${MG_CERTS_HTTP_PORT} - environment: - MG_CERTS_LOG_LEVEL: ${MG_CERTS_LOG_LEVEL} - MG_CERTS_SIGN_CA_PATH: ${MG_CERTS_SIGN_CA_PATH} - MG_CERTS_SIGN_CA_KEY_PATH: ${MG_CERTS_SIGN_CA_KEY_PATH} - MG_CERTS_VAULT_HOST: ${MG_CERTS_VAULT_HOST} - MG_CERTS_VAULT_NAMESPACE: ${MG_CERTS_VAULT_NAMESPACE} - MG_CERTS_VAULT_APPROLE_ROLEID: ${MG_CERTS_VAULT_APPROLE_ROLEID} - MG_CERTS_VAULT_APPROLE_SECRET: ${MG_CERTS_VAULT_APPROLE_SECRET} - MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH: ${MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH} - MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME: ${MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME} - MG_CERTS_HTTP_HOST: ${MG_CERTS_HTTP_HOST} - MG_CERTS_HTTP_PORT: ${MG_CERTS_HTTP_PORT} - MG_CERTS_HTTP_SERVER_CERT: ${MG_CERTS_HTTP_SERVER_CERT} - MG_CERTS_HTTP_SERVER_KEY: ${MG_CERTS_HTTP_SERVER_KEY} - MG_CERTS_DB_HOST: ${MG_CERTS_DB_HOST} - MG_CERTS_DB_PORT: ${MG_CERTS_DB_PORT} - MG_CERTS_DB_PASS: ${MG_CERTS_DB_PASS} - MG_CERTS_DB_USER: ${MG_CERTS_DB_USER} - MG_CERTS_DB_NAME: ${MG_CERTS_DB_NAME} - MG_CERTS_DB_SSL_MODE: ${MG_CERTS_DB_SSL_MODE} - MG_CERTS_DB_SSL_CERT: ${MG_CERTS_DB_SSL_CERT} - MG_CERTS_DB_SSL_KEY: ${MG_CERTS_DB_SSL_KEY} - MG_CERTS_DB_SSL_ROOT_CERT: ${MG_CERTS_DB_SSL_ROOT_CERT} - MG_CERTS_SDK_HOST: ${MG_CERTS_SDK_HOST} - MG_CERTS_SDK_CERTS_URL: ${MG_CERTS_SDK_CERTS_URL} - MG_CERTS_SDK_TLS_VERIFICATION: ${MG_CERTS_SDK_TLS_VERIFICATION} - 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_THINGS_URL: ${MG_THINGS_URL} - MG_JAEGER_URL: ${MG_JAEGER_URL} - MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_CERTS_INSTANCE_ID: ${MG_CERTS_INSTANCE_ID} - volumes: - - ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key - - ../../ssl/certs/ca.crt:/etc/ssl/certs/ca.crt - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca} - target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true - - am-certs-db: - image: postgres:16.2-alpine - container_name: magistrala-am-certs-db - restart: on-failure - networks: - - magistrala-base-net - command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" - environment: - POSTGRES_USER: ${MG_CERTS_DB_USER} - POSTGRES_PASSWORD: ${MG_CERTS_DB_PASS} - POSTGRES_DB: ${MG_CERTS_DB_NAME} - ports: - - 5454:5432 - volumes: - - magistrala-certs-db-volume:/var/lib/postgresql/data - - am-certs: - image: ghcr.io/absmach/certs:${MG_RELEASE_TAG} - container_name: magistrala-am-certs - depends_on: - - am-certs-db - restart: on-failure - networks: - - magistrala-base-net - environment: - AM_CERTS_LOG_LEVEL: ${MG_CERTS_LOG_LEVEL} - AM_CERTS_DB_HOST: ${MG_CERTS_DB_HOST} - AM_CERTS_DB_PORT: ${MG_CERTS_DB_PORT} - AM_CERTS_DB_USER: ${MG_CERTS_DB_USER} - AM_CERTS_DB_PASS: ${MG_CERTS_DB_PASS} - AM_CERTS_DB: ${MG_CERTS_DB_NAME} - AM_CERTS_DB_SSL_MODE: ${MG_CERTS_DB_SSL_MODE} - AM_CERTS_HTTP_HOST: magistrala-am-certs - AM_CERTS_HTTP_PORT: 9010 - AM_CERTS_GRPC_HOST: magistrala-am-certs - AM_CERTS_GRPC_PORT: 7012 - AM_JAEGER_URL: ${MG_JAEGER_URL} - AM_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} - volumes: - - ./config.yml:/config/config.yml - ports: - - 9010:9010 - - 7012:7012 diff --git a/docker/addons/vault/scripts/docker/addons/journal/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/journal/docker-compose.yml deleted file mode 100644 index 0b7d9506..00000000 --- a/docker/addons/vault/scripts/docker/addons/journal/docker-compose.yml +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional Postgres and journal services -# for Magistrala platform. Since these are optional, this file is dependent of docker-compose file -# from <project_root>/docker. In order to run these services, execute command: -# docker-compose -f docker/docker-compose.yml -f docker/addons/journal/docker-compose.yml up -# from project root. PostgreSQL default port (5432) is exposed, so you can use various tools for database -# inspection and data visualization. - -networks: - magistrala-base-net: - -volumes: - magistrala-journal-volume: - -services: - journal-db: - image: postgres:16.2-alpine - container_name: magistrala-journal-db - restart: on-failure - command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" - environment: - POSTGRES_USER: ${MG_JOURNAL_DB_USER} - POSTGRES_PASSWORD: ${MG_JOURNAL_DB_PASS} - POSTGRES_DB: ${MG_JOURNAL_DB_NAME} - MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} - networks: - - magistrala-base-net - volumes: - - magistrala-journal-volume:/var/lib/postgresql/data - - journal: - image: magistrala/journal:${MG_RELEASE_TAG} - container_name: magistrala-journal - depends_on: - - journal-db - restart: on-failure - environment: - MG_JOURNAL_LOG_LEVEL: ${MG_JOURNAL_LOG_LEVEL} - MG_JOURNAL_HTTP_HOST: ${MG_JOURNAL_HTTP_HOST} - MG_JOURNAL_HTTP_PORT: ${MG_JOURNAL_HTTP_PORT} - MG_JOURNAL_HTTP_SERVER_CERT: ${MG_JOURNAL_HTTP_SERVER_CERT} - MG_JOURNAL_HTTP_SERVER_KEY: ${MG_JOURNAL_HTTP_SERVER_KEY} - MG_JOURNAL_DB_HOST: ${MG_JOURNAL_DB_HOST} - MG_JOURNAL_DB_PORT: ${MG_JOURNAL_DB_PORT} - MG_JOURNAL_DB_USER: ${MG_JOURNAL_DB_USER} - MG_JOURNAL_DB_PASS: ${MG_JOURNAL_DB_PASS} - MG_JOURNAL_DB_NAME: ${MG_JOURNAL_DB_NAME} - MG_JOURNAL_DB_SSL_MODE: ${MG_JOURNAL_DB_SSL_MODE} - MG_JOURNAL_DB_SSL_CERT: ${MG_JOURNAL_DB_SSL_CERT} - MG_JOURNAL_DB_SSL_KEY: ${MG_JOURNAL_DB_SSL_KEY} - MG_JOURNAL_DB_SSL_ROOT_CERT: ${MG_JOURNAL_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_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_JOURNAL_INSTANCE_ID: ${MG_JOURNAL_INSTANCE_ID} - ports: - - ${MG_JOURNAL_HTTP_PORT}:${MG_JOURNAL_HTTP_PORT} - networks: - - magistrala-base-net diff --git a/docker/addons/vault/scripts/docker/addons/postgres-reader/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/postgres-reader/docker-compose.yml deleted file mode 100644 index 3b84d6c9..00000000 --- a/docker/addons/vault/scripts/docker/addons/postgres-reader/docker-compose.yml +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional Postgres-reader service for Magistrala platform. -# Since this service is optional, this file is dependent of docker-compose.yml file -# from <project_root>/docker. In order to run this service, execute command: -# docker compose -f docker/docker-compose.yml -f docker/addons/postgres-reader/docker-compose.yml up -# from project root. - -networks: - magistrala-base-net: - -services: - postgres-reader: - image: magistrala/postgres-reader:${MG_RELEASE_TAG} - container_name: magistrala-postgres-reader - restart: on-failure - environment: - MG_POSTGRES_READER_LOG_LEVEL: ${MG_POSTGRES_READER_LOG_LEVEL} - MG_POSTGRES_READER_HTTP_HOST: ${MG_POSTGRES_READER_HTTP_HOST} - MG_POSTGRES_READER_HTTP_PORT: ${MG_POSTGRES_READER_HTTP_PORT} - MG_POSTGRES_READER_HTTP_SERVER_CERT: ${MG_POSTGRES_READER_HTTP_SERVER_CERT} - MG_POSTGRES_READER_HTTP_SERVER_KEY: ${MG_POSTGRES_READER_HTTP_SERVER_KEY} - MG_POSTGRES_HOST: ${MG_POSTGRES_HOST} - MG_POSTGRES_PORT: ${MG_POSTGRES_PORT} - MG_POSTGRES_USER: ${MG_POSTGRES_USER} - MG_POSTGRES_PASS: ${MG_POSTGRES_PASS} - MG_POSTGRES_NAME: ${MG_POSTGRES_NAME} - MG_POSTGRES_SSL_MODE: ${MG_POSTGRES_SSL_MODE} - MG_POSTGRES_SSL_CERT: ${MG_POSTGRES_SSL_CERT} - MG_POSTGRES_SSL_KEY: ${MG_POSTGRES_SSL_KEY} - MG_POSTGRES_SSL_ROOT_CERT: ${MG_POSTGRES_SSL_ROOT_CERT} - MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL} - MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT} - MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt} - MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} - MG_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_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_POSTGRES_READER_INSTANCE_ID: ${MG_POSTGRES_READER_INSTANCE_ID} - ports: - - ${MG_POSTGRES_READER_HTTP_PORT}:${MG_POSTGRES_READER_HTTP_PORT} - networks: - - magistrala-base-net - volumes: - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca} - target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true - # Things gRPC mTLS client certificates - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true diff --git a/docker/addons/vault/scripts/docker/addons/postgres-writer/config.toml b/docker/addons/vault/scripts/docker/addons/postgres-writer/config.toml deleted file mode 100644 index b04ce56f..00000000 --- a/docker/addons/vault/scripts/docker/addons/postgres-writer/config.toml +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# To listen all messsage broker subjects use default value "channels.>". -# To subscribe to specific subjects use values starting by "channels." and -# followed by a subtopic (e.g ["channels.<channel_id>.sub.topic.x", ...]). -[subscriber] -subjects = ["channels.>"] - -[transformer] -# SenML or JSON -format = "senml" -# Used if format is SenML -content_type = "application/senml+json" -# Used as timestamp fields if format is JSON -time_fields = [{ field_name = "seconds_key", field_format = "unix", location = "UTC"}, - { field_name = "millis_key", field_format = "unix_ms", location = "UTC"}, - { field_name = "micros_key", field_format = "unix_us", location = "UTC"}, - { field_name = "nanos_key", field_format = "unix_ns", location = "UTC"}] diff --git a/docker/addons/vault/scripts/docker/addons/postgres-writer/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/postgres-writer/docker-compose.yml deleted file mode 100644 index c5e1964c..00000000 --- a/docker/addons/vault/scripts/docker/addons/postgres-writer/docker-compose.yml +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional Postgres and Postgres-writer services -# for Magistrala platform. Since these are optional, this file is dependent of docker-compose file -# from <project_root>/docker. In order to run these services, execute command: -# docker compose -f docker/docker-compose.yml -f docker/addons/postgres-writer/docker-compose.yml up -# from project root. PostgreSQL default port (5432) is exposed, so you can use various tools for database -# inspection and data visualization. - -networks: - magistrala-base-net: - -volumes: - magistrala-postgres-writer-volume: - -services: - postgres: - image: postgres:16.2-alpine - container_name: magistrala-postgres - restart: on-failure - environment: - POSTGRES_USER: ${MG_POSTGRES_USER} - POSTGRES_PASSWORD: ${MG_POSTGRES_PASS} - POSTGRES_DB: ${MG_POSTGRES_NAME} - networks: - - magistrala-base-net - volumes: - - magistrala-postgres-writer-volume:/var/lib/postgresql/data - - postgres-writer: - image: magistrala/postgres-writer:${MG_RELEASE_TAG} - container_name: magistrala-postgres-writer - depends_on: - - postgres - restart: on-failure - environment: - MG_POSTGRES_WRITER_LOG_LEVEL: ${MG_POSTGRES_WRITER_LOG_LEVEL} - MG_POSTGRES_WRITER_CONFIG_PATH: ${MG_POSTGRES_WRITER_CONFIG_PATH} - MG_POSTGRES_WRITER_HTTP_HOST: ${MG_POSTGRES_WRITER_HTTP_HOST} - MG_POSTGRES_WRITER_HTTP_PORT: ${MG_POSTGRES_WRITER_HTTP_PORT} - MG_POSTGRES_WRITER_HTTP_SERVER_CERT: ${MG_POSTGRES_WRITER_HTTP_SERVER_CERT} - MG_POSTGRES_WRITER_HTTP_SERVER_KEY: ${MG_POSTGRES_WRITER_HTTP_SERVER_KEY} - MG_POSTGRES_HOST: ${MG_POSTGRES_HOST} - MG_POSTGRES_PORT: ${MG_POSTGRES_PORT} - MG_POSTGRES_USER: ${MG_POSTGRES_USER} - MG_POSTGRES_PASS: ${MG_POSTGRES_PASS} - MG_POSTGRES_NAME: ${MG_POSTGRES_NAME} - MG_POSTGRES_SSL_MODE: ${MG_POSTGRES_SSL_MODE} - MG_POSTGRES_SSL_CERT: ${MG_POSTGRES_SSL_CERT} - MG_POSTGRES_SSL_KEY: ${MG_POSTGRES_SSL_KEY} - MG_POSTGRES_SSL_ROOT_CERT: ${MG_POSTGRES_SSL_ROOT_CERT} - MG_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_POSTGRES_WRITER_INSTANCE_ID: ${MG_POSTGRES_WRITER_INSTANCE_ID} - ports: - - ${MG_POSTGRES_WRITER_HTTP_PORT}:${MG_POSTGRES_WRITER_HTTP_PORT} - networks: - - magistrala-base-net - volumes: - - ./config.toml:/config.toml diff --git a/docker/addons/vault/scripts/docker/addons/prometheus/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/prometheus/docker-compose.yml deleted file mode 100644 index 100319be..00000000 --- a/docker/addons/vault/scripts/docker/addons/prometheus/docker-compose.yml +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional Prometheus and Grafana service for Magistrala platform. -# Since this service is optional, this file is dependent of docker-compose.yml file -# from <project_root>/docker. In order to run this service, execute command: -# docker compose -f docker/addons/prometheus/docker-compose.yml up -# from project root. - -networks: - magistrala-base-net: - -volumes: - magistrala-prometheus-volume: - -services: - promethues: - image: prom/prometheus:v2.49.1 - container_name: magistrala-prometheus - restart: on-failure - ports: - - ${MG_PROMETHEUS_PORT}:${MG_PROMETHEUS_PORT} - networks: - - magistrala-base-net - volumes: - - type: bind - source: ./metrics/prometheus.yml - target: /etc/prometheus/prometheus.yml - - magistrala-prometheus-volume:/prometheus - - grafana: - image: grafana/grafana:10.2.3 - container_name: magistrala-grafana - depends_on: - - promethues - restart: on-failure - ports: - - ${MG_GRAFANA_PORT}:${MG_GRAFANA_PORT} - environment: - - GF_SECURITY_ADMIN_USER=${MG_GRAFANA_ADMIN_USER} - - GF_SECURITY_ADMIN_PASSWORD=${MG_GRAFANA_ADMIN_PASSWORD} - networks: - - magistrala-base-net - volumes: - - type: bind - source: ./grafana/datasource.yml - target: /etc/grafana/provisioning/datasources/datasource.yml - - type: bind - source: ./grafana/dashboard.yml - target: /etc/grafana/provisioning/dashboards/main.yaml - - type: bind - source: ./grafana/example-dashboard.json - target: /var/lib/grafana/dashboards/example-dashboard.json diff --git a/docker/addons/vault/scripts/docker/addons/prometheus/grafana/dashboard.yml b/docker/addons/vault/scripts/docker/addons/prometheus/grafana/dashboard.yml deleted file mode 100644 index 91f95f3a..00000000 --- a/docker/addons/vault/scripts/docker/addons/prometheus/grafana/dashboard.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -apiVersion: 1 - -providers: - - name: "Dashboard provider" - orgId: 1 - type: file - disableDeletion: false - updateIntervalSeconds: 10 - allowUiUpdates: false - options: - path: /var/lib/grafana/dashboards - foldersFromFilesStructure: true diff --git a/docker/addons/vault/scripts/docker/addons/prometheus/grafana/datasource.yml b/docker/addons/vault/scripts/docker/addons/prometheus/grafana/datasource.yml deleted file mode 100644 index 4db83aa3..00000000 --- a/docker/addons/vault/scripts/docker/addons/prometheus/grafana/datasource.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -apiVersion: 1 - -datasources: -- name: Prometheus - type: prometheus - url: http://magistrala-prometheus:9090 - isDefault: true - access: proxy - editable: true diff --git a/docker/addons/vault/scripts/docker/addons/prometheus/grafana/example-dashboard.json b/docker/addons/vault/scripts/docker/addons/prometheus/grafana/example-dashboard.json deleted file mode 100644 index 56041031..00000000 --- a/docker/addons/vault/scripts/docker/addons/prometheus/grafana/example-dashboard.json +++ /dev/null @@ -1,1317 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": 1, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 39, - "panels": [], - "title": "General", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "0": { - "color": "red", - "index": 1, - "text": "down" - }, - "1": { - "color": "green", - "index": 0, - "text": "up" - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "green", - "value": 1 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 12, - "x": 0, - "y": 1 - }, - "id": 14, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "vertical", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": false, - "expr": "up{}", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{instance}}", - "refId": "A" - } - ], - "title": "State", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 12, - "x": 12, - "y": 1 - }, - "id": 8, - "interval": "30s", - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "go_memstats_alloc_bytes{}", - "format": "time_series", - "instant": false, - "interval": "", - "intervalFactor": 10, - "legendFormat": "{{instance}}", - "refId": "A" - } - ], - "title": "Allocated Bytes", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 22, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 4, - "interval": "15s", - "options": { - "legend": { - "calcs": [ - "mean", - "sum", - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "promhttp_metric_handler_requests_total{}", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{instance}} - Code {{code}}", - "refId": "A" - } - ], - "title": "Total HTTP Requests", - "transformations": [], - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 14 - }, - "id": 2, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "go_goroutines{}", - "interval": "", - "legendFormat": "{{instance}}", - "refId": "A" - } - ], - "title": "Goroutines instaces", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 22 - }, - "id": 35, - "panels": [], - "title": "Things-Service", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 10, - "x": 0, - "y": 23 - }, - "id": 10, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "text": {} - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "things_api_request_count{}", - "instant": false, - "interval": "", - "legendFormat": "{{method}}", - "refId": "A" - } - ], - "title": "Things Request Count", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 35, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [ - { - "options": { - "NaN": { - "index": 0, - "text": "0" - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "µs" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 14, - "x": 10, - "y": 23 - }, - "id": 42, - "interval": "30", - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "label_replace(label_replace(label_replace(things_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", - "format": "time_series", - "instant": false, - "interval": "", - "key": "Q-cc5a9d33-5437-4862-abd9-60afd75f3f39-0", - "legendFormat": "{{method}} - {{quantile}}", - "range": true, - "refId": "A" - } - ], - "title": "Things Latency Quantiles", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 33 - }, - "id": 33, - "panels": [], - "title": "Users-Service", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 10, - "x": 0, - "y": 34 - }, - "id": 22, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "text": {} - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "users_api_request_count{}", - "interval": "", - "legendFormat": "{{method}}", - "refId": "A" - } - ], - "title": "Users Request Count", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 35, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [ - { - "options": { - "NaN": { - "index": 0, - "text": "0" - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "µs" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 14, - "x": 10, - "y": 34 - }, - "id": 41, - "interval": "30", - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "label_replace(label_replace(label_replace(users_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", - "format": "time_series", - "instant": false, - "interval": "", - "key": "Q-cc5a9d33-5437-4862-abd9-60afd75f3f39-0", - "legendFormat": "{{method}} - {{quantile}}", - "range": true, - "refId": "A" - } - ], - "title": "Users Latency Quantiles", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 44 - }, - "id": 31, - "panels": [], - "title": "CoAP-Adapter", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 10, - "x": 0, - "y": 45 - }, - "id": 18, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "text": {} - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "coap_adapter_api_request_count{}", - "interval": "", - "legendFormat": "{{method}}", - "refId": "A" - } - ], - "title": "Coap Adapter Request Count", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 35, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [ - { - "options": { - "NaN": { - "index": 0, - "text": "0" - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "µs" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 14, - "x": 10, - "y": 45 - }, - "id": 44, - "interval": "30", - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "label_replace(label_replace(label_replace(coap_adapter_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", - "format": "time_series", - "instant": false, - "interval": "", - "key": "Q-cc5a9d33-5437-4862-abd9-60afd75f3f39-0", - "legendFormat": "{{method}} - {{quantile}}", - "range": true, - "refId": "A" - } - ], - "title": "CoAP Latency Quantiles", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 55 - }, - "id": 29, - "panels": [], - "title": "Web Sockets-Adapter", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 10, - "x": 0, - "y": 56 - }, - "id": 20, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "text": {} - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "ws_adapter_api_request_count{}", - "interval": "", - "legendFormat": "{{method}}", - "refId": "A" - } - ], - "title": "Web Sockets Request Count", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 35, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [ - { - "options": { - "NaN": { - "index": 0, - "text": "0" - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "µs" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 14, - "x": 10, - "y": 56 - }, - "id": 23, - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "label_replace(label_replace(label_replace(ws_adapter_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", - "format": "time_series", - "instant": false, - "interval": "", - "key": "Q-cc5a9d33-5437-4862-abd9-60afd75f3f39-0", - "legendFormat": "{{method}} - {{quantile}}", - "range": true, - "refId": "A" - } - ], - "title": "WS Latency Quantiles", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 66 - }, - "id": 27, - "panels": [], - "title": "HTTP-Adapter", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 10, - "x": 0, - "y": 67 - }, - "id": 6, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "text": {} - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "exemplar": true, - "expr": "http_adapter_api_request_count{}", - "format": "time_series", - "instant": false, - "interval": "", - "legendFormat": "{{method}}", - "refId": "A" - } - ], - "title": "HTTP Adapter Request Count", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 35, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "µs" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 14, - "x": 10, - "y": 67 - }, - "id": 40, - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "label_replace(label_replace(label_replace(http_adapter_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", - "format": "time_series", - "instant": false, - "interval": "", - "key": "Q-cc5a9d33-5437-4862-abd9-60afd75f3f39-0", - "legendFormat": "{{method}} - {{quantile}}", - "range": true, - "refId": "A" - } - ], - "title": "HTTP Latency Quantiles", - "type": "timeseries" - } - ], - "refresh": "5s", - "revision": 1, - "schemaVersion": 38, - "style": "dark", - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "magistrala", - "uid": "sgKwOwY4k", - "version": 1, - "weekStart": "" -} diff --git a/docker/addons/vault/scripts/docker/addons/prometheus/metrics/prometheus.yml b/docker/addons/vault/scripts/docker/addons/prometheus/metrics/prometheus.yml deleted file mode 100644 index ecac123d..00000000 --- a/docker/addons/vault/scripts/docker/addons/prometheus/metrics/prometheus.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -global: - scrape_interval: 15s - evaluation_interval: 15s - -scrape_configs: - - job_name: 'magistrala' - honor_timestamps: true - scrape_interval: 15s - scrape_timeout: 10s - metrics_path: /metrics - follow_redirects: true - enable_http2: true - static_configs: - - targets: - - magistrala-things:9000 - - magistrala-users:9002 - - magistrala-http:8008 - - magistrala-ws:8186 - - magistrala-coap:5683 diff --git a/docker/addons/vault/scripts/docker/addons/provision/configs/config.toml b/docker/addons/vault/scripts/docker/addons/provision/configs/config.toml deleted file mode 100644 index ec1ee38b..00000000 --- a/docker/addons/vault/scripts/docker/addons/provision/configs/config.toml +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -[bootstrap] - [bootstrap.content] - [bootstrap.content.agent.edgex] - url = "http://localhost:48090/api/v1/" - - [bootstrap.content.agent.log] - level = "info" - - [bootstrap.content.agent.mqtt] - mtls = false - qos = 0 - retain = false - skip_tls_ver = true - url = "localhost:1883" - - [bootstrap.content.agent.server] - nats_url = "localhost:4222" - port = "9000" - - [bootstrap.content.agent.heartbeat] - interval = "30s" - - [bootstrap.content.agent.terminal] - session_timeout = "30s" - - - [bootstrap.content.export.exp] - log_level = "debug" - nats = "nats://localhost:4222" - port = "8172" - cache_url = "localhost:6379" - cache_pass = "" - cache_db = "0" - - [bootstrap.content.export.mqtt] - ca_path = "ca.crt" - cert_path = "thing.crt" - channel = "" - host = "tcp://localhost:1883" - mtls = false - password = "" - priv_key_path = "thing.key" - qos = 0 - retain = false - skip_tls_ver = false - username = "" - - [[bootstrap.content.export.routes]] - mqtt_topic = "" - nats_topic = ">" - subtopic = "" - type = "plain" - workers = 10 - -[[things]] - name = "thing" - - [things.metadata] - external_id = "xxxxxx" - -[[channels]] - name = "control-channel" - - [channels.metadata] - type = "control" - -[[channels]] - name = "data-channel" - - [channels.metadata] - type = "data" diff --git a/docker/addons/vault/scripts/docker/addons/provision/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/provision/docker-compose.yml deleted file mode 100644 index da8befad..00000000 --- a/docker/addons/vault/scripts/docker/addons/provision/docker-compose.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional provision services. Since it's optional, this file is -# dependent of docker-compose file from <project_root>/docker. In order to run this services, execute command: -# docker compose -f docker/docker-compose.yml -f docker/addons/provision/docker-compose.yml up -# from project root. - -networks: - magistrala-base-net: - -services: - provision: - image: magistrala/provision:${MG_RELEASE_TAG} - container_name: magistrala-provision - restart: on-failure - networks: - - magistrala-base-net - ports: - - ${MG_PROVISION_HTTP_PORT}:${MG_PROVISION_HTTP_PORT} - environment: - MG_PROVISION_LOG_LEVEL: ${MG_PROVISION_LOG_LEVEL} - MG_PROVISION_HTTP_PORT: ${MG_PROVISION_HTTP_PORT} - MG_PROVISION_CONFIG_FILE: ${MG_PROVISION_CONFIG_FILE} - MG_PROVISION_ENV_CLIENTS_TLS: ${MG_PROVISION_ENV_CLIENTS_TLS} - MG_PROVISION_SERVER_CERT: ${MG_PROVISION_SERVER_CERT} - MG_PROVISION_SERVER_KEY: ${MG_PROVISION_SERVER_KEY} - MG_PROVISION_USERS_LOCATION: ${MG_PROVISION_USERS_LOCATION} - MG_PROVISION_THINGS_LOCATION: ${MG_PROVISION_THINGS_LOCATION} - MG_PROVISION_USER: ${MG_PROVISION_USER} - MG_PROVISION_USERNAME: ${MG_PROVISION_USERNAME} - MG_PROVISION_PASS: ${MG_PROVISION_PASS} - MG_PROVISION_API_KEY: ${MG_PROVISION_API_KEY} - MG_PROVISION_CERTS_SVC_URL: ${MG_PROVISION_CERTS_SVC_URL} - MG_PROVISION_X509_PROVISIONING: ${MG_PROVISION_X509_PROVISIONING} - MG_PROVISION_BS_SVC_URL: ${MG_PROVISION_BS_SVC_URL} - MG_PROVISION_BS_CONFIG_PROVISIONING: ${MG_PROVISION_BS_CONFIG_PROVISIONING} - MG_PROVISION_BS_AUTO_WHITELIST: ${MG_PROVISION_BS_AUTO_WHITELIST} - MG_PROVISION_BS_CONTENT: ${MG_PROVISION_BS_CONTENT} - MG_PROVISION_CERTS_HOURS_VALID: ${MG_PROVISION_CERTS_HOURS_VALID} - MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_PROVISION_INSTANCE_ID: ${MG_PROVISION_INSTANCE_ID} - volumes: - - ./configs:/configs - - ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key - - ../../ssl/certs/ca.crt:/etc/ssl/certs/ca.crt diff --git a/docker/addons/vault/scripts/docker/addons/timescale-reader/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/timescale-reader/docker-compose.yml deleted file mode 100644 index 269e1c60..00000000 --- a/docker/addons/vault/scripts/docker/addons/timescale-reader/docker-compose.yml +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional Timescale-reader service for Magistrala platform. -# Since this service is optional, this file is dependent of docker-compose.yml file -# from <project_root>/docker. In order to run this service, execute command: -# docker compose -f docker/docker-compose.yml -f docker/addons/timescale-reader/docker-compose.yml up -# from project root. - -networks: - magistrala-base-net: - -services: - timescale-reader: - image: magistrala/timescale-reader:${MG_RELEASE_TAG} - container_name: magistrala-timescale-reader - restart: on-failure - environment: - MG_TIMESCALE_READER_LOG_LEVEL: ${MG_TIMESCALE_READER_LOG_LEVEL} - MG_TIMESCALE_READER_HTTP_HOST: ${MG_TIMESCALE_READER_HTTP_HOST} - MG_TIMESCALE_READER_HTTP_PORT: ${MG_TIMESCALE_READER_HTTP_PORT} - MG_TIMESCALE_READER_HTTP_SERVER_CERT: ${MG_TIMESCALE_READER_HTTP_SERVER_CERT} - MG_TIMESCALE_READER_HTTP_SERVER_KEY: ${MG_TIMESCALE_READER_HTTP_SERVER_KEY} - MG_TIMESCALE_HOST: ${MG_TIMESCALE_HOST} - MG_TIMESCALE_PORT: ${MG_TIMESCALE_PORT} - MG_TIMESCALE_USER: ${MG_TIMESCALE_USER} - MG_TIMESCALE_PASS: ${MG_TIMESCALE_PASS} - MG_TIMESCALE_NAME: ${MG_TIMESCALE_NAME} - MG_TIMESCALE_SSL_MODE: ${MG_TIMESCALE_SSL_MODE} - MG_TIMESCALE_SSL_CERT: ${MG_TIMESCALE_SSL_CERT} - MG_TIMESCALE_SSL_KEY: ${MG_TIMESCALE_SSL_KEY} - MG_TIMESCALE_SSL_ROOT_CERT: ${MG_TIMESCALE_SSL_ROOT_CERT} - MG_THINGS_AUTH_GRPC_URL: ${MG_THINGS_AUTH_GRPC_URL} - MG_THINGS_AUTH_GRPC_TIMEOUT: ${MG_THINGS_AUTH_GRPC_TIMEOUT} - MG_THINGS_AUTH_GRPC_CLIENT_CERT: ${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+/things-grpc-client.crt} - MG_THINGS_AUTH_GRPC_CLIENT_KEY: ${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+/things-grpc-client.key} - MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS: ${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+/things-grpc-server-ca.crt} - MG_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_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} - MG_TIMESCALE_READER_INSTANCE_ID: ${MG_TIMESCALE_READER_INSTANCE_ID} - ports: - - ${MG_TIMESCALE_READER_HTTP_PORT}:${MG_TIMESCALE_READER_HTTP_PORT} - networks: - - magistrala-base-net - volumes: - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_CERT:-./ssl/certs/dummy/client_cert} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_CERT:+.crt} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_CLIENT_KEY:-./ssl/certs/dummy/client_key} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_AUTH_GRPC_SERVER_CA_CERTS:-./ssl/certs/dummy/server_ca} - target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true - # Things gRPC mTLS client certificates - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_CERT:+.crt} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /things-grpc-client${MG_THINGS_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_ADDONS_CERTS_PATH_PREFIX}${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /things-grpc-server-ca${MG_THINGS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true diff --git a/docker/addons/vault/scripts/docker/addons/timescale-writer/config.toml b/docker/addons/vault/scripts/docker/addons/timescale-writer/config.toml deleted file mode 100644 index f3ad91d1..00000000 --- a/docker/addons/vault/scripts/docker/addons/timescale-writer/config.toml +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# To listen all messsage broker subjects use default value "channels.>". -# To subscribe to specific subjects use values starting by "channels." and -# followed by a subtopic (e.g ["channels.<channel_id>.sub.topic.x", ...]). -[subjects] -filter = ["channels.>"] diff --git a/docker/addons/vault/scripts/docker/addons/timescale-writer/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/timescale-writer/docker-compose.yml deleted file mode 100644 index 125315a4..00000000 --- a/docker/addons/vault/scripts/docker/addons/timescale-writer/docker-compose.yml +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional Timescale and Timescale-writer services -# for Magistrala platform. Since these are optional, this file is dependent of docker-compose file -# from <project_root>/docker. In order to run these services, execute command: -# docker compose -f docker/docker-compose.yml -f docker/addons/timescale-writer/docker-compose.yml up -# from project root. PostgreSQL default port (5432) is exposed, so you can use various tools for database -# inspection and data visualization. - -networks: - magistrala-base-net: - -volumes: - magistrala-timescale-writer-volume: - -services: - timescale: - image: timescale/timescaledb:2.13.1-pg16 - container_name: magistrala-timescale - restart: on-failure - environment: - POSTGRES_PASSWORD: ${MG_TIMESCALE_PASS} - POSTGRES_USER: ${MG_TIMESCALE_USER} - POSTGRES_DB: ${MG_TIMESCALE_NAME} - ports: - - 5433:5432 - networks: - - magistrala-base-net - volumes: - - magistrala-timescale-writer-volume:/var/lib/timescalesql/data - - timescale-writer: - image: magistrala/timescale-writer:${MG_RELEASE_TAG} - container_name: magistrala-timescale-writer - depends_on: - - timescale - restart: on-failure - environment: - MG_TIMESCALE_WRITER_LOG_LEVEL: ${MG_TIMESCALE_WRITER_LOG_LEVEL} - MG_TIMESCALE_WRITER_CONFIG_PATH: ${MG_TIMESCALE_WRITER_CONFIG_PATH} - MG_TIMESCALE_WRITER_HTTP_HOST: ${MG_TIMESCALE_WRITER_HTTP_HOST} - MG_TIMESCALE_WRITER_HTTP_PORT: ${MG_TIMESCALE_WRITER_HTTP_PORT} - MG_TIMESCALE_WRITER_HTTP_SERVER_CERT: ${MG_TIMESCALE_WRITER_HTTP_SERVER_CERT} - MG_TIMESCALE_WRITER_HTTP_SERVER_KEY: ${MG_TIMESCALE_WRITER_HTTP_SERVER_KEY} - MG_TIMESCALE_HOST: ${MG_TIMESCALE_HOST} - MG_TIMESCALE_PORT: ${MG_TIMESCALE_PORT} - MG_TIMESCALE_USER: ${MG_TIMESCALE_USER} - MG_TIMESCALE_PASS: ${MG_TIMESCALE_PASS} - MG_TIMESCALE_NAME: ${MG_TIMESCALE_NAME} - MG_TIMESCALE_SSL_MODE: ${MG_TIMESCALE_SSL_MODE} - MG_TIMESCALE_SSL_CERT: ${MG_TIMESCALE_SSL_CERT} - MG_TIMESCALE_SSL_KEY: ${MG_TIMESCALE_SSL_KEY} - MG_TIMESCALE_SSL_ROOT_CERT: ${MG_TIMESCALE_SSL_ROOT_CERT} - MG_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_TIMESCALE_WRITER_INSTANCE_ID: ${MG_TIMESCALE_WRITER_INSTANCE_ID} - ports: - - ${MG_TIMESCALE_WRITER_HTTP_PORT}:${MG_TIMESCALE_WRITER_HTTP_PORT} - networks: - - magistrala-base-net - volumes: - - ./config.toml:/config.toml diff --git a/docker/addons/vault/scripts/docker/addons/vault/README.md b/docker/addons/vault/scripts/docker/addons/vault/README.md deleted file mode 100644 index ab9f1fc7..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/README.md +++ /dev/null @@ -1,290 +0,0 @@ -# Vault - -This is Vault service deployment to be used with Magistrala. - -When the Vault service is started, some initialization steps need to be done to set things up. - -## Configuration - -| Variable | Description | Default | -| :-------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------- | -| MG_VAULT_ADDR | Vault Address | http://vault:8200 | -| MG_VAULT_UNSEAL_KEY_1 | Vault unseal key | "" | -| MG_VAULT_UNSEAL_KEY_2 | Vault unseal key | "" | -| MG_VAULT_UNSEAL_KEY_3 | Vault unseal key | "" | -| MG_VAULT_TOKEN | Vault cli access token | "" | -| MG_VAULT_PKI_PATH | Vault secrets engine path for Root CA | pki | -| MG_VAULT_PKI_ROLE_NAME | Vault Root CA role name to issue intermediate CA | magistrala_int_ca | -| MG_VAULT_PKI_FILE_NAME | Root CA Certificates name used by`vault_set_pki.sh` | mg_root | -| MG_VAULT_PKI_CA_CN | Common name used for Root CA creation by`vault_set_pki.sh` | Magistrala Root Certificate Authority | -| MG_VAULT_PKI_CA_OU | Organization unit used for Root CA creation by`vault_set_pki.sh` | Magistrala | -| MG_VAULT_PKI_CA_O | Organization used for Root CA creation by`vault_set_pki.sh` | Magistrala | -| MG_VAULT_PKI_CA_C | Country used for Root CA creation by`vault_set_pki.sh` | FRANCE | -| MG_VAULT_PKI_CA_L | Location used for Root CA creation by`vault_set_pki.sh` | PARIS | -| MG_VAULT_PKI_CA_ST | State or Provisions used for Root CA creation by`vault_set_pki.sh` | PARIS | -| MG_VAULT_PKI_CA_ADDR | Address used for Root CA creation by`vault_set_pki.sh` | 5 Av. Anatole | -| MG_VAULT_PKI_CA_PO | Postal code used for Root CA creation by`vault_set_pki.sh` | 75007 | -| MG_VAULT_PKI_CLUSTER_PATH | Vault Root CA Cluster Path | http://localhost | -| MG_VAULT_PKI_CLUSTER_AIA_PATH | Vault Root CA Cluster AIA Path | http://localhost | -| MG_VAULT_PKI_INT_PATH | Vault secrets engine path for Intermediate CA | pki_int | -| MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue server certificate | magistrala_server_certs | -| MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue Things certificates | magistrala_things_certs | -| MG_VAULT_PKI_INT_FILE_NAME | Intermediate CA Certificates name used by`vault_set_pki.sh` | mg_root | -| MG_VAULT_PKI_INT_CA_CN | Common name used for Intermediate CA creation by`vault_set_pki.sh` | Magistrala Root Certificate Authority | -| MG_VAULT_PKI_INT_CA_OU | Organization unit used for Root CA creation by`vault_set_pki.sh` | Magistrala | -| MG_VAULT_PKI_INT_CA_O | Organization used for Intermediate CA creation by`vault_set_pki.sh` | Magistrala | -| MG_VAULT_PKI_INT_CA_C | Country used for Intermediate CA creation by`vault_set_pki.sh` | FRANCE | -| MG_VAULT_PKI_INT_CA_L | Location used for Intermediate CA creation by`vault_set_pki.sh` | PARIS | -| MG_VAULT_PKI_INT_CA_ST | State or Provisions used for Intermediate CA creation by`vault_set_pki.sh` | PARIS | -| MG_VAULT_PKI_INT_CA_ADDR | Address used for Intermediate CA creation by`vault_set_pki.sh` | 5 Av. Anatole | -| MG_VAULT_PKI_INT_CA_PO | Postal code used for Intermediate CA creation by`vault_set_pki.sh` | 75007 | -| MG_VAULT_PKI_INT_CLUSTER_PATH | Vault Intermediate CA Cluster Path | http://localhost | -| MG_VAULT_PKI_INT_CLUSTER_AIA_PATH | Vault Intermediate CA Cluster AIA Path | http://localhost | -| MG_VAULT_THINGS_CERTS_ISSUER_ROLEID | Vault Intermediate CA Things Certificate issuer AppRole authentication RoleID | magistrala | -| MG_VAULT_THINGS_CERTS_ISSUER_SECRET | Vault Intermediate CA Things Certificate issuer AppRole authentication Secret | magistrala | - -## Setup - -The following scripts are provided, which work on the running Vault service from within the `docker/addons/vault/scripts` directory. - -### 1. `vault_init.sh` - -Calls `vault operator init` to perform the initial vault initialization and generates a `docker/addons/vault/scripts/data/secrets` file which contains the Vault unseal keys and root tokens. - -### 2. `vault_copy_env.sh` - -After the initial setup, the Vault-related environment variables (`MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, `MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`) need to be updated in the `.env` file. - -The `vault_copy_env.sh` script automatically retrieves these values from the `docker/addons/vault/scripts/data/secrets` file and updates the corresponding environment variables in your `.env` file. - -Example: - -```sh -Vault environment variables have been successfully set in ~/magistrala/docker/.env -``` - -### 3. `vault_unseal.sh` - -This can be run after the initialization to unseal Vault, which is necessary for it to be used to store and/or get secrets. - -This can be used if you don't want to restart the service. - -The unseal environment variables need to be set in `.env` for the script to work (`MG_VAULT_TOKEN`,`MG_VAULT_UNSEAL_KEY_1`, `MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`). - -This script should not be necessary to run after the initial setup, since the Vault service unseals itself when starting the container. - -Example output: - -```bash -Key Value ---- ----- -Seal Type shamir -Initialized true -Sealed true -Total Shares 5 -Threshold 3 -Unseal Progress 1/3 -Unseal Nonce 4c248cc8-e9f5-055e-319b-00ee06f998a0 -Version 1.15.4 -Build Date 2023-12-04T17:45:28Z -Storage Type file -HA Enabled false -Key Value ---- ----- -Seal Type shamir -Initialized true -Sealed true -Total Shares 5 -Threshold 3 -Unseal Progress 2/3 -Unseal Nonce 4c248cc8-e9f5-055e-319b-00ee06f998a0 -Version 1.15.4 -Build Date 2023-12-04T17:45:28Z -Storage Type file -HA Enabled false -Key Value ---- ----- -Seal Type shamir -Initialized true -Sealed false -Total Shares 5 -Threshold 3 -Unseal Progress 3/3 -Unseal Nonce 4c248cc8-e9f5-055e-319b-00ee06f998a0 -Version 1.15.4 -Build Date 2023-12-04T17:45:28Z -Storage Type file -HA Enabled false -``` - -### 4. vault_set_pki.sh - -The `vault_set_pki.sh` script is responsible for generating the root certificate, intermediate certificate, and HTTPS server certificate. All generated certificates, keys, and CSR files are stored in the `docker/addons/vault/scripts/data` directory. - -The script pulls necessary parameters for certificate generation from environment variables, which are, by default, loaded from `docker/.env`. - -- Environment variables prefixed with `MG_VAULT_PKI` in the `docker/.env` file are used for generating the root CA. -- Environment variables prefixed with `MG_VAULT_PKI_INT` are used for generating the intermediate CA. - -To skip generating the server certificate and key, you can pass the `--skip-server-cert` option to the script: - -```sh -./vault_set_pki.sh --skip-server-cert -``` - -#### Troubleshooting: - -If you encounter the following error: - -```sh -jq command could not be found, please install it and try again. -``` - -Install `jq` using: - -```sh -sudo apt-get update && sudo apt-get install -y jq -``` - -After installing `jq`, rerun the script. - -### 5. `vault_create_approle.sh` - -This script enables AppRole authorization in Vault. The certs service uses these AppRole credentials to issue and revoke certificates from the Vault intermediate CA. - -Example output: - -```sh -Success! You are now authenticated. The token information displayed below -is already stored in the token helper. You do NOT need to run "vault login" -again. Future Vault requests will automatically use this token. - -Key Value ---- ----- -token <token_value> -token_accessor i6YVeKh4wQ4e0Aj0ONiyGw1Z -token_duration ∞ -token_renewable false -token_policies ["root"] -identity_policies [] -policies ["root"] -Creating new policy for AppRole -Successfully copied 2.56kB to magistrala-vault:/vault/magistrala_things_certs_issue.hcl -Success! Uploaded policy: magistrala_things_certs_issue -Enabling AppRole -Success! Enabled approle auth method at: approle/ -Deleting old AppRole -Success! Data deleted (if it existed) at: auth/approle/role/magistrala_things_certs_issuer -Creating new AppRole -Success! Data written to: auth/approle/role/magistrala_things_certs_issuer -Writing custom role ID -Key Value ---- ----- -role_id f23942b3-62b9-7456-784f-220ca3f703b9 -Success! Data written to: auth/approle/role/magistrala_things_certs_issuer/role-id -Writing custom secret -Key Value ---- ----- -secret_id 61d5a30f-634c-6027-f5b6-4934e6fc49b2 -secret_id_accessor 1d744f6e-e0c2-5431-a87a-2b23fde584a7 -secret_id_num_uses 0 -secret_id_ttl 0s -Testing custom role ID and secret by logging in -Key Value ---- ----- -token <token_value> -token_accessor 9cuwS4mrLHKhJQMv0pl9Bbg9 -token_duration 1h -token_renewable true -token_policies ["default" "magistrala_things_certs_issue"] -identity_policies [] -policies ["default" "magistrala_things_certs_issue"] -token_meta_role_name magistrala_things_certs_issuer -``` - -By default, the `vault_create_approle.sh` script tries to enable the AppRole authentication method. Certs service uses the approle credentials to issue and revoke things certificate from vault intermedate CA. If AppRole is already enabled, you can skip this step by passing the `--skip-enable-approle` argument: - -```sh -./vault_create_approle.sh --skip-enable-approle -``` - -### 6. `vault_copy_certs.sh` - -This script copies the required certificates and keys from `docker/addons/vault/scripts/data` to the `docker/ssl/certs` folder. - -Example output: - -```bash -Copying certificate files -'data/localhost.crt' -> '~/Documents/magistrala/docker/ssl/certs/magistrala-server.crt' -'data/localhost.key' -> '~/Documents/magistrala/docker/ssl/certs/magistrala-server.key' -'data/mg_int.key' -> '~/Documents/magistrala/docker/ssl/certs/ca.key' -'data/mg_int_bundle.crt' -> '~/Documents/magistrala/docker/ssl/certs/ca.crt' -``` - -## Custom `.env` Path Support - -Vault scripts support specifying a custom `.env` file path using the `--env-file` argument. If this argument is not provided, the scripts will use the default `.env` file located at `docker/.env`. - -To use a different `.env` file, include the `--env-file` argument followed by the path to your `.env` file when running the Vault scripts. Below are examples of how to execute each script with a custom `.env` file path: - -```bash -./vault_init.sh --env-file /custom/path/.env -./vault_copy_env.sh --env-file /custom/path/.env -./vault_unseal.sh --env-file /custom/path/.env -./vault_set_pki.sh --env-file /custom/path/.env -./vault_create_approle.sh --env-file /custom/path/.env -./vault_copy_certs.sh --env-file /custom/path/.env -``` - -## Hashicorp Cloud Platform (HCP) Vault - -To have the same PKI setup can done in Hashicorp Cloud Platform (HCP) Vault follow the below steps: -Requirement: [VAULT CLI](https://developer.hashicorp.com/vault/tutorials/getting-started/getting-started-install) - -- Replace the environmental variable `MG_VAULT_ADDR` in `docker/.env` with HCP Vault address. -- Replace the environmental variable `MG_VAULT_TOKEN` in `docker/.env` with HCP Vault Admin token. -- Run script `vault_set_pki.sh` and `vault_create_approle.sh`. -- Optional step, run script `vault_copy_certs.sh` to copy certificates to magistrala default path. - -## Vault CLI - -It can also be useful to run the Vault CLI for inspection and administration work. - -```bash -Usage: vault <command> [args] - -Common commands: - read Read data and retrieves secrets - write Write data, configuration, and secrets - delete Delete secrets and configuration - list List data or secrets - login Authenticate locally - agent Start a Vault agent - server Start a Vault server - status Print seal and HA status - unwrap Unwrap a wrapped secret - -Other commands: - audit Interact with audit devices - auth Interact with auth methods - debug Runs the debug command - kv Interact with Vault's Key-Value storage - lease Interact with leases - monitor Stream log messages from a Vault server - namespace Interact with namespaces - operator Perform operator-specific tasks - path-help Retrieve API help for paths - plugin Interact with Vault plugins and catalog - policy Interact with policies - print Prints runtime configurations - secrets Interact with secrets engines - ssh Initiate an SSH session - token Interact with tokens -``` - -If the Vault is setup through `docker/addons/vault`, then Vault CLI can be run directly using the Vault image in Docker: `docker run -it magistrala/vault:latest vault` - -## Vault Web UI - -If the Vault is setup through `docker/addons/vault`, Then Vault Web UI is accessible by default on `http://localhost:8200/ui`. diff --git a/docker/addons/vault/scripts/docker/addons/vault/config.hcl b/docker/addons/vault/scripts/docker/addons/vault/config.hcl deleted file mode 100644 index 192dd5af..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/config.hcl +++ /dev/null @@ -1,10 +0,0 @@ -storage "file" { - path = "/vault/file" -} - -listener "tcp" { - address = "0.0.0.0:8200" - tls_disable = 1 -} - -ui = true diff --git a/docker/addons/vault/scripts/docker/addons/vault/docker-compose.yml b/docker/addons/vault/scripts/docker/addons/vault/docker-compose.yml deleted file mode 100644 index 8f380b47..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/docker-compose.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This docker-compose file contains optional Vault service for Magistrala platform. -# Since this is optional, this file is dependent of docker-compose file -# from <project_root>/docker. In order to run these services, execute command: -# docker compose -f docker/docker-compose.yml -f docker/addons/vault/docker-compose.yml up -# from project root. Vault default port (8200) is exposed, so you can use Vault CLI tool for -# vault inspection and administration, as well as access the UI. - -networks: - magistrala-base-net: - -volumes: - magistrala-vault-volume: - -services: - vault: - image: hashicorp/vault:1.15.4 - container_name: magistrala-vault - ports: - - ${MG_VAULT_PORT}:8200 - networks: - - magistrala-base-net - volumes: - - magistrala-vault-volume:/vault/file - - magistrala-vault-volume:/vault/logs - - ./config.hcl:/vault/config/config.hcl - - ./entrypoint.sh:/entrypoint.sh - environment: - VAULT_ADDR: http://127.0.0.1:${MG_VAULT_PORT} - MG_VAULT_PORT: ${MG_VAULT_PORT} - MG_VAULT_UNSEAL_KEY_1: ${MG_VAULT_UNSEAL_KEY_1} - MG_VAULT_UNSEAL_KEY_2: ${MG_VAULT_UNSEAL_KEY_2} - MG_VAULT_UNSEAL_KEY_3: ${MG_VAULT_UNSEAL_KEY_3} - entrypoint: /bin/sh - command: /entrypoint.sh - cap_add: - - IPC_LOCK diff --git a/docker/addons/vault/scripts/docker/addons/vault/entrypoint.sh b/docker/addons/vault/scripts/docker/addons/vault/entrypoint.sh deleted file mode 100644 index efc6f5a7..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/entrypoint.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/dumb-init /bin/sh -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -VAULT_CONFIG_DIR=/vault/config - -docker-entrypoint.sh server & -VAULT_PID=$! - -sleep 2 - -echo $MG_VAULT_UNSEAL_KEY_1 -echo $MG_VAULT_UNSEAL_KEY_2 -echo $MG_VAULT_UNSEAL_KEY_3 - -if [[ ! -z "${MG_VAULT_UNSEAL_KEY_1}" ]] && - [[ ! -z "${MG_VAULT_UNSEAL_KEY_2}" ]] && - [[ ! -z "${MG_VAULT_UNSEAL_KEY_3}" ]]; then - echo "Unsealing Vault" - vault operator unseal ${MG_VAULT_UNSEAL_KEY_1} - vault operator unseal ${MG_VAULT_UNSEAL_KEY_2} - vault operator unseal ${MG_VAULT_UNSEAL_KEY_3} -fi - -wait $VAULT_PID \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/.gitignore b/docker/addons/vault/scripts/docker/addons/vault/scripts/.gitignore deleted file mode 100644 index 4f14d396..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -data -magistrala_things_certs_issue.hcl diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl b/docker/addons/vault/scripts/docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl deleted file mode 100644 index 1b13f6db..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl +++ /dev/null @@ -1,32 +0,0 @@ - -# Allow issue certificate with role with default issuer from Intermediate PKI -path "${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME}" { - capabilities = ["create", "update"] -} - -## Revole certificate from Intermediate PKI -path "${MG_VAULT_PKI_INT_PATH}/revoke" { - capabilities = ["create", "update"] -} - -## List Revoked Certificates from Intermediate PKI -path "${MG_VAULT_PKI_INT_PATH}/certs/revoked" { - capabilities = ["list"] -} - - -## List Certificates from Intermediate PKI -path "${MG_VAULT_PKI_INT_PATH}/certs" { - capabilities = ["list"] -} - -## Read Certificate from Intermediate PKI -path "${MG_VAULT_PKI_INT_PATH}/cert/+" { - capabilities = ["read"] -} -path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw" { - capabilities = ["read"] -} -path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw/pem" { - capabilities = ["read"] -} diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_cmd.sh b/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_cmd.sh deleted file mode 100644 index 97a8cc92..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_cmd.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -vault() { - if is_container_running "magistrala-vault"; then - docker exec -it magistrala-vault vault "$@" - else - if which vault &> /dev/null; then - $(which vault) "$@" - else - echo "magistrala-vault container or vault command not found. Please refer to the documentation: https://github.com/absmach/magistrala/blob/main/docker/addons/vault/README.md" - fi - fi -} - -is_container_running() { - local container_name="$1" - if [ "$(docker inspect --format '{{.State.Running}}' "$container_name" 2>/dev/null)" = "true" ]; then - return 0 - else - return 1 - fi -} diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_copy_certs.sh b/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_copy_certs.sh deleted file mode 100755 index 62521a44..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_copy_certs.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" - -# default env file path -env_file="docker/.env" - -# default certs copy path -certs_copy_path="docker/ssl/certs/" - -while [[ "$#" -gt 0 ]]; do - case $1 in - --env-file) - if [[ -z "${2:-}" ]]; then - echo "Error: --env-file requires a non-empty option argument." - exit 1 - fi - env_file="$2" - if [[ ! -f "$env_file" ]]; then - echo "Error: .env file not found at $env_file" - exit 1 - fi - shift - ;; - --certs-copy-path) - if [[ -z "${2:-}" ]]; then - echo "Error: --certs-copy-path requires a non-empty option argument." - exit 1 - fi - certs_copy_path="$2" - shift - ;; - *) - echo "Error: Unknown parameter passed: $1" - exit 1 - ;; - esac - shift -done - -readDotEnv() { - set -o allexport - source "$env_file" - set +o allexport -} - -readDotEnv - -server_name="localhost" - -# Check if MG_NGINX_SERVER_NAME is set or not empty -if [ -n "${MG_NGINX_SERVER_NAME:-}" ]; then - server_name="$MG_NGINX_SERVER_NAME" -fi - -echo "Copying certificate files to ${certs_copy_path}" - -if [ -e "$scriptdir/data/${server_name}.crt" ]; then - cp -v "$scriptdir/data/${server_name}.crt" "${certs_copy_path}magistrala-server.crt" -else - echo "${server_name}.crt file not available" -fi - -if [ -e "$scriptdir/data/${server_name}.key" ]; then - cp -v "$scriptdir/data/${server_name}.key" "${certs_copy_path}magistrala-server.key" -else - echo "${server_name}.key file not available" -fi - -if [ -e "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.key" ]; then - cp -v "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.key" "${certs_copy_path}ca.key" -else - echo "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.key file not available" -fi - -if [ -e "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt" ]; then - cp -v "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt" "${certs_copy_path}ca.crt" -else - echo "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt file not available" -fi - -exit 0 diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_copy_env.sh b/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_copy_env.sh deleted file mode 100755 index a04697d0..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_copy_env.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" - -# default env file path -env_file="docker/.env" - -while [[ "$#" -gt 0 ]]; do - case $1 in - --env-file) - if [[ -z "${2:-}" ]]; then - echo "Error: --env-file requires a non-empty option argument." - exit 1 - fi - env_file="$2" - if [[ ! -f "$env_file" ]]; then - echo "Error: .env file not found at $env_file" - exit 1 - fi - shift - ;; - *) - echo "Unknown parameter passed: $1" - exit 1 - ;; - esac - shift -done - -write_env() { - if [ -e "$scriptdir/data/secrets" ]; then - sed -i "s,MG_VAULT_UNSEAL_KEY_1=.*,MG_VAULT_UNSEAL_KEY_1=$(awk -F ": " '$1 == "Unseal Key 1" {print $2}' $scriptdir/data/secrets)," "$env_file" - sed -i "s,MG_VAULT_UNSEAL_KEY_2=.*,MG_VAULT_UNSEAL_KEY_2=$(awk -F ": " '$1 == "Unseal Key 2" {print $2}' $scriptdir/data/secrets)," "$env_file" - sed -i "s,MG_VAULT_UNSEAL_KEY_3=.*,MG_VAULT_UNSEAL_KEY_3=$(awk -F ": " '$1 == "Unseal Key 3" {print $2}' $scriptdir/data/secrets)," "$env_file" - sed -i "s,MG_VAULT_TOKEN=.*,MG_VAULT_TOKEN=$(awk -F ": " '$1 == "Initial Root Token" {print $2}' $scriptdir/data/secrets)," "$env_file" - echo "Vault environment variables are set successfully in $env_file" - else - echo "Error: Source file '$scriptdir/data/secrets' not found." - fi -} - -write_env diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_create_approle.sh b/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_create_approle.sh deleted file mode 100755 index c95eb742..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_create_approle.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" - -# default env file path -env_file="docker/.env" - -SKIP_ENABLE_APP_ROLE="" - -while [[ "$#" -gt 0 ]]; do - case $1 in - --env-file) - if [[ -z "${2:-}" ]]; then - echo "Error: --env-file requires a non-empty option argument." - exit 1 - fi - env_file="$2" - if [[ ! -f "$env_file" ]]; then - echo "Error: .env file not found at $env_file" - exit 1 - fi - shift - ;; - --skip-enable-approle) - SKIP_ENABLE_APP_ROLE="true" - ;; - *) - echo "Unknown parameter passed: $1" - exit 1 - ;; - esac - shift -done - -readDotEnv() { - set -o allexport - source "$env_file" - set +o allexport -} - -source "$scriptdir/vault_cmd.sh" - -vaultCreatePolicyFile() { - envsubst ' - ${MG_VAULT_PKI_INT_PATH} - ${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} - ' < "$scriptdir/magistrala_things_certs_issue.template.hcl" > "$scriptdir/magistrala_things_certs_issue.hcl" -} - -vaultCreatePolicy() { - echo "Creating new policy for AppRole" - if is_container_running "magistrala-vault"; then - docker cp "$scriptdir/magistrala_things_certs_issue.hcl" magistrala-vault:/vault/magistrala_things_certs_issue.hcl - vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue /vault/magistrala_things_certs_issue.hcl - else - vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue "$scriptdir/magistrala_things_certs_issue.hcl" - fi -} - -vaultEnableAppRole() { - if [[ "$SKIP_ENABLE_APP_ROLE" == "true" ]]; then - echo "Skipping Enable AppRole" - else - echo "Enabling AppRole" - vault auth enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} approle - fi -} - -vaultDeleteRole() { - echo "Deleting old AppRole" - vault delete -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer -} - -vaultCreateRole() { - echo "Creating new AppRole" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer \ - token_policies=magistrala_things_certs_issue secret_id_num_uses=0 \ - secret_id_ttl=0 token_ttl=1h token_max_ttl=3h token_num_uses=0 -} - -vaultWriteCustomRoleID() { - echo "Writing custom role id" - vault read -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/role-id - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/role-id role_id=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} -} - -vaultWriteCustomSecret() { - echo "Writing custom secret" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -f auth/approle/role/magistrala_things_certs_issuer/secret-id - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/custom-secret-id secret_id=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} num_uses=0 ttl=0 -} - -vaultTestRoleLogin() { - echo "Testing custom roleid secret by logging in" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/login \ - role_id=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} \ - secret_id=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} -} - -if ! command -v jq &> /dev/null; then - echo "jq command could not be found, please install it and try again." - exit 1 -fi - -readDotEnv - -vault login -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_TOKEN} - -vaultCreatePolicyFile -vaultCreatePolicy -vaultEnableAppRole -vaultDeleteRole -vaultCreateRole -vaultWriteCustomRoleID -vaultWriteCustomSecret -vaultTestRoleLogin - -exit 0 diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_init.sh b/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_init.sh deleted file mode 100755 index e65de29c..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_init.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" - -# default env file path -env_file="docker/.env" - -while [[ "$#" -gt 0 ]]; do - case $1 in - --env-file) - if [[ -z "${2:-}" ]]; then - echo "Error: --env-file requires a non-empty option argument." - exit 1 - fi - env_file="$2" - if [[ ! -f "$env_file" ]]; then - echo "Error: .env file not found at $env_file" - exit 1 - fi - shift - ;; - *) - echo "Unknown parameter passed: $1" - exit 1 - ;; - esac - shift -done - -readDotEnv() { - set -o allexport - source "$env_file" - set +o allexport -} - -source "$scriptdir/vault_cmd.sh" - -readDotEnv - -mkdir -p "$scriptdir/data" - -vault operator init -address="$MG_VAULT_ADDR" 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > "$scriptdir/data/secrets") diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_set_pki.sh b/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_set_pki.sh deleted file mode 100755 index fb8f3894..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_set_pki.sh +++ /dev/null @@ -1,251 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" - -# edfault env file path -env_file="docker/.env" - -SKIP_SERVER_CERT="" - -while [[ "$#" -gt 0 ]]; do - case $1 in - --env-file) - if [[ -z "${2:-}" ]]; then - echo "Error: --env-file requires a non-empty option argument." - exit 1 - fi - env_file="$2" - if [[ ! -f "$env_file" ]]; then - echo "Error: .env file not found at $env_file" - exit 1 - fi - shift - ;; - --skip-server-cert) - SKIP_SERVER_CERT="--skip-server-cert" - ;; - *) - echo "Unknown parameter passed: $1" - exit 1 - ;; - esac - shift -done - -readDotEnv() { - set -o allexport - source "$env_file" - set +o allexport -} - -server_name="localhost" - -# Check if MG_NGINX_SERVER_NAME is set or not empty -if [ -n "${MG_NGINX_SERVER_NAME:-}" ]; then - server_name="$MG_NGINX_SERVER_NAME" -fi - -source "$scriptdir/vault_cmd.sh" - -vaultEnablePKI() { - vault secrets enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -path ${MG_VAULT_PKI_PATH} pki - vault secrets tune -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -max-lease-ttl=87600h ${MG_VAULT_PKI_PATH} -} - -vaultConfigPKIClusterPath() { - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/cluster aia_path=${MG_VAULT_PKI_CLUSTER_AIA_PATH} path=${MG_VAULT_PKI_CLUSTER_PATH} -} - -vaultConfigPKICrl() { - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/crl expiry="5m" ocsp_disable=false ocsp_expiry=0 auto_rebuild=true auto_rebuild_grace_period="2m" enable_delta=true delta_rebuild_interval="1m" -} - -vaultAddRoleToSecret() { - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/roles/${MG_VAULT_PKI_ROLE_NAME} \ - allow_any_name=true \ - max_ttl="8760h" \ - default_ttl="8760h" \ - generate_lease=true -} - -vaultGenerateRootCACertificate() { - echo "Generate root CA certificate" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/generate/exported \ - common_name="\"$MG_VAULT_PKI_CA_CN\"" \ - ou="\"$MG_VAULT_PKI_CA_OU\"" \ - organization="\"$MG_VAULT_PKI_CA_O\"" \ - country="\"$MG_VAULT_PKI_CA_C\"" \ - locality="\"$MG_VAULT_PKI_CA_L\"" \ - province="\"$MG_VAULT_PKI_CA_ST\"" \ - street_address="\"$MG_VAULT_PKI_CA_ADDR\"" \ - postal_code="\"$MG_VAULT_PKI_CA_PO\"" \ - ttl=87600h | tee >(jq -r .data.certificate >"$scriptdir/data/${MG_VAULT_PKI_FILE_NAME}_ca.crt") \ - >(jq -r .data.issuing_ca >"$scriptdir/data/${MG_VAULT_PKI_FILE_NAME}_issuing_ca.crt") \ - >(jq -r .data.private_key >"$scriptdir/data/${MG_VAULT_PKI_FILE_NAME}_ca.key") -} - -vaultSetupRootCAIssuingURLs() { - echo "Setup URLs for CRL and issuing" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/urls \ - issuing_certificates="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/ca" \ - crl_distribution_points="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/crl" \ - ocsp_servers="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/ocsp" \ - enable_templating=true -} - -vaultGenerateIntermediateCAPKI() { - echo "Generate Intermediate CA PKI" - vault secrets enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -path=${MG_VAULT_PKI_INT_PATH} pki - vault secrets tune -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -max-lease-ttl=43800h ${MG_VAULT_PKI_INT_PATH} -} - -vaultConfigIntermediatePKIClusterPath() { - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/cluster aia_path=${MG_VAULT_PKI_INT_CLUSTER_AIA_PATH} path=${MG_VAULT_PKI_INT_CLUSTER_PATH} -} - -vaultConfigIntermediatePKICrl() { - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/crl expiry="5m" ocsp_disable=false ocsp_expiry=0 auto_rebuild=true auto_rebuild_grace_period="2m" enable_delta=true delta_rebuild_interval="1m" -} - -vaultGenerateIntermediateCSR() { - echo "Generate intermediate CSR" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_INT_PATH}/intermediate/generate/exported \ - common_name="\"$MG_VAULT_PKI_INT_CA_CN\"" \ - ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ - organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ - country="\"$MG_VAULT_PKI_INT_CA_C\"" \ - locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ - province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ - street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ - postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ - | tee >(jq -r .data.csr >"$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.csr") \ - >(jq -r .data.private_key >"$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.key") -} - -vaultSignIntermediateCSR() { - echo "Sign intermediate CSR" - if is_container_running "magistrala-vault"; then - docker cp "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.csr" magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ - csr=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr ttl="8760h" \ - ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ - organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ - country="\"$MG_VAULT_PKI_INT_CA_C\"" \ - locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ - province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ - street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ - postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ - | tee >(jq -r .data.certificate >"$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.crt") \ - >(jq -r .data.issuing_ca >"$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}_issuing_ca.crt") - else - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ - csr=@"$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.csr" ttl="8760h" \ - ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ - organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ - country="\"$MG_VAULT_PKI_INT_CA_C\"" \ - locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ - province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ - street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ - postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ - | tee >(jq -r .data.certificate >"$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.crt") \ - >(jq -r .data.issuing_ca >"$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}_issuing_ca.crt") - fi -} - -vaultInjectIntermediateCertificate() { - echo "Inject Intermediate Certificate" - if is_container_running "magistrala-vault"; then - docker cp "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.crt" magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt - else - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@"$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.crt" - fi -} - -vaultGenerateIntermediateCertificateBundle() { - echo "Generate intermediate certificate bundle" - cat "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}.crt" "$scriptdir/data/${MG_VAULT_PKI_FILE_NAME}_ca.crt" \ - > "$scriptdir/data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt" -} - -vaultSetupIntermediateIssuingURLs() { - echo "Setup URLs for CRL and issuing" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/urls \ - issuing_certificates="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/ca" \ - crl_distribution_points="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/crl" \ - ocsp_servers="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/ocsp" \ - enable_templating=true -} - -vaultSetupServerCertsRole() { - if [ "$SKIP_SERVER_CERT" == "--skip-server-cert" ]; then - echo "Skipping server certificate role" - else - echo "Setup Server certificate role" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ - allow_subdomains=true \ - max_ttl="4320h" - fi -} - -vaultGenerateServerCertificate() { - if [ "$SKIP_SERVER_CERT" == "--skip-server-cert" ]; then - echo "Skipping generate server certificate" - else - echo "Generate server certificate" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ - common_name="$server_name" ttl="4320h" \ - | tee >(jq -r .data.certificate >"$scriptdir/data/${server_name}.crt") \ - >(jq -r .data.private_key >"$scriptdir/data/${server_name}.key") - fi -} - -vaultSetupThingCertsRole() { - echo "Setup Thing Certs role" - vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} \ - allow_subdomains=true \ - allow_any_name=true \ - max_ttl="2160h" -} - -vaultCleanupFiles() { - if is_container_running "magistrala-vault"; then - docker exec magistrala-vault sh -c 'rm -rf /vault/*.{crt,csr}' - fi -} - -if ! command -v jq &> /dev/null; then - echo "jq command could not be found, please install it and try again." - exit 1 -fi - -readDotEnv - -mkdir -p "$scriptdir/data" - -vault login -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_TOKEN} - -vaultEnablePKI -vaultConfigPKIClusterPath -vaultConfigPKICrl -vaultAddRoleToSecret -vaultGenerateRootCACertificate -vaultSetupRootCAIssuingURLs -vaultGenerateIntermediateCAPKI -vaultConfigIntermediatePKIClusterPath -vaultConfigIntermediatePKICrl -vaultGenerateIntermediateCSR -vaultSignIntermediateCSR -vaultInjectIntermediateCertificate -vaultGenerateIntermediateCertificateBundle -vaultSetupIntermediateIssuingURLs -vaultSetupServerCertsRole -vaultGenerateServerCertificate -vaultSetupThingCertsRole -vaultCleanupFiles - -exit 0 diff --git a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_unseal.sh b/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_unseal.sh deleted file mode 100755 index d85c14f2..00000000 --- a/docker/addons/vault/scripts/docker/addons/vault/scripts/vault_unseal.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" - -# default env file path -env_file="docker/.env" - -while [[ "$#" -gt 0 ]]; do - case $1 in - --env-file) - if [[ -z "${2:-}" ]]; then - echo "Error: --env-file requires a non-empty option argument." - exit 1 - fi - env_file="$2" - if [[ ! -f "$env_file" ]]; then - echo "Error: .env file not found at $env_file" - exit 1 - fi - shift - ;; - *) - echo "Unknown parameter passed: $1" - exit 1 - ;; - esac - shift -done - -readDotEnv() { - set -o allexport - source "$env_file" - set +o allexport -} - -source "$scriptdir/vault_cmd.sh" - -readDotEnv - -vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_1} -vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_2} -vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_3} diff --git a/docker/addons/vault/scripts/docker/docker-compose.yml b/docker/addons/vault/scripts/docker/docker-compose.yml deleted file mode 100644 index 804389ea..00000000 --- a/docker/addons/vault/scripts/docker/docker-compose.yml +++ /dev/null @@ -1,774 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -name: "magistrala" - -networks: - magistrala-base-net: - driver: bridge - -volumes: - magistrala-users-db-volume: - magistrala-things-db-volume: - magistrala-things-redis-volume: - magistrala-broker-volume: - magistrala-mqtt-broker-volume: - magistrala-spicedb-db-volume: - magistrala-auth-db-volume: - magistrala-invitations-db-volume: - magistrala-ui-db-volume: - -services: - spicedb: - image: "authzed/spicedb:v1.30.0" - container_name: magistrala-spicedb - command: "serve" - restart: "always" - networks: - - magistrala-base-net - ports: - - "8080:8080" - - "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" - depends_on: - - spicedb-migrate - - spicedb-migrate: - image: "authzed/spicedb:v1.30.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" - depends_on: - - spicedb-db - - spicedb-db: - image: "postgres:16.2-alpine" - container_name: magistrala-spicedb-db - networks: - - magistrala-base-net - ports: - - "6010:5432" - environment: - POSTGRES_USER: ${MG_SPICEDB_DB_USER} - POSTGRES_PASSWORD: ${MG_SPICEDB_DB_PASS} - POSTGRES_DB: ${MG_SPICEDB_DB_NAME} - volumes: - - magistrala-spicedb-db-volume:/var/lib/postgresql/data - - auth-db: - image: postgres:16.2-alpine - container_name: magistrala-auth-db - restart: on-failure - ports: - - 6004:5432 - environment: - POSTGRES_USER: ${MG_AUTH_DB_USER} - POSTGRES_PASSWORD: ${MG_AUTH_DB_PASS} - POSTGRES_DB: ${MG_AUTH_DB_NAME} - networks: - - magistrala-base-net - volumes: - - magistrala-auth-db-volume:/var/lib/postgresql/data - - auth: - image: magistrala/auth:${MG_RELEASE_TAG} - container_name: magistrala-auth - depends_on: - - auth-db - - spicedb - expose: - - ${MG_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} - ## 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} - ports: - - ${MG_AUTH_HTTP_PORT}:${MG_AUTH_HTTP_PORT} - - ${MG_AUTH_GRPC_PORT}:${MG_AUTH_GRPC_PORT} - networks: - - magistrala-base-net - volumes: - - ./spicedb/schema.zed:${MG_SPICEDB_SCHEMA_FILE} - # 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} - 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} - 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} - 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} - bind: - create_host_path: true - - invitations-db: - image: postgres:16.2-alpine - container_name: magistrala-invitations-db - restart: on-failure - command: postgres -c "max_connections=${MG_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} - ports: - - 6021:5432 - networks: - - magistrala-base-net - volumes: - - magistrala-invitations-db-volume:/var/lib/postgresql/data - - invitations: - image: magistrala/invitations:${MG_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} - ports: - - ${MG_INVITATIONS_HTTP_PORT}:${MG_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} - bind: - create_host_path: true - - type: bind - source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true - - nginx: - image: nginx:1.25.4-alpine - container_name: magistrala-nginx - restart: on-failure - volumes: - - ./nginx/nginx-${AUTH-key}.conf:/etc/nginx/nginx.conf.template - - ./nginx/entrypoint.sh:/docker-entrypoint.d/entrypoint.sh - - ./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 - - type: bind - source: ${MG_NGINX_SERVER_KEY:-./ssl/certs/magistrala-server.key} - target: /etc/ssl/private/magistrala-server.key - - type: bind - source: ${MG_NGINX_SERVER_CLIENT_CA:-./ssl/certs/ca.crt} - target: /etc/ssl/certs/ca.crt - - type: bind - source: ${MG_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} - networks: - - magistrala-base-net - env_file: - - .env - depends_on: - - auth - - things - - users - - mqtt-adapter - - http-adapter - - ws-adapter - - coap-adapter - - things-db: - image: postgres:16.2-alpine - container_name: magistrala-things-db - restart: on-failure - command: postgres -c "max_connections=${MG_POSTGRES_MAX_CONNECTIONS}" - environment: - POSTGRES_USER: ${MG_THINGS_DB_USER} - POSTGRES_PASSWORD: ${MG_THINGS_DB_PASS} - POSTGRES_DB: ${MG_THINGS_DB_NAME} - MG_POSTGRES_MAX_CONNECTIONS: ${MG_POSTGRES_MAX_CONNECTIONS} - networks: - - magistrala-base-net - ports: - - 6006:5432 - volumes: - - magistrala-things-db-volume:/var/lib/postgresql/data - - things-redis: - image: redis:7.2.4-alpine - container_name: magistrala-things-redis - restart: on-failure - networks: - - magistrala-base-net - volumes: - - magistrala-things-redis-volume:/data - - things: - image: magistrala/things:${MG_RELEASE_TAG} - container_name: magistrala-things - depends_on: - - things-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} - ## 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} - ports: - - ${MG_THINGS_HTTP_PORT}:${MG_THINGS_HTTP_PORT} - - ${MG_THINGS_AUTH_GRPC_PORT}:${MG_THINGS_AUTH_GRPC_PORT} - networks: - - magistrala-base-net - volumes: - # Things 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} - 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} - 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} - 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} - 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} - bind: - create_host_path: true - - type: bind - source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true - - users-db: - image: postgres:16.2-alpine - container_name: magistrala-users-db - restart: on-failure - command: postgres -c "max_connections=${MG_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} - ports: - - 6000:5432 - networks: - - magistrala-base-net - volumes: - - magistrala-users-db-volume:/var/lib/postgresql/data - - users: - image: magistrala/users:${MG_RELEASE_TAG} - container_name: magistrala-users - depends_on: - - users-db - - auth - - 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} - ports: - - ${MG_USERS_HTTP_PORT}:${MG_USERS_HTTP_PORT} - networks: - - magistrala-base-net - volumes: - - ./templates/${MG_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} - bind: - create_host_path: true - - type: bind - source: ${MG_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /auth-grpc-client${MG_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${MG_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /auth-grpc-server-ca${MG_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true - - jaeger: - image: jaegertracing/all-in-one:1.60 - container_name: magistrala-jaeger - environment: - COLLECTOR_OTLP_ENABLED: ${MG_JAEGER_COLLECTOR_OTLP_ENABLED} - command: --memory.max-traces ${MG_JAEGER_MEMORY_MAX_TRACES} - ports: - - ${MG_JAEGER_FRONTEND}:${MG_JAEGER_FRONTEND} - - ${MG_JAEGER_OLTP_HTTP}:${MG_JAEGER_OLTP_HTTP} - networks: - - magistrala-base-net - - mqtt-adapter: - image: magistrala/mqtt:${MG_RELEASE_TAG} - container_name: magistrala-mqtt - depends_on: - - things - - 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} - networks: - - magistrala-base-net - volumes: - # Things 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} - 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} - 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} - bind: - create_host_path: true - - http-adapter: - image: magistrala/http:${MG_RELEASE_TAG} - container_name: magistrala-http - depends_on: - - things - - 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} - ports: - - ${MG_HTTP_ADAPTER_PORT}:${MG_HTTP_ADAPTER_PORT} - networks: - - magistrala-base-net - volumes: - # Things 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} - 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} - 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} - bind: - create_host_path: true - - coap-adapter: - image: magistrala/coap:${MG_RELEASE_TAG} - container_name: magistrala-coap - depends_on: - - things - - 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} - ports: - - ${MG_COAP_ADAPTER_PORT}:${MG_COAP_ADAPTER_PORT}/udp - - ${MG_COAP_ADAPTER_HTTP_PORT}:${MG_COAP_ADAPTER_HTTP_PORT}/tcp - networks: - - magistrala-base-net - volumes: - # Things 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} - 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} - 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} - bind: - create_host_path: true - - ws-adapter: - image: magistrala/ws:${MG_RELEASE_TAG} - container_name: magistrala-ws - depends_on: - - things - - 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} - ports: - - ${MG_WS_ADAPTER_HTTP_PORT}:${MG_WS_ADAPTER_HTTP_PORT} - networks: - - magistrala-base-net - volumes: - # Things 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} - 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} - 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} - bind: - create_host_path: true - - vernemq: - image: magistrala/vernemq:${MG_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} - networks: - - magistrala-base-net - volumes: - - magistrala-mqtt-broker-volume:/var/lib/vernemq - - nats: - image: nats:2.10.9-alpine - container_name: magistrala-nats - 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} - ports: - - ${MG_NATS_PORT}:${MG_NATS_PORT} - - ${MG_NATS_HTTP_PORT}:${MG_NATS_HTTP_PORT} - volumes: - - magistrala-broker-volume:/data - - ./nats:/etc/nats - networks: - - magistrala-base-net - - ui: - image: magistrala/ui:${MG_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} - ports: - - ${MG_UI_PORT}:${MG_UI_PORT} - networks: - - magistrala-base-net - - ui-db: - image: postgres:16.2-alpine - container_name: magistrala-ui-db - restart: on-failure - command: postgres -c "max_connections=${MG_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} - ports: - - 6007:5432 - networks: - - magistrala-base-net - volumes: - - magistrala-ui-db-volume:/var/lib/postgresql/data diff --git a/docker/addons/vault/scripts/docker/nats/nats.conf b/docker/addons/vault/scripts/docker/nats/nats.conf deleted file mode 100644 index 688a58d2..00000000 --- a/docker/addons/vault/scripts/docker/nats/nats.conf +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -server_name: "nats_internal_broker" -max_payload: 1MB -max_connections: 1M -port: $MG_NATS_PORT -http_port: $MG_NATS_HTTP_PORT -trace: true - -jetstream { - store_dir: "/data" - cipher: "aes" - key: $MG_NATS_JETSTREAM_KEY - max_mem: 1G -} - -mqtt { - port: 1883 - max_ack_pending: 1 -} - -websocket { - port: 8080 - - no_tls: true -} diff --git a/docker/addons/vault/scripts/docker/nginx/.gitignore b/docker/addons/vault/scripts/docker/nginx/.gitignore deleted file mode 100644 index 9453269c..00000000 --- a/docker/addons/vault/scripts/docker/nginx/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -snippets/mqtt-upstream.conf -snippets/mqtt-ws-upstream.conf \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/nginx/entrypoint.sh b/docker/addons/vault/scripts/docker/nginx/entrypoint.sh deleted file mode 100755 index 6b903770..00000000 --- a/docker/addons/vault/scripts/docker/nginx/entrypoint.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/ash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -if [ -z "$MG_MQTT_CLUSTER" ] -then - envsubst '${MG_MQTT_ADAPTER_MQTT_PORT}' < /etc/nginx/snippets/mqtt-upstream-single.conf > /etc/nginx/snippets/mqtt-upstream.conf - envsubst '${MG_MQTT_ADAPTER_WS_PORT}' < /etc/nginx/snippets/mqtt-ws-upstream-single.conf > /etc/nginx/snippets/mqtt-ws-upstream.conf -else - envsubst '${MG_MQTT_ADAPTER_MQTT_PORT}' < /etc/nginx/snippets/mqtt-upstream-cluster.conf > /etc/nginx/snippets/mqtt-upstream.conf - envsubst '${MG_MQTT_ADAPTER_WS_PORT}' < /etc/nginx/snippets/mqtt-ws-upstream-cluster.conf > /etc/nginx/snippets/mqtt-ws-upstream.conf -fi - -envsubst ' - ${MG_NGINX_SERVER_NAME} - ${MG_AUTH_HTTP_PORT} - ${MG_USERS_HTTP_PORT} - ${MG_THINGS_HTTP_PORT} - ${MG_THINGS_AUTH_HTTP_PORT} - ${MG_HTTP_ADAPTER_PORT} - ${MG_NGINX_MQTT_PORT} - ${MG_NGINX_MQTTS_PORT} - ${MG_INVITATIONS_HTTP_PORT} - ${MG_WS_ADAPTER_HTTP_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf - -exec nginx -g "daemon off;" diff --git a/docker/addons/vault/scripts/docker/nginx/nginx-key.conf b/docker/addons/vault/scripts/docker/nginx/nginx-key.conf deleted file mode 100644 index 153a7b7a..00000000 --- a/docker/addons/vault/scripts/docker/nginx/nginx-key.conf +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This is the default Magistrala NGINX configuration. - -user nginx; -worker_processes auto; -worker_cpu_affinity auto; -pid /run/nginx.pid; -include /etc/nginx/modules-enabled/*.conf; - -events { - # Explanation: https://serverfault.com/questions/787919/optimal-value-for-nginx-worker-connections - # We'll keep 10k connections per core (assuming one worker per core) - worker_connections 10000; -} - -http { - include snippets/http_access_log.conf; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - - # Include single-node or multiple-node (cluster) upstream - include snippets/mqtt-ws-upstream.conf; - - server { - listen 80 default_server; - listen [::]:80 default_server; - listen 443 ssl default_server; - listen [::]:443 ssl default_server; - http2 on; - - set $dynamic_server_name "$MG_NGINX_SERVER_NAME"; - - if ($dynamic_server_name = '') { - set $dynamic_server_name "localhost"; - } - - server_name $dynamic_server_name; - - include snippets/ssl.conf; - - add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header Access-Control-Allow-Origin '*'; - add_header Access-Control-Allow-Methods '*'; - add_header Access-Control-Allow-Headers '*'; - - location ~ ^/(channels)/(.+)/(things)/(.+) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - # Proxy pass to users & groups id to things service for listing of channels - # /users/{userID}/channels - Listing of channels belongs to userID - # /groups/{userGroupID}/channels - Listing of channels belongs to userGroupID - location ~ ^/(users|groups)/(.+)/(channels|things) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - break; - } - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - # Proxy pass to channel id to users service for listing of channels - # /channels/{channelID}/users - Listing of Users belongs to channelID - # /channels/{channelID}/groups - Listing of User Groups belongs to channelID - location ~ ^/(channels|things)/(.+)/(users|groups) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - break; - } - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - # Proxy pass to user id to auth service for listing of domains - # /users/{userID}/domains - Listing of Domains belongs to userID - location ~ ^/(users)/(.+)/(domains) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - break; - } - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - # Proxy pass to domain id to users service for listing of users - # /domains/{domainID}/users - Listing of Users belongs to domainID - location ~ ^/(domains)/(.+)/(users) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - break; - } - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - } - - - # Proxy pass to auth service - location ~ ^/(domains) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - } - - # Proxy pass to users service - location ~ ^/(users|groups|password|authorize|oauth/callback/[^/]+) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - location ^~ /users/policies { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://users:${MG_USERS_HTTP_PORT}/policies; - } - - # Proxy pass to things service - location ~ ^/(things|channels|connect|disconnect|identify) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - location ^~ /things/policies { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}/policies; - } - - # Proxy pass to invitations service - location ~ ^/(invitations) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://invitations:${MG_INVITATIONS_HTTP_PORT}; - } - - location /health { - include snippets/proxy-headers.conf; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - location /metrics { - include snippets/proxy-headers.conf; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - # Proxy pass to magistrala-http-adapter - location /http/ { - include snippets/proxy-headers.conf; - - # Trailing `/` is mandatory. Refer to the http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass - # If the proxy_pass directive is specified with a URI, then when a request is passed to the server, - # the part of a normalized request URI matching the location is replaced by a URI specified in the directive - proxy_pass http://http-adapter:${MG_HTTP_ADAPTER_PORT}/; - } - - # Proxy pass to magistrala-mqtt-adapter over WS - location /mqtt { - include snippets/proxy-headers.conf; - include snippets/ws-upgrade.conf; - proxy_pass http://mqtt_ws_cluster; - } - - # Proxy pass to magistrala-ws-adapter - location /ws/ { - include snippets/proxy-headers.conf; - include snippets/ws-upgrade.conf; - proxy_pass http://ws-adapter:${MG_WS_ADAPTER_HTTP_PORT}/; - } - } -} - -# MQTT -stream { - include snippets/stream_access_log.conf; - - # Include single-node or multiple-node (cluster) upstream - include snippets/mqtt-upstream.conf; - - server { - listen ${MG_NGINX_MQTT_PORT}; - listen [::]:${MG_NGINX_MQTT_PORT}; - listen ${MG_NGINX_MQTTS_PORT} ssl; - listen [::]:${MG_NGINX_MQTTS_PORT} ssl; - - include snippets/ssl.conf; - - proxy_pass mqtt_cluster; - } -} - -error_log info.log info; diff --git a/docker/addons/vault/scripts/docker/nginx/nginx-x509.conf b/docker/addons/vault/scripts/docker/nginx/nginx-x509.conf deleted file mode 100644 index 1da22b0f..00000000 --- a/docker/addons/vault/scripts/docker/nginx/nginx-x509.conf +++ /dev/null @@ -1,232 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This is the Magistrala NGINX configuration for mututal authentication based on X.509 certifiactes. - -user nginx; -worker_processes auto; -worker_cpu_affinity auto; -pid /run/nginx.pid; -load_module /etc/nginx/modules/ngx_stream_js_module.so; -load_module /etc/nginx/modules/ngx_http_js_module.so; -include /etc/nginx/modules-enabled/*.conf; - -events { - # Explanation: https://serverfault.com/questions/787919/optimal-value-for-nginx-worker-connections - # We'll keep 10k connections per core (assuming one worker per core) - worker_connections 10000; -} - -http { - include snippets/http_access_log.conf; - - js_path "/etc/nginx/njs/"; - js_import authorization from /etc/nginx/authorization.js; - - js_set $auth_key authorization.setKey; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - - # Include single-node or multiple-node (cluster) upstream - include snippets/mqtt-ws-upstream.conf; - - server { - listen 80 default_server; - listen [::]:80 default_server; - listen 443 ssl default_server; - listen [::]:443 ssl default_server; - http2 on; - - set $dynamic_server_name "$MG_NGINX_SERVER_NAME"; - - if ($dynamic_server_name = '') { - set $dynamic_server_name "localhost"; - } - - server_name $dynamic_server_name; - - ssl_verify_client optional; - include snippets/ssl.conf; - include snippets/ssl-client.conf; - - add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header Access-Control-Allow-Origin '*'; - add_header Access-Control-Allow-Methods '*'; - add_header Access-Control-Allow-Headers '*'; - - location ~ ^/(channels)/(.+)/(things)/(.+) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - # Proxy pass to users & groups id to things service for listing of channels - # /users/{userID}/channels - Listing of channels belongs to userID - # /groups/{userGroupID}/channels - Listing of channels belongs to userGroupID - location ~ ^/(users|groups)/(.+)/(channels|things) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - break; - } - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - # Proxy pass to channel id to users service for listing of channels - # /channels/{channelID}/users - Listing of Users belongs to channelID - # /channels/{channelID}/groups - Listing of User Groups belongs to channelID - location ~ ^/(channels|things)/(.+)/(users|groups) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - break; - } - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - # Proxy pass to user id to auth service for listing of domains - # /users/{userID}/domains - Listing of Domains belongs to userID - location ~ ^/(users)/(.+)/(domains) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - break; - } - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - # Proxy pass to domain id to users service for listing of users - # /domains/{domainID}/users - Listing of Users belongs to domainID - location ~ ^/(domains)/(.+)/(users) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - if ($request_method = GET) { - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - break; - } - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - } - - - # Proxy pass to auth service - location ~ ^/(domains) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://auth:${MG_AUTH_HTTP_PORT}; - } - - # Proxy pass to users service - location ~ ^/(users|groups|password|authorize|oauth/callback/[^/]+) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://users:${MG_USERS_HTTP_PORT}; - } - - location ^~ /users/policies { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://users:${MG_USERS_HTTP_PORT}/policies; - } - - # Proxy pass to things service - location ~ ^/(things|channels|connect|disconnect|identify) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - location ^~ /things/policies { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}/policies; - } - - # Proxy pass to invitations service - location ~ ^/(invitations) { - include snippets/proxy-headers.conf; - add_header Access-Control-Expose-Headers Location; - proxy_pass http://invitations:${MG_INVITATIONS_HTTP_PORT}; - } - - location /health { - include snippets/proxy-headers.conf; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - location /metrics { - include snippets/proxy-headers.conf; - proxy_pass http://things:${MG_THINGS_HTTP_PORT}; - } - - # Proxy pass to magistrala-http-adapter - location /http/ { - include snippets/verify-ssl-client.conf; - include snippets/proxy-headers.conf; - proxy_set_header Authorization $auth_key; - - # Trailing `/` is mandatory. Refer to the http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass - # If the proxy_pass directive is specified with a URI, then when a request is passed to the server, - # the part of a normalized request URI matching the location is replaced by a URI specified in the directive - proxy_pass http://http-adapter:${MG_HTTP_ADAPTER_PORT}/; - } - - # Proxy pass to magistrala-mqtt-adapter over WS - location /mqtt { - include snippets/verify-ssl-client.conf; - include snippets/proxy-headers.conf; - include snippets/ws-upgrade.conf; - proxy_pass http://mqtt_ws_cluster; - } - - # Proxy pass to magistrala-ws-adapter - location /ws/ { - include snippets/verify-ssl-client.conf; - include snippets/proxy-headers.conf; - include snippets/ws-upgrade.conf; - proxy_pass http://ws-adapter:${MG_WS_ADAPTER_HTTP_PORT}/; - } - } -} - -# MQTT -stream { - include snippets/stream_access_log.conf; - - # Include JS script for mTLS - js_path "/etc/nginx/njs/"; - - js_import authorization from /etc/nginx/authorization.js; - - # Include single-node or multiple-node (cluster) upstream - include snippets/mqtt-upstream.conf; - ssl_verify_client on; - include snippets/ssl-client.conf; - - server { - listen ${MG_NGINX_MQTT_PORT}; - listen [::]:${MG_NGINX_MQTT_PORT}; - listen ${MG_NGINX_MQTTS_PORT} ssl; - listen [::]:${MG_NGINX_MQTTS_PORT} ssl; - - include snippets/ssl.conf; - js_preread authorization.authenticate; - - proxy_pass mqtt_cluster; - } -} - -error_log info.log info; diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/http_access_log.conf b/docker/addons/vault/scripts/docker/nginx/snippets/http_access_log.conf deleted file mode 100644 index d9adfa19..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/http_access_log.conf +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -log_format access_log_format 'HTTP/WS ' - '$remote_addr: ' - '"$request" $status; ' - 'request time=$request_time upstream connect time=$upstream_connect_time upstream response time=$upstream_response_time'; -access_log access.log access_log_format; diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-upstream-cluster.conf b/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-upstream-cluster.conf deleted file mode 100644 index 72db846b..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-upstream-cluster.conf +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -upstream mqtt_cluster { - least_conn; - server mqtt-adapter-1:${MG_MQTT_ADAPTER_MQTT_PORT}; - server mqtt-adapter-2:${MG_MQTT_ADAPTER_MQTT_PORT}; - server mqtt-adapter-3:${MG_MQTT_ADAPTER_MQTT_PORT}; -} \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-upstream-single.conf b/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-upstream-single.conf deleted file mode 100644 index 1613dc75..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-upstream-single.conf +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -upstream mqtt_cluster { - server mqtt-adapter:${MG_MQTT_ADAPTER_MQTT_PORT}; -} \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-ws-upstream-cluster.conf b/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-ws-upstream-cluster.conf deleted file mode 100644 index 1103c8f2..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-ws-upstream-cluster.conf +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -upstream mqtt_ws_cluster { - least_conn; - server mqtt-adapter-1:${MG_MQTT_ADAPTER_WS_PORT}; - server mqtt-adapter-2:${MG_MQTT_ADAPTER_WS_PORT}; - server mqtt-adapter-3:${MG_MQTT_ADAPTER_WS_PORT}; -} \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-ws-upstream-single.conf b/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-ws-upstream-single.conf deleted file mode 100644 index 637a953f..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/mqtt-ws-upstream-single.conf +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -upstream mqtt_ws_cluster { - server mqtt-adapter:${MG_MQTT_ADAPTER_WS_PORT}; -} \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/proxy-headers.conf b/docker/addons/vault/scripts/docker/nginx/snippets/proxy-headers.conf deleted file mode 100644 index 08905787..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/proxy-headers.conf +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -proxy_redirect off; -proxy_set_header Host $host; -proxy_set_header X-Real-IP $remote_addr; -proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -proxy_set_header X-Forwarded-Proto $scheme; - -# Allow OPTIONS method CORS -if ($request_method = OPTIONS) { - add_header Content-Length 0; - add_header Content-Type text/plain; - return 200; -} \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/ssl-client.conf b/docker/addons/vault/scripts/docker/nginx/snippets/ssl-client.conf deleted file mode 100644 index 712d46a9..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/ssl-client.conf +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -ssl_client_certificate /etc/ssl/certs/ca.crt; -ssl_verify_depth 2; diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/ssl.conf b/docker/addons/vault/scripts/docker/nginx/snippets/ssl.conf deleted file mode 100644 index 9650f1fa..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/ssl.conf +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# These paths are set to its default values as -# a volume in the docker/docker-compose.yml file. -ssl_certificate /etc/ssl/certs/magistrala-server.crt; -ssl_certificate_key /etc/ssl/private/magistrala-server.key; -ssl_dhparam /etc/ssl/certs/dhparam.pem; - -ssl_protocols TLSv1.2 TLSv1.3; -ssl_prefer_server_ciphers on; -ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; -ssl_ecdh_curve secp384r1; -ssl_session_tickets off; -resolver 8.8.8.8 8.8.4.4 valid=300s; -resolver_timeout 5s; diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/stream_access_log.conf b/docker/addons/vault/scripts/docker/nginx/snippets/stream_access_log.conf deleted file mode 100644 index 7e066120..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/stream_access_log.conf +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -log_format access_log_format '$protocol ' - '$remote_addr: ' - 'status=$status; upstream connect time=$upstream_connect_time'; -access_log access.log access_log_format; diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/verify-ssl-client.conf b/docker/addons/vault/scripts/docker/nginx/snippets/verify-ssl-client.conf deleted file mode 100644 index 991e1fb4..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/verify-ssl-client.conf +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -if ($ssl_client_verify != SUCCESS) { - return 403; -} -if ($auth_key = '') { - return 403; -} \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/nginx/snippets/ws-upgrade.conf b/docker/addons/vault/scripts/docker/nginx/snippets/ws-upgrade.conf deleted file mode 100644 index a2be04ed..00000000 --- a/docker/addons/vault/scripts/docker/nginx/snippets/ws-upgrade.conf +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -proxy_http_version 1.1; -proxy_set_header Upgrade $http_upgrade; -proxy_set_header Connection "Upgrade"; -proxy_connect_timeout 7d; -proxy_send_timeout 7d; -proxy_read_timeout 7d; \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/spicedb/schema.zed b/docker/addons/vault/scripts/docker/spicedb/schema.zed deleted file mode 100644 index 215797a9..00000000 --- a/docker/addons/vault/scripts/docker/spicedb/schema.zed +++ /dev/null @@ -1,78 +0,0 @@ -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 - relation member: user - relation guest: user - - 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 -} - -definition domain { - relation administrator: user // combination domain + user id - relation editor: user - relation contributor: user - relation member: user - relation guest: user - - relation platform: platform - - 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 -} - -definition platform { - relation administrator: user - relation member: user - - permission admin = administrator - permission membership = administrator + member -} diff --git a/docker/addons/vault/scripts/docker/ssl/.gitignore b/docker/addons/vault/scripts/docker/ssl/.gitignore deleted file mode 100644 index 9ea7050a..00000000 --- a/docker/addons/vault/scripts/docker/ssl/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -*grpc-server* -*grpc-client* -*srl -*conf diff --git a/docker/addons/vault/scripts/docker/ssl/Makefile b/docker/addons/vault/scripts/docker/ssl/Makefile deleted file mode 100644 index f0561b87..00000000 --- a/docker/addons/vault/scripts/docker/ssl/Makefile +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -CRT_LOCATION = certs -O = Magistrala -OU_CA = magistrala_ca -OU_CRT = magistrala_crt -EA = info@magistrala.com -CN_CA = Magistrala_Self_Signed_CA -CN_SRV = localhost -THING_SECRET = <THING_SECRET> # e.g. 8f65ed04-0770-4ce4-a291-6d1bf2000f4d -CRT_FILE_NAME = thing -THINGS_GRPC_SERVER_CONF_FILE_NAME=thing-grpc-server.conf -THINGS_GRPC_CLIENT_CONF_FILE_NAME=thing-grpc-client.conf -THINGS_GRPC_SERVER_CN=things -THINGS_GRPC_CLIENT_CN=things-client -THINGS_GRPC_SERVER_CRT_FILE_NAME=things-grpc-server -THINGS_GRPC_CLIENT_CRT_FILE_NAME=things-grpc-client -AUTH_GRPC_SERVER_CONF_FILE_NAME=auth-grpc-server.conf -AUTH_GRPC_CLIENT_CONF_FILE_NAME=auth-grpc-client.conf -AUTH_GRPC_SERVER_CN=auth -AUTH_GRPC_CLIENT_CN=auth-client -AUTH_GRPC_SERVER_CRT_FILE_NAME=auth-grpc-server -AUTH_GRPC_CLIENT_CRT_FILE_NAME=auth-grpc-client - -define GRPC_CERT_CONFIG -[req] -req_extensions = v3_req -distinguished_name = dn -prompt = no - -[dn] -CN = mg.svc -C = RS -ST = RS -L = BELGRADE -O = MAGISTRALA -OU = MAGISTRALA - -[v3_req] -subjectAltName = @alt_names - -[alt_names] -DNS.1 = <<SERVICE_NAME>> -endef - -define ANNOUNCE_BODY -Version $(VERSION) of $(PACKAGE_NAME) has been released. - -It can be downloaded from $(DOWNLOAD_URL). - -etc, etc. -endef -all: clean_certs ca server_cert things_grpc_certs auth_grpc_certs - -# CA name and key is "ca". -ca: - openssl req -newkey rsa:2048 -x509 -nodes -sha512 -days 1095 \ - -keyout $(CRT_LOCATION)/ca.key -out $(CRT_LOCATION)/ca.crt -subj "/CN=$(CN_CA)/O=$(O)/OU=$(OU_CA)/emailAddress=$(EA)" - -# Server cert and key name is "magistrala-server". -server_cert: - # Create magistrala server key and CSR. - openssl req -new -sha256 -newkey rsa:4096 -nodes -keyout $(CRT_LOCATION)/magistrala-server.key \ - -out $(CRT_LOCATION)/magistrala-server.csr -subj "/CN=$(CN_SRV)/O=$(O)/OU=$(OU_CRT)/emailAddress=$(EA)" - - # Sign server CSR. - openssl x509 -req -days 1000 -in $(CRT_LOCATION)/magistrala-server.csr -CA $(CRT_LOCATION)/ca.crt -CAkey $(CRT_LOCATION)/ca.key -CAcreateserial -out $(CRT_LOCATION)/magistrala-server.crt - - # Remove CSR. - rm $(CRT_LOCATION)/magistrala-server.csr - -thing_cert: - # Create magistrala server key and CSR. - openssl req -new -sha256 -newkey rsa:4096 -nodes -keyout $(CRT_LOCATION)/$(CRT_FILE_NAME).key \ - -out $(CRT_LOCATION)/$(CRT_FILE_NAME).csr -subj "/CN=$(THING_SECRET)/O=$(O)/OU=$(OU_CRT)/emailAddress=$(EA)" - - # Sign client CSR. - openssl x509 -req -days 730 -in $(CRT_LOCATION)/$(CRT_FILE_NAME).csr -CA $(CRT_LOCATION)/ca.crt -CAkey $(CRT_LOCATION)/ca.key -CAcreateserial -out $(CRT_LOCATION)/$(CRT_FILE_NAME).crt - - # Remove CSR. - rm $(CRT_LOCATION)/$(CRT_FILE_NAME).csr - -things_grpc_certs: - # Things server grpc certificates - $(file > $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).conf,$(subst <<SERVICE_NAME>>,$(THINGS_GRPC_SERVER_CN),$(GRPC_CERT_CONFIG)) ) - - openssl req -new -sha256 -newkey rsa:4096 -nodes \ - -keyout $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).key \ - -out $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).csr \ - -config $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).conf \ - -extensions v3_req - - openssl x509 -req -sha256 \ - -in $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).csr \ - -CA $(CRT_LOCATION)/ca.crt \ - -CAkey $(CRT_LOCATION)/ca.key \ - -CAcreateserial \ - -out $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).crt \ - -days 365 \ - -extfile $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).conf \ - -extensions v3_req - - rm -rf $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(THINGS_GRPC_SERVER_CRT_FILE_NAME).conf - # Things client grpc certificates - $(file > $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).conf,$(subst <<SERVICE_NAME>>,$(THINGS_GRPC_CLIENT_CN),$(GRPC_CERT_CONFIG)) ) - - openssl req -new -sha256 -newkey rsa:4096 -nodes \ - -keyout $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).key \ - -out $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).csr \ - -config $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).conf \ - -extensions v3_req - - openssl x509 -req -sha256 \ - -in $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).csr \ - -CA $(CRT_LOCATION)/ca.crt \ - -CAkey $(CRT_LOCATION)/ca.key \ - -CAcreateserial \ - -out $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).crt \ - -days 365 \ - -extfile $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).conf \ - -extensions v3_req - - rm -rf $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(THINGS_GRPC_CLIENT_CRT_FILE_NAME).conf - -auth_grpc_certs: - # Auth gRPC server certificate - $(file > $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).conf,$(subst <<SERVICE_NAME>>,$(AUTH_GRPC_SERVER_CN),$(GRPC_CERT_CONFIG)) ) - - openssl req -new -sha256 -newkey rsa:4096 -nodes \ - -keyout $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).key \ - -out $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).csr \ - -config $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).conf \ - -extensions v3_req - - openssl x509 -req -sha256 \ - -in $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).csr \ - -CA $(CRT_LOCATION)/ca.crt \ - -CAkey $(CRT_LOCATION)/ca.key \ - -CAcreateserial \ - -out $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).crt \ - -days 365 \ - -extfile $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).conf \ - -extensions v3_req - - rm -rf $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(AUTH_GRPC_SERVER_CRT_FILE_NAME).conf - # Auth gRPC client certificate - $(file > $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).conf,$(subst <<SERVICE_NAME>>,$(AUTH_GRPC_CLIENT_CN),$(GRPC_CERT_CONFIG)) ) - - openssl req -new -sha256 -newkey rsa:4096 -nodes \ - -keyout $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).key \ - -out $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).csr \ - -config $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).conf \ - -extensions v3_req - - openssl x509 -req -sha256 \ - -in $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).csr \ - -CA $(CRT_LOCATION)/ca.crt \ - -CAkey $(CRT_LOCATION)/ca.key \ - -CAcreateserial \ - -out $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).crt \ - -days 365 \ - -extfile $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).conf \ - -extensions v3_req - - rm -rf $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).csr $(CRT_LOCATION)/$(AUTH_GRPC_CLIENT_CRT_FILE_NAME).conf - -clean_certs: - rm -r $(CRT_LOCATION)/*.crt - rm -r $(CRT_LOCATION)/*.key diff --git a/docker/addons/vault/scripts/docker/ssl/authorization.js b/docker/addons/vault/scripts/docker/ssl/authorization.js deleted file mode 100644 index 5bfedbe9..00000000 --- a/docker/addons/vault/scripts/docker/ssl/authorization.js +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -var clientKey = ''; - -// Check certificate MQTTS. -function authenticate(s) { - if (!s.variables.ssl_client_s_dn || !s.variables.ssl_client_s_dn.length || - !s.variables.ssl_client_verify || s.variables.ssl_client_verify != "SUCCESS") { - s.deny(); - return; - } - - s.on('upload', function (data) { - if (data == '') { - return; - } - - var packet_type_flags_byte = data.codePointAt(0); - // First MQTT packet contain message type and flags. CONNECT message type - // is encoded as 0001, and we're not interested in flags, so only values - // 0001xxxx (which is between 16 and 32) should be checked. - if (packet_type_flags_byte < 16 || packet_type_flags_byte >= 32) { - s.off('upload'); - s.allow(); - return; - } - - if (clientKey === '') { - clientKey = parseCert(s.variables.ssl_client_s_dn, 'CN'); - } - - var pass = parsePackage(s, data); - - if (!clientKey.length || !clientKey.endsWith(pass) ) { - s.error('Cert CN (' + clientKey + ') does not contain client password'); - s.off('upload') - s.deny(); - return; - } - - s.off('upload'); - s.allow(); - }) -} - -function parsePackage(s, data) { - // An explanation of MQTT packet structure can be found here: - // https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#msg-format. - - // CONNECT message is explained here: - // https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect. - - /* - 0 1 2 3 - 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | TYPE | RSRVD | REMAINING LEN | PROTOCOL NAME LEN | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | PROTOCOL NAME | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| - | VERSION | FLAGS | KEEP ALIVE | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| - | Payload (if any) ... | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - First byte with remaining length represents fixed header. - Remaining Length is the length of the variable header (10 bytes) plus the length of the Payload. - It is encoded in the manner described here: - http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180836. - - Connect flags byte looks like this: - | 7 | 6 | 5 | 4 3 | 2 | 1 | 0 | - | Username Flag | Password Flag | Will Retain | Will QoS | Will Flag | Clean Session | Reserved | - - The payload is determined by the flags and comes in this order: - 1. Client ID (2 bytes length + ID value) - 2. Will Topic (2 bytes length + Will Topic value) if Will Flag is 1. - 3. Will Message (2 bytes length + Will Message value) if Will Flag is 1. - 4. User Name (2 bytes length + User Name value) if User Name Flag is 1. - 5. Password (2 bytes length + Password value) if Password Flag is 1. - - This method extracts Password field. - */ - - // Extract variable length header. It's 1-4 bytes. As long as continuation byte is - // 1, there are more bytes in this header. This algorithm is explained here: - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180836 - var len_size = 1; - for (var remaining_len = 1; remaining_len < 5; remaining_len++) { - if (data.codePointAt(remaining_len) > 128) { - len_size += 1; - continue; - } - break; - } - - // CONTROL(1) + MSG_LEN(1-4) + PROTO_NAME_LEN(2) + PROTO_NAME(4) + PROTO_VERSION(1) - var flags_pos = 1 + len_size + 2 + 4 + 1; - var flags = data.codePointAt(flags_pos); - - // If there are no username and password flags (11xxxxxx), return. - if (flags < 192) { - s.error('MQTT username or password not provided'); - return ''; - } - - // FLAGS(1) + KEEP_ALIVE(2) - var shift = flags_pos + 1 + 2; - - // Number of bytes to encode length. - var len_bytes_num = 2; - - // If Wil Flag is present, Will Topic and Will Message need to be skipped as well. - var shift_flags = 196 <= flags ? 5 : 3; - var len_msb, len_lsb, len; - - for (var i = 0; i < shift_flags; i++) { - len_msb = data.codePointAt(shift).toString(16); - len_lsb = data.codePointAt(shift + 1).toString(16); - len = calcLen(len_msb, len_lsb); - shift += len_bytes_num; - if (i != shift_flags - 1) { - shift += len; - } - } - - var password = data.substring(shift, shift + len); - return password; -} - -// Check certificate HTTPS and WSS. -function setKey(r) { - if (clientKey === '') { - clientKey = parseCert(r.variables.ssl_client_s_dn, 'CN'); - } - - var auth = r.headersIn['Authorization']; - if (auth && auth.length && auth != clientKey) { - r.error('Authorization header does not match certificate'); - return ''; - } - - if (r.uri.startsWith('/ws') && (!auth || !auth.length)) { - var a; - for (a in r.args) { - if (a == 'authorization' && r.args[a] === clientKey) { - return clientKey - } - } - - r.error('Authorization param does not match certificate') - return ''; - } - - return clientKey; -} - -function calcLen(msb, lsb) { - if (lsb < 2) { - lsb = '0' + lsb; - } - - return parseInt(msb + lsb, 16); -} - -function parseCert(cert, key) { - if (cert.length) { - var pairs = cert.split(','); - for (var i = 0; i < pairs.length; i++) { - var pair = pairs[i].split('='); - if (pair[0].toUpperCase() == key) { - return "Thing " + pair[1].replace("\\", "").trim(); - } - } - } - - return ''; -} - -export default {setKey,authenticate}; diff --git a/docker/addons/vault/scripts/docker/ssl/certs/ca.crt b/docker/addons/vault/scripts/docker/ssl/certs/ca.crt deleted file mode 100644 index 34f07283..00000000 --- a/docker/addons/vault/scripts/docker/ssl/certs/ca.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDyzCCArOgAwIBAgIUDIJg63dQVzoD9nmWi9YPscQwTgIwDQYJKoZIhvcNAQEN -BQAwdTEiMCAGA1UEAwwZTWFnaXN0cmFsYV9TZWxmX1NpZ25lZF9DQTETMBEGA1UE -CgwKTWFnaXN0cmFsYTEWMBQGA1UECwwNbWFnaXN0cmFsYV9jYTEiMCAGCSqGSIb3 -DQEJARYTaW5mb0BtYWdpc3RyYWxhLmNvbTAeFw0yMzEwMzAwODE5MDFaFw0yNjEw -MjkwODE5MDFaMHUxIjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0Ex -EzARBgNVBAoMCk1hZ2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAg -BgkqhkiG9w0BCQEWE2luZm9AbWFnaXN0cmFsYS5jb20wggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCWNIeGfo/SePOvviJE6UHJhBzWcPfNVbzSF6A42WgB -DEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7xCgxcqFwEo33SyhAivwoHL2pRVHXn -oee3z9U757T63YLE0qrXQY2cbyChX/OU99rZxyd5l5jUGN7MCu+RYurfTIiYN+Uv -NZdl8a3X84g7fa70EOYas7cTunWUt9x64/jYDoYmn+XPXET1yEU1dQTnKY4cRjhv -HS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknhFHTu8PVPxfowrVv/xzmxOe0zSZFd -SbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW91WzOLS9AgMBAAGjUzBRMB0GA1Ud -DgQWBBQkE4koZctEZpTz9pq6a6s6xg+myTAfBgNVHSMEGDAWgBQkE4koZctEZpTz -9pq6a6s6xg+myTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQA7 -w/oh5U9loJsigf3X3T3jQM8PVmhsUfNMJ3kc1Yumr72S4sGKjdWwuU0vk+B3eQzh -zXAj65BHhs1pXcukeoLR7YcHABEsEMg6lar/E4A+MgAZfZFVSvPpsByIK8I5ARk+ -K1V/lWso+GJJM/lImPPnpvUWBdbntqC5WtjoMMGL9uyV3kVS6yT/kJ2ercnPzhPh -uBkL1ZH3ivDn/0JDY+T8Sfeq08vNWaTcoC7qpPwqXhuT0ytY7oaBS5wmPcvvzpZg -6zZYPZfhjhdEFYY1hDrrPYNYO72jncUnwQVp3X0DQpSvbxp681hVkcEtwHB2B8l0 -tBGhgoH+TqZs0AUjoXM0 ------END CERTIFICATE----- diff --git a/docker/addons/vault/scripts/docker/ssl/certs/ca.key b/docker/addons/vault/scripts/docker/ssl/certs/ca.key deleted file mode 100644 index 0ba786be..00000000 --- a/docker/addons/vault/scripts/docker/ssl/certs/ca.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWNIeGfo/SePOv -viJE6UHJhBzWcPfNVbzSF6A42WgBDEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7x -CgxcqFwEo33SyhAivwoHL2pRVHXnoee3z9U757T63YLE0qrXQY2cbyChX/OU99rZ -xyd5l5jUGN7MCu+RYurfTIiYN+UvNZdl8a3X84g7fa70EOYas7cTunWUt9x64/jY -DoYmn+XPXET1yEU1dQTnKY4cRjhvHS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknh -FHTu8PVPxfowrVv/xzmxOe0zSZFdSbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW -91WzOLS9AgMBAAECggEAEOxEq6jFO/WgIPgHROPR42ok1J1AMgx7nGEIjnciImIX -mJYBAtlOM+oUAYKoFBh/2eQTSyN2t4jo5AvZhjP6wBQKeE4HQN7supADRrwBF7KU -WI+MKvZpW81KrzG8CUoLsikMEFpu52UAbYJkZmznzVeq/GqsAKGYLEXjauD7S5Tu -GeGVKO4novus6t3AHnBvfalIQ1JUuJFvcd5ZDhPljlzPbbWdM4WpRPaFZIKmfXft -G7Izt58yPCYwhxohjrunRudyX3oKvmCBUOBXC8HdHzND/dLxwlrVu7OjmXprmC6P -8ggNpjAPeO8Y6+EKGne1fETNsKgODY/lXGOwECY4eQKBgQDSGi3WuoT/+DecVeSF -GfmavdGCQKOD0kdl7qCeQYAL+SPVz4157AtxZs3idapvlbrc7wvw4Ev1XT7ZmWUj -Lc4/UAITR8EkkFRVbxt2PvV86AiQtmXFguTNEX5vTszRwZ2+eqijZga5niBkqyAi -SRuTwR8WrDZau4mRNnF8bUl8dQKBgQC3BKYifRp4hHqBycHe9rSMZ8Xz+ZOy+IFA -vYap1Az+e8KuqlmD9Kfpp2Mjba1+HL5WKeTJGpFE7bhvb/xMPJgbMgtQ/cw4uDJ/ -fwv4m6arf76ebOhaZtkT1vD4NyiyB+z6xP0TRgQRr2Or98XBSvGAYDXIn5vL7fUg -KrDF0ePuKQKBgDfaOcFRiDW7uJzYwI0ZoJ8gQufLYyyR4+UXEJ/BbdbA/mPCbyuw -MkKNP8Ip4YsUVL6S1avNFKQ/i4uxGY/Gh4ORM1wIwTGFJMYpaTV/+yafUFeYBWoC -J+zT77aLTiucuuB+HwKBBtylSps4WqyCntAikK8oTLLGFAYEYRrgup5ZAoGAbQ8j -JNghxwFCs0aT9ZZTfnt0NW9auUJmWzrVHSxUVe1P1J+EWiKXUJ/DbuAzizv7nAK4 -57GiMU3rItS7pn5RMZt/rNKgOIhi5yDA9HNkPTwRTfyd9QjmgHEMBQ1xfa1FZSWv -nSWS1SsLnPU37XgIMzShuByMTVhOQs3NqwPo7AkCgYAf8AzQNjFCoTwU3SJezJ4H -9j1jvMO232hAl8UDNtqvJ1APn87tOtnfX48OMoRrP9kKI0oygE3pq7rFxu1qmTns -Zir0+KLeWGg58fSZkUEAp6kbO5CKwoeVAY9EMgd7BYBqlXLqUNfdH0L+KUOFKHha -7e82VxpgBeskzAqN1e7YRA== ------END PRIVATE KEY----- diff --git a/docker/addons/vault/scripts/docker/ssl/certs/magistrala-server.crt b/docker/addons/vault/scripts/docker/ssl/certs/magistrala-server.crt deleted file mode 100644 index 4e893c1e..00000000 --- a/docker/addons/vault/scripts/docker/ssl/certs/magistrala-server.crt +++ /dev/null @@ -1,26 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEYjCCA0oCFGXr7rfGAynaa4KMTG1+23EEF0lYMA0GCSqGSIb3DQEBCwUAMHUx -IjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0ExEzARBgNVBAoMCk1h -Z2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAgBgkqhkiG9w0BCQEW -E2luZm9AbWFnaXN0cmFsYS5jb20wHhcNMjMxMDMwMDgxOTA4WhcNMjYwNzI2MDgx -OTA4WjBmMRIwEAYDVQQDDAlsb2NhbGhvc3QxEzARBgNVBAoMCk1hZ2lzdHJhbGEx -FzAVBgNVBAsMDm1hZ2lzdHJhbGFfY3J0MSIwIAYJKoZIhvcNAQkBFhNpbmZvQG1h -Z2lzdHJhbGEuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAojas -t6M294uS5q8oFmYM6DULVQ1lY3K659VusJshjGvn8bi50vhKo8PpxL6ygVpjWcHG -+/gclQnTaYZumC1TUohibpBnrFx1PZUvGiryAPudFY2nC5af5BQnYGi845FcVWx5 -FNLq+IsedgSZf7FuGcZruXiukBCWVyWJRJh+8FDakc65BPeG9FpCxbeLZ1nrDpnQ -bhHbwEQrwwHk0FHZ/3cuVFJAjwqJSivJ9598eU0YWAsqsLM3uYyvOMd8alMs5vCZ -9tMCpO2v6xTdJ6kr68SwQQAiefRy6gsD5J5A4ySyCz7KX9fHCrqx1kdcDJ/CXZmh -mXxrCFKSjqjuSn2qtm+gxvAc26Zbt5z5eihpdISDUKrjW11+yapNZLATGBX8ktek -gW467V9DQYOsbA3fNkWgd5UcV5HIViUpqFMFvi1NpWc2INi/PTDWuAIBLUiVNk0W -qMtG7/HqFRPn6MrNGpvFpglgxXGNfjsggkK/3INtFnAou2rN9+ieeuzO7Zjrtwsq -sP64GVw/vLv3tgT6TIZmDnCDCqtEGEVutt7ldu3M0/fLm4qOUsZqFGrIOO1cfI4x -7FRnHwaTsTB1Og+I7lEujb4efHV+uRjKyrGh6L6hDt94IkGm6ZEj5z/iEmq16jRX -dUbYsu4f1KlfTYdHWGHp+6kAmDn0jGCwz2BBrnsCAwEAATANBgkqhkiG9w0BAQsF -AAOCAQEAKyg5kvDk+TQ6ZDCK7qxKY+uN9setYvvsLfde+Uy51a3zj8RIHRgkOT2C -LuuTtTYKu3XmfCKId0oTXynGuP+yDAIuVwuZz3S0VmA8ijoZ87LJXzsLjjTjQSzZ -ar6RmlRDH+8Bm4AOrT4TDupqifag4J0msHkNPo0jVK6fnuniqJoSlhIbbHrJTHhv -jKNXrThjr/irgg1MZ7slojieOS0QoZHRE9eunIR5enDJwB5pWUJSmZWlisI7+Ibi -06+j8wZegU0nqeWp4wFSZxKnrzz5B5Qu9SrALwlHWirzBpyr0gAcF2v7nzbWviZ/ -0VMyY4FGEbkp6trMxwJs5hGYhAiyXg== ------END CERTIFICATE----- diff --git a/docker/addons/vault/scripts/docker/ssl/certs/magistrala-server.key b/docker/addons/vault/scripts/docker/ssl/certs/magistrala-server.key deleted file mode 100644 index f2b56f41..00000000 --- a/docker/addons/vault/scripts/docker/ssl/certs/magistrala-server.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCiNqy3ozb3i5Lm -rygWZgzoNQtVDWVjcrrn1W6wmyGMa+fxuLnS+Eqjw+nEvrKBWmNZwcb7+ByVCdNp -hm6YLVNSiGJukGesXHU9lS8aKvIA+50VjacLlp/kFCdgaLzjkVxVbHkU0ur4ix52 -BJl/sW4Zxmu5eK6QEJZXJYlEmH7wUNqRzrkE94b0WkLFt4tnWesOmdBuEdvARCvD -AeTQUdn/dy5UUkCPColKK8n3n3x5TRhYCyqwsze5jK84x3xqUyzm8Jn20wKk7a/r -FN0nqSvrxLBBACJ59HLqCwPknkDjJLILPspf18cKurHWR1wMn8JdmaGZfGsIUpKO -qO5Kfaq2b6DG8Bzbplu3nPl6KGl0hINQquNbXX7Jqk1ksBMYFfyS16SBbjrtX0NB -g6xsDd82RaB3lRxXkchWJSmoUwW+LU2lZzYg2L89MNa4AgEtSJU2TRaoy0bv8eoV -E+foys0am8WmCWDFcY1+OyCCQr/cg20WcCi7as336J567M7tmOu3Cyqw/rgZXD+8 -u/e2BPpMhmYOcIMKq0QYRW623uV27czT98ubio5SxmoUasg47Vx8jjHsVGcfBpOx -MHU6D4juUS6Nvh58dX65GMrKsaHovqEO33giQabpkSPnP+ISarXqNFd1Rtiy7h/U -qV9Nh0dYYen7qQCYOfSMYLDPYEGuewIDAQABAoICACvgzTyJTkOMwipbQ+U3KpOf -UZbqnjvV23/9iEkGVX9V6vJETSOnnQ0KYBAjo0aBLDGpzIj41sZr13+KaR0J2amQ -EcwljJ2fjukfExQpfLfOV/HuFLr6Pfrkhrg57KpD9i13P5Nl8EBV5WH4IYtcc9NO -DHKpldKLYhdlpGllNKUNwenB+ONCj4NGbRxtZyyIMqCK88nqU76A0jOYLgw5r9W+ -J86QRz1KFNP231V3kyR+ubCLKLuOZuruhrE9qMZcBF/dwk/1SRhS4QyeYqopRSOr -2x9iCXFisbjkTOPI+PVYRj7rd7OQOxuIX7V+LQSPLHTEK2XItW0VZOZpBLgqoQP1 -Eu19LOOs77DI5FBia1qhSpjjVGOE6koQmCki8KSFZM+CzuflTPkWNVvTNzjKrhUj -Rbezx40VVFt+q38bsTjWJbimMSo1jChianwjtotGnGpC6pD0KnHsBmfceWaL7+eC -n9KtSeAbnXlFN/rHdK7ZeP/PTSjHa+6i1awGZxhwdVsERJy/2xwZzh3uMLS2ZhXM -Tuh1D5GzlUlkMP8K23rfaXnaOXkwYxHFGi23NmxHGSqzA3TVVreWLqRSZJd/Ar67 -9Pl4S9p9f+Xkvq8tQANfoaTbjc//dpK8rjCKnwdWA3cL7eekq9sm4+lTmik9Bn2v -Bo+3/89Fr1FvlkuQvktJAoIBAQDNuc2r/9sthHZg1hOCFd5XmnMX/mXNPs+SDPRW -/VZBHjxGApz+CoZS7qk0q7f/vzYFTB6N3778f7RsgwrZYSD4I4jumvSFNFsxsHCY -K3O4kkd2YaFaZPwUYbbAcBr6nVnW/9b1aagEfWIMQ18FHLaQ6u2OfUOcNDGZEqwj -YqJmZr8plhWLeKP2c673j6g/ztnL0w77y3LnIuLjFGex17l1lQzbUgOPSKyoQj03 -d5eRoJv2aQTaOXaBzGrDtBDDd3BpXrriJEMqSZbZFRLM28jD+VuHjfHOZRUMy1hw -vZCifRrBYA6Frko7ZweRxIkcOwQsQjV/tkzVkg9FHrVhMKQTAoIBAQDJ2r+lR73d -va1JjWoXKe5qAWtprRyI8DpJM/G2/V/V3+RVOGgBeRlu6WDiMpMd9hFB6bAmX+1y -S17svw1f4DQskkTKi9EWBsWRnh2Pnd4q91TjKFsBuci8/EtAXb7C0KV5nEtasEUJ -klMmO1evAXMhn7VzmE3Ic/ttcQHxQZ+TC4G5dGsYcideJ5zOeEIATtFypDNG/0Bw -rvmBbIIylY2KwUAx3UexRgH1hRSecTzkokT39WJbefUg952h7yZXrrhb71AfWLTC -A5MJeArqPK6z/RMxDyvnk7xW326dtBBgqYyTOIHCANRB1kAG0xEyia/WI94uyNfH -YfIHglDFGIj5AoIBAEVVNEqeXPi3Jso1+7cgtaFijR1uAFMusvfu474ZfSNPFFMn -+E7pryFuC5qTsNxBTex1HesEmDIyu9TCSTq/sEPQfgqkMHpgDcfuRdQS+NogenMc -Livv0sDvuY6beYwy0Z9S89gbtqNkulGVtwVbCvBGLK+T6eBP+tMy5s66JC9Mu2pB -iZtKmj+p9zK5uKNgjChURj138I6TRFHxg4z9PiSxifa0ajy06nN+d3ElHfDXZxih -hiAhs53FDcpM+kVWEI2CfotOW1B6IpugrYhbHgtmE4HYxcCgcnqwYWsFiCQq84Ru -YhaNibkBXRy0Vt0rypk76xnSj4x+wCS0V76cjP8CggEAHXdoaJlLdzY8OLODHDSL -0D+6zWdu9fKTn6IMlBjyx4byjxo33JcwBkfdU8fsQABuzn9trnxsbjXgepD9Q9S3 -6RXFIwg8EooUh0hcql1yVDVc1/hJKLxVOHlgBtpogYnxzgnp2ihHO7l3l+orx6lf -hDYLR/+gwzVjK7vGe9CHmfChFFCRXbU0WANSWbWmdOMMoj6kGaYjYw+37pPHgdjh -G7NQSrcxwwgkOxIdS2/eYsXpaYURwabRCOn8wenmYABqe0k5GgpaAMSCz2wNs9n9 -6tpz1cKQNzMS2F+vhygFCAdYNRmXn5l9YssC97wSE52T5J/BzHSXQ0ziBwSYA92s -CQKCAQAFPujh1HhOBtn3FOT3I2jNSTv9OJsmAeiFrhVfIw+Ij8XzzUf0aV04Et/R -/EetirP6WjNQuJ5/YYVUFWj07vSl20YP7NtDGFUlvWugJUvQByidHt5DkmehBWax -cfp5LWwZ4W/wm4F/DtPkgEXgEwY/TMXHvhvN6+JaQPO7iemWL7qsRAPea0oDLkMm -0phT3hKgcnbyewH6GU53KQgr2hUzhgGOKibAo+4ud9lY6M/X1axCepetKMl78Cz9 -rK2MgJOhDr6Nu/K2bKL8Q3zSB1n1WRNaTVnH6wY4j/FpeQvVv+qTAbZhJm7cRT5m -+C7JCqJGg66liqIMq6YyYXK//Ddl ------END PRIVATE KEY----- diff --git a/docker/addons/vault/scripts/docker/ssl/dhparam.pem b/docker/addons/vault/scripts/docker/ssl/dhparam.pem deleted file mode 100644 index e0f2ebb7..00000000 --- a/docker/addons/vault/scripts/docker/ssl/dhparam.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEAquN8NRcSdLOM9RiumqWH8Jw3CGVR/eQQeq+jvT3zpxlUQPAMExQb -MRCspm1oRgDWGvch3Z4zfMmBZyzKJA4BDTh4USzcE5zvnx8aUcUPZPQpwSicKgzb -QGnl0Xf/75GAWrwhxn8GNyMP29wrpcd1Qg8fEQ3HAW1fCd9girKMKY9aBaHli/h2 -R9Rd/KTbeqN88aoMjUvZHooIIZXu0A+kyulOajYQO4k3Sp6CBqv0FFcoLQnYNH13 -kMUE5qJ68U732HybTw8sofTCOxKcCfM2kVP7dVoF3prlGjUw3z3l3STY8vuTdq0B -R7PslkoQHNmqcL+2gouoWP3GI+IeRzGSSwIBAg== ------END DH PARAMETERS----- diff --git a/docker/addons/vault/scripts/docker/templates/smtp-notifier.tmpl b/docker/addons/vault/scripts/docker/templates/smtp-notifier.tmpl deleted file mode 100644 index 64caa944..00000000 --- a/docker/addons/vault/scripts/docker/templates/smtp-notifier.tmpl +++ /dev/null @@ -1,8 +0,0 @@ -To: {{range $index, $v := .To}}{{if $index}},{{end}}{{$v}}{{end}} -From: {{.From}} -Subject: {{.Subject}} -{{.Header}} -You have a new message: -{{.Content}} -{{.Footer}} - diff --git a/docker/addons/vault/scripts/docker/templates/users.tmpl b/docker/addons/vault/scripts/docker/templates/users.tmpl deleted file mode 100644 index 642dae74..00000000 --- a/docker/addons/vault/scripts/docker/templates/users.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -Dear {{.User}}, - -We have received a request to reset your password for your account on {{.Host}}. To proceed with resetting your password, please click on the link below: - -{{.Content}} - -If you did not initiate this request, please disregard this message and your password will remain unchanged. - -Thank you for using {{.Host}}. - -Best regards, - -{{.Footer}} diff --git a/docker/addons/vault/scripts/docker/vernemq/Dockerfile b/docker/addons/vault/scripts/docker/vernemq/Dockerfile deleted file mode 100644 index 76152b1f..00000000 --- a/docker/addons/vault/scripts/docker/vernemq/Dockerfile +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# Builder -FROM erlang:25.3.2.8-alpine AS builder -RUN apk add --update git build-base bsd-compat-headers openssl-dev snappy-dev curl \ - && git clone -b 1.13.0 https://github.com/vernemq/vernemq \ - && cd vernemq \ - && make -j 16 rel - -# Executor -FROM alpine:3.19 - -COPY --from=builder /vernemq/_build/default/rel / - -RUN apk --no-cache --update --available upgrade && \ - apk add --no-cache ncurses-libs openssl libstdc++ jq curl bash snappy-dev && \ - addgroup --gid 10000 vernemq && \ - adduser --uid 10000 -H -D -G vernemq -h /vernemq vernemq && \ - install -d -o vernemq -g vernemq /vernemq - -# Defaults -ENV DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR="app=vernemq" \ - DOCKER_VERNEMQ_LOG__CONSOLE=console \ - PATH="/vernemq/bin:$PATH" \ - VERNEMQ_VERSION="1.13.0" - -WORKDIR /vernemq - -COPY --chown=10000:10000 bin/vernemq.sh /usr/sbin/start_vernemq -COPY --chown=10000:10000 files/vm.args /vernemq/etc/vm.args - -RUN chown -R 10000:10000 /vernemq && \ - ln -s /vernemq/etc /etc/vernemq && \ - ln -s /vernemq/data /var/lib/vernemq && \ - ln -s /vernemq/log /var/log/vernemq - -# Ports -# 1883 MQTT -# 8883 MQTT/SSL -# 8080 MQTT WebSockets -# 44053 VerneMQ Message Distribution -# 4369 EPMD - Erlang Port Mapper Daemon -# 8888 Health, API, Prometheus Metrics -# 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 Specific Distributed Erlang Port Range - -EXPOSE 1883 8883 8080 44053 4369 8888 \ - 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 - - -VOLUME ["/vernemq/log", "/vernemq/data", "/vernemq/etc"] - -HEALTHCHECK CMD vernemq ping | grep -q pong - -USER vernemq -CMD ["start_vernemq"] \ No newline at end of file diff --git a/docker/addons/vault/scripts/docker/vernemq/bin/vernemq.sh b/docker/addons/vault/scripts/docker/vernemq/bin/vernemq.sh deleted file mode 100755 index 4c990daf..00000000 --- a/docker/addons/vault/scripts/docker/vernemq/bin/vernemq.sh +++ /dev/null @@ -1,352 +0,0 @@ -#!/usr/bin/env sh - -NET_INTERFACE=$(route | grep '^default' | grep -o '[^ ]*$') -NET_INTERFACE=${DOCKER_NET_INTERFACE:-${NET_INTERFACE}} -IP_ADDRESS=$(ip -4 addr show ${NET_INTERFACE} | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sed -e "s/^[[:space:]]*//" | head -n 1) -IP_ADDRESS=${DOCKER_IP_ADDRESS:-${IP_ADDRESS}} - -VERNEMQ_ETC_DIR="/vernemq/etc" -VERNEMQ_VM_ARGS_FILE="${VERNEMQ_ETC_DIR}/vm.args" -VERNEMQ_CONF_FILE="${VERNEMQ_ETC_DIR}/vernemq.conf" -VERNEMQ_CONF_LOCAL_FILE="${VERNEMQ_ETC_DIR}/vernemq.conf.local" - -SECRETS_KUBERNETES_DIR="/var/run/secrets/kubernetes.io/serviceaccount" - -# Function to check istio readiness -istio_health() { - cmd=$(curl -s http://localhost:15021/healthz/ready > /dev/null) - status=$? - return $status -} - -# Ensure we have all files and needed directory write permissions -if [ ! -d ${VERNEMQ_ETC_DIR} ]; then - echo "Configuration directory at ${VERNEMQ_ETC_DIR} does not exist, exiting" >&2 - exit 1 -fi -if [ ! -f ${VERNEMQ_VM_ARGS_FILE} ]; then - echo "ls -l ${VERNEMQ_ETC_DIR}" - ls -l ${VERNEMQ_ETC_DIR} - echo "###" >&2 - echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} does not exist, exiting" >&2 - echo "###" >&2 - exit 1 -fi -if [ ! -w ${VERNEMQ_VM_ARGS_FILE} ]; then - echo "# whoami" - whoami - echo "# ls -l ${VERNEMQ_ETC_DIR}" - ls -l ${VERNEMQ_ETC_DIR} - echo "###" >&2 - echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} exists, but there are no write permissions! Exiting." >&2 - echo "###" >&2 - exit 1 -fi -if [ ! -s ${VERNEMQ_VM_ARGS_FILE} ]; then - echo "ls -l ${VERNEMQ_ETC_DIR}" - ls -l ${VERNEMQ_ETC_DIR} - echo "###" >&2 - echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} is empty! This will not work." >&2 - echo "### Exiting now." >&2 - echo "###" >&2 - exit 1 -fi - -# Ensure the Erlang node name is set correctly -if env | grep "DOCKER_VERNEMQ_NODENAME" -q; then - sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${DOCKER_VERNEMQ_NODENAME}/" ${VERNEMQ_VM_ARGS_FILE} -else - if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then - NODENAME=$(hostname -i) - sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${NODENAME}/" ${VERNEMQ_VM_ARGS_FILE} - else - sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${IP_ADDRESS}/" ${VERNEMQ_VM_ARGS_FILE} - fi -fi - -if env | grep "DOCKER_VERNEMQ_DISCOVERY_NODE" -q; then - discovery_node=$DOCKER_VERNEMQ_DISCOVERY_NODE - if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then - tmp='' - while [[ -z "$tmp" ]]; do - tmp=$(getent hosts tasks.$discovery_node | awk '{print $1}' | head -n 1) - sleep 1 - done - discovery_node=$tmp - fi - if [ -n "$DOCKER_VERNEMQ_COMPOSE" ]; then - tmp='' - while [[ -z "$tmp" ]]; do - tmp=$(getent hosts $discovery_node | awk '{print $1}' | head -n 1) - sleep 1 - done - discovery_node=$tmp - fi - - sed -i.bak -r "/-eval.+/d" ${VERNEMQ_VM_ARGS_FILE} - echo "-eval \"vmq_server_cmd:node_join('VerneMQ@$discovery_node')\"" >> ${VERNEMQ_VM_ARGS_FILE} -fi - -# If you encounter "SSL certification error (subject name does not match the host name)", you may try to set DOCKER_VERNEMQ_KUBERNETES_INSECURE to "1". -insecure="" -if env | grep "DOCKER_VERNEMQ_KUBERNETES_INSECURE" -q; then - echo "Using curl with \"--insecure\" argument to access kubernetes API without matching SSL certificate" - insecure="--insecure" -fi - -if env | grep "DOCKER_VERNEMQ_KUBERNETES_ISTIO_ENABLED" -q; then - istio_health - while [ $status != 0 ]; do - istio_health - sleep 1 - done - echo "Istio ready" -fi - -# Function to call a HTTP GET request on the given URL Path, using the hostname -# of the current k8s cluster name. Usage: "k8sCurlGet /my/path" -function k8sCurlGet () { - local urlPath=$1 - - local hostname="kubernetes.default.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" - local certsFile="${SECRETS_KUBERNETES_DIR}/ca.crt" - local token=$(cat ${SECRETS_KUBERNETES_DIR}/token) - local header="Authorization: Bearer ${token}" - local url="https://${hostname}/${urlPath}" - - curl -sS ${insecure} --cacert ${certsFile} -H "${header}" ${url} \ - || ( echo "### Error on accessing URL ${url}" ) -} - -DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME=${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME:-cluster.local} -if [ -d "${SECRETS_KUBERNETES_DIR}" ] ; then - # Let's get the namespace if it isn't set - DOCKER_VERNEMQ_KUBERNETES_NAMESPACE=${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE:-$(cat "${SECRETS_KUBERNETES_DIR}/namespace")} - - # Check the API access that will be needed in the TERM signal handler - podResponse=$(k8sCurlGet api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods/$(hostname) ) - statefulSetName=$(echo ${podResponse} | jq -r '.metadata.ownerReferences[0].name') - statefulSetPath="apis/apps/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/statefulsets/${statefulSetName}" - statefulSetResponse=$(k8sCurlGet ${statefulSetPath} ) - isCodeForbidden=$(echo ${statefulSetResponse} | jq '.code == 403') - if [[ ${isCodeForbidden} == "true" ]]; then - echo "Permission error: Cannot access URL ${statefulSetPath}: $(echo ${statefulSetResponse} | jq '.reason,.code,.message')" - exit 1 - else - numReplicas=$(echo ${statefulSetResponse} | jq '.status.replicas') - echo "Permissions ok: Our pod $(hostname) belongs to StatefulSet ${statefulSetName} with ${numReplicas} replicas" - fi -fi - -# Set up kubernetes node discovery -start_join_cluster=0 -if env | grep "DOCKER_VERNEMQ_DISCOVERY_KUBERNETES" -q; then - # Let's set our nodename correctly - # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#list-pod-v1-core - podList=$(k8sCurlGet "api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods?labelSelector=${DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR}") - VERNEMQ_KUBERNETES_SUBDOMAIN=${DOCKER_VERNEMQ_KUBERNETES_SUBDOMAIN:-$(echo ${podList} | jq '.items[0].spec.subdomain' | tr '\n' '"' | sed 's/"//g')} - if [[ $VERNEMQ_KUBERNETES_SUBDOMAIN == "null" ]]; then - VERNEMQ_KUBERNETES_HOSTNAME=${MY_POD_NAME}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME} - else - VERNEMQ_KUBERNETES_HOSTNAME=${MY_POD_NAME}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME} - fi - - sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${VERNEMQ_KUBERNETES_HOSTNAME}/" ${VERNEMQ_VM_ARGS_FILE} - # Hack into K8S DNS resolution (temporarily) - kube_pod_names=$(echo ${podList} | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ' | sed 's/ *$//') - - for kube_pod_name in $kube_pod_names; do - if [[ $kube_pod_name == "null" ]]; then - echo "Kubernetes discovery selected, but no pods found. Maybe we're the first?" - echo "Anyway, we won't attempt to join any cluster." - break - fi - if [[ $kube_pod_name != $MY_POD_NAME ]]; then - discoveryHostname="${kube_pod_name}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" - start_join_cluster=1 - echo "Will join an existing Kubernetes cluster with discovery node at ${discoveryHostname}" - echo "-eval \"vmq_server_cmd:node_join('VerneMQ@${discoveryHostname}')\"" >> ${VERNEMQ_VM_ARGS_FILE} - echo "Did I previously leave the cluster? If so, purging old state." - curl -fsSL http://${discoveryHostname}:8888/status.json >/dev/null 2>&1 || - (echo "Can't download status.json, better to exit now" && exit 1) - curl -fsSL http://${discoveryHostname}:8888/status.json | grep -q ${VERNEMQ_KUBERNETES_HOSTNAME} || - (echo "Cluster doesn't know about me, this means I've left previously. Purging old state..." && rm -rf /vernemq/data/*) - break - fi - done -fi - -if [ -f "${VERNEMQ_CONF_LOCAL_FILE}" ]; then - cp "${VERNEMQ_CONF_LOCAL_FILE}" ${VERNEMQ_CONF_FILE} - sed -i -r "s/###IPADDRESS###/${IP_ADDRESS}/" ${VERNEMQ_CONF_FILE} -else - sed -i '/########## Start ##########/,/########## End ##########/d' ${VERNEMQ_CONF_FILE} - - echo "########## Start ##########" >> ${VERNEMQ_CONF_FILE} - - env | grep DOCKER_VERNEMQ | grep -v 'DISCOVERY_NODE\|KUBERNETES\|SWARM\|COMPOSE\|DOCKER_VERNEMQ_USER' | cut -c 16- | awk '{match($0,/^[A-Z0-9_]*/)}{print tolower(substr($0,RSTART,RLENGTH)) substr($0,RLENGTH+1)}' | sed 's/__/./g' >> ${VERNEMQ_CONF_FILE} - - users_are_set=$(env | grep DOCKER_VERNEMQ_USER) - if [ ! -z "$users_are_set" ]; then - echo "vmq_passwd.password_file = /vernemq/etc/vmq.passwd" >> ${VERNEMQ_CONF_FILE} - touch /vernemq/etc/vmq.passwd - fi - - for vernemq_user in $(env | grep DOCKER_VERNEMQ_USER); do - username=$(echo $vernemq_user | awk -F '=' '{ print $1 }' | sed 's/DOCKER_VERNEMQ_USER_//g' | tr '[:upper:]' '[:lower:]') - password=$(echo $vernemq_user | awk -F '=' '{ print $2 }') - /vernemq/bin/vmq-passwd /vernemq/etc/vmq.passwd $username <<EOF -$password -$password -EOF - done - - if [ -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION__PORT_RANGE__MINIMUM" ]; then - echo "erlang.distribution.port_range.minimum = 9100" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION__PORT_RANGE__MAXIMUM" ]; then - echo "erlang.distribution.port_range.maximum = 9109" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_LISTENER__TCP__DEFAULT" ]; then - echo "listener.tcp.default = ${IP_ADDRESS}:1883" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_LISTENER__WS__DEFAULT" ]; then - echo "listener.ws.default = ${IP_ADDRESS}:8080" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_LISTENER__VMQ__CLUSTERING" ]; then - echo "listener.vmq.clustering = ${IP_ADDRESS}:44053" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_LISTENER__HTTP__METRICS" ]; then - echo "listener.http.metrics = ${IP_ADDRESS}:8888" >> ${VERNEMQ_CONF_FILE} - fi - - echo "########## End ##########" >> ${VERNEMQ_CONF_FILE} -fi - -if [ ! -z "$DOCKER_VERNEMQ_ERLANG__MAX_PORTS" ]; then - sed -i.bak -r "s/\+Q.+/\+Q ${DOCKER_VERNEMQ_ERLANG__MAX_PORTS}/" ${VERNEMQ_VM_ARGS_FILE} -fi - -if [ ! -z "$DOCKER_VERNEMQ_ERLANG__PROCESS_LIMIT" ]; then - sed -i.bak -r "s/\+P.+/\+P ${DOCKER_VERNEMQ_ERLANG__PROCESS_LIMIT}/" ${VERNEMQ_VM_ARGS_FILE} -fi - -if [ ! -z "$DOCKER_VERNEMQ_ERLANG__MAX_ETS_TABLES" ]; then - sed -i.bak -r "s/\+e.+/\+e ${DOCKER_VERNEMQ_ERLANG__MAX_ETS_TABLES}/" ${VERNEMQ_VM_ARGS_FILE} -fi - -if [ ! -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION_BUFFER_SIZE" ]; then - sed -i.bak -r "s/\+zdbbl.+/\+zdbbl ${DOCKER_VERNEMQ_ERLANG__DISTRIBUTION_BUFFER_SIZE}/" ${VERNEMQ_VM_ARGS_FILE} -fi - -# Check configuration file -/vernemq/bin/vernemq config generate 2>&1 > /dev/null | tee /tmp/config.out | grep error - -if [ $? -ne 1 ]; then - echo "configuration error, exit" - echo "$(cat /tmp/config.out)" - exit $? -fi - -pid=0 - -# SIGUSR1-handler -siguser1_handler() { - echo "stopped" -} - -# SIGTERM-handler -sigterm_handler() { - if [ $pid -ne 0 ]; then - if [ -d "${SECRETS_KUBERNETES_DIR}" ] ; then - # this will stop the VerneMQ process, but first drain the node from all existing client sessions (-k) - if [ -n "$VERNEMQ_KUBERNETES_HOSTNAME" ]; then - terminating_node_name=VerneMQ@$VERNEMQ_KUBERNETES_HOSTNAME - else - terminating_node_name=VerneMQ@$IP_ADDRESS - fi - podList=$(k8sCurlGet "api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods?labelSelector=${DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR}") - kube_pod_names=$(echo ${podList} | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ' | sed 's/ *$//') - if [ "$kube_pod_names" = "$MY_POD_NAME" ]; then - echo "I'm the only pod remaining. Not performing leave and/or state purge." - /vernemq/bin/vmq-admin node stop >/dev/null - else - # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-pod-v1-core - podResponse=$(k8sCurlGet api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods/$(hostname) ) - statefulSetName=$(echo ${podResponse} | jq -r '.metadata.ownerReferences[0].name') - - # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#-strong-read-operations-statefulset-v1-apps-strong- - statefulSetResponse=$(k8sCurlGet "apis/apps/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/statefulsets/${statefulSetName}" ) - - isCodeForbidden=$(echo ${statefulSetResponse} | jq '.code == 403') - if [[ ${isCodeForbidden} == "true" ]]; then - echo "Permission error: Cannot access URL ${statefulSetPath}: $(echo ${statefulSetResponse} | jq '.reason,.code,.message')" - fi - - reschedule=$(echo ${statefulSetResponse} | jq '.status.replicas == .status.readyReplicas') - scaled_down=$(echo ${statefulSetResponse} | jq '.status.currentReplicas == .status.updatedReplicas') - - if [[ $reschedule == "true" ]]; then - # Perhaps is an scale down? - if [[ $scaled_down == "true" ]]; then - echo "Seems that this is a scale down scenario. Leaving cluster." - /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* - else - echo "Reschedule is true. Not leaving the cluster." - /vernemq/bin/vmq-admin node stop >/dev/null - fi - else - echo "Reschedule is false. Leaving the cluster." - /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* - fi - fi - else - if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then - terminating_node_name=VerneMQ@$(hostname -i) - # For Swarm we keep the old "cluster leave" approach for now - echo "Swarm node is leaving the cluster." - /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* - else - # In non-k8s mode: Stop the vernemq node gracefully - /vernemq/bin/vmq-admin node stop >/dev/null - fi - fi - kill -s TERM ${pid} - WAITFOR_PID=${pid} - pid=0 - wait ${WAITFOR_PID} - fi - exit 143; # 128 + 15 -- SIGTERM -} - -if [ ! -s ${VERNEMQ_VM_ARGS_FILE} ]; then - echo "ls -l ${VERNEMQ_ETC_DIR}" - ls -l ${VERNEMQ_ETC_DIR} - echo "###" >&2 - echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} is empty! This will not work." >&2 - echo "### Exiting now." >&2 - echo "###" >&2 - exit 1 -fi - -# Setup OS signal handlers -trap 'siguser1_handler' SIGUSR1 -trap 'sigterm_handler' SIGTERM - -# Start VerneMQ -/vernemq/bin/vernemq console -noshell -noinput $@ & -pid=$! -if [ $start_join_cluster -eq 1 ]; then - mkdir -p /var/log/vernemq/log - join_cluster > /var/log/vernemq/log/join_cluster.log & -fi -if [ -n "$API_KEY" ]; then - sleep 10 && echo "Adding API_KEY..." && /vernemq/bin/vmq-admin api-key add key="${API_KEY:-DEFAULT}" - vmq-admin api-key show -fi -wait $pid diff --git a/docker/addons/vault/scripts/docker/vernemq/files/vm.args b/docker/addons/vault/scripts/docker/vernemq/files/vm.args deleted file mode 100644 index afb3c022..00000000 --- a/docker/addons/vault/scripts/docker/vernemq/files/vm.args +++ /dev/null @@ -1,15 +0,0 @@ -+P 512000 -+e 256000 --env ERL_CRASH_DUMP /erl_crash.dump --env ERL_FULLSWEEP_AFTER 0 -+Q 512000 -+A 64 --setcookie vmq --name VerneMQ@127.0.0.1 -+K true -+W w -+sbwt none -+sbwtdcpu none -+sbwtdio none --smp enable -+zdbbl 32768 diff --git a/docker/addons/vault/scripts/go.mod b/docker/addons/vault/scripts/go.mod deleted file mode 100644 index d46b97fb..00000000 --- a/docker/addons/vault/scripts/go.mod +++ /dev/null @@ -1,176 +0,0 @@ -module github.com/absmach/magistrala - -go 1.23.0 - -toolchain go1.23.1 - -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.5 - github.com/authzed/authzed-go v1.1.1 - 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/eclipse/paho.mqtt.golang v1.5.0 - github.com/fatih/color v1.18.0 - github.com/go-chi/chi/v5 v5.1.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/jmoiron/sqlx v1.4.0 - github.com/lestrrat-go/jwx/v2 v2.1.2 - 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/spf13/cobra v1.8.1 - github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 - go.opentelemetry.io/otel v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 - go.opentelemetry.io/otel/sdk v1.32.0 - go.opentelemetry.io/otel/trace v1.32.0 - golang.org/x/crypto v0.29.0 - golang.org/x/oauth2 v0.24.0 - golang.org/x/sync v0.9.0 - gonum.org/v1/gonum v0.15.1 - google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 - google.golang.org/grpc v1.68.0 - 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.1 // 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/beorn7/perks v1.0.1 // 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/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.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.23.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/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/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/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/nuid v1.0.1 // 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/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/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/procfs v0.15.1 // indirect - github.com/ryanuber/go-glob v1.0.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/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - github.com/x448/float16 v0.8.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - go.opentelemetry.io/otel/metric v1.32.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // 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.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect - golang.org/x/time v0.6.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // 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 -) diff --git a/docker/addons/vault/scripts/go.sum b/docker/addons/vault/scripts/go.sum deleted file mode 100644 index a0177a23..00000000 --- a/docker/addons/vault/scripts/go.sum +++ /dev/null @@ -1,653 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs= -cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/0x6flab/namegenerator v1.4.0 h1:QnkI813SZsI/hYnKD9pg3mkIlcYzCx0N4hnzb0YYME4= -github.com/0x6flab/namegenerator v1.4.0/go.mod h1:2sQzXuS6dX/KEwWtB6GJU729O3m4gBdD5oAU8hd0SyY= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -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/mgate v0.4.5 h1:l6RmrEsR9jxkdb9WHUSecmT0HA41TkZZQVffFfUAIfI= -github.com/absmach/mgate v0.4.5/go.mod h1:IvRIHZexZPEIAPmmaJF0L5DY2ERjj+GxRGitOW4s6qo= -github.com/absmach/senml v1.0.5 h1:zNPRYpGr2Wsb8brAusz8DIfFqemy1a2dNbmMnegY3GE= -github.com/absmach/senml v1.0.5/go.mod h1:NDEjk3O4V4YYu9Bs2/+t/AZ/F+0wu05ikgecp+/FsSU= -github.com/authzed/authzed-go v1.1.1 h1:grE9+P4tMezZ6uX13upUk5yxgHHY9NZJKDIvymO0igY= -github.com/authzed/authzed-go v1.1.1/go.mod h1:YPOLEX/XGtSGfq4HsG7iBjWnnATxN4qu0IDF/vOBQwQ= -github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b h1:wbh8IK+aMLTCey9sZasO7b6BWLAJnHHvb79fvWCXwxw= -github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b/go.mod h1:s3qC7V7XIbiNWERv7Lfljy/Lx25/V1Qlexb0WJuA8uQ= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -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/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= -github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= -github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/docker/cli v26.1.4+incompatible h1:I8PHdc0MtxEADqYJZvhBrW9bo8gawKwwenxRM7/rLu8= -github.com/docker/cli v26.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -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= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= -github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -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/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-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= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= -github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -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= -github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= -github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= -github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= -github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= -github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -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.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= -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/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= -github.com/ivanpirog/coloredcobra v1.0.1 h1:aURSdEmlR90/tSiWS0dMjdwOvCVUeYLfltLfbgNxrN4= -github.com/ivanpirog/coloredcobra v1.0.1/go.mod h1:iho4nEKcnwZFiniGSdcgdvRgZNjxm+h20acv8vqmN6Q= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= -github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8= -github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -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/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= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= -github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfUXas= -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/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= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= -github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= -github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.1.2 h1:6poete4MPsO8+LAEVhpdrNI4Xp2xdiafgl2RD89moBc= -github.com/lestrrat-go/jwx/v2 v2.1.2/go.mod h1:pO+Gz9whn7MPdbsqSJzG8TlEpMZCwQDXnFJ+zsUVh8Y= -github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= -github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -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/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= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -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= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -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/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= -github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -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/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= -github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -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/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= -github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= -github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -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/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= -github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= -github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= -github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -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/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= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= -github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= -github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= -github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= -github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= -github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -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/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= -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/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= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -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= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -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/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= -go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= -go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= -go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= -go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -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= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -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/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= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -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= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -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= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= -gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -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-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -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= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/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= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= -gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= -moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= diff --git a/docker/addons/vault/scripts/health.go b/docker/addons/vault/scripts/health.go deleted file mode 100644 index 833a3c0b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/http/README.md b/docker/addons/vault/scripts/http/README.md deleted file mode 100644 index 5aeaa751..00000000 --- a/docker/addons/vault/scripts/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 | <localhost:7000> | -| 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 | <nats://localhost:4222> | -| MG_JAEGER_URL | Jaeger server URL | <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_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/docker/addons/vault/scripts/http/api/doc.go b/docker/addons/vault/scripts/http/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/http/api/endpoint.go b/docker/addons/vault/scripts/http/api/endpoint.go deleted file mode 100644 index 1808f03e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/http/api/endpoint_test.go b/docker/addons/vault/scripts/http/api/endpoint_test.go deleted file mode 100644 index b41f223f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/http/api/request.go b/docker/addons/vault/scripts/http/api/request.go deleted file mode 100644 index b4e3df88..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/http/api/response.go b/docker/addons/vault/scripts/http/api/response.go deleted file mode 100644 index 5b43c92d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/http/api/transport.go b/docker/addons/vault/scripts/http/api/transport.go deleted file mode 100644 index 52ed2420..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/http/doc.go b/docker/addons/vault/scripts/http/doc.go deleted file mode 100644 index a7348a00..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/http/handler.go b/docker/addons/vault/scripts/http/handler.go deleted file mode 100644 index f81059c5..00000000 --- a/docker/addons/vault/scripts/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/<channel_id>/messages/<subtopic>/.../ct/<content_type> - - 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/docker/addons/vault/scripts/internal/api/auth.go b/docker/addons/vault/scripts/internal/api/auth.go deleted file mode 100644 index 7831c428..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/api/common.go b/docker/addons/vault/scripts/internal/api/common.go deleted file mode 100644 index 7c61ed26..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/api/common_test.go b/docker/addons/vault/scripts/internal/api/common_test.go deleted file mode 100644 index 15bd938d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/api/doc.go b/docker/addons/vault/scripts/internal/api/doc.go deleted file mode 100644 index 6bffadcf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/clients/doc.go b/docker/addons/vault/scripts/internal/clients/doc.go deleted file mode 100644 index ad1239b1..00000000 --- a/docker/addons/vault/scripts/internal/clients/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package clients contains the domain concept definitions needed to support -// Magistrala clients functionality for example: postgres, redis, grpc, jaeger. -package clients diff --git a/docker/addons/vault/scripts/internal/clients/redis/doc.go b/docker/addons/vault/scripts/internal/clients/redis/doc.go deleted file mode 100644 index 8496ce31..00000000 --- a/docker/addons/vault/scripts/internal/clients/redis/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package redis contains the domain concept definitions needed to support -// Magistrala redis cache functionality. -// -// It provides the abstraction of the redis cache service, which is used -// to configure, setup and connect to the redis cache. -package redis diff --git a/docker/addons/vault/scripts/internal/clients/redis/redis.go b/docker/addons/vault/scripts/internal/clients/redis/redis.go deleted file mode 100644 index 4a776409..00000000 --- a/docker/addons/vault/scripts/internal/clients/redis/redis.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package redis - -import "github.com/redis/go-redis/v9" - -// Connect create new RedisDB client and connect to RedisDB server. -func Connect(url string) (*redis.Client, error) { - opts, err := redis.ParseURL(url) - if err != nil { - return nil, err - } - - return redis.NewClient(opts), nil -} diff --git a/docker/addons/vault/scripts/internal/email/README.md b/docker/addons/vault/scripts/internal/email/README.md deleted file mode 100644 index a152d685..00000000 --- a/docker/addons/vault/scripts/internal/email/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Magistrala Email Agent - -Magistrala Email Agent is used for sending emails. It wraps basic SMTP features and -provides a simple API that Magistrala services can use to send email notifications. - -## Configuration - -Magistrala Email Agent is configured using the following configuration parameters: - -| Parameter | Description | -| ----------------------------------- | ----------------------------------------------------------------------- | -| MG_EMAIL_HOST | Mail server host | -| MG_EMAIL_PORT | Mail server port | -| 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 notification emails | - -There are two authentication methods supported: Basic Auth and CRAM-MD5. -If `MG_EMAIL_USERNAME` is empty, no authentication will be used. diff --git a/docker/addons/vault/scripts/internal/email/doc.go b/docker/addons/vault/scripts/internal/email/doc.go deleted file mode 100644 index f5d4a0b3..00000000 --- a/docker/addons/vault/scripts/internal/email/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package email contains the domain concept definitions needed to support -// Magistrala email functionality. -package email diff --git a/docker/addons/vault/scripts/internal/email/email.go b/docker/addons/vault/scripts/internal/email/email.go deleted file mode 100644 index 8925c380..00000000 --- a/docker/addons/vault/scripts/internal/email/email.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package email - -import ( - "bytes" - "net/mail" - "strconv" - "strings" - "text/template" - - "github.com/absmach/magistrala/pkg/errors" - "gopkg.in/gomail.v2" -) - -var ( - // errMissingEmailTemplate missing email template file. - errMissingEmailTemplate = errors.New("Missing e-mail template file") - errParseTemplate = errors.New("Parse e-mail template failed") - errExecTemplate = errors.New("Execute e-mail template failed") - errSendMail = errors.New("Sending e-mail failed") -) - -type email struct { - To []string - From string - Subject string - Header string - User string - Content string - Host string - Footer string -} - -// Config email agent configuration. -type Config struct { - Host string `env:"MG_EMAIL_HOST" envDefault:"localhost"` - Port string `env:"MG_EMAIL_PORT" envDefault:"25"` - Username string `env:"MG_EMAIL_USERNAME" envDefault:"root"` - Password string `env:"MG_EMAIL_PASSWORD" envDefault:""` - FromAddress string `env:"MG_EMAIL_FROM_ADDRESS" envDefault:""` - FromName string `env:"MG_EMAIL_FROM_NAME" envDefault:""` - Template string `env:"MG_EMAIL_TEMPLATE" envDefault:"email.tmpl"` -} - -// Agent for mailing. -type Agent struct { - conf *Config - tmpl *template.Template - dial *gomail.Dialer -} - -// New creates new email agent. -func New(c *Config) (*Agent, error) { - a := &Agent{} - a.conf = c - port, err := strconv.Atoi(c.Port) - if err != nil { - return a, err - } - d := gomail.NewDialer(c.Host, port, c.Username, c.Password) - a.dial = d - - tmpl, err := template.ParseFiles(c.Template) - if err != nil { - return a, errors.Wrap(errParseTemplate, err) - } - a.tmpl = tmpl - return a, nil -} - -// Send sends e-mail. -func (a *Agent) Send(to []string, from, subject, header, user, content, footer string) error { - if a.tmpl == nil { - return errMissingEmailTemplate - } - - buff := new(bytes.Buffer) - e := email{ - To: to, - From: from, - Subject: subject, - Header: header, - User: user, - Content: content, - Host: strings.Split(content, "?")[0], - Footer: footer, - } - if from == "" { - from := mail.Address{Name: a.conf.FromName, Address: a.conf.FromAddress} - e.From = from.String() - } - - if err := a.tmpl.Execute(buff, e); err != nil { - return errors.Wrap(errExecTemplate, err) - } - - m := gomail.NewMessage() - m.SetHeader("From", e.From) - m.SetHeader("To", to...) - m.SetHeader("Subject", subject) - m.SetBody("text/plain", buff.String()) - - if err := a.dial.DialAndSend(m); err != nil { - return errors.Wrap(errSendMail, err) - } - - return nil -} diff --git a/docker/addons/vault/scripts/internal/groups/api/decode.go b/docker/addons/vault/scripts/internal/groups/api/decode.go deleted file mode 100644 index c560f508..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/api/decode_test.go b/docker/addons/vault/scripts/internal/groups/api/decode_test.go deleted file mode 100644 index 2e45e348..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/api/doc.go b/docker/addons/vault/scripts/internal/groups/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/api/endpoint_test.go b/docker/addons/vault/scripts/internal/groups/api/endpoint_test.go deleted file mode 100644 index 4a69f2fc..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/api/endpoints.go b/docker/addons/vault/scripts/internal/groups/api/endpoints.go deleted file mode 100644 index 7082c3e5..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/api/requests.go b/docker/addons/vault/scripts/internal/groups/api/requests.go deleted file mode 100644 index 7144ef23..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/api/requests_test.go b/docker/addons/vault/scripts/internal/groups/api/requests_test.go deleted file mode 100644 index ed9fa15a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/api/responses.go b/docker/addons/vault/scripts/internal/groups/api/responses.go deleted file mode 100644 index a2c30795..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/events/doc.go b/docker/addons/vault/scripts/internal/groups/events/doc.go deleted file mode 100644 index f1cd64cb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/events/events.go b/docker/addons/vault/scripts/internal/groups/events/events.go deleted file mode 100644 index eb65fd41..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/events/streams.go b/docker/addons/vault/scripts/internal/groups/events/streams.go deleted file mode 100644 index b473c5e1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/middleware/authorization.go b/docker/addons/vault/scripts/internal/groups/middleware/authorization.go deleted file mode 100644 index d6a2e0ac..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/middleware/doc.go b/docker/addons/vault/scripts/internal/groups/middleware/doc.go deleted file mode 100644 index 2ffa0936..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/middleware/logging.go b/docker/addons/vault/scripts/internal/groups/middleware/logging.go deleted file mode 100644 index 220f924d..00000000 --- a/docker/addons/vault/scripts/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 to group failed", args...) - return - } - lm.logger.Info("Unassign member to 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/docker/addons/vault/scripts/internal/groups/middleware/metrics.go b/docker/addons/vault/scripts/internal/groups/middleware/metrics.go deleted file mode 100644 index 7d6fa13f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/postgres/doc.go b/docker/addons/vault/scripts/internal/groups/postgres/doc.go deleted file mode 100644 index 96fe2117..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/postgres/groups.go b/docker/addons/vault/scripts/internal/groups/postgres/groups.go deleted file mode 100644 index 15d9b397..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/postgres/groups_test.go b/docker/addons/vault/scripts/internal/groups/postgres/groups_test.go deleted file mode 100644 index 7bbbee20..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/postgres/init.go b/docker/addons/vault/scripts/internal/groups/postgres/init.go deleted file mode 100644 index 0b799c46..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/postgres/setup_test.go b/docker/addons/vault/scripts/internal/groups/postgres/setup_test.go deleted file mode 100644 index a809a2b4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/service.go b/docker/addons/vault/scripts/internal/groups/service.go deleted file mode 100644 index 807a9177..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/service_test.go b/docker/addons/vault/scripts/internal/groups/service_test.go deleted file mode 100644 index 799a03f9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/status.go b/docker/addons/vault/scripts/internal/groups/status.go deleted file mode 100644 index d967dbc0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/status_test.go b/docker/addons/vault/scripts/internal/groups/status_test.go deleted file mode 100644 index a715ee39..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/tracing/doc.go b/docker/addons/vault/scripts/internal/groups/tracing/doc.go deleted file mode 100644 index 6a419f3b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/groups/tracing/tracing.go b/docker/addons/vault/scripts/internal/groups/tracing/tracing.go deleted file mode 100644 index 19018866..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/internal/testsutil/common.go b/docker/addons/vault/scripts/internal/testsutil/common.go deleted file mode 100644 index f6048a85..00000000 --- a/docker/addons/vault/scripts/internal/testsutil/common.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package testsutil - -import ( - "fmt" - "testing" - - "github.com/absmach/magistrala/pkg/uuid" - "github.com/stretchr/testify/require" -) - -func GenerateUUID(t *testing.T) string { - idProvider := uuid.New() - ulid, err := idProvider.ID() - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - return ulid -} diff --git a/docker/addons/vault/scripts/invitations/README.md b/docker/addons/vault/scripts/invitations/README.md deleted file mode 100644 index de5c65fb..00000000 --- a/docker/addons/vault/scripts/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 | <http://localhost:9002> | -| MG_DOMAINS_URL | Domains service URL | <http://localhost:8189> | -| 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/docker/addons/vault/scripts/invitations/api/doc.go b/docker/addons/vault/scripts/invitations/api/doc.go deleted file mode 100644 index 7cd03c09..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/api/endpoint.go b/docker/addons/vault/scripts/invitations/api/endpoint.go deleted file mode 100644 index 08adfc43..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/api/endpoint_test.go b/docker/addons/vault/scripts/invitations/api/endpoint_test.go deleted file mode 100644 index c81e5ee0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/api/requests.go b/docker/addons/vault/scripts/invitations/api/requests.go deleted file mode 100644 index 74c42aca..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/api/requests_test.go b/docker/addons/vault/scripts/invitations/api/requests_test.go deleted file mode 100644 index 17d731d7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/api/responses.go b/docker/addons/vault/scripts/invitations/api/responses.go deleted file mode 100644 index 300ce90d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/api/transport.go b/docker/addons/vault/scripts/invitations/api/transport.go deleted file mode 100644 index b8d6b692..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/doc.go b/docker/addons/vault/scripts/invitations/doc.go deleted file mode 100644 index 124fb757..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/invitations.go b/docker/addons/vault/scripts/invitations/invitations.go deleted file mode 100644 index 86973f3f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/invitations_test.go b/docker/addons/vault/scripts/invitations/invitations_test.go deleted file mode 100644 index 2dce3164..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/middleware/authorization.go b/docker/addons/vault/scripts/invitations/middleware/authorization.go deleted file mode 100644 index 1f89b1fe..00000000 --- a/docker/addons/vault/scripts/invitations/middleware/authorization.go +++ /dev/null @@ -1,125 +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) { - if err := am.checkAdmin(ctx, session.UserID, session.DomainID); err != nil { - return err - } - 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.DomainUserID, invitation.DomainID); 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.DomainUserID, domain); 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.DomainUserID, 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.DomainUserID, domainID); 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, userID, domainID string) error { - if err := am.authorize(ctx, userID, policies.AdminPermission, policies.DomainType, domainID); err == nil { - return nil - } - - if err := am.authorize(ctx, 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/docker/addons/vault/scripts/invitations/middleware/doc.go b/docker/addons/vault/scripts/invitations/middleware/doc.go deleted file mode 100644 index 1fdf252f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/middleware/logging.go b/docker/addons/vault/scripts/invitations/middleware/logging.go deleted file mode 100644 index 1a64e5a9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/middleware/metrics.go b/docker/addons/vault/scripts/invitations/middleware/metrics.go deleted file mode 100644 index 82acac84..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/middleware/tracing.go b/docker/addons/vault/scripts/invitations/middleware/tracing.go deleted file mode 100644 index 16d39d64..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/mocks/doc.go b/docker/addons/vault/scripts/invitations/mocks/doc.go deleted file mode 100644 index 4d95a3c1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/mocks/repository.go b/docker/addons/vault/scripts/invitations/mocks/repository.go deleted file mode 100644 index e7d6832f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/mocks/service.go b/docker/addons/vault/scripts/invitations/mocks/service.go deleted file mode 100644 index 3992c7cb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/postgres/doc.go b/docker/addons/vault/scripts/invitations/postgres/doc.go deleted file mode 100644 index 086a7bb4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/postgres/init.go b/docker/addons/vault/scripts/invitations/postgres/init.go deleted file mode 100644 index 442d8e61..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/postgres/invitations.go b/docker/addons/vault/scripts/invitations/postgres/invitations.go deleted file mode 100644 index f1de8c41..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/postgres/invitations_test.go b/docker/addons/vault/scripts/invitations/postgres/invitations_test.go deleted file mode 100644 index 147539e0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/postgres/setup_test.go b/docker/addons/vault/scripts/invitations/postgres/setup_test.go deleted file mode 100644 index 5d220b3e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/service.go b/docker/addons/vault/scripts/invitations/service.go deleted file mode 100644 index 5b81d7ea..00000000 --- a/docker/addons/vault/scripts/invitations/service.go +++ /dev/null @@ -1,142 +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() - - return svc.repo.Create(ctx, invitation) -} - -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/docker/addons/vault/scripts/invitations/service_test.go b/docker/addons/vault/scripts/invitations/service_test.go deleted file mode 100644 index 92538652..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/state.go b/docker/addons/vault/scripts/invitations/state.go deleted file mode 100644 index afd392da..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/invitations/state_test.go b/docker/addons/vault/scripts/invitations/state_test.go deleted file mode 100644 index 006072ef..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/api/doc.go b/docker/addons/vault/scripts/journal/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/api/endpoint.go b/docker/addons/vault/scripts/journal/api/endpoint.go deleted file mode 100644 index a248b20e..00000000 --- a/docker/addons/vault/scripts/journal/api/endpoint.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "context" - - "github.com/absmach/magistrala/journal" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "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) - } - - page, err := svc.RetrieveAll(ctx, req.token, req.page) - if err != nil { - return nil, err - } - - return pageRes{ - JournalsPage: page, - }, nil - } -} diff --git a/docker/addons/vault/scripts/journal/api/endpoint_test.go b/docker/addons/vault/scripts/journal/api/endpoint_test.go deleted file mode 100644 index 994a1b1c..00000000 --- a/docker/addons/vault/scripts/journal/api/endpoint_test.go +++ /dev/null @@ -1,282 +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/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" - 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) { - svc := new(mocks.Service) - - logger := mglog.NewMock() - mux := api.MakeHandler(svc, logger, "journal-log", "test") - return httptest.NewServer(mux), svc -} - -func TestListJournalsEndpoint(t *testing.T) { - es, svc := newjournalServer() - - cases := []struct { - desc string - token string - 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 invalid entity type", - token: validToken, - url: "/invalid/123", - status: http.StatusBadRequest, - svcErr: nil, - }, - { - desc: "with all query params", - token: validToken, - url: "/user/123?offset=10&limit=10&operation=user.create&from=0&to=10&with_attributes=true&with_metadata=true&dir=asc", - status: http.StatusOK, - 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.StatusBadRequest, - 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) { - svcCall := svc.On("RetrieveAll", mock.Anything, c.token, 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() - }) - } -} diff --git a/docker/addons/vault/scripts/journal/api/requests.go b/docker/addons/vault/scripts/journal/api/requests.go deleted file mode 100644 index ba633e55..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/api/requests_test.go b/docker/addons/vault/scripts/journal/api/requests_test.go deleted file mode 100644 index 31b9b419..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/api/responses.go b/docker/addons/vault/scripts/journal/api/responses.go deleted file mode 100644 index 81b3702c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/api/transport.go b/docker/addons/vault/scripts/journal/api/transport.go deleted file mode 100644 index 5c22bcc2..00000000 --- a/docker/addons/vault/scripts/journal/api/transport.go +++ /dev/null @@ -1,129 +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" - "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, logger *slog.Logger, svcName, instanceID string) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - mux := chi.NewRouter() - - mux.Get("/journal/{entityType}/{entityID}", otelhttp.NewHandler(kithttp.NewServer( - retrieveJournalsEndpoint(svc), - decodeRetrieveJournalReq, - api.EncodeResponse, - opts..., - ), "list_journals").ServeHTTP) - - mux.Get("/health", magistrala.Health(svcName, instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} - -func decodeRetrieveJournalReq(_ 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) - } - operation, err := apiutil.ReadStringQuery(r, operationKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - from, err := apiutil.ReadNumQuery[int64](r, fromKey, 0) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - if from > math.MaxInt32 { - return nil, 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 nil, errors.Wrap(apiutil.ErrValidation, err) - } - if to > math.MaxInt32 { - return nil, 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 nil, errors.Wrap(apiutil.ErrValidation, err) - } - metadata, err := apiutil.ReadBoolQuery(r, metadataKey, false) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DescDir) - 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) - } - if entityType == journal.ChannelEntity { - operation = strings.ReplaceAll(operation, "channel", "group") - } - - req := retrieveJournalsReq{ - token: apiutil.ExtractBearerToken(r), - page: journal.Page{ - Offset: offset, - Limit: limit, - Operation: operation, - From: fromTime, - To: toTime, - WithAttributes: attributes, - WithMetadata: metadata, - EntityID: chi.URLParam(r, "entityID"), - EntityType: entityType, - Direction: dir, - }, - } - - return req, nil -} diff --git a/docker/addons/vault/scripts/journal/doc.go b/docker/addons/vault/scripts/journal/doc.go deleted file mode 100644 index 3b686067..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/events/consumer.go b/docker/addons/vault/scripts/journal/events/consumer.go deleted file mode 100644 index e2636ed7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/events/consumer_test.go b/docker/addons/vault/scripts/journal/events/consumer_test.go deleted file mode 100644 index 712c8fb8..00000000 --- a/docker/addons/vault/scripts/journal/events/consumer_test.go +++ /dev/null @@ -1,280 +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" - authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" - authzmocks "github.com/absmach/magistrala/pkg/authz/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) - authn := new(authnmocks.Authentication) - authz := new(authzmocks.Authorization) - svc := journal.NewService(authn, authz, 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/docker/addons/vault/scripts/journal/events/doc.go b/docker/addons/vault/scripts/journal/events/doc.go deleted file mode 100644 index 5023696f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/journal.go b/docker/addons/vault/scripts/journal/journal.go deleted file mode 100644 index 883d094c..00000000 --- a/docker/addons/vault/scripts/journal/journal.go +++ /dev/null @@ -1,158 +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" - "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, token string, 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/docker/addons/vault/scripts/journal/journal_test.go b/docker/addons/vault/scripts/journal/journal_test.go deleted file mode 100644 index 0772ed00..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/middleware/doc.go b/docker/addons/vault/scripts/journal/middleware/doc.go deleted file mode 100644 index 71d25713..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/middleware/logging.go b/docker/addons/vault/scripts/journal/middleware/logging.go deleted file mode 100644 index 5ab991a6..00000000 --- a/docker/addons/vault/scripts/journal/middleware/logging.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "log/slog" - "time" - - "github.com/absmach/magistrala/journal" -) - -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, token string, 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, token, page) -} diff --git a/docker/addons/vault/scripts/journal/middleware/metrics.go b/docker/addons/vault/scripts/journal/middleware/metrics.go deleted file mode 100644 index fdd098d9..00000000 --- a/docker/addons/vault/scripts/journal/middleware/metrics.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - "time" - - "github.com/absmach/magistrala/journal" - "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, token string, 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, token, page) -} diff --git a/docker/addons/vault/scripts/journal/middleware/tracing.go b/docker/addons/vault/scripts/journal/middleware/tracing.go deleted file mode 100644 index 9ea96ff9..00000000 --- a/docker/addons/vault/scripts/journal/middleware/tracing.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "context" - - "github.com/absmach/magistrala/journal" - "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, token string, 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, token, page) -} diff --git a/docker/addons/vault/scripts/journal/mocks/doc.go b/docker/addons/vault/scripts/journal/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/mocks/repository.go b/docker/addons/vault/scripts/journal/mocks/repository.go deleted file mode 100644 index 8b3fb512..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/mocks/service.go b/docker/addons/vault/scripts/journal/mocks/service.go deleted file mode 100644 index ac7c34c1..00000000 --- a/docker/addons/vault/scripts/journal/mocks/service.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" -) - -// Service is an autogenerated mock type for the Service type -type Service struct { - mock.Mock -} - -// RetrieveAll provides a mock function with given fields: ctx, token, page -func (_m *Service) RetrieveAll(ctx context.Context, token string, page journal.Page) (journal.JournalsPage, error) { - ret := _m.Called(ctx, token, 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, string, journal.Page) (journal.JournalsPage, error)); ok { - return rf(ctx, token, page) - } - if rf, ok := ret.Get(0).(func(context.Context, string, journal.Page) journal.JournalsPage); ok { - r0 = rf(ctx, token, page) - } else { - r0 = ret.Get(0).(journal.JournalsPage) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, journal.Page) error); ok { - r1 = rf(ctx, token, 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/docker/addons/vault/scripts/journal/postgres/doc.go b/docker/addons/vault/scripts/journal/postgres/doc.go deleted file mode 100644 index 1007b312..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/postgres/init.go b/docker/addons/vault/scripts/journal/postgres/init.go deleted file mode 100644 index adad7979..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/postgres/journal.go b/docker/addons/vault/scripts/journal/postgres/journal.go deleted file mode 100644 index ff6606ef..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/postgres/journal_test.go b/docker/addons/vault/scripts/journal/postgres/journal_test.go deleted file mode 100644 index 677d38bc..00000000 --- a/docker/addons/vault/scripts/journal/postgres/journal_test.go +++ /dev/null @@ -1,724 +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, - } -) - -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{}{} - } - 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/docker/addons/vault/scripts/journal/postgres/setup_test.go b/docker/addons/vault/scripts/journal/postgres/setup_test.go deleted file mode 100644 index bb9a1307..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/journal/service.go b/docker/addons/vault/scripts/journal/service.go deleted file mode 100644 index bb46cf4c..00000000 --- a/docker/addons/vault/scripts/journal/service.go +++ /dev/null @@ -1,83 +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" - mgauthz "github.com/absmach/magistrala/pkg/authz" - "github.com/absmach/magistrala/pkg/policies" -) - -type service struct { - authn mgauthn.Authentication - authz mgauthz.Authorization - idProvider magistrala.IDProvider - repository Repository -} - -func NewService(authn mgauthn.Authentication, authz mgauthz.Authorization, idp magistrala.IDProvider, repository Repository) Service { - return &service{ - idProvider: idp, - authn: authn, - authz: authz, - 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, token string, page Page) (JournalsPage, error) { - if err := svc.authorize(ctx, token, page.EntityID, page.EntityType.AuthString()); err != nil { - return JournalsPage{}, err - } - - return svc.repository.RetrieveAll(ctx, page) -} - -func (svc *service) authorize(ctx context.Context, token, entityID, entityType string) error { - session, err := svc.authn.Authenticate(ctx, token) - if err != nil { - return err - } - - permission := policies.ViewPermission - objectType := entityType - object := entityID - subject := session.DomainUserID - - // If the entity is a user, we need to check if the user is an admin - if entityType == 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 := svc.authz.Authorize(ctx, req); err != nil { - return err - } - - return nil -} diff --git a/docker/addons/vault/scripts/journal/service_test.go b/docker/addons/vault/scripts/journal/service_test.go deleted file mode 100644 index f6176d0f..00000000 --- a/docker/addons/vault/scripts/journal/service_test.go +++ /dev/null @@ -1,208 +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" - authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" - mgauthz "github.com/absmach/magistrala/pkg/authz" - authzmocks "github.com/absmach/magistrala/pkg/authz/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/policies" - "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) - authn := new(authnmocks.Authentication) - authz := new(authzmocks.Authorization) - svc := journal.NewService(authn, authz, 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) - authn := new(authnmocks.Authentication) - authz := new(authzmocks.Authorization) - svc := journal.NewService(authn, authz, idProvider, repo) - - validToken := "token" - validPage := journal.Page{ - Offset: 0, - Limit: 10, - EntityID: testsutil.GenerateUUID(t), - EntityType: journal.ThingEntity, - } - - cases := []struct { - desc string - token string - page journal.Page - resp journal.JournalsPage - identifyRes mgauthn.Session - identifyErr error - authErr error - repoErr error - err error - }{ - { - desc: "successful", - token: validToken, - page: validPage, - resp: journal.JournalsPage{ - Total: 1, - Offset: 0, - Limit: 10, - Journals: []journal.Journal{validJournal}, - }, - identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)}, - authErr: nil, - repoErr: nil, - err: nil, - }, - { - desc: "successful for user", - token: validToken, - 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}, - }, - identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)}, - authErr: nil, - repoErr: nil, - err: nil, - }, - { - desc: "with identify error", - token: validToken, - page: validPage, - resp: journal.JournalsPage{}, - identifyRes: mgauthn.Session{}, - identifyErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "with repo error", - token: validToken, - page: validPage, - resp: journal.JournalsPage{}, - identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)}, - repoErr: repoerr.ErrViewEntity, - err: repoerr.ErrViewEntity, - }, - { - desc: "with failed to authorize", - token: validToken, - page: validPage, - resp: journal.JournalsPage{}, - identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)}, - authErr: svcerr.ErrAuthorization, - repoErr: nil, - err: svcerr.ErrAuthorization, - }, - { - desc: "with error on authorize", - token: validToken, - page: validPage, - resp: journal.JournalsPage{}, - identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)}, - authErr: svcerr.ErrAuthorization, - repoErr: nil, - err: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - authReq := mgauthz.PolicyReq{ - SubjectType: policies.UserType, - SubjectKind: policies.UsersKind, - Subject: tc.identifyRes.DomainUserID, - ObjectType: tc.page.EntityType.AuthString(), - Object: tc.page.EntityID, - Permission: policies.ViewPermission, - } - if tc.page.EntityType == journal.UserEntity { - authReq.Permission = policies.AdminPermission - authReq.ObjectType = policies.PlatformType - authReq.Object = policies.MagistralaObject - authReq.Subject = tc.identifyRes.UserID - } - authCall := authn.On("Authenticate", context.Background(), tc.token).Return(tc.identifyRes, tc.identifyErr) - authCall1 := authz.On("Authorize", context.Background(), authReq).Return(tc.authErr) - repoCall := repo.On("RetrieveAll", context.Background(), tc.page).Return(tc.resp, tc.repoErr) - resp, err := svc.RetrieveAll(context.Background(), tc.token, 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() - authCall.Unset() - authCall1.Unset() - }) - } -} diff --git a/docker/addons/vault/scripts/logger/doc.go b/docker/addons/vault/scripts/logger/doc.go deleted file mode 100644 index e2f32e36..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/logger/exit.go b/docker/addons/vault/scripts/logger/exit.go deleted file mode 100644 index e8dde049..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/logger/logger.go b/docker/addons/vault/scripts/logger/logger.go deleted file mode 100644 index edaf84e3..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/logger/logger_test.go b/docker/addons/vault/scripts/logger/logger_test.go deleted file mode 100644 index 9612f889..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/logger/mock.go b/docker/addons/vault/scripts/logger/mock.go deleted file mode 100644 index 190fc229..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/README.md b/docker/addons/vault/scripts/mqtt/README.md deleted file mode 100644 index 49a66d83..00000000 --- a/docker/addons/vault/scripts/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 | <localhost:7000> | -| 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 | <nats://localhost:4222> | -| MG_MESSAGE_BROKER_URL | Message broker instance URL | <nats://localhost:4222> | -| MG_JAEGER_URL | Jaeger server URL | <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_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/docker/addons/vault/scripts/mqtt/doc.go b/docker/addons/vault/scripts/mqtt/doc.go deleted file mode 100644 index 112d3df1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/events/doc.go b/docker/addons/vault/scripts/mqtt/events/doc.go deleted file mode 100644 index 83ccf23c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/events/events.go b/docker/addons/vault/scripts/mqtt/events/events.go deleted file mode 100644 index 9ae960be..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/events/streams.go b/docker/addons/vault/scripts/mqtt/events/streams.go deleted file mode 100644 index 780d1a6e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/forwarder.go b/docker/addons/vault/scripts/mqtt/forwarder.go deleted file mode 100644 index 735b29c2..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/handler.go b/docker/addons/vault/scripts/mqtt/handler.go deleted file mode 100644 index e3999fbb..00000000 --- a/docker/addons/vault/scripts/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/<channel_id>/messages/<subtopic>/.../ct/<content_type> - - 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/<channel_id>/messages/<subtopic>/.../ct/<content_type> - 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/docker/addons/vault/scripts/mqtt/handler_test.go b/docker/addons/vault/scripts/mqtt/handler_test.go deleted file mode 100644 index 8f0ff954..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/mocks/doc.go b/docker/addons/vault/scripts/mqtt/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/mocks/events.go b/docker/addons/vault/scripts/mqtt/mocks/events.go deleted file mode 100644 index 7dcebfd7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/mocks/publisher.go b/docker/addons/vault/scripts/mqtt/mocks/publisher.go deleted file mode 100644 index b86a5621..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/tracing/doc.go b/docker/addons/vault/scripts/mqtt/tracing/doc.go deleted file mode 100644 index 88ed02e7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/mqtt/tracing/forwarder.go b/docker/addons/vault/scripts/mqtt/tracing/forwarder.go deleted file mode 100644 index 2300d2dc..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/README.md b/docker/addons/vault/scripts/pkg/README.md deleted file mode 100644 index f260bd55..00000000 --- a/docker/addons/vault/scripts/pkg/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Standalone packages - -The `pkg` directory (the current directory) contains a set of standalone packages that can be imported and used by external applications. The packages are specifically meant for the development of the Magistrala based back-end applications and implement common tasks needed by the programmatic operation of Magistrala platform. diff --git a/docker/addons/vault/scripts/pkg/apiutil/errors.go b/docker/addons/vault/scripts/pkg/apiutil/errors.go deleted file mode 100644 index 2b533751..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/apiutil/responses.go b/docker/addons/vault/scripts/pkg/apiutil/responses.go deleted file mode 100644 index 9b032d7c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/apiutil/token.go b/docker/addons/vault/scripts/pkg/apiutil/token.go deleted file mode 100644 index 563b60a1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/apiutil/token_test.go b/docker/addons/vault/scripts/pkg/apiutil/token_test.go deleted file mode 100644 index 6194b9bb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/apiutil/transport.go b/docker/addons/vault/scripts/pkg/apiutil/transport.go deleted file mode 100644 index 35e22a3b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/apiutil/transport_test.go b/docker/addons/vault/scripts/pkg/apiutil/transport_test.go deleted file mode 100644 index fec20d97..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/authn/authn.go b/docker/addons/vault/scripts/pkg/authn/authn.go deleted file mode 100644 index d5f91060..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/authn/authsvc/authn.go b/docker/addons/vault/scripts/pkg/authn/authsvc/authn.go deleted file mode 100644 index 88b44c51..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/authn/doc.go b/docker/addons/vault/scripts/pkg/authn/doc.go deleted file mode 100644 index e2d3aaa8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/authn/mocks/authn.go b/docker/addons/vault/scripts/pkg/authn/mocks/authn.go deleted file mode 100644 index 9360870c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/authz/authsvc/authz.go b/docker/addons/vault/scripts/pkg/authz/authsvc/authz.go deleted file mode 100644 index 47db088e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/authz/authz.go b/docker/addons/vault/scripts/pkg/authz/authz.go deleted file mode 100644 index a76993ef..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/authz/doc.go b/docker/addons/vault/scripts/pkg/authz/doc.go deleted file mode 100644 index 83cb21a4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/authz/mocks/authz.go b/docker/addons/vault/scripts/pkg/authz/mocks/authz.go deleted file mode 100644 index fe190f2c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/doc.go b/docker/addons/vault/scripts/pkg/doc.go deleted file mode 100644 index ec156938..00000000 --- a/docker/addons/vault/scripts/pkg/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package pkg contains library packages used by Magistrala services -// and external services that integrate with Magistrala. -package pkg diff --git a/docker/addons/vault/scripts/pkg/errors/README.md b/docker/addons/vault/scripts/pkg/errors/README.md deleted file mode 100644 index fc5ba548..00000000 --- a/docker/addons/vault/scripts/pkg/errors/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Errors - -`errors` package serve to build an arbitrary long error chain in order to capture errors returned from nested service calls. - -`errors` package contains the custom Go `error` interface implementation, `Error`. You use the `Error` interface to **wrap** two errors in a containing error as well as to test recursively if a given error **contains** some other error. diff --git a/docker/addons/vault/scripts/pkg/errors/doc.go b/docker/addons/vault/scripts/pkg/errors/doc.go deleted file mode 100644 index 021c4839..00000000 --- a/docker/addons/vault/scripts/pkg/errors/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package errors contains Magistrala errors definitions. -package errors diff --git a/docker/addons/vault/scripts/pkg/errors/errors.go b/docker/addons/vault/scripts/pkg/errors/errors.go deleted file mode 100644 index 6ca1637d..00000000 --- a/docker/addons/vault/scripts/pkg/errors/errors.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors - -import ( - "encoding/json" -) - -// Error specifies an API that must be fullfiled by error type. -type Error interface { - // Error implements the error interface. - Error() string - - // Msg returns error message. - Msg() string - - // Err returns wrapped error. - Err() Error - - // MarshalJSON returns a marshaled error. - MarshalJSON() ([]byte, error) -} - -var _ Error = (*customError)(nil) - -// customError represents a Magistrala error. -type customError struct { - msg string - err Error -} - -// New returns an Error that formats as the given text. -func New(text string) Error { - return &customError{ - msg: text, - err: nil, - } -} - -func (ce *customError) Error() string { - if ce == nil { - return "" - } - if ce.err == nil { - return ce.msg - } - return ce.msg + " : " + ce.err.Error() -} - -func (ce *customError) Msg() string { - return ce.msg -} - -func (ce *customError) Err() Error { - return ce.err -} - -func (ce *customError) MarshalJSON() ([]byte, error) { - var val string - if e := ce.Err(); e != nil { - val = e.Msg() - } - return json.Marshal(&struct { - Err string `json:"error"` - Msg string `json:"message"` - }{ - Err: val, - Msg: ce.Msg(), - }) -} - -// Contains inspects if e2 error is contained in any layer of e1 error. -func Contains(e1, e2 error) bool { - if e1 == nil || e2 == nil { - return e2 == e1 - } - ce, ok := e1.(Error) - if ok { - if ce.Msg() == e2.Error() { - return true - } - return Contains(ce.Err(), e2) - } - return e1.Error() == e2.Error() -} - -// Wrap returns an Error that wrap err with wrapper. -func Wrap(wrapper, err error) error { - if wrapper == nil || err == nil { - return wrapper - } - if w, ok := wrapper.(Error); ok { - return &customError{ - msg: w.Msg(), - err: cast(err), - } - } - return &customError{ - msg: wrapper.Error(), - err: cast(err), - } -} - -// Unwrap returns the wrapper and the error by separating the Wrapper from the error. -func Unwrap(err error) (error, error) { - if ce, ok := err.(Error); ok { - if ce.Err() == nil { - return nil, New(ce.Msg()) - } - return New(ce.Msg()), ce.Err() - } - - return nil, err -} - -func cast(err error) Error { - if err == nil { - return nil - } - if e, ok := err.(Error); ok { - return e - } - return &customError{ - msg: err.Error(), - err: nil, - } -} diff --git a/docker/addons/vault/scripts/pkg/errors/errors_test.go b/docker/addons/vault/scripts/pkg/errors/errors_test.go deleted file mode 100644 index 925e9568..00000000 --- a/docker/addons/vault/scripts/pkg/errors/errors_test.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors_test - -import ( - nerrors "errors" - "fmt" - "strconv" - "testing" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" -) - -const level = 10 - -var ( - err0 = errors.New("0") - err1 = errors.New("1") - err2 = errors.New("2") - nat = nerrors.New("native error") -) - -func TestError(t *testing.T) { - cases := []struct { - desc string - err error - msg string - bytes []byte - bytesErr error - }{ - { - desc: "level 0 wrapped error", - err: err0, - msg: "0", - bytes: []byte(`{"error":"","message":"0"}`), - bytesErr: nil, - }, - { - desc: "level 1 wrapped error", - err: wrap(1), - msg: message(1), - bytes: []byte(`{"error":"0","message":"1"}`), - bytesErr: nil, - }, - { - desc: "level 2 wrapped error", - err: wrap(2), - msg: message(2), - bytes: []byte(`{"error":"1","message":"2"}`), - bytesErr: nil, - }, - { - desc: fmt.Sprintf("level %d wrapped error", level), - err: wrap(level), - msg: message(level), - bytes: []byte(`{"error":"9","message":"` + strconv.Itoa(level) + `"}`), - bytesErr: nil, - }, - { - desc: "nil error", - err: errors.New(""), - msg: "", - bytes: []byte(`{"error":"","message":""}`), - bytesErr: nil, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - errMsg := c.err.Error() - assert.Equal(t, c.msg, errMsg) - err := c.err.(errors.Error) - data, derr := err.MarshalJSON() - assert.Equal(t, c.bytesErr, derr) - assert.Equal(t, c.bytes, data) - }) - } -} - -func TestContains(t *testing.T) { - cases := []struct { - desc string - container error - contained error - contains bool - }{ - { - desc: "nil contains nil", - container: nil, - contained: nil, - contains: true, - }, - { - desc: "nil contains non-nil", - container: nil, - contained: err0, - contains: false, - }, - { - desc: "non-nil contains nil", - container: err0, - contained: nil, - contains: false, - }, - { - desc: "non-nil contains non-nil", - container: err0, - contained: err1, - contains: false, - }, - { - desc: "res of errors.Wrap(err1, err0) contains err0", - container: errors.Wrap(err1, err0), - contained: err0, - contains: true, - }, - { - desc: "res of errors.Wrap(err1, err0) contains err1", - container: errors.Wrap(err1, err0), - contained: err1, - contains: true, - }, - { - desc: "res of errors.Wrap(err2, errors.Wrap(err1, err0)) contains err1", - container: errors.Wrap(err2, errors.Wrap(err1, err0)), - contained: err1, - contains: true, - }, - { - desc: fmt.Sprintf("level %d wrapped error contains", level), - container: wrap(level), - contained: errors.New(strconv.Itoa(level / 2)), - contains: true, - }, - { - desc: "superset wrapper error contains subset wrapper error", - container: wrap(level), - contained: wrap(level / 2), - contains: false, - }, - { - desc: "native error contains error", - container: nat, - contained: err0, - contains: false, - }, - { - desc: "res of errors.Wrap(err1, errors.New('')) contains err1", - container: errors.Wrap(err1, nat), - contained: err1, - contains: true, - }, - { - desc: "error contains native error", - container: err0, - contained: nat, - contains: false, - }, - { - desc: "res of errors.Wrap(errors.New(''), err0) contains err0", - container: errors.Wrap(nat, err0), - contained: err0, - contains: true, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - contains := errors.Contains(c.container, c.contained) - assert.Equal(t, c.contains, contains) - }) - } -} - -func TestWrap(t *testing.T) { - cases := []struct { - desc string - wrapper error - wrapped error - contained error - contains bool - }{ - { - desc: "err 1 wraps err 2", - wrapper: err1, - wrapped: err0, - contained: err0, - contains: true, - }, - { - desc: "err2 wraps err1 wraps err0 and contains err0", - wrapper: err2, - wrapped: errors.Wrap(err1, err0), - contained: err0, - contains: true, - }, - { - desc: "err2 wraps err1 wraps err0 and contains err1", - wrapper: err2, - wrapped: errors.Wrap(err1, err0), - contained: err1, - contains: true, - }, - { - desc: "nil wraps nil", - wrapper: nil, - wrapped: nil, - contained: nil, - contains: true, - }, - { - desc: "err0 wraps nil", - wrapper: err0, - wrapped: nil, - contained: nil, - contains: false, - }, - { - desc: "nil wraps err0", - wrapper: nil, - wrapped: err0, - contained: err0, - contains: false, - }, - { - desc: "err0 wraps native error", - wrapper: err0, - wrapped: nat, - contained: nat, - contains: true, - }, - { - desc: "nil wraps native error", - wrapper: nil, - wrapped: nat, - contained: nat, - contains: false, - }, - { - desc: "native error wraps err0", - wrapper: nat, - wrapped: err0, - contained: err0, - contains: true, - }, - { - desc: "native error wraps nil", - wrapper: nat, - wrapped: nil, - contained: nil, - contains: false, - }, - { - desc: "err0 wraps err1 wraps native error", - wrapper: err0, - wrapped: errors.Wrap(err1, nat), - contained: nat, - contains: true, - }, - { - desc: "native error wraps err1 wraps err0", - wrapper: nat, - wrapped: errors.Wrap(err1, err0), - contained: err0, - contains: true, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - err := errors.Wrap(c.wrapper, c.wrapped) - contains := errors.Contains(err, c.contained) - assert.Equal(t, c.contains, contains) - }) - } -} - -func TestUnwrap(t *testing.T) { - cases := []struct { - desc string - err error - wrapper error - wrapped error - }{ - { - desc: "err 1 wraped err 2", - err: errors.Wrap(err1, err2), - wrapper: err1, - wrapped: err2, - }, - { - desc: "err2 wraps err1 wraps err0", - err: errors.Wrap(err2, errors.Wrap(err1, err0)), - wrapper: err2, - wrapped: errors.Wrap(err1, err0), - }, - { - desc: "nil wraps nil", - err: errors.Wrap(nil, nil), - wrapper: nil, - wrapped: nil, - }, - { - desc: "err0 wraps nil", - err: errors.Wrap(err0, nil), - wrapper: nil, - wrapped: err0, - }, - { - desc: "nil wraps err0", - err: errors.Wrap(nil, err0), - wrapper: nil, - wrapped: nil, - }, - { - desc: "nil wraps native error", - err: errors.Wrap(nil, nat), - wrapper: nil, - wrapped: nil, - }, - { - desc: "native error wraps nil", - err: errors.Wrap(nat, nil), - wrapper: nil, - wrapped: nat, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - wrapper, wrapped := errors.Unwrap(c.err) - assert.Equal(t, c.wrapper, wrapper) - assert.Equal(t, c.wrapped, wrapped) - }) - } -} - -func wrap(level int) error { - if level == 0 { - return errors.New(strconv.Itoa(level)) - } - return errors.Wrap(errors.New(strconv.Itoa(level)), wrap(level-1)) -} - -// message generates error message of wrap() generated wrapper error. -func message(level int) string { - if level == 0 { - return "0" - } - return strconv.Itoa(level) + " : " + message(level-1) -} diff --git a/docker/addons/vault/scripts/pkg/errors/repository/types.go b/docker/addons/vault/scripts/pkg/errors/repository/types.go deleted file mode 100644 index a189ae9e..00000000 --- a/docker/addons/vault/scripts/pkg/errors/repository/types.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package repository - -import "github.com/absmach/magistrala/pkg/errors" - -// Wrapper for Repository errors. -var ( - // ErrMalformedEntity indicates a malformed entity specification. - ErrMalformedEntity = errors.New("malformed entity specification") - - // ErrNotFound indicates a non-existent entity request. - ErrNotFound = errors.New("entity not found") - - // ErrConflict indicates that entity already exists. - ErrConflict = errors.New("entity already exists") - - // ErrCreateEntity indicates error in creating entity or entities. - ErrCreateEntity = errors.New("failed to create entity in the db") - - // ErrViewEntity indicates error in viewing entity or entities. - ErrViewEntity = errors.New("view entity failed") - - // ErrUpdateEntity indicates error in updating entity or entities. - ErrUpdateEntity = errors.New("update entity failed") - - // ErrRemoveEntity indicates error in removing entity. - ErrRemoveEntity = errors.New("failed to remove entity") - - // ErrFailedOpDB indicates a failure in a database operation. - ErrFailedOpDB = errors.New("operation on db element failed") - - // ErrFailedToRetrieveAllGroups failed to retrieve groups. - ErrFailedToRetrieveAllGroups = errors.New("failed to retrieve all groups") - - // ErrMissingNames indicates missing first and last names. - ErrMissingNames = errors.New("missing first or last name") -) diff --git a/docker/addons/vault/scripts/pkg/errors/sdk_errors.go b/docker/addons/vault/scripts/pkg/errors/sdk_errors.go deleted file mode 100644 index 61535c91..00000000 --- a/docker/addons/vault/scripts/pkg/errors/sdk_errors.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors - -import ( - "encoding/json" - "fmt" - "io" - "net/http" -) - -type errorRes struct { - Err string `json:"error"` - Msg string `json:"message"` -} - -// Failed to read response body. -var errRespBody = New("failed to read response body") - -// SDKError is an error type for Magistrala SDK. -type SDKError interface { - Error - StatusCode() int -} - -var _ SDKError = (*sdkError)(nil) - -type sdkError struct { - *customError - statusCode int -} - -func (ce *sdkError) Error() string { - if ce == nil { - return "" - } - if ce.customError == nil { - return http.StatusText(ce.statusCode) - } - return fmt.Sprintf("Status: %s: %s", http.StatusText(ce.statusCode), ce.customError.Error()) -} - -func (ce *sdkError) StatusCode() int { - return ce.statusCode -} - -// NewSDKError returns an SDK Error that formats as the given text. -func NewSDKError(err error) SDKError { - if err == nil { - return nil - } - - if e, ok := err.(Error); ok { - return &sdkError{ - statusCode: 0, - customError: &customError{ - msg: e.Msg(), - err: cast(e.Err()), - }, - } - } - return &sdkError{ - customError: &customError{ - msg: err.Error(), - err: nil, - }, - statusCode: 0, - } -} - -// NewSDKErrorWithStatus returns an SDK Error setting the status code. -func NewSDKErrorWithStatus(err error, statusCode int) SDKError { - if err == nil { - return nil - } - - if e, ok := err.(Error); ok { - return &sdkError{ - statusCode: statusCode, - customError: &customError{ - msg: e.Msg(), - err: cast(e.Err()), - }, - } - } - return &sdkError{ - statusCode: statusCode, - customError: &customError{ - msg: err.Error(), - err: nil, - }, - } -} - -// CheckError will check the HTTP response status code and matches it with the given status codes. -// Since multiple status codes can be valid, we can pass multiple status codes to the function. -// The function then checks for errors in the HTTP response. -func CheckError(resp *http.Response, expectedStatusCodes ...int) SDKError { - if resp == nil { - return nil - } - - for _, expectedStatusCode := range expectedStatusCodes { - if resp.StatusCode == expectedStatusCode { - return nil - } - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return NewSDKErrorWithStatus(Wrap(errRespBody, err), resp.StatusCode) - } - var content errorRes - if err := json.Unmarshal(body, &content); err != nil { - return NewSDKErrorWithStatus(err, resp.StatusCode) - } - if content.Err == "" { - return NewSDKErrorWithStatus(New(content.Msg), resp.StatusCode) - } - - return NewSDKErrorWithStatus(Wrap(New(content.Msg), New(content.Err)), resp.StatusCode) -} diff --git a/docker/addons/vault/scripts/pkg/errors/sdk_errors_test.go b/docker/addons/vault/scripts/pkg/errors/sdk_errors_test.go deleted file mode 100644 index ac31a235..00000000 --- a/docker/addons/vault/scripts/pkg/errors/sdk_errors_test.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors_test - -import ( - "bytes" - "fmt" - "io" - "net/http" - "testing" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" -) - -var body = []byte(`{"error":"error","message":"message"}`) - -func TestNewSDKError(t *testing.T) { - cases := []struct { - desc string - err error - }{ - { - desc: "nil error", - err: nil, - }, - { - desc: "non nil error", - err: err0, - }, - { - desc: "non nil error with wrapped error", - err: errors.Wrap(err0, err1), - }, - { - desc: "native error", - err: nat, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - sdk := errors.NewSDKError(c.err) - if c.err != nil { - assert.Equal(t, sdk.StatusCode(), 0) - assert.Equal(t, sdk.Error(), fmt.Sprintf("Status: %s: %s", http.StatusText(0), c.err.Error())) - } - }) - } -} - -func TestNewSDKErrorWithStatus(t *testing.T) { - cases := []struct { - desc string - err error - sc int - }{ - { - desc: "nil error with 0 status code", - err: nil, - sc: 0, - }, - { - desc: "nil error with 404 status code", - err: nil, - sc: 404, - }, - { - desc: "non nil error with 0 status code", - err: err0, - sc: 0, - }, - { - desc: "non nil error with 404 status code", - err: err0, - sc: 404, - }, - { - desc: "non nil error with wrapped error and 0 status code", - err: errors.Wrap(err0, err1), - sc: 0, - }, - { - desc: "non nil error with wrapped error and 404 status code", - err: errors.Wrap(err0, err1), - sc: 404, - }, - { - desc: "native error with 0 status code", - err: nat, - sc: 0, - }, - { - desc: "native error with 404 status code", - err: nat, - sc: 404, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - sdk := errors.NewSDKErrorWithStatus(c.err, c.sc) - if c.err != nil { - assert.Equal(t, sdk.StatusCode(), c.sc) - assert.Equal(t, sdk.Error(), fmt.Sprintf("Status: %s: %s", http.StatusText(c.sc), c.err.Error())) - } - }) - } -} - -func TestCheckError(t *testing.T) { - cases := []struct { - desc string - resp *http.Response - codes []int - err errors.SDKError - }{ - { - desc: "nil response", - resp: nil, - codes: []int{http.StatusOK}, - err: nil, - }, - { - desc: "nil response with 404 status code", - resp: nil, - codes: []int{http.StatusNotFound}, - err: nil, - }, - { - desc: "valid response with 200 status code", - resp: &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusOK}, - err: nil, - }, - { - desc: "valid response with 404 status code", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusNotFound}, - err: nil, - }, - { - desc: "invalid response with 200 status code", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusOK}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.New("message"), errors.New("error")), http.StatusNotFound), - }, - { - desc: "invalid response with 404 status code", - resp: &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusNotFound}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.New("message"), errors.New("error")), http.StatusOK), - }, - { - desc: "valid response with 200 status code and 404 status code", - resp: &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusOK, http.StatusNotFound}, - err: nil, - }, - { - desc: "error in JSON marshalling", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewReader([]byte(`"error":`))), - }, - codes: []int{http.StatusOK}, - err: errors.NewSDKErrorWithStatus(errors.New("invalid character ':' after top-level value"), http.StatusNotFound), - }, - { - desc: "empty error message", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewReader([]byte(`{"error":"","message":""}`))), - }, - codes: []int{http.StatusOK}, - err: errors.NewSDKErrorWithStatus(errors.New(""), http.StatusNotFound), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - sdk := errors.CheckError(c.resp, c.codes...) - assert.Equal(t, sdk, c.err) - if c.err != nil { - assert.Equal(t, sdk, c.err) - assert.Equal(t, sdk.StatusCode(), c.resp.StatusCode) - } - }) - } -} diff --git a/docker/addons/vault/scripts/pkg/errors/service/types.go b/docker/addons/vault/scripts/pkg/errors/service/types.go deleted file mode 100644 index 2eb33ace..00000000 --- a/docker/addons/vault/scripts/pkg/errors/service/types.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package service - -import "github.com/absmach/magistrala/pkg/errors" - -// Wrapper for Service errors. -var ( - // ErrAuthentication indicates failure occurred while authenticating the entity. - ErrAuthentication = errors.New("failed to perform authentication over the entity") - - // ErrAuthorization indicates failure occurred while authorizing the entity. - ErrAuthorization = errors.New("failed to perform authorization over the entity") - - // ErrDomainAuthorization indicates failure occurred while authorizing the domain. - ErrDomainAuthorization = errors.New("failed to perform authorization over the domain") - - // ErrLogin indicates wrong login credentials. - ErrLogin = errors.New("invalid user id or secret") - - // ErrMalformedEntity indicates a malformed entity specification. - ErrMalformedEntity = errors.New("malformed entity specification") - - // ErrNotFound indicates a non-existent entity request. - ErrNotFound = errors.New("entity not found") - - // ErrConflict indicates that entity already exists. - ErrConflict = errors.New("entity already exists") - - // ErrCreateEntity indicates error in creating entity or entities. - ErrCreateEntity = errors.New("failed to create entity") - - // ErrRemoveEntity indicates error in removing entity. - ErrRemoveEntity = errors.New("failed to remove entity") - - // ErrViewEntity indicates error in viewing entity or entities. - ErrViewEntity = errors.New("view entity failed") - - // ErrUpdateEntity indicates error in updating entity or entities. - ErrUpdateEntity = errors.New("update entity failed") - - // ErrInvalidStatus indicates an invalid status. - ErrInvalidStatus = errors.New("invalid status") - - // ErrInvalidRole indicates that an invalid role. - ErrInvalidRole = errors.New("invalid client role") - - // ErrInvalidPolicy indicates that an invalid policy. - ErrInvalidPolicy = errors.New("invalid policy") - - // 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") - - // ErrAddPolicies indicates error in adding policies. - ErrAddPolicies = errors.New("failed to add policies") - - // ErrDeletePolicies indicates error in removing policies. - ErrDeletePolicies = errors.New("failed to remove policies") - - // ErrSearch indicates error in searching clients. - ErrSearch = errors.New("failed to search clients") - - // ErrInvitationAlreadyRejected indicates that the invitation is already rejected. - ErrInvitationAlreadyRejected = errors.New("invitation already rejected") - - // ErrInvitationAlreadyAccepted indicates that the invitation is already accepted. - ErrInvitationAlreadyAccepted = errors.New("invitation already accepted") - - // ErrParentGroupAuthorization indicates failure occurred while authorizing the parent group. - ErrParentGroupAuthorization = errors.New("failed to authorize parent group") - - // ErrMissingUsername indicates that the user's names are missing. - ErrMissingUsername = errors.New("missing usernames") -) diff --git a/docker/addons/vault/scripts/pkg/errors/types.go b/docker/addons/vault/scripts/pkg/errors/types.go deleted file mode 100644 index dab06016..00000000 --- a/docker/addons/vault/scripts/pkg/errors/types.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors - -import "errors" - -var ( - // ErrMalformedEntity indicates a malformed entity specification. - ErrMalformedEntity = New("malformed entity specification") - - // ErrUnsupportedContentType indicates invalid content type. - ErrUnsupportedContentType = errors.New("invalid content type") - - // ErrUnidentified indicates unidentified error. - ErrUnidentified = errors.New("unidentified error") - - // ErrEmptyPath indicates empty file path. - ErrEmptyPath = errors.New("empty file path") - - // ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. - ErrStatusAlreadyAssigned = errors.New("status already assigned") - - // ErrRollbackTx indicates failed to rollback transaction. - ErrRollbackTx = errors.New("failed to rollback transaction") - - // ErrAuthentication indicates failure occurred while authenticating the entity. - ErrAuthentication = errors.New("failed to perform authentication over the entity") - - // ErrAuthorization indicates failure occurred while authorizing the entity. - ErrAuthorization = errors.New("failed to perform authorization over the entity") -) diff --git a/docker/addons/vault/scripts/pkg/events/events.go b/docker/addons/vault/scripts/pkg/events/events.go deleted file mode 100644 index 65845a78..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/mocks/publisher.go b/docker/addons/vault/scripts/pkg/events/mocks/publisher.go deleted file mode 100644 index 7159efd4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/mocks/subscriber.go b/docker/addons/vault/scripts/pkg/events/mocks/subscriber.go deleted file mode 100644 index acad2e96..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/nats/doc.go b/docker/addons/vault/scripts/pkg/events/nats/doc.go deleted file mode 100644 index 9b372ff5..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/nats/publisher.go b/docker/addons/vault/scripts/pkg/events/nats/publisher.go deleted file mode 100644 index e711f970..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/nats/publisher_test.go b/docker/addons/vault/scripts/pkg/events/nats/publisher_test.go deleted file mode 100644 index 20086ea5..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/nats/setup_test.go b/docker/addons/vault/scripts/pkg/events/nats/setup_test.go deleted file mode 100644 index e539aca5..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/nats/subscriber.go b/docker/addons/vault/scripts/pkg/events/nats/subscriber.go deleted file mode 100644 index ca99f831..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/rabbitmq/doc.go b/docker/addons/vault/scripts/pkg/events/rabbitmq/doc.go deleted file mode 100644 index a39b21dc..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/rabbitmq/publisher.go b/docker/addons/vault/scripts/pkg/events/rabbitmq/publisher.go deleted file mode 100644 index ba7d735a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/rabbitmq/publisher_test.go b/docker/addons/vault/scripts/pkg/events/rabbitmq/publisher_test.go deleted file mode 100644 index f1453465..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/rabbitmq/setup_test.go b/docker/addons/vault/scripts/pkg/events/rabbitmq/setup_test.go deleted file mode 100644 index dcbf066a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/rabbitmq/subscriber.go b/docker/addons/vault/scripts/pkg/events/rabbitmq/subscriber.go deleted file mode 100644 index bba6b163..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/redis/doc.go b/docker/addons/vault/scripts/pkg/events/redis/doc.go deleted file mode 100644 index 24925626..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/redis/publisher.go b/docker/addons/vault/scripts/pkg/events/redis/publisher.go deleted file mode 100644 index 77bb537b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/redis/publisher_test.go b/docker/addons/vault/scripts/pkg/events/redis/publisher_test.go deleted file mode 100644 index 5760d79d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/redis/setup_test.go b/docker/addons/vault/scripts/pkg/events/redis/setup_test.go deleted file mode 100644 index 1c98ae8c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/redis/subscriber.go b/docker/addons/vault/scripts/pkg/events/redis/subscriber.go deleted file mode 100644 index dc1f981c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/store/store_nats.go b/docker/addons/vault/scripts/pkg/events/store/store_nats.go deleted file mode 100644 index dd9c2d13..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/store/store_rabbitmq.go b/docker/addons/vault/scripts/pkg/events/store/store_rabbitmq.go deleted file mode 100644 index 233ff78c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/events/store/store_redis.go b/docker/addons/vault/scripts/pkg/events/store/store_redis.go deleted file mode 100644 index 12241c48..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/groups/doc.go b/docker/addons/vault/scripts/pkg/groups/doc.go deleted file mode 100644 index 55e0840d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/groups/errors.go b/docker/addons/vault/scripts/pkg/groups/errors.go deleted file mode 100644 index b6665fa0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/groups/groups.go b/docker/addons/vault/scripts/pkg/groups/groups.go deleted file mode 100644 index 8719424c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/groups/mocks/doc.go b/docker/addons/vault/scripts/pkg/groups/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/groups/mocks/repository.go b/docker/addons/vault/scripts/pkg/groups/mocks/repository.go deleted file mode 100644 index 918b852c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/groups/mocks/service.go b/docker/addons/vault/scripts/pkg/groups/mocks/service.go deleted file mode 100644 index 9fd14189..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/groups/page.go b/docker/addons/vault/scripts/pkg/groups/page.go deleted file mode 100644 index e49ec669..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/groups/status.go b/docker/addons/vault/scripts/pkg/groups/status.go deleted file mode 100644 index 273dbdc7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/grpcclient/client.go b/docker/addons/vault/scripts/pkg/grpcclient/client.go deleted file mode 100644 index 5c295711..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/grpcclient/client_test.go b/docker/addons/vault/scripts/pkg/grpcclient/client_test.go deleted file mode 100644 index acc0ebbe..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/grpcclient/connect.go b/docker/addons/vault/scripts/pkg/grpcclient/connect.go deleted file mode 100644 index e8678ed1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/grpcclient/connect_test.go b/docker/addons/vault/scripts/pkg/grpcclient/connect_test.go deleted file mode 100644 index 4f5e3045..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/grpcclient/doc.go b/docker/addons/vault/scripts/pkg/grpcclient/doc.go deleted file mode 100644 index 1d9ce2fe..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/jaeger/doc.go b/docker/addons/vault/scripts/pkg/jaeger/doc.go deleted file mode 100644 index 54eb78e6..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/jaeger/provider.go b/docker/addons/vault/scripts/pkg/jaeger/provider.go deleted file mode 100644 index 436c6b2c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/README.md b/docker/addons/vault/scripts/pkg/messaging/README.md deleted file mode 100644 index f8b07f8e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/brokers/brokers_nats.go b/docker/addons/vault/scripts/pkg/messaging/brokers/brokers_nats.go deleted file mode 100644 index 1cc25ffe..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/brokers/brokers_rabbitmq.go b/docker/addons/vault/scripts/pkg/messaging/brokers/brokers_rabbitmq.go deleted file mode 100644 index 4ccaec61..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/brokers/tracing/brokers_nats.go b/docker/addons/vault/scripts/pkg/messaging/brokers/tracing/brokers_nats.go deleted file mode 100644 index 608a9f3a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/brokers/tracing/brokers_rabbitmq.go b/docker/addons/vault/scripts/pkg/messaging/brokers/tracing/brokers_rabbitmq.go deleted file mode 100644 index c3d07acb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/handler/logging.go b/docker/addons/vault/scripts/pkg/messaging/handler/logging.go deleted file mode 100644 index ed379aa2..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/handler/metrics.go b/docker/addons/vault/scripts/pkg/messaging/handler/metrics.go deleted file mode 100644 index b9283409..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/handler/tracing.go b/docker/addons/vault/scripts/pkg/messaging/handler/tracing.go deleted file mode 100644 index 5069180a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/message.pb.go b/docker/addons/vault/scripts/pkg/messaging/message.pb.go deleted file mode 100644 index 804b02e7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/message.proto b/docker/addons/vault/scripts/pkg/messaging/message.proto deleted file mode 100644 index c1b13b06..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/mocks/pubsub.go b/docker/addons/vault/scripts/pkg/messaging/mocks/pubsub.go deleted file mode 100644 index daa32f8e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/mqtt/docs.go b/docker/addons/vault/scripts/pkg/messaging/mqtt/docs.go deleted file mode 100644 index f799242b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/mqtt/publisher.go b/docker/addons/vault/scripts/pkg/messaging/mqtt/publisher.go deleted file mode 100644 index 1a2308ba..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/mqtt/pubsub.go b/docker/addons/vault/scripts/pkg/messaging/mqtt/pubsub.go deleted file mode 100644 index 4b642283..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/mqtt/pubsub_test.go b/docker/addons/vault/scripts/pkg/messaging/mqtt/pubsub_test.go deleted file mode 100644 index d0bdafc4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/mqtt/setup_test.go b/docker/addons/vault/scripts/pkg/messaging/mqtt/setup_test.go deleted file mode 100644 index faa8ddfb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/doc.go b/docker/addons/vault/scripts/pkg/messaging/nats/doc.go deleted file mode 100644 index 5c9d8477..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/options.go b/docker/addons/vault/scripts/pkg/messaging/nats/options.go deleted file mode 100644 index 71368290..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/publisher.go b/docker/addons/vault/scripts/pkg/messaging/nats/publisher.go deleted file mode 100644 index 2aca0b84..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/pubsub.go b/docker/addons/vault/scripts/pkg/messaging/nats/pubsub.go deleted file mode 100644 index 7161a0d9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/pubsub_test.go b/docker/addons/vault/scripts/pkg/messaging/nats/pubsub_test.go deleted file mode 100644 index d9e49b49..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/setup_test.go b/docker/addons/vault/scripts/pkg/messaging/nats/setup_test.go deleted file mode 100644 index f140197b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/tracing/doc.go b/docker/addons/vault/scripts/pkg/messaging/nats/tracing/doc.go deleted file mode 100644 index 5f8df0d9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/tracing/publisher.go b/docker/addons/vault/scripts/pkg/messaging/nats/tracing/publisher.go deleted file mode 100644 index 84c2bc5b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/nats/tracing/pubsub.go b/docker/addons/vault/scripts/pkg/messaging/nats/tracing/pubsub.go deleted file mode 100644 index c8f6b0cf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/pubsub.go b/docker/addons/vault/scripts/pkg/messaging/pubsub.go deleted file mode 100644 index 08ea6381..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/doc.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/doc.go deleted file mode 100644 index e331069f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/options.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/options.go deleted file mode 100644 index b0727b34..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/publisher.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/publisher.go deleted file mode 100644 index 3f52d38f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/pubsub.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/pubsub.go deleted file mode 100644 index 59b06a49..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/pubsub_test.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/pubsub_test.go deleted file mode 100644 index 2dcf3ecf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/setup_test.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/setup_test.go deleted file mode 100644 index af8328ac..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/doc.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/doc.go deleted file mode 100644 index 5f8df0d9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/publisher.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/publisher.go deleted file mode 100644 index 6998bf88..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/pubsub.go b/docker/addons/vault/scripts/pkg/messaging/rabbitmq/tracing/pubsub.go deleted file mode 100644 index c8f6b0cf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/tracing/doc.go b/docker/addons/vault/scripts/pkg/messaging/tracing/doc.go deleted file mode 100644 index 5f8df0d9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/messaging/tracing/tracing.go b/docker/addons/vault/scripts/pkg/messaging/tracing/tracing.go deleted file mode 100644 index e3b92514..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/oauth2/doc.go b/docker/addons/vault/scripts/pkg/oauth2/doc.go deleted file mode 100644 index 2d7e006f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/oauth2/google/doc.go b/docker/addons/vault/scripts/pkg/oauth2/google/doc.go deleted file mode 100644 index 74f7ada5..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/oauth2/google/provider.go b/docker/addons/vault/scripts/pkg/oauth2/google/provider.go deleted file mode 100644 index 0c3c531c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/oauth2/mocks/provider.go b/docker/addons/vault/scripts/pkg/oauth2/mocks/provider.go deleted file mode 100644 index 1f911984..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/oauth2/oauth2.go b/docker/addons/vault/scripts/pkg/oauth2/oauth2.go deleted file mode 100644 index f788ef9f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/policies/doc.go b/docker/addons/vault/scripts/pkg/policies/doc.go deleted file mode 100644 index 59958f84..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/policies/evaluator.go b/docker/addons/vault/scripts/pkg/policies/evaluator.go deleted file mode 100644 index c6288697..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/policies/mocks/evaluator.go b/docker/addons/vault/scripts/pkg/policies/mocks/evaluator.go deleted file mode 100644 index 82afcc37..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/policies/mocks/service.go b/docker/addons/vault/scripts/pkg/policies/mocks/service.go deleted file mode 100644 index 7cfddcc8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/policies/service.go b/docker/addons/vault/scripts/pkg/policies/service.go deleted file mode 100644 index 446926c1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/policies/spicedb/doc.go b/docker/addons/vault/scripts/pkg/policies/spicedb/doc.go deleted file mode 100644 index beac2694..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/policies/spicedb/evaluator.go b/docker/addons/vault/scripts/pkg/policies/spicedb/evaluator.go deleted file mode 100644 index e40b7207..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/policies/spicedb/service.go b/docker/addons/vault/scripts/pkg/policies/spicedb/service.go deleted file mode 100644 index 6abbf596..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/postgres/common.go b/docker/addons/vault/scripts/pkg/postgres/common.go deleted file mode 100644 index 3f394f77..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/postgres/doc.go b/docker/addons/vault/scripts/pkg/postgres/doc.go deleted file mode 100644 index 58e34057..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/postgres/errors.go b/docker/addons/vault/scripts/pkg/postgres/errors.go deleted file mode 100644 index 541f7f2e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/postgres/postgres.go b/docker/addons/vault/scripts/pkg/postgres/postgres.go deleted file mode 100644 index 975ed1ee..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/postgres/tracing.go b/docker/addons/vault/scripts/pkg/postgres/tracing.go deleted file mode 100644 index dfd4e934..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/prometheus/doc.go b/docker/addons/vault/scripts/pkg/prometheus/doc.go deleted file mode 100644 index 2d654b8a..00000000 --- a/docker/addons/vault/scripts/pkg/prometheus/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package prometheus provides a framework for defining and collecting metrics -// for prometheus. -package prometheus diff --git a/docker/addons/vault/scripts/pkg/prometheus/metrics.go b/docker/addons/vault/scripts/pkg/prometheus/metrics.go deleted file mode 100644 index 333c8614..00000000 --- a/docker/addons/vault/scripts/pkg/prometheus/metrics.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package prometheus - -import ( - kitprometheus "github.com/go-kit/kit/metrics/prometheus" - stdprometheus "github.com/prometheus/client_golang/prometheus" -) - -// MakeMetrics returns an instance of Prometheus implementations for metrics. -// It returns a request counter and a request latency summary. -// -// counter, latency := metrics.MakeMetrics("demo-service", "api") -func MakeMetrics(namespace, subsystem string) (*kitprometheus.Counter, *kitprometheus.Summary) { - counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "request_count", - Help: "Number of requests received.", - }, []string{"method"}) - latency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ - Namespace: namespace, - Subsystem: subsystem, - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - Name: "request_latency_microseconds", - Help: "Total duration of requests in microseconds.", - }, []string{"method"}) - - return counter, latency -} diff --git a/docker/addons/vault/scripts/pkg/sdk/README.md b/docker/addons/vault/scripts/pkg/sdk/README.md deleted file mode 100644 index c5a945c7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/README.md b/docker/addons/vault/scripts/pkg/sdk/go/README.md deleted file mode 100644 index f82f782f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/bootstrap.go b/docker/addons/vault/scripts/pkg/sdk/go/bootstrap.go deleted file mode 100644 index 7fd9ba96..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/bootstrap_test.go b/docker/addons/vault/scripts/pkg/sdk/go/bootstrap_test.go deleted file mode 100644 index b091bc97..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/certs.go b/docker/addons/vault/scripts/pkg/sdk/go/certs.go deleted file mode 100644 index 35d68509..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/certs_test.go b/docker/addons/vault/scripts/pkg/sdk/go/certs_test.go deleted file mode 100644 index 13055db6..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/channels.go b/docker/addons/vault/scripts/pkg/sdk/go/channels.go deleted file mode 100644 index d68b92c8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/channels_test.go b/docker/addons/vault/scripts/pkg/sdk/go/channels_test.go deleted file mode 100644 index d4b02dc6..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/consumers.go b/docker/addons/vault/scripts/pkg/sdk/go/consumers.go deleted file mode 100644 index ad3cdb3b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/consumers_test.go b/docker/addons/vault/scripts/pkg/sdk/go/consumers_test.go deleted file mode 100644 index f2ce2891..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/doc.go b/docker/addons/vault/scripts/pkg/sdk/go/doc.go deleted file mode 100644 index b060484b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/domains.go b/docker/addons/vault/scripts/pkg/sdk/go/domains.go deleted file mode 100644 index 70b82eff..00000000 --- a/docker/addons/vault/scripts/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/%s", domainsEndpoint, 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/docker/addons/vault/scripts/pkg/sdk/go/domains_test.go b/docker/addons/vault/scripts/pkg/sdk/go/domains_test.go deleted file mode 100644 index ea1c484e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/groups.go b/docker/addons/vault/scripts/pkg/sdk/go/groups.go deleted file mode 100644 index 0dcb0ee0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/groups_test.go b/docker/addons/vault/scripts/pkg/sdk/go/groups_test.go deleted file mode 100644 index 82271465..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/health.go b/docker/addons/vault/scripts/pkg/sdk/go/health.go deleted file mode 100644 index 4334b294..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/health_test.go b/docker/addons/vault/scripts/pkg/sdk/go/health_test.go deleted file mode 100644 index f30cf045..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/invitations.go b/docker/addons/vault/scripts/pkg/sdk/go/invitations.go deleted file mode 100644 index 97c42255..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/invitations_test.go b/docker/addons/vault/scripts/pkg/sdk/go/invitations_test.go deleted file mode 100644 index cc662a37..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/journal.go b/docker/addons/vault/scripts/pkg/sdk/go/journal.go deleted file mode 100644 index a64b4174..00000000 --- a/docker/addons/vault/scripts/pkg/sdk/go/journal.go +++ /dev/null @@ -1,57 +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 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) - } - - url, err := sdk.withQueryParams(sdk.journalURL, fmt.Sprintf("%s/%s/%s", journalEndpoint, entityType, entityID), 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/docker/addons/vault/scripts/pkg/sdk/go/journal_test.go b/docker/addons/vault/scripts/pkg/sdk/go/journal_test.go deleted file mode 100644 index 5c4701a2..00000000 --- a/docker/addons/vault/scripts/pkg/sdk/go/journal_test.go +++ /dev/null @@ -1,257 +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" - "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) { - svc := new(mocks.Service) - - logger := mglog.NewMock() - mux := api.MakeHandler(svc, logger, "journal-log", "test") - return httptest.NewServer(mux), svc -} - -func TestRetrieveJournal(t *testing.T) { - js, svc := setupJournal() - defer js.Close() - - testJournal := generateTestJournal(t) - validEntityType := "user" - - sdkConf := sdk.Config{ - JournalURL: js.URL, - } - - mgsdk := sdk.NewSDK(sdkConf) - - cases := []struct { - desc string - token string - entityType string - entityID string - pageMeta sdk.PageMetadata - svcReq journal.Page - svcRes journal.JournalsPage - svcErr error - response sdk.JournalsPage - err error - }{ - { - desc: "retrieve journal successfully", - token: validToken, - entityType: validEntityType, - entityID: validID, - 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 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.UserEntity, - Direction: "desc", - }, - svcRes: journal.JournalsPage{}, - svcErr: 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(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - }, - { - desc: "retrieve journal with invalid entity type", - token: validToken, - entityType: "invalid", - entityID: validID, - 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: "", - 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, - 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, - 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, - 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, - 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{{ - 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) { - svcCall := svc.On("RetrieveAll", mock.Anything, tc.token, tc.svcReq).Return(tc.svcRes, tc.svcErr) - resp, err := mgsdk.Journal(tc.entityType, tc.entityID, 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.token, tc.svcReq) - assert.True(t, ok) - } - svcCall.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/docker/addons/vault/scripts/pkg/sdk/go/message.go b/docker/addons/vault/scripts/pkg/sdk/go/message.go deleted file mode 100644 index 0ff16e8d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/message_test.go b/docker/addons/vault/scripts/pkg/sdk/go/message_test.go deleted file mode 100644 index 3f5ad3df..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/metadata.go b/docker/addons/vault/scripts/pkg/sdk/go/metadata.go deleted file mode 100644 index b9341560..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/requests.go b/docker/addons/vault/scripts/pkg/sdk/go/requests.go deleted file mode 100644 index 21e8f62a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/responses.go b/docker/addons/vault/scripts/pkg/sdk/go/responses.go deleted file mode 100644 index c51f0426..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/sdk.go b/docker/addons/vault/scripts/pkg/sdk/go/sdk.go deleted file mode 100644 index 8cb1bf6f..00000000 --- a/docker/addons/vault/scripts/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", PageMetadata{Offset: 0, Limit: 10, Operation: "users.create"}, "token") - // fmt.Println(journals) - Journal(entityType, entityID 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/docker/addons/vault/scripts/pkg/sdk/go/setup_test.go b/docker/addons/vault/scripts/pkg/sdk/go/setup_test.go deleted file mode 100644 index be8b586c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/things.go b/docker/addons/vault/scripts/pkg/sdk/go/things.go deleted file mode 100644 index a8cd234f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/things_test.go b/docker/addons/vault/scripts/pkg/sdk/go/things_test.go deleted file mode 100644 index 5a83b63f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/tokens.go b/docker/addons/vault/scripts/pkg/sdk/go/tokens.go deleted file mode 100644 index 6f79aeec..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/tokens_test.go b/docker/addons/vault/scripts/pkg/sdk/go/tokens_test.go deleted file mode 100644 index 809d4536..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/users.go b/docker/addons/vault/scripts/pkg/sdk/go/users.go deleted file mode 100644 index 125b8c13..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/go/users_test.go b/docker/addons/vault/scripts/pkg/sdk/go/users_test.go deleted file mode 100644 index 71500053..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/sdk/mocks/sdk.go b/docker/addons/vault/scripts/pkg/sdk/mocks/sdk.go deleted file mode 100644 index 9ef786d7..00000000 --- a/docker/addons/vault/scripts/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, pm, token -func (_m *SDK) Journal(entityType string, entityID string, pm sdk.PageMetadata, token string) (sdk.JournalsPage, error) { - ret := _m.Called(entityType, entityID, 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, sdk.PageMetadata, string) (sdk.JournalsPage, error)); ok { - return rf(entityType, entityID, pm, token) - } - if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) sdk.JournalsPage); ok { - r0 = rf(entityType, entityID, pm, token) - } else { - r0 = ret.Get(0).(sdk.JournalsPage) - } - - if rf, ok := ret.Get(1).(func(string, string, sdk.PageMetadata, string) error); ok { - r1 = rf(entityType, entityID, 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/docker/addons/vault/scripts/pkg/server/coap/coap.go b/docker/addons/vault/scripts/pkg/server/coap/coap.go deleted file mode 100644 index 62e7963e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/server/coap/doc.go b/docker/addons/vault/scripts/pkg/server/coap/doc.go deleted file mode 100644 index 5abb027a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/server/doc.go b/docker/addons/vault/scripts/pkg/server/doc.go deleted file mode 100644 index d5514a24..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/server/grpc/doc.go b/docker/addons/vault/scripts/pkg/server/grpc/doc.go deleted file mode 100644 index 7e56327f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/server/grpc/grpc.go b/docker/addons/vault/scripts/pkg/server/grpc/grpc.go deleted file mode 100644 index c57c9a67..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/server/http/doc.go b/docker/addons/vault/scripts/pkg/server/http/doc.go deleted file mode 100644 index 769fa7d4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/server/http/http.go b/docker/addons/vault/scripts/pkg/server/http/http.go deleted file mode 100644 index d8a33332..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/server/server.go b/docker/addons/vault/scripts/pkg/server/server.go deleted file mode 100644 index 1ae357e3..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/README.md b/docker/addons/vault/scripts/pkg/transformers/README.md deleted file mode 100644 index 44a21202..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/doc.go b/docker/addons/vault/scripts/pkg/transformers/doc.go deleted file mode 100644 index 59ccb9a1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/json/README.md b/docker/addons/vault/scripts/pkg/transformers/json/README.md deleted file mode 100644 index 4e34ed51..00000000 --- a/docker/addons/vault/scripts/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/<channelID>/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/<channelID>/messages/home/temperature/* -``` diff --git a/docker/addons/vault/scripts/pkg/transformers/json/doc.go b/docker/addons/vault/scripts/pkg/transformers/json/doc.go deleted file mode 100644 index dc1b6c39..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/json/example_test.go b/docker/addons/vault/scripts/pkg/transformers/json/example_test.go deleted file mode 100644 index 27eaa276..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/json/message.go b/docker/addons/vault/scripts/pkg/transformers/json/message.go deleted file mode 100644 index ab5b1b6d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/json/time.go b/docker/addons/vault/scripts/pkg/transformers/json/time.go deleted file mode 100644 index 6495ea8f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/json/transformer.go b/docker/addons/vault/scripts/pkg/transformers/json/transformer.go deleted file mode 100644 index cf266679..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/json/transformer_test.go b/docker/addons/vault/scripts/pkg/transformers/json/transformer_test.go deleted file mode 100644 index 6856a94e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/senml/README.md b/docker/addons/vault/scripts/pkg/transformers/senml/README.md deleted file mode 100644 index d5dbd00e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/senml/doc.go b/docker/addons/vault/scripts/pkg/transformers/senml/doc.go deleted file mode 100644 index b7eceffe..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/senml/message.go b/docker/addons/vault/scripts/pkg/transformers/senml/message.go deleted file mode 100644 index 7278abd0..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/senml/transformer.go b/docker/addons/vault/scripts/pkg/transformers/senml/transformer.go deleted file mode 100644 index cce7f31f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/senml/transformer_test.go b/docker/addons/vault/scripts/pkg/transformers/senml/transformer_test.go deleted file mode 100644 index defed273..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/transformer.go b/docker/addons/vault/scripts/pkg/transformers/transformer.go deleted file mode 100644 index aa538876..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/transformers/transformer_test.go b/docker/addons/vault/scripts/pkg/transformers/transformer_test.go deleted file mode 100644 index bcaa4125..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/ulid/README.md b/docker/addons/vault/scripts/pkg/ulid/README.md deleted file mode 100644 index 208b3111..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/ulid/doc.go b/docker/addons/vault/scripts/pkg/ulid/doc.go deleted file mode 100644 index 622ced2e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/ulid/ulid.go b/docker/addons/vault/scripts/pkg/ulid/ulid.go deleted file mode 100644 index a3c6fbc9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/uuid/README.md b/docker/addons/vault/scripts/pkg/uuid/README.md deleted file mode 100644 index e19a38f2..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/uuid/doc.go b/docker/addons/vault/scripts/pkg/uuid/doc.go deleted file mode 100644 index 7262babf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/uuid/mock.go b/docker/addons/vault/scripts/pkg/uuid/mock.go deleted file mode 100644 index 04052512..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/pkg/uuid/uuid.go b/docker/addons/vault/scripts/pkg/uuid/uuid.go deleted file mode 100644 index 872cc2c6..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/provision/README.md b/docker/addons/vault/scripts/provision/README.md deleted file mode 100644 index 73f6c863..00000000 --- a/docker/addons/vault/scripts/provision/README.md +++ /dev/null @@ -1,194 +0,0 @@ -# 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. - -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 `<external_id>` and `<external_key>` 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. - -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`. - -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). - -## 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_PROVISION_LOG_LEVEL | Service log level | debug | -| MG_PROVISION_USER | User (email) for accessing Magistrala | <user@example.com> | -| 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 | <http://users:9002> | -| MG_PROVISION_THINGS_LOCATION | Things service URL | <http://things:9000> | -| MG_PROVISION_BS_SVC_URL | Magistrala Bootstrap service URL | <http://bootstrap:9013> | -| MG_PROVISION_CERTS_SVC_URL | Certificates service URL | <http://certs:9019> | -| 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. - -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. - -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]. -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" - - [things.metadata] - external_id = "xxxxxx" - - -[[channels]] - name = "control-channel" - - [channels.metadata] - type = "control" - -[[channels]] - name = "data-channel" - - [channels.metadata] - type = "data" - -[[channels]] - name = "export-channel" - - [channels.metadata] - type = "data" -``` - -## 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`. - -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)) -- `Authorization: Bearer Token` - request authorization header containing either users token. - -## Running - -Provision service can be run as a standalone or in docker composition as addon to the core docker composition. - -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 -``` - -Docker composition: - -```bash -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:<MG_PROVISION_HTTP_PORT>/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:<MG_PROVISION_HTTP_PORT>/mapping -H "Authorization: Bearer <token|api_key>" -H 'Content-Type: application/json' -d '{"external_id": "<external_id>", "external_key": "<external_key>"}' -``` - -Or if you want to specify a name for thing different than in `config.toml` you can specify post data as: - -```json -{ - "name": "<name>", - "external_id": "<external_id>", - "external_key": "<external_key>" -} -``` - -Response contains created things, channels and certificates if any: - -```json -{ - "things": [ - { - "id": "c22b0c0f-8c03-40da-a06b-37ed3a72c8d1", - "name": "thing", - "key": "007cce56-e0eb-40d6-b2b9-ed348a97d1eb", - "metadata": { - "external_id": "33:52:79:C3:43" - } - } - ], - "channels": [ - { - "id": "064c680e-181b-4b58-975e-6983313a5170", - "name": "control-channel", - "metadata": { - "type": "control" - } - }, - { - "id": "579da92d-6078-4801-a18a-dd1cfa2aa44f", - "name": "data-channel", - "metadata": { - "type": "data" - } - } - ], - "whitelisted": { - "c22b0c0f-8c03-40da-a06b-37ed3a72c8d1": true - } -} -``` - -## Certificates - -Provision service has `/certs` endpoint that can be used to generate certificates for things 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 - -```bash -curl -s -X POST http://localhost:8190/certs -H "Authorization: Bearer <users_token>" -H 'Content-Type: application/json' -d '{"thing_id": "<thing_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" -} -``` - -[magistrala]: https://github.com/absmach/magistrala -[bootstrap]: https://github.com/absmach/magistrala/tree/master/bootstrap -[export]: https://github.com/absmach/export -[agent]: https://github.com/absmach/agent -[mgxui]: https://github.com/absmach/magistrala/ui diff --git a/docker/addons/vault/scripts/provision/api/doc.go b/docker/addons/vault/scripts/provision/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/provision/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/docker/addons/vault/scripts/provision/api/endpoint.go b/docker/addons/vault/scripts/provision/api/endpoint.go deleted file mode 100644 index ec21527a..00000000 --- a/docker/addons/vault/scripts/provision/api/endpoint.go +++ /dev/null @@ -1,54 +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/absmach/magistrala/provision" - "github.com/go-kit/kit/endpoint" -) - -func doProvision(svc provision.Service) endpoint.Endpoint { - return func(_ context.Context, request interface{}) (interface{}, error) { - req := request.(provisionReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - res, err := svc.Provision(req.domainID, req.token, req.Name, req.ExternalID, req.ExternalKey) - if err != nil { - return nil, err - } - - provisionResponse := provisionRes{ - Things: res.Things, - Channels: res.Channels, - ClientCert: res.ClientCert, - ClientKey: res.ClientKey, - CACert: res.CACert, - Whitelisted: res.Whitelisted, - } - - return provisionResponse, nil - } -} - -func getMapping(svc provision.Service) endpoint.Endpoint { - return func(_ context.Context, request interface{}) (interface{}, error) { - req := request.(mappingReq) - if err := req.validate(); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - res, err := svc.Mapping(req.token) - if err != nil { - return nil, err - } - - return mappingRes{Data: res}, nil - } -} diff --git a/docker/addons/vault/scripts/provision/api/endpoint_test.go b/docker/addons/vault/scripts/provision/api/endpoint_test.go deleted file mode 100644 index 369be0d9..00000000 --- a/docker/addons/vault/scripts/provision/api/endpoint_test.go +++ /dev/null @@ -1,223 +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" - 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" - "github.com/stretchr/testify/assert" -) - -var ( - validToken = "valid" - validContenType = "application/json" - validID = 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 newProvisionServer() (*httptest.Server, *mocks.Service) { - svc := new(mocks.Service) - - logger := mglog.NewMock() - mux := api.MakeHandler(svc, logger, "test") - return httptest.NewServer(mux), svc -} - -func TestProvision(t *testing.T) { - is, svc := newProvisionServer() - - cases := []struct { - desc string - token string - domainID string - data string - contentType string - status int - svcErr error - }{ - { - desc: "valid request", - token: validToken, - domainID: validID, - data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID), - status: http.StatusCreated, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "request with empty external id", - token: validToken, - domainID: validID, - data: fmt.Sprintf(`{"name": "test", "external_key": "%s"}`, validID), - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "request with empty external key", - token: validToken, - domainID: validID, - data: fmt.Sprintf(`{"name": "test", "external_id": "%s"}`, validID), - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "empty token", - token: "", - domainID: validID, - data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID), - status: http.StatusCreated, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid content type", - token: validToken, - domainID: validID, - data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID), - status: http.StatusUnsupportedMediaType, - contentType: "text/plain", - svcErr: nil, - }, - { - desc: "invalid request", - token: validToken, - domainID: validID, - data: `data`, - status: http.StatusBadRequest, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "service error", - token: validToken, - domainID: validID, - data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID), - status: http.StatusForbidden, - contentType: validContenType, - svcErr: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall := svc.On("Provision", validID, tc.token, "test", validID, validID).Return(provision.Result{}, tc.svcErr) - req := testRequest{ - client: is.Client(), - method: http.MethodPost, - url: is.URL + fmt.Sprintf("/%s/mapping", tc.domainID), - token: tc.token, - contentType: tc.contentType, - body: strings.NewReader(tc.data), - } - - resp, err := req.make() - assert.Nil(t, err, tc.desc) - assert.Equal(t, tc.status, resp.StatusCode, tc.desc) - repocall.Unset() - }) - } -} - -func TestMapping(t *testing.T) { - is, svc := newProvisionServer() - - cases := []struct { - desc string - token string - domainID string - contentType string - status int - svcErr error - }{ - { - desc: "valid request", - token: validToken, - domainID: validID, - status: http.StatusOK, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "empty token", - token: "", - domainID: validID, - status: http.StatusUnauthorized, - contentType: validContenType, - svcErr: nil, - }, - { - desc: "invalid content type", - token: validToken, - domainID: validID, - status: http.StatusUnsupportedMediaType, - contentType: "text/plain", - svcErr: nil, - }, - { - desc: "service error", - token: validToken, - domainID: validID, - status: http.StatusForbidden, - contentType: validContenType, - svcErr: svcerr.ErrAuthorization, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - repocall := svc.On("Mapping", tc.token).Return(map[string]interface{}{}, tc.svcErr) - req := testRequest{ - client: is.Client(), - method: http.MethodGet, - url: is.URL + fmt.Sprintf("/%s/mapping", tc.domainID), - token: tc.token, - contentType: tc.contentType, - } - - resp, err := req.make() - assert.Nil(t, err, tc.desc) - assert.Equal(t, tc.status, resp.StatusCode, tc.desc) - repocall.Unset() - }) - } -} diff --git a/docker/addons/vault/scripts/provision/api/logging.go b/docker/addons/vault/scripts/provision/api/logging.go deleted file mode 100644 index 4d19af3c..00000000 --- a/docker/addons/vault/scripts/provision/api/logging.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "log/slog" - "time" - - "github.com/absmach/magistrala/provision" -) - -var _ provision.Service = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc provision.Service -} - -// NewLoggingMiddleware adds logging facilities to the core service. -func NewLoggingMiddleware(svc provision.Service, logger *slog.Logger) provision.Service { - return &loggingMiddleware{logger, svc} -} - -func (lm *loggingMiddleware) Provision(domainID, token, name, externalID, externalKey string) (res provision.Result, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("name", name), - slog.String("external_id", externalID), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Provision failed", args...) - return - } - lm.logger.Info("Provision completed successfully", args...) - }(time.Now()) - - return lm.svc.Provision(domainID, token, name, externalID, externalKey) -} - -func (lm *loggingMiddleware) Cert(domainID, token, thingID, 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("ttl", duration), - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Thing certificate failed to create successfully", args...) - return - } - lm.logger.Info("Thing certificate created successfully", args...) - }(time.Now()) - - return lm.svc.Cert(domainID, token, thingID, duration) -} - -func (lm *loggingMiddleware) Mapping(token string) (res map[string]interface{}, 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("Mapping failed", args...) - return - } - lm.logger.Info("Mapping completed successfully", args...) - }(time.Now()) - - return lm.svc.Mapping(token) -} diff --git a/docker/addons/vault/scripts/provision/api/requests.go b/docker/addons/vault/scripts/provision/api/requests.go deleted file mode 100644 index 847a235f..00000000 --- a/docker/addons/vault/scripts/provision/api/requests.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import "github.com/absmach/magistrala/pkg/apiutil" - -type provisionReq struct { - token string - domainID string - Name string `json:"name"` - ExternalID string `json:"external_id"` - ExternalKey string `json:"external_key"` -} - -func (req provisionReq) validate() error { - if req.ExternalID == "" { - return apiutil.ErrMissingID - } - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - - if req.ExternalKey == "" { - return apiutil.ErrBearerKey - } - - if req.Name == "" { - return apiutil.ErrMissingName - } - - return nil -} - -type mappingReq struct { - token string - domainID string -} - -func (req mappingReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - if req.domainID == "" { - return apiutil.ErrMissingDomainID - } - return nil -} diff --git a/docker/addons/vault/scripts/provision/api/requests_test.go b/docker/addons/vault/scripts/provision/api/requests_test.go deleted file mode 100644 index 5cc5428a..00000000 --- a/docker/addons/vault/scripts/provision/api/requests_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "testing" - - "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" -) - -func TestProvisioReq(t *testing.T) { - cases := []struct { - desc string - req provisionReq - err error - }{ - { - desc: "valid request", - req: provisionReq{ - token: "token", - domainID: testsutil.GenerateUUID(t), - Name: "name", - ExternalID: testsutil.GenerateUUID(t), - ExternalKey: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "empty external id", - req: provisionReq{ - token: "token", - domainID: testsutil.GenerateUUID(t), - Name: "name", - ExternalID: "", - ExternalKey: testsutil.GenerateUUID(t), - }, - err: apiutil.ErrMissingID, - }, - { - desc: "empty domain id", - req: provisionReq{ - token: "token", - domainID: "", - Name: "name", - ExternalID: testsutil.GenerateUUID(t), - ExternalKey: testsutil.GenerateUUID(t), - }, - err: apiutil.ErrMissingDomainID, - }, - { - desc: "empty external key", - req: provisionReq{ - token: "token", - domainID: testsutil.GenerateUUID(t), - Name: "name", - ExternalID: testsutil.GenerateUUID(t), - ExternalKey: "", - }, - err: apiutil.ErrBearerKey, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err)) - } -} - -func TestMappingReq(t *testing.T) { - cases := []struct { - desc string - req mappingReq - err error - }{ - { - desc: "valid request", - req: mappingReq{ - token: "token", - domainID: testsutil.GenerateUUID(t), - }, - err: nil, - }, - { - desc: "empty token", - req: mappingReq{ - token: "", - domainID: testsutil.GenerateUUID(t), - }, - err: apiutil.ErrBearerToken, - }, - { - desc: "empty domain id", - req: mappingReq{ - token: "token", - domainID: "", - }, - err: apiutil.ErrMissingDomainID, - }, - } - - for _, tc := range cases { - err := tc.req.validate() - assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err)) - } -} diff --git a/docker/addons/vault/scripts/provision/api/responses.go b/docker/addons/vault/scripts/provision/api/responses.go deleted file mode 100644 index 87c10522..00000000 --- a/docker/addons/vault/scripts/provision/api/responses.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "encoding/json" - "net/http" - - "github.com/absmach/magistrala" - sdk "github.com/absmach/magistrala/pkg/sdk/go" -) - -var _ magistrala.Response = (*provisionRes)(nil) - -type provisionRes struct { - Things []sdk.Thing `json:"things"` - Channels []sdk.Channel `json:"channels"` - ClientCert map[string]string `json:"client_cert,omitempty"` - ClientKey map[string]string `json:"client_key,omitempty"` - CACert string `json:"ca_cert,omitempty"` - Whitelisted map[string]bool `json:"whitelisted,omitempty"` -} - -func (res provisionRes) Code() int { - return http.StatusCreated -} - -func (res provisionRes) Headers() map[string]string { - return map[string]string{} -} - -func (res provisionRes) Empty() bool { - return false -} - -type mappingRes struct { - Data interface{} -} - -func (res mappingRes) Code() int { - return http.StatusOK -} - -func (res mappingRes) Headers() map[string]string { - return map[string]string{} -} - -func (res mappingRes) Empty() bool { - return false -} - -func (res mappingRes) MarshalJSON() ([]byte, error) { - return json.Marshal(res.Data) -} diff --git a/docker/addons/vault/scripts/provision/api/transport.go b/docker/addons/vault/scripts/provision/api/transport.go deleted file mode 100644 index ae26a86b..00000000 --- a/docker/addons/vault/scripts/provision/api/transport.go +++ /dev/null @@ -1,83 +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/internal/api" - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/provision" - "github.com/go-chi/chi/v5" - kithttp "github.com/go-kit/kit/transport/http" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -const ( - contentType = "application/json" -) - -// MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string) http.Handler { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), - } - - r := chi.NewRouter() - - r.Route("/{domainID}", func(r chi.Router) { - r.Route("/mapping", func(r chi.Router) { - r.Post("/", kithttp.NewServer( - doProvision(svc), - decodeProvisionRequest, - api.EncodeResponse, - opts..., - ).ServeHTTP) - r.Get("/", kithttp.NewServer( - getMapping(svc), - decodeMappingRequest, - api.EncodeResponse, - opts..., - ).ServeHTTP) - }) - }) - r.Handle("/metrics", promhttp.Handler()) - r.Get("/health", magistrala.Health("provision", instanceID)) - - return r -} - -func decodeProvisionRequest(_ context.Context, r *http.Request) (interface{}, error) { - if r.Header.Get("Content-Type") != contentType { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := provisionReq{ - 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 decodeMappingRequest(_ context.Context, r *http.Request) (interface{}, error) { - if r.Header.Get("Content-Type") != contentType { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) - } - - req := mappingReq{ - token: apiutil.ExtractBearerToken(r), - domainID: chi.URLParam(r, "domainID"), - } - - return req, nil -} diff --git a/docker/addons/vault/scripts/provision/config.go b/docker/addons/vault/scripts/provision/config.go deleted file mode 100644 index 7540e440..00000000 --- a/docker/addons/vault/scripts/provision/config.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package provision - -import ( - "fmt" - "os" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/groups" - "github.com/absmach/magistrala/things" - "github.com/pelletier/go-toml" -) - -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"` -} - -// 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"` - Content map[string]interface{} `toml:"content"` -} - -// Gateway represetns the Gateway config. -type Gateway struct { - Type string `toml:"type" json:"type"` - ExternalID string `toml:"external_id" json:"external_id"` - ExternalKey string `toml:"external_key" json:"external_key"` - CtrlChannelID string `toml:"ctrl_channel_id" json:"ctrl_channel_id"` - DataChannelID string `toml:"data_channel_id" json:"data_channel_id"` - ExportChannelID string `toml:"export_channel_id" json:"export_channel_id"` - CfgID string `toml:"cfg_id" json:"cfg_id"` -} - -// Cert represetns the certificate config. -type Cert struct { - TTL string `json:"ttl" toml:"ttl" env:"MG_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:""` -} - -// Save - store config in a file. -func Save(c Config, file string) error { - if file == "" { - return errors.ErrEmptyPath - } - - b, err := toml.Marshal(c) - if err != nil { - return errors.Wrap(errFailedToReadConfig, err) - } - if err := os.WriteFile(file, b, 0o644); err != nil { - return fmt.Errorf("Error writing toml: %w", err) - } - - return nil -} - -// Read - retrieve config from a file. -func Read(file string) (Config, error) { - data, err := os.ReadFile(file) - if err != nil { - return Config{}, errors.Wrap(errFailedToReadConfig, err) - } - - var c Config - if err := toml.Unmarshal(data, &c); err != nil { - return Config{}, fmt.Errorf("Error unmarshaling toml: %w", err) - } - - return c, nil -} diff --git a/docker/addons/vault/scripts/provision/config_test.go b/docker/addons/vault/scripts/provision/config_test.go deleted file mode 100644 index 6857b826..00000000 --- a/docker/addons/vault/scripts/provision/config_test.go +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package provision_test - -import ( - "fmt" - "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/pelletier/go-toml" - "github.com/stretchr/testify/assert" -) - -var ( - validConfig = provision.Config{ - Server: provision.ServiceConf{ - Port: "9016", - LogLevel: "info", - TLS: false, - }, - Bootstrap: provision.Bootstrap{ - X509Provision: true, - Provision: true, - AutoWhiteList: true, - Content: map[string]interface{}{ - "test": "test", - }, - }, - Things: []things.Client{ - { - ID: "1234567890", - Name: "test", - Tags: []string{"test"}, - Metadata: map[string]interface{}{ - "test": "test", - }, - Permissions: []string{"test"}, - }, - }, - Channels: []groups.Group{ - { - ID: "1234567890", - Name: "test", - Metadata: map[string]interface{}{ - "test": "test", - }, - Permissions: []string{"test"}, - }, - }, - Cert: provision.Cert{}, - SendTelemetry: true, - InstanceID: "1234567890", - } - validConfigFile = "./config.toml" - invalidConfig = provision.Config{ - Bootstrap: provision.Bootstrap{ - Content: map[string]interface{}{ - "invalid": make(chan int), - }, - }, - } - invalidConfigFile = "./invalid.toml" -) - -func createInvalidConfigFile() error { - config := map[string]interface{}{ - "invalid": "invalid", - } - b, err := toml.Marshal(config) - if err != nil { - return err - } - - f, err := os.Create(invalidConfigFile) - if err != nil { - return err - } - - if _, err = f.Write(b); err != nil { - return err - } - - return nil -} - -func createValidConfigFile() error { - b, err := toml.Marshal(validConfig) - if err != nil { - return err - } - - f, err := os.Create(validConfigFile) - if err != nil { - return err - } - - if _, err = f.Write(b); err != nil { - return err - } - - return nil -} - -func TestSave(t *testing.T) { - cases := []struct { - desc string - cfg provision.Config - file string - err error - }{ - { - desc: "save valid config", - cfg: validConfig, - file: validConfigFile, - err: nil, - }, - { - desc: "save valid config with empty file name", - cfg: validConfig, - file: "", - err: errors.ErrEmptyPath, - }, - { - desc: "save empty config with valid config file", - cfg: provision.Config{}, - file: validConfigFile, - err: nil, - }, - { - desc: "save empty config with empty file name", - cfg: provision.Config{}, - file: "", - err: errors.ErrEmptyPath, - }, - { - desc: "save invalid config", - cfg: invalidConfig, - file: invalidConfigFile, - err: errors.New("failed to read config file"), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - err := provision.Save(c.cfg, c.file) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected: %v, got: %v", c.err, err)) - - if err == nil { - defer func() { - if c.file != "" { - err := os.Remove(c.file) - assert.NoError(t, err) - } - }() - - cfg, err := provision.Read(c.file) - if c.cfg.Bootstrap.Content == nil { - c.cfg.Bootstrap.Content = map[string]interface{}{} - } - assert.Equal(t, c.err, err) - assert.Equal(t, c.cfg, cfg) - } - }) - } -} - -func TestRead(t *testing.T) { - err := createInvalidConfigFile() - assert.NoError(t, err) - - err = createValidConfigFile() - assert.NoError(t, err) - - t.Cleanup(func() { - err := os.Remove(invalidConfigFile) - assert.NoError(t, err) - err = os.Remove(validConfigFile) - assert.NoError(t, err) - }) - - cases := []struct { - desc string - file string - cfg provision.Config - err error - }{ - { - desc: "read valid config", - file: validConfigFile, - cfg: validConfig, - err: nil, - }, - { - desc: "read invalid config", - file: invalidConfigFile, - cfg: invalidConfig, - err: nil, - }, - { - desc: "read empty config", - file: "", - cfg: provision.Config{}, - err: errors.New("failed to read config file"), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - cfg, err := provision.Read(c.file) - if c.desc == "read invalid config" { - c.cfg.Bootstrap.Content = nil - } - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected: %v, got: %v", c.err, err)) - assert.Equal(t, c.cfg, cfg) - }) - } -} diff --git a/docker/addons/vault/scripts/provision/configs/config.toml b/docker/addons/vault/scripts/provision/configs/config.toml deleted file mode 100644 index 38455eb2..00000000 --- a/docker/addons/vault/scripts/provision/configs/config.toml +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -file = "config.toml" - -[bootstrap] - autowhite_list = true - content = "" - provision = true - x509_provision = false - - -[server] - LogLevel = "info" - ca_certs = "" - http_port = "8190" - mg_api_key = "" - mg_bs_url = "http://localhost:9013" - mg_certs_url = "http://localhost:9019" - mg_pass = "" - mg_user = "" - mqtt_url = "" - port = "" - server_cert = "" - server_key = "" - things_location = "http://localhost:9000" - tls = true - users_location = "" - -[[things]] - name = "thing" - - [things.metadata] - external_id = "xxxxxx" - - -[[channels]] - name = "control-channel" - - [channels.metadata] - type = "control" - -[[channels]] - name = "data-channel" - - [channels.metadata] - type = "data" diff --git a/docker/addons/vault/scripts/provision/doc.go b/docker/addons/vault/scripts/provision/doc.go deleted file mode 100644 index e9b85529..00000000 --- a/docker/addons/vault/scripts/provision/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package provision contains domain concept definitions needed to support -// Provision service feature, i.e. automate provision process. -package provision diff --git a/docker/addons/vault/scripts/provision/mocks/service.go b/docker/addons/vault/scripts/provision/mocks/service.go deleted file mode 100644 index ff45e5fa..00000000 --- a/docker/addons/vault/scripts/provision/mocks/service.go +++ /dev/null @@ -1,122 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - provision "github.com/absmach/magistrala/provision" - mock "github.com/stretchr/testify/mock" -) - -// Service is an autogenerated mock type for the Service type -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) - - if len(ret) == 0 { - panic("no return value specified for Cert") - } - - var r0 string - 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) - } - if rf, ok := ret.Get(0).(func(string, string, string, string) string); ok { - r0 = rf(domainID, token, thingID, 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) - } 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) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// Mapping provides a mock function with given fields: token -func (_m *Service) Mapping(token string) (map[string]interface{}, error) { - ret := _m.Called(token) - - if len(ret) == 0 { - panic("no return value specified for Mapping") - } - - var r0 map[string]interface{} - var r1 error - if rf, ok := ret.Get(0).(func(string) (map[string]interface{}, error)); ok { - return rf(token) - } - if rf, ok := ret.Get(0).(func(string) map[string]interface{}); ok { - r0 = rf(token) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]interface{}) - } - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(token) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Provision provides a mock function with given fields: domainID, token, name, externalID, externalKey -func (_m *Service) Provision(domainID string, token string, name string, externalID string, externalKey string) (provision.Result, error) { - ret := _m.Called(domainID, token, name, externalID, externalKey) - - if len(ret) == 0 { - panic("no return value specified for Provision") - } - - var r0 provision.Result - var r1 error - if rf, ok := ret.Get(0).(func(string, string, string, string, string) (provision.Result, error)); ok { - return rf(domainID, token, name, externalID, externalKey) - } - if rf, ok := ret.Get(0).(func(string, string, string, string, string) provision.Result); ok { - r0 = rf(domainID, token, name, externalID, externalKey) - } else { - r0 = ret.Get(0).(provision.Result) - } - - if rf, ok := ret.Get(1).(func(string, string, string, string, string) error); ok { - r1 = rf(domainID, token, name, externalID, externalKey) - } 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/docker/addons/vault/scripts/provision/service.go b/docker/addons/vault/scripts/provision/service.go deleted file mode 100644 index 228586aa..00000000 --- a/docker/addons/vault/scripts/provision/service.go +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package provision - -import ( - "encoding/json" - "fmt" - "log/slog" - - "github.com/absmach/magistrala/pkg/errors" - sdk "github.com/absmach/magistrala/pkg/sdk/go" -) - -const ( - externalIDKey = "external_id" - gateway = "gateway" - Active = 1 - - control = "control" - data = "data" - export = "export" -) - -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") - 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") - ErrMissingCredentials = errors.New("missing credentials") - ErrFailedBootstrapRetrieval = errors.New("failed to retrieve bootstrap") - ErrFailedCertCreation = errors.New("failed to create certificates") - ErrFailedCertView = errors.New("failed to view certificate") - ErrFailedBootstrap = errors.New("failed to create bootstrap config") - ErrFailedBootstrapValidate = errors.New("failed to validate bootstrap config creation") - ErrGatewayUpdate = errors.New("failed to updated gateway metadata") - - limit uint = 10 - offset uint = 0 -) - -var _ Service = (*provisionService)(nil) - -// Service specifies Provision service API. -// -//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" -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 multiple Channels - // - create Bootstrap configuration - // - whitelist Thing in Bootstrap configuration == connect Thing to Channels - Provision(domainID, token, name, externalID, externalKey string) (Result, error) - - // Mapping returns current configuration used for provision - // useful for using in ui to create configuration that matches - // one created with Provision method. - Mapping(token string) (map[string]interface{}, error) - - // Certs creates certificate for things 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) -} - -type provisionService struct { - logger *slog.Logger - sdk sdk.SDK - conf Config -} - -// Result represent what is created with additional info. -type Result struct { - Things []sdk.Thing `json:"things,omitempty"` - Channels []sdk.Channel `json:"channels,omitempty"` - ClientCert map[string]string `json:"client_cert,omitempty"` - ClientKey map[string]string `json:"client_key,omitempty"` - CACert string `json:"ca_cert,omitempty"` - Whitelisted map[string]bool `json:"whitelisted,omitempty"` - Error string `json:"error,omitempty"` -} - -// New returns new provision service. -func New(cfg Config, mgsdk sdk.SDK, logger *slog.Logger) Service { - return &provisionService{ - logger: logger, - conf: cfg, - sdk: mgsdk, - } -} - -// Mapping retrieves current configuration. -func (ps *provisionService) Mapping(token string) (map[string]interface{}, error) { - pm := sdk.PageMetadata{ - Offset: uint64(offset), - Limit: uint64(limit), - } - - if _, err := ps.sdk.Users(pm, token); err != nil { - return map[string]interface{}{}, errors.Wrap(ErrUnauthorized, err) - } - - return ps.conf.Bootstrap.Content, nil -} - -// Provision is provision method for creating setup according to -// 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) - - 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.Channels) == 0 { - return res, ErrEmptyChannelsList - } - for _, thing := range ps.conf.Things { - // If thing 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 - } - - th := sdk.Thing{ - Metadata: thing.Metadata, - } - if name == "" { - name = thing.Name - } - th.Name = name - th, err := ps.sdk.CreateThing(th, domainID, token) - if err != nil { - res.Error = err.Error() - return res, errors.Wrap(ErrFailedThingCreation, err) - } - - // Get newly created thing (in order to get the key). - th, err = ps.sdk.Thing(th.ID, domainID, token) - if err != nil { - e := errors.Wrap(err, fmt.Errorf("thing id: %s", th.ID)) - return res, errors.Wrap(ErrFailedThingRetrieval, e) - } - things = append(things, th) - } - - for _, channel := range ps.conf.Channels { - ch := sdk.Channel{ - Name: name + "_" + channel.Name, - Metadata: sdk.Metadata(channel.Metadata), - } - ch, err := ps.sdk.CreateChannel(ch, domainID, token) - if err != nil { - return res, errors.Wrap(ErrFailedChannelCreation, err) - } - ch, err = ps.sdk.Channel(ch.ID, domainID, token) - if err != nil { - e := errors.Wrap(err, fmt.Errorf("channel id: %s", ch.ID)) - return res, errors.Wrap(ErrFailedChannelRetrieval, e) - } - channels = append(channels, ch) - } - - res = Result{ - Things: things, - Channels: channels, - Whitelisted: map[string]bool{}, - ClientCert: map[string]string{}, - ClientKey: map[string]string{}, - } - - var cert sdk.Cert - var bsConfig sdk.BootstrapConfig - for _, thing := range things { - var chanIDs []string - - for _, ch := range channels { - chanIDs = append(chanIDs, ch.ID) - } - content, err := json.Marshal(ps.conf.Bootstrap.Content) - if err != nil { - return Result{}, errors.Wrap(ErrFailedBootstrap, err) - } - - if ps.conf.Bootstrap.Provision && needsBootstrap(thing) { - bsReq := sdk.BootstrapConfig{ - ThingID: thing.ID, - ExternalID: externalID, - ExternalKey: externalKey, - Channels: chanIDs, - CACert: res.CACert, - ClientCert: cert.Certificate, - ClientKey: cert.Key, - Content: string(content), - } - bsid, err := ps.sdk.AddBootstrap(bsReq, domainID, token) - if err != nil { - return Result{}, errors.Wrap(ErrFailedBootstrap, err) - } - - bsConfig, err = ps.sdk.ViewBootstrap(bsid, domainID, token) - if err != nil { - return Result{}, errors.Wrap(ErrFailedBootstrapValidate, err) - } - } - - if ps.conf.Bootstrap.X509Provision { - var cert sdk.Cert - - cert, err = ps.sdk.IssueCert(thing.ID, ps.conf.Cert.TTL, domainID, token) - if err != nil { - e := errors.Wrap(err, fmt.Errorf("thing id: %s", thing.ID)) - return res, errors.Wrap(ErrFailedCertCreation, e) - } - cert, err := ps.sdk.ViewCert(cert.SerialNumber, domainID, token) - if err != nil { - return res, errors.Wrap(ErrFailedCertView, err) - } - - res.ClientCert[thing.ID] = cert.Certificate - res.ClientKey[thing.ID] = cert.Key - res.CACert = "" - - if needsBootstrap(thing) { - if _, err = ps.sdk.UpdateBootstrapCerts(bsConfig.ThingID, 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 { - res.Error = err.Error() - return res, ErrThingUpdate - } - res.Whitelisted[thing.ID] = true - } - } - - if err = ps.updateGateway(domainID, token, bsConfig, channels); err != nil { - return res, err - } - return res, nil -} - -func (ps *provisionService) Cert(domainID, token, thingID, 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) - if err != nil { - return "", "", errors.Wrap(ErrUnauthorized, err) - } - cert, err := ps.sdk.IssueCert(th.ID, ps.conf.Cert.TTL, domainID, token) - if err != nil { - return "", "", errors.Wrap(ErrFailedCertCreation, err) - } - cert, err = ps.sdk.ViewCert(cert.SerialNumber, domainID, token) - if err != nil { - return "", "", errors.Wrap(ErrFailedCertView, err) - } - return cert.Certificate, cert.Key, err -} - -func (ps *provisionService) createTokenIfEmpty(token string) (string, error) { - if token != "" { - return token, nil - } - - // If no token in request is provided - // use API key provided in config file or env - if ps.conf.Server.MgAPIKey != "" { - return ps.conf.Server.MgAPIKey, nil - } - - // If no API key use username and password provided to create access token. - if ps.conf.Server.MgUsername == "" || ps.conf.Server.MgPass == "" { - return token, ErrMissingCredentials - } - - u := sdk.Login{ - Identity: ps.conf.Server.MgUsername, - Secret: ps.conf.Server.MgPass, - } - tkn, err := ps.sdk.CreateToken(u) - if err != nil { - return token, errors.Wrap(ErrFailedToCreateToken, err) - } - - return tkn.AccessToken, nil -} - -func (ps *provisionService) updateGateway(domainID, token string, bs sdk.BootstrapConfig, channels []sdk.Channel) error { - var gw Gateway - for _, ch := range channels { - switch ch.Metadata["type"] { - case control: - gw.CtrlChannelID = ch.ID - case data: - gw.DataChannelID = ch.ID - case export: - gw.ExportChannelID = ch.ID - } - } - gw.ExternalID = bs.ExternalID - gw.ExternalKey = bs.ExternalKey - gw.CfgID = bs.ThingID - gw.Type = gateway - - th, sdkerr := ps.sdk.Thing(bs.ThingID, domainID, token) - if sdkerr != nil { - return errors.Wrap(ErrGatewayUpdate, sdkerr) - } - b, err := json.Marshal(gw) - if err != nil { - return errors.Wrap(ErrGatewayUpdate, err) - } - if err := json.Unmarshal(b, &th.Metadata); err != nil { - return errors.Wrap(ErrGatewayUpdate, err) - } - if _, err := ps.sdk.UpdateThing(th, domainID, token); err != nil { - return errors.Wrap(ErrGatewayUpdate, err) - } - return nil -} - -func (ps *provisionService) errLog(err error) { - if err != nil { - ps.logger.Error(fmt.Sprintf("Error recovering: %s", err)) - } -} - -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) - ps.errLog(err) - } - for _, c := range channels { - err := ps.sdk.DeleteChannel(c.ID, domainID, token) - ps.errLog(err) - } -} - -func (ps *provisionService) recover(e *error, ths *[]sdk.Thing, chs *[]sdk.Channel, dm, tkn *string) { - if e == nil { - return - } - things, 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) - ps.errLog(err) - } - return - } - - if errors.Contains(err, ErrFailedBootstrap) || errors.Contains(err, ErrFailedChannelRetrieval) { - clean(ps, things, channels, domainID, token) - return - } - - if errors.Contains(err, ErrFailedBootstrapValidate) || errors.Contains(err, ErrFailedCertCreation) { - clean(ps, things, channels, domainID, token) - for _, th := range things { - if needsBootstrap(th) { - ps.errLog(ps.sdk.RemoveBootstrap(th.ID, domainID, token)) - } - } - return - } - - if errors.Contains(err, ErrFailedBootstrapValidate) || errors.Contains(err, ErrFailedCertCreation) { - clean(ps, things, channels, domainID, token) - for _, th := range things { - 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)) - } - } - } - - if errors.Contains(err, ErrThingUpdate) || errors.Contains(err, ErrGatewayUpdate) { - clean(ps, things, channels, domainID, token) - for _, th := range things { - if ps.conf.Bootstrap.X509Provision && needsBootstrap(th) { - _, err := ps.sdk.RevokeCert(th.ID, domainID, token) - ps.errLog(err) - } - 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)) - } - } - return - } -} - -func needsBootstrap(th sdk.Thing) bool { - if th.Metadata == nil { - return false - } - - if _, ok := th.Metadata[externalIDKey]; ok { - return true - } - return false -} diff --git a/docker/addons/vault/scripts/provision/service_test.go b/docker/addons/vault/scripts/provision/service_test.go deleted file mode 100644 index 4e3fd314..00000000 --- a/docker/addons/vault/scripts/provision/service_test.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package provision_test - -import ( - "fmt" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var validToken = "valid" - -func TestMapping(t *testing.T) { - mgsdk := new(sdkmocks.SDK) - svc := provision.New(validConfig, mgsdk, mglog.NewMock()) - - cases := []struct { - desc string - token string - content map[string]interface{} - sdkerr error - err error - }{ - { - desc: "valid token", - token: validToken, - content: validConfig.Bootstrap.Content, - sdkerr: nil, - err: nil, - }, - { - desc: "invalid token", - token: "invalid", - content: map[string]interface{}{}, - sdkerr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401), - err: provision.ErrUnauthorized, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - pm := sdk.PageMetadata{Offset: uint64(0), Limit: uint64(10)} - repocall := mgsdk.On("Users", pm, c.token).Return(sdk.UsersPage{}, c.sdkerr) - content, err := svc.Mapping(c.token) - assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected error %v, got %v", c.err, err)) - assert.Equal(t, c.content, content) - repocall.Unset() - }) - } -} - -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: "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: "empty token with config API key", - config: provision.Config{ - 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, - }, - { - desc: "empty token with username and password", - config: provision.Config{ - Server: provision.ServiceConf{ - MgUsername: "testUsername", - MgPass: "12345678", - MgDomainID: testsutil.GenerateUUID(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, - }, - { - desc: "empty token with username and invalid password", - config: provision.Config{ - Server: provision.ServiceConf{ - MgUsername: "testUsername", - MgPass: "12345678", - MgDomainID: testsutil.GenerateUUID(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, - }, - { - desc: "empty token with empty username and password", - config: provision.Config{ - 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, - }, - { - 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 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: "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, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - mgsdk := new(sdkmocks.SDK) - svc := provision.New(c.config, mgsdk, mglog.NewMock()) - - mgsdk.On("Thing", c.thingID, c.domainID, mock.Anything).Return(sdk.Thing{ID: c.thingID}, c.sdkThingErr) - mgsdk.On("IssueCert", c.thingID, c.config.Cert.TTL, c.domainID, mock.Anything).Return(sdk.Cert{SerialNumber: c.serial}, c.sdkCertErr) - mgsdk.On("ViewCert", c.serial, mock.Anything, mock.Anything).Return(sdk.Cert{Certificate: c.cert, Key: c.key}, c.sdkCertErr) - login := sdk.Login{ - Identity: c.config.Server.MgUsername, - Secret: c.config.Server.MgPass, - } - mgsdk.On("CreateToken", login).Return(sdk.Token{AccessToken: validToken}, c.sdkTokenErr) - cert, key, err := svc.Cert(c.domainID, c.token, c.thingID, c.ttl) - 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/docker/addons/vault/scripts/readers/README.md b/docker/addons/vault/scripts/readers/README.md deleted file mode 100644 index 4c7be593..00000000 --- a/docker/addons/vault/scripts/readers/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# 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. - -For an in-depth explanation of the usage of `reader`, as well as thorough understanding of Magistrala, please check out the [official documentation][doc]. - -[doc]: https://docs.magistrala.abstractmachines.fr diff --git a/docker/addons/vault/scripts/readers/api/doc.go b/docker/addons/vault/scripts/readers/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/readers/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/docker/addons/vault/scripts/readers/api/endpoint.go b/docker/addons/vault/scripts/readers/api/endpoint.go deleted file mode 100644 index 794063f7..00000000 --- a/docker/addons/vault/scripts/readers/api/endpoint.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -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" - "github.com/go-kit/kit/endpoint" -) - -func listMessagesEndpoint(svc readers.MessageRepository, authn mgauthn.Authentication, authz mgauthz.Authorization, thingsClient magistrala.ThingsServiceClient) 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 { - return nil, errors.Wrap(svcerr.ErrAuthorization, err) - } - - page, err := svc.ReadAll(req.chanID, req.pageMeta) - if err != nil { - return nil, err - } - - return pageRes{ - PageMetadata: page.PageMetadata, - Total: page.Total, - Messages: page.Messages, - }, nil - } -} diff --git a/docker/addons/vault/scripts/readers/api/endpoint_test.go b/docker/addons/vault/scripts/readers/api/endpoint_test.go deleted file mode 100644 index 156e79ec..00000000 --- a/docker/addons/vault/scripts/readers/api/endpoint_test.go +++ /dev/null @@ -1,1024 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api_test - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const ( - svcName = "test-service" - thingToken = "1" - userToken = "token" - invalidToken = "invalid" - email = "user@example.com" - invalid = "invalid" - numOfMessages = 100 - valueFields = 5 - subtopic = "topic" - mqttProt = "mqtt" - httpProt = "http" - msgName = "temperature" - instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" -) - -var ( - v float64 = 5 - vs = "value" - vb = true - vd = "dataValue" - sum float64 = 42 - domainID = testsutil.GenerateUUID(&testing.T{}) - validSession = mgauthn.Session{UserID: testsutil.GenerateUUID(&testing.T{})} -) - -func newServer(repo *mocks.MessageRepository, authn *authnmocks.Authentication, authz *authzmocks.Authorization, thingsAuthzClient *thmocks.ThingsServiceClient) *httptest.Server { - mux := api.MakeHandler(repo, authn, authz, thingsAuthzClient, svcName, instanceID) - return httptest.NewServer(mux) -} - -type testRequest struct { - client *http.Client - method string - url string - token string - key string -} - -func (tr testRequest) make() (*http.Response, error) { - req, err := http.NewRequest(tr.method, tr.url, http.NoBody) - if err != nil { - return nil, err - } - if tr.token != "" { - req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token) - } - if tr.key != "" { - req.Header.Set("Authorization", apiutil.ThingPrefix+tr.key) - } - - return tr.client.Do(req) -} - -func TestReadAll(t *testing.T) { - chanID := testsutil.GenerateUUID(t) - pubID := testsutil.GenerateUUID(t) - pubID2 := testsutil.GenerateUUID(t) - - now := time.Now().Unix() - - var messages []senml.Message - var queryMsgs []senml.Message - var valueMsgs []senml.Message - var boolMsgs []senml.Message - var stringMsgs []senml.Message - var dataMsgs []senml.Message - - for i := 0; i < numOfMessages; i++ { - // Mix possible values as well as value sum. - msg := senml.Message{ - Channel: chanID, - Publisher: pubID, - Protocol: mqttProt, - Time: float64(now - int64(i)), - Name: "name", - } - - count := i % valueFields - switch count { - case 0: - msg.Value = &v - valueMsgs = append(valueMsgs, msg) - case 1: - msg.BoolValue = &vb - boolMsgs = append(boolMsgs, msg) - case 2: - msg.StringValue = &vs - stringMsgs = append(stringMsgs, msg) - case 3: - msg.DataValue = &vd - dataMsgs = append(dataMsgs, msg) - case 4: - msg.Sum = &sum - msg.Subtopic = subtopic - msg.Protocol = httpProt - msg.Publisher = pubID2 - msg.Name = msgName - queryMsgs = append(queryMsgs, msg) - } - - messages = append(messages, msg) - } - - repo := new(mocks.MessageRepository) - authz := new(authzmocks.Authorization) - authn := new(authnmocks.Authentication) - things := new(thmocks.ThingsServiceClient) - ts := newServer(repo, authn, authz, things) - defer ts.Close() - - cases := []struct { - desc string - req string - url string - token string - key string - authResponse bool - status int - res pageRes - authnErr error - err error - }{ - { - desc: "read page with valid offset and limit", - url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages"}, - Total: uint64(len(messages)), - Messages: messages[0:10], - }, - }, - { - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10}, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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), - token: "", - authResponse: false, - authnErr: svcerr.ErrAuthentication, - status: http.StatusUnauthorized, - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10}, - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{}, - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Subtopic: subtopic, Format: "messages", Protocol: httpProt}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Subtopic: subtopic, Format: "messages", Protocol: httpProt}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - desc: "read page with publisher as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?publisher=%s", ts.URL, "", chanID, pubID2), - key: thingToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Publisher: pubID2}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - desc: "read page with protocol as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?protocol=http", ts.URL, "", chanID), - key: thingToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Protocol: httpProt}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - desc: "read page with name as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?name=%s", ts.URL, "", chanID, msgName), - key: thingToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Name: msgName}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - desc: "read page with value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f", ts.URL, "", chanID, v), - key: thingToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v, Comparator: readers.EqualKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v + 1, Comparator: readers.LowerThanKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v + 1, Comparator: readers.LowerThanEqualKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v - 1, Comparator: readers.GreaterThanKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v - 1, Comparator: readers.GreaterThanEqualKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - desc: "read page with non-float value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=ab01", ts.URL, "", chanID), - key: thingToken, - 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, - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", BoolValue: true}, - Total: uint64(len(boolMsgs)), - Messages: boolMsgs[0:10], - }, - }, - { - desc: "read page with non-boolean value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=yes", ts.URL, "", chanID), - key: thingToken, - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", StringValue: vs}, - Total: uint64(len(stringMsgs)), - Messages: stringMsgs[0:10], - }, - }, - { - desc: "read page with data value as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vd=%s", ts.URL, "", chanID, vd), - key: thingToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", DataValue: vd}, - Total: uint64(len(dataMsgs)), - Messages: dataMsgs[0:10], - }, - }, - { - desc: "read page with non-float from as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?from=ABCD", ts.URL, "", chanID), - key: thingToken, - 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, - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", From: messages[19].Time, To: messages[4].Time}, - Total: uint64(len(messages[5:20])), - Messages: messages[5:15], - }, - }, - { - desc: "read page with aggregation as thing", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX", ts.URL, "", chanID), - key: thingToken, - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Interval: "10h"}, - 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, - 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, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Aggregation: "MAX", Interval: "10h", From: messages[19].Time, To: messages[4].Time}, - Total: uint64(len(messages[5:20])), - Messages: messages[5:15], - }, - }, - { - 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, - 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, - 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, - 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, - 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, - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10}, - 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), - 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), - 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), - 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), - 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), - 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), - 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), - token: invalidToken, - authResponse: false, - status: http.StatusUnauthorized, - err: svcerr.ErrAuthorization, - }, - { - 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), - 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), - 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), - token: "", - authResponse: false, - status: http.StatusUnauthorized, - err: svcerr.ErrAuthorization, - }, - { - desc: "read page with default offset as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?limit=10", ts.URL, domainID, chanID), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10}, - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{}, - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Subtopic: subtopic, Protocol: httpProt}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - desc: "read page with publisher as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?publisher=%s", ts.URL, domainID, chanID, pubID2), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Publisher: pubID2}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - desc: "read page with protocol as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?protocol=http", ts.URL, domainID, chanID), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Protocol: httpProt}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - desc: "read page with name as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?name=%s", ts.URL, domainID, chanID, msgName), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Name: msgName}, - Total: uint64(len(queryMsgs)), - Messages: queryMsgs[0:10], - }, - }, - { - desc: "read page with value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f", ts.URL, domainID, chanID, v), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v, Comparator: readers.EqualKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v + 1, Comparator: readers.LowerThanKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v + 1, Comparator: readers.LowerThanEqualKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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), - token: userToken, - status: http.StatusOK, - authResponse: true, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v - 1, Comparator: readers.GreaterThanKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Value: v - 1, Comparator: readers.GreaterThanEqualKey}, - Total: uint64(len(valueMsgs)), - Messages: valueMsgs[0:10], - }, - }, - { - desc: "read page with non-float value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?v=ab01", ts.URL, domainID, 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), - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", BoolValue: true}, - Total: uint64(len(boolMsgs)), - Messages: boolMsgs[0:10], - }, - }, - { - desc: "read page with non-boolean value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=yes", ts.URL, domainID, 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", StringValue: vs}, - Total: uint64(len(stringMsgs)), - Messages: stringMsgs[0:10], - }, - }, - { - desc: "read page with data value as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?vd=%s", ts.URL, domainID, chanID, vd), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", DataValue: vd}, - Total: uint64(len(dataMsgs)), - Messages: dataMsgs[0:10], - }, - }, - { - desc: "read page with non-float from as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?from=ABCD", ts.URL, domainID, 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), - 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), - token: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", From: messages[19].Time, To: messages[4].Time}, - Total: uint64(len(messages[5:20])), - Messages: messages[5:15], - }, - }, - { - desc: "read page with aggregation as user", - url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX", ts.URL, domainID, 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), - key: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Interval: "10h"}, - 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), - 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), - key: userToken, - authResponse: true, - status: http.StatusOK, - res: pageRes{ - PageMetadata: readers.PageMetadata{Limit: 10, Format: "messages", Aggregation: "MAX", Interval: "10h", From: messages[19].Time, To: messages[4].Time}, - Total: uint64(len(messages[5:20])), - Messages: messages[5:15], - }, - }, - { - 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), - 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), - 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), - 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), - 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), - key: userToken, - authResponse: true, - status: http.StatusBadRequest, - }, - } - - for _, tc := range cases { - authCall := authz.On("Authorize", mock.Anything, mock.Anything).Return(tc.err) - authCall1 := authn.On("Authenticate", mock.Anything, tc.token).Return(validSession, tc.authnErr) - repo.On("ReadAll", chanID, tc.res.PageMetadata).Return(readers.MessagesPage{Total: tc.res.Total, Messages: fromSenml(tc.res.Messages)}, nil) - if tc.key != "" { - authCall = things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.ThingsAuthzRes{Authorized: tc.authResponse}, tc.err) - } - req := testRequest{ - client: ts.Client(), - method: http.MethodGet, - url: tc.url, - token: tc.token, - key: tc.key, - } - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - - 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() - } -} - -type pageRes struct { - readers.PageMetadata - Total uint64 `json:"total"` - Messages []senml.Message `json:"messages,omitempty"` -} - -func fromSenml(in []senml.Message) []readers.Message { - var ret []readers.Message - for _, m := range in { - ret = append(ret, m) - } - return ret -} diff --git a/docker/addons/vault/scripts/readers/api/logging.go b/docker/addons/vault/scripts/readers/api/logging.go deleted file mode 100644 index 49eedcbc..00000000 --- a/docker/addons/vault/scripts/readers/api/logging.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "log/slog" - "time" - - "github.com/absmach/magistrala/readers" -) - -var _ readers.MessageRepository = (*loggingMiddleware)(nil) - -type loggingMiddleware struct { - logger *slog.Logger - svc readers.MessageRepository -} - -// LoggingMiddleware adds logging facilities to the core service. -func LoggingMiddleware(svc readers.MessageRepository, logger *slog.Logger) readers.MessageRepository { - return &loggingMiddleware{ - logger: logger, - svc: svc, - } -} - -func (lm *loggingMiddleware) ReadAll(chanID string, rpm readers.PageMetadata) (page readers.MessagesPage, err error) { - defer func(begin time.Time) { - args := []any{ - slog.String("duration", time.Since(begin).String()), - slog.String("channel_id", chanID), - slog.Group("page", - slog.Uint64("offset", rpm.Offset), - slog.Uint64("limit", rpm.Limit), - slog.Uint64("total", page.Total), - ), - } - if rpm.Subtopic != "" { - args = append(args, slog.String("subtopic", rpm.Subtopic)) - } - if rpm.Publisher != "" { - args = append(args, slog.String("publisher", rpm.Publisher)) - } - if err != nil { - args = append(args, slog.Any("error", err)) - lm.logger.Warn("Read all failed", args...) - return - } - lm.logger.Info("Read all completed successfully", args...) - }(time.Now()) - - return lm.svc.ReadAll(chanID, rpm) -} diff --git a/docker/addons/vault/scripts/readers/api/metrics.go b/docker/addons/vault/scripts/readers/api/metrics.go deleted file mode 100644 index 026f3f43..00000000 --- a/docker/addons/vault/scripts/readers/api/metrics.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//go:build !test - -package api - -import ( - "time" - - "github.com/absmach/magistrala/readers" - "github.com/go-kit/kit/metrics" -) - -var _ readers.MessageRepository = (*metricsMiddleware)(nil) - -type metricsMiddleware struct { - counter metrics.Counter - latency metrics.Histogram - svc readers.MessageRepository -} - -// MetricsMiddleware instruments core service by tracking request count and latency. -func MetricsMiddleware(svc readers.MessageRepository, counter metrics.Counter, latency metrics.Histogram) readers.MessageRepository { - return &metricsMiddleware{ - counter: counter, - latency: latency, - svc: svc, - } -} - -func (mm *metricsMiddleware) ReadAll(chanID string, rpm readers.PageMetadata) (readers.MessagesPage, error) { - defer func(begin time.Time) { - mm.counter.With("method", "read_all").Add(1) - mm.latency.With("method", "read_all").Observe(time.Since(begin).Seconds()) - }(time.Now()) - - return mm.svc.ReadAll(chanID, rpm) -} diff --git a/docker/addons/vault/scripts/readers/api/requests.go b/docker/addons/vault/scripts/readers/api/requests.go deleted file mode 100644 index df08f796..00000000 --- a/docker/addons/vault/scripts/readers/api/requests.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "slices" - "strings" - "time" - - "github.com/absmach/magistrala/pkg/apiutil" - "github.com/absmach/magistrala/readers" -) - -const maxLimitSize = 1000 - -var validAggregations = []string{"MAX", "MIN", "AVG", "SUM", "COUNT"} - -type listMessagesReq struct { - chanID string - token string - key string - domainID string - pageMeta readers.PageMetadata -} - -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 - } - - if req.pageMeta.Limit < 1 || req.pageMeta.Limit > maxLimitSize { - return apiutil.ErrLimitSize - } - - if req.pageMeta.Comparator != "" && - req.pageMeta.Comparator != readers.EqualKey && - req.pageMeta.Comparator != readers.LowerThanKey && - req.pageMeta.Comparator != readers.LowerThanEqualKey && - req.pageMeta.Comparator != readers.GreaterThanKey && - req.pageMeta.Comparator != readers.GreaterThanEqualKey { - return apiutil.ErrInvalidComparator - } - - if req.pageMeta.Aggregation != "" { - if req.pageMeta.From == 0 { - return apiutil.ErrMissingFrom - } - - if req.pageMeta.To == 0 { - return apiutil.ErrMissingTo - } - - if !slices.Contains(validAggregations, strings.ToUpper(req.pageMeta.Aggregation)) { - return apiutil.ErrInvalidAggregation - } - - if _, err := time.ParseDuration(req.pageMeta.Interval); err != nil { - return apiutil.ErrInvalidInterval - } - } - - return nil -} diff --git a/docker/addons/vault/scripts/readers/api/responses.go b/docker/addons/vault/scripts/readers/api/responses.go deleted file mode 100644 index 980f2346..00000000 --- a/docker/addons/vault/scripts/readers/api/responses.go +++ /dev/null @@ -1,31 +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/readers" -) - -var _ magistrala.Response = (*pageRes)(nil) - -type pageRes struct { - readers.PageMetadata - Total uint64 `json:"total"` - Messages []readers.Message `json:"messages,omitempty"` -} - -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/docker/addons/vault/scripts/readers/api/transport.go b/docker/addons/vault/scripts/readers/api/transport.go deleted file mode 100644 index 194da47f..00000000 --- a/docker/addons/vault/scripts/readers/api/transport.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" - - "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/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 ( - contentType = "application/json" - offsetKey = "offset" - limitKey = "limit" - formatKey = "format" - subtopicKey = "subtopic" - publisherKey = "publisher" - protocolKey = "protocol" - nameKey = "name" - valueKey = "v" - stringValueKey = "vs" - dataValueKey = "vd" - boolValueKey = "vb" - comparatorKey = "comparator" - fromKey = "from" - toKey = "to" - aggregationKey = "aggregation" - intervalKey = "interval" - defInterval = "1s" - defLimit = 10 - defOffset = 0 - 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 { - opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(encodeError), - } - - mux := chi.NewRouter() - mux.Get("/{domainID}/channels/{chanID}/messages", kithttp.NewServer( - listMessagesEndpoint(svc, authn, authz, things), - decodeList, - encodeResponse, - opts..., - ).ServeHTTP) - - mux.Get("/health", magistrala.Health(svcName, instanceID)) - mux.Handle("/metrics", promhttp.Handler()) - - return mux -} - -func decodeList(_ context.Context, r *http.Request) (interface{}, error) { - offset, err := apiutil.ReadNumQuery[uint64](r, offsetKey, defOffset) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - limit, err := apiutil.ReadNumQuery[uint64](r, limitKey, defLimit) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - format, err := apiutil.ReadStringQuery(r, formatKey, defFormat) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - subtopic, err := apiutil.ReadStringQuery(r, subtopicKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - publisher, err := apiutil.ReadStringQuery(r, publisherKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - protocol, err := apiutil.ReadStringQuery(r, protocolKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - name, err := apiutil.ReadStringQuery(r, nameKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - v, err := apiutil.ReadNumQuery[float64](r, valueKey, 0) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - comparator, err := apiutil.ReadStringQuery(r, comparatorKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - vs, err := apiutil.ReadStringQuery(r, stringValueKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - vd, err := apiutil.ReadStringQuery(r, dataValueKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - vb, err := apiutil.ReadBoolQuery(r, boolValueKey, false) - if err != nil && err != apiutil.ErrNotFoundParam { - return nil, err - } - - from, err := apiutil.ReadNumQuery[float64](r, fromKey, 0) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - to, err := apiutil.ReadNumQuery[float64](r, toKey, 0) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - aggregation, err := apiutil.ReadStringQuery(r, aggregationKey, "") - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - - var interval string - if aggregation != "" { - interval, err = apiutil.ReadStringQuery(r, intervalKey, defInterval) - if err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, err) - } - } - - req := listMessagesReq{ - chanID: chi.URLParam(r, "chanID"), - token: apiutil.ExtractBearerToken(r), - key: apiutil.ExtractThingKey(r), - domainID: chi.URLParam(r, "domainID"), - pageMeta: readers.PageMetadata{ - Offset: offset, - Limit: limit, - Format: format, - Subtopic: subtopic, - Publisher: publisher, - Protocol: protocol, - Name: name, - Value: v, - Comparator: comparator, - StringValue: vs, - DataValue: vd, - BoolValue: vb, - From: from, - To: to, - Aggregation: aggregation, - Interval: interval, - }, - } - return req, nil -} - -func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { - w.Header().Set("Content-Type", contentType) - - if ar, ok := response.(magistrala.Response); ok { - for k, v := range ar.Headers() { - w.Header().Set(k, v) - } - - w.WriteHeader(ar.Code()) - - if ar.Empty() { - return nil - } - } - - return json.NewEncoder(w).Encode(response) -} - -func encodeError(_ context.Context, err error, w http.ResponseWriter) { - var wrapper error - if errors.Contains(err, apiutil.ErrValidation) { - wrapper, err = errors.Unwrap(err) - } - - switch { - case errors.Contains(err, nil): - case errors.Contains(err, apiutil.ErrInvalidQueryParams), - errors.Contains(err, svcerr.ErrMalformedEntity), - errors.Contains(err, apiutil.ErrMissingID), - errors.Contains(err, apiutil.ErrLimitSize), - errors.Contains(err, apiutil.ErrOffsetSize), - errors.Contains(err, apiutil.ErrInvalidComparator), - errors.Contains(err, apiutil.ErrInvalidAggregation), - errors.Contains(err, apiutil.ErrInvalidInterval), - errors.Contains(err, apiutil.ErrMissingFrom), - errors.Contains(err, apiutil.ErrMissingTo), - errors.Contains(err, apiutil.ErrMissingDomainID): - w.WriteHeader(http.StatusBadRequest) - case errors.Contains(err, svcerr.ErrAuthentication), - errors.Contains(err, svcerr.ErrAuthorization), - errors.Contains(err, apiutil.ErrBearerToken): - w.WriteHeader(http.StatusUnauthorized) - case errors.Contains(err, readers.ErrReadMessages): - w.WriteHeader(http.StatusInternalServerError) - default: - w.WriteHeader(http.StatusInternalServerError) - } - - if wrapper != nil { - err = errors.Wrap(wrapper, err) - } - if errorVal, ok := err.(errors.Error); ok { - w.Header().Set("Content-Type", contentType) - if err := json.NewEncoder(w).Encode(errorVal); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - } -} - -func authorize(ctx context.Context, req listMessagesReq, authn mgauthn.Authentication, authz mgauthz.Authorization, things magistrala.ThingsServiceClient) (err error) { - switch { - case req.token != "": - session, err := authn.Authenticate(ctx, req.token) - if err != nil { - return errors.Wrap(svcerr.ErrAuthentication, 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 - 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 - } - return nil - default: - return svcerr.ErrAuthorization - } -} diff --git a/docker/addons/vault/scripts/readers/doc.go b/docker/addons/vault/scripts/readers/doc.go deleted file mode 100644 index e02d4326..00000000 --- a/docker/addons/vault/scripts/readers/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package readers provides a set of readers for various formats. -package readers diff --git a/docker/addons/vault/scripts/readers/messages.go b/docker/addons/vault/scripts/readers/messages.go deleted file mode 100644 index 19ce1c08..00000000 --- a/docker/addons/vault/scripts/readers/messages.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package readers - -import "errors" - -const ( - // EqualKey represents the equal comparison operator key. - EqualKey = "eq" - // LowerThanKey represents the lower-than comparison operator key. - LowerThanKey = "lt" - // LowerThanEqualKey represents the lower-than-or-equal comparison operator key. - LowerThanEqualKey = "le" - // GreaterThanKey represents the greater-than-or-equal comparison operator key. - GreaterThanKey = "gt" - // GreaterThanEqualKey represents the greater-than-or-equal comparison operator key. - GreaterThanEqualKey = "ge" -) - -// ErrReadMessages indicates failure occurred while reading messages from database. -var ErrReadMessages = errors.New("failed to read messages from database") - -// MessageRepository specifies message reader API. -// -//go:generate mockery --name MessageRepository --output=./mocks --filename messages.go --quiet --note "Copyright (c) Abstract Machines" -type MessageRepository interface { - // ReadAll skips given number of messages for given channel and returns next - // limited number of messages. - ReadAll(chanID string, pm PageMetadata) (MessagesPage, error) -} - -// Message represents any message format. -type Message interface{} - -// MessagesPage contains page related metadata as well as list of messages that -// belong to this page. -type MessagesPage struct { - PageMetadata - Total uint64 - Messages []Message -} - -// PageMetadata represents the parameters used to create database queries. -type PageMetadata struct { - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` - Subtopic string `json:"subtopic,omitempty"` - Publisher string `json:"publisher,omitempty"` - Protocol string `json:"protocol,omitempty"` - Name string `json:"name,omitempty"` - Value float64 `json:"v,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"` - Format string `json:"format,omitempty"` - Aggregation string `json:"aggregation,omitempty"` - Interval string `json:"interval,omitempty"` -} - -// ParseValueComparator convert comparison operator keys into mathematic anotation. -func ParseValueComparator(query map[string]interface{}) string { - comparator := "=" - val, ok := query["comparator"] - if ok { - switch val.(string) { - case EqualKey: - comparator = "=" - case LowerThanKey: - comparator = "<" - case LowerThanEqualKey: - comparator = "<=" - case GreaterThanKey: - comparator = ">" - case GreaterThanEqualKey: - comparator = ">=" - } - } - - return comparator -} diff --git a/docker/addons/vault/scripts/readers/mocks/doc.go b/docker/addons/vault/scripts/readers/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/readers/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/docker/addons/vault/scripts/readers/mocks/messages.go b/docker/addons/vault/scripts/readers/mocks/messages.go deleted file mode 100644 index 3968840e..00000000 --- a/docker/addons/vault/scripts/readers/mocks/messages.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -// Copyright (c) Abstract Machines - -package mocks - -import ( - readers "github.com/absmach/magistrala/readers" - mock "github.com/stretchr/testify/mock" -) - -// MessageRepository is an autogenerated mock type for the MessageRepository type -type MessageRepository struct { - mock.Mock -} - -// ReadAll provides a mock function with given fields: chanID, pm -func (_m *MessageRepository) ReadAll(chanID string, pm readers.PageMetadata) (readers.MessagesPage, error) { - ret := _m.Called(chanID, pm) - - if len(ret) == 0 { - panic("no return value specified for ReadAll") - } - - var r0 readers.MessagesPage - var r1 error - if rf, ok := ret.Get(0).(func(string, readers.PageMetadata) (readers.MessagesPage, error)); ok { - return rf(chanID, pm) - } - if rf, ok := ret.Get(0).(func(string, readers.PageMetadata) readers.MessagesPage); ok { - r0 = rf(chanID, pm) - } else { - r0 = ret.Get(0).(readers.MessagesPage) - } - - if rf, ok := ret.Get(1).(func(string, readers.PageMetadata) error); ok { - r1 = rf(chanID, pm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewMessageRepository creates a new instance of MessageRepository. 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 NewMessageRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *MessageRepository { - mock := &MessageRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/docker/addons/vault/scripts/readers/postgres/README.md b/docker/addons/vault/scripts/readers/postgres/README.md deleted file mode 100644 index 66e289d4..00000000 --- a/docker/addons/vault/scripts/readers/postgres/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# Postgres reader - -Postgres reader provides message repository implementation for Postgres. - -## 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_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 | | - -## 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 -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 - -cd magistrala - -# compile the postgres writer -make postgres-writer - -# copy binary to bin -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 -``` - -## Usage - -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). diff --git a/docker/addons/vault/scripts/readers/postgres/doc.go b/docker/addons/vault/scripts/readers/postgres/doc.go deleted file mode 100644 index a92d4f9b..00000000 --- a/docker/addons/vault/scripts/readers/postgres/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package postgres contains repository implementations using Postgres as -// the underlying database. -package postgres diff --git a/docker/addons/vault/scripts/readers/postgres/init.go b/docker/addons/vault/scripts/readers/postgres/init.go deleted file mode 100644 index 10bc5f1e..00000000 --- a/docker/addons/vault/scripts/readers/postgres/init.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "fmt" - - "github.com/jmoiron/sqlx" - migrate "github.com/rubenv/sql-migrate" -) - -// Table for SenML messages. -const defTable = "messages" - -// Config defines the options that are used when connecting to a PostgreSQL instance. -type Config struct { - Host string - Port string - User string - Pass string - Name string - SSLMode string - SSLCert string - SSLKey string - SSLRootCert string -} - -// Connect creates a connection to the PostgreSQL instance and applies any -// unapplied database migrations. A non-nil error is returned to indicate -// failure. -func Connect(cfg Config) (*sqlx.DB, error) { - url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert) - - db, err := sqlx.Open("pgx", url) - if err != nil { - return nil, err - } - - if err := migrateDB(db); err != nil { - return nil, err - } - - return db, nil -} - -func migrateDB(db *sqlx.DB) error { - migrations := &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "messages_1", - Up: []string{ - `CREATE TABLE IF NOT EXISTS messages ( - id UUID, - channel UUID, - subtopic VARCHAR(254), - publisher UUID, - protocol TEXT, - name TEXT, - unit TEXT, - value FLOAT, - string_value TEXT, - bool_value BOOL, - data_value TEXT, - sum FLOAT, - time FlOAT, - update_time FLOAT, - PRIMARY KEY (id) - )`, - }, - Down: []string{ - "DROP TABLE messages", - }, - }, - }, - } - - _, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up) - return err -} diff --git a/docker/addons/vault/scripts/readers/postgres/messages.go b/docker/addons/vault/scripts/readers/postgres/messages.go deleted file mode 100644 index 4037b5b3..00000000 --- a/docker/addons/vault/scripts/readers/postgres/messages.go +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres - -import ( - "encoding/json" - "fmt" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/transformers/senml" - "github.com/absmach/magistrala/readers" - "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jmoiron/sqlx" -) - -var _ readers.MessageRepository = (*postgresRepository)(nil) - -type postgresRepository struct { - db *sqlx.DB -} - -// New returns new PostgreSQL writer. -func New(db *sqlx.DB) readers.MessageRepository { - return &postgresRepository{ - db: db, - } -} - -func (tr postgresRepository) ReadAll(chanID string, rpm readers.PageMetadata) (readers.MessagesPage, error) { - order := "time" - format := defTable - - if rpm.Format != "" && rpm.Format != defTable { - order = "created" - format = rpm.Format - } - cond := fmtCondition(chanID, rpm) - - q := fmt.Sprintf(`SELECT * FROM %s - WHERE %s ORDER BY %s DESC - LIMIT :limit OFFSET :offset;`, format, cond, order) - - params := map[string]interface{}{ - "channel": chanID, - "limit": rpm.Limit, - "offset": rpm.Offset, - "subtopic": rpm.Subtopic, - "publisher": rpm.Publisher, - "name": rpm.Name, - "protocol": rpm.Protocol, - "value": rpm.Value, - "bool_value": rpm.BoolValue, - "string_value": rpm.StringValue, - "data_value": rpm.DataValue, - "from": rpm.From, - "to": rpm.To, - } - rows, err := tr.db.NamedQuery(q, params) - if err != nil { - if pgErr, ok := err.(*pgconn.PgError); ok { - if pgErr.Code == pgerrcode.UndefinedTable { - return readers.MessagesPage{}, nil - } - } - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - defer rows.Close() - - page := readers.MessagesPage{ - PageMetadata: rpm, - Messages: []readers.Message{}, - } - switch format { - case defTable: - for rows.Next() { - msg := senmlMessage{Message: senml.Message{}} - if err := rows.StructScan(&msg); err != nil { - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - - page.Messages = append(page.Messages, msg.Message) - } - default: - for rows.Next() { - msg := jsonMessage{} - if err := rows.StructScan(&msg); err != nil { - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - m, err := msg.toMap() - if err != nil { - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - page.Messages = append(page.Messages, m) - } - } - - q = fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE %s;`, format, cond) - rows, err = tr.db.NamedQuery(q, params) - if err != nil { - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - defer rows.Close() - - total := uint64(0) - if rows.Next() { - if err := rows.Scan(&total); err != nil { - return page, err - } - } - page.Total = total - - return page, nil -} - -func fmtCondition(chanID string, rpm readers.PageMetadata) string { - condition := `channel = :channel` - - var query map[string]interface{} - meta, err := json.Marshal(rpm) - if err != nil { - return condition - } - if err := json.Unmarshal(meta, &query); err != nil { - return condition - } - - for name := range query { - switch name { - case - "subtopic", - "publisher", - "name", - "protocol": - condition = fmt.Sprintf(`%s AND %s = :%s`, condition, name, name) - case "v": - comparator := readers.ParseValueComparator(query) - condition = fmt.Sprintf(`%s AND value %s :value`, condition, comparator) - case "vb": - condition = fmt.Sprintf(`%s AND bool_value = :bool_value`, condition) - case "vs": - comparator := readers.ParseValueComparator(query) - switch comparator { - case "=": - condition = fmt.Sprintf("%s AND string_value = :string_value ", condition) - case ">": - condition = fmt.Sprintf("%s AND string_value LIKE '%%' || :string_value || '%%' AND string_value <> :string_value", condition) - case ">=": - condition = fmt.Sprintf("%s AND string_value LIKE '%%' || :string_value || '%%'", condition) - case "<=": - condition = fmt.Sprintf("%s AND :string_value LIKE '%%' || string_value || '%%'", condition) - case "<": - condition = fmt.Sprintf("%s AND :string_value LIKE '%%' || string_value || '%%' AND string_value <> :string_value", condition) - } - case "vd": - comparator := readers.ParseValueComparator(query) - condition = fmt.Sprintf(`%s AND data_value %s :data_value`, condition, comparator) - case "from": - condition = fmt.Sprintf(`%s AND time >= :from`, condition) - case "to": - condition = fmt.Sprintf(`%s AND time < :to`, condition) - } - } - return condition -} - -type senmlMessage struct { - ID string `db:"id"` - senml.Message -} - -type jsonMessage struct { - ID string `db:"id"` - Channel string `db:"channel"` - Created int64 `db:"created"` - Subtopic string `db:"subtopic"` - Publisher string `db:"publisher"` - Protocol string `db:"protocol"` - Payload []byte `db:"payload"` -} - -func (msg jsonMessage) toMap() (map[string]interface{}, error) { - ret := map[string]interface{}{ - "id": msg.ID, - "channel": msg.Channel, - "created": msg.Created, - "subtopic": msg.Subtopic, - "publisher": msg.Publisher, - "protocol": msg.Protocol, - "payload": map[string]interface{}{}, - } - pld := make(map[string]interface{}) - if err := json.Unmarshal(msg.Payload, &pld); err != nil { - return nil, err - } - ret["payload"] = pld - return ret, nil -} diff --git a/docker/addons/vault/scripts/readers/postgres/messages_test.go b/docker/addons/vault/scripts/readers/postgres/messages_test.go deleted file mode 100644 index 52b0e402..00000000 --- a/docker/addons/vault/scripts/readers/postgres/messages_test.go +++ /dev/null @@ -1,687 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package postgres_test - -import ( - "context" - "fmt" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - subtopic = "subtopic" - msgsNum = 100 - limit = 10 - valueFields = 5 - mqttProt = "mqtt" - httpProt = "http" - msgName = "temperature" - format1 = "format1" - format2 = "format2" - wrongID = "0" -) - -var ( - v float64 = 5 - vs = "stringValue" - vb = true - vd = "dataValue" - sum float64 = 42 -) - -func TestReadSenml(t *testing.T) { - writer := pwriter.New(db) - - chanID := testsutil.GenerateUUID(t) - pubID := testsutil.GenerateUUID(t) - pubID2 := testsutil.GenerateUUID(t) - wrongID := testsutil.GenerateUUID(t) - - m := senml.Message{ - Channel: chanID, - Publisher: pubID, - Protocol: mqttProt, - } - - messages := []senml.Message{} - valueMsgs := []senml.Message{} - boolMsgs := []senml.Message{} - stringMsgs := []senml.Message{} - dataMsgs := []senml.Message{} - queryMsgs := []senml.Message{} - - now := float64(time.Now().Unix()) - for i := 0; i < msgsNum; i++ { - // Mix possible values as well as value sum. - msg := m - msg.Time = now - float64(i) - - count := i % valueFields - switch count { - case 0: - msg.Value = &v - valueMsgs = append(valueMsgs, msg) - case 1: - msg.BoolValue = &vb - boolMsgs = append(boolMsgs, msg) - case 2: - msg.StringValue = &vs - stringMsgs = append(stringMsgs, msg) - case 3: - msg.DataValue = &vd - dataMsgs = append(dataMsgs, msg) - case 4: - msg.Sum = &sum - msg.Subtopic = subtopic - msg.Protocol = httpProt - msg.Publisher = pubID2 - msg.Name = msgName - queryMsgs = append(queryMsgs, msg) - } - - messages = append(messages, msg) - } - - err := writer.ConsumeBlocking(context.TODO(), messages) - require.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) - - reader := preader.New(db) - - // Since messages are not saved in natural order, - // cases that return subset of messages are only - // checking data result set size, but not content. - cases := []struct { - desc string - chanID string - pageMeta readers.PageMetadata - page readers.MessagesPage - }{ - { - desc: "read message page for existing channel", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: msgsNum, - }, - page: readers.MessagesPage{ - Total: msgsNum, - Messages: fromSenml(messages), - }, - }, - { - desc: "read message page for non-existent channel", - chanID: wrongID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: msgsNum, - }, - page: readers.MessagesPage{ - Messages: []readers.Message{}, - }, - }, - { - desc: "read message last page", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: msgsNum - 20, - Limit: msgsNum, - }, - page: readers.MessagesPage{ - Total: msgsNum, - Messages: fromSenml(messages[msgsNum-20 : msgsNum]), - }, - }, - { - desc: "read message with non-existent subtopic", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: msgsNum, - Subtopic: "not-present", - }, - page: readers.MessagesPage{ - Messages: []readers.Message{}, - }, - }, - { - desc: "read message with subtopic", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(queryMsgs)), - Subtopic: subtopic, - }, - page: readers.MessagesPage{ - Total: uint64(len(queryMsgs)), - Messages: fromSenml(queryMsgs), - }, - }, - { - desc: "read message with publisher", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(queryMsgs)), - Publisher: pubID2, - }, - page: readers.MessagesPage{ - Total: uint64(len(queryMsgs)), - Messages: fromSenml(queryMsgs), - }, - }, - { - desc: "read message with wrong format", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Format: "messagess", - Offset: 0, - Limit: uint64(len(queryMsgs)), - Publisher: pubID2, - }, - page: readers.MessagesPage{ - Total: 0, - Messages: []readers.Message{}, - }, - }, - { - desc: "read message with protocol", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(queryMsgs)), - Protocol: httpProt, - }, - page: readers.MessagesPage{ - Total: uint64(len(queryMsgs)), - Messages: fromSenml(queryMsgs), - }, - }, - { - desc: "read message with name", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Name: msgName, - }, - page: readers.MessagesPage{ - Total: uint64(len(queryMsgs)), - Messages: fromSenml(queryMsgs[0:limit]), - }, - }, - { - desc: "read message with value", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v, - Comparator: readers.EqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and lower-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v + 1, - Comparator: readers.LowerThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and lower-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v + 1, - Comparator: readers.LowerThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and greater-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v - 1, - Comparator: readers.GreaterThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and greater-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v - 1, - Comparator: readers.GreaterThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with boolean value", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - BoolValue: vb, - }, - page: readers.MessagesPage{ - Total: uint64(len(boolMsgs)), - Messages: fromSenml(boolMsgs[0:limit]), - }, - }, - { - desc: "read message with string value", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: vs, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: vs, - Comparator: readers.EqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and lower-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: "a stringValues b", - Comparator: readers.LowerThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and lower-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: vs, - Comparator: readers.LowerThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and greater-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: "alu", - Comparator: readers.GreaterThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and greater-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: vs, - Comparator: readers.GreaterThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with data value", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with data value and lower-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd + string(rune(1)), - Comparator: readers.LowerThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with data value and lower-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd + string(rune(1)), - Comparator: readers.LowerThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with data value and greater-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd[:len(vd)-1], - Comparator: readers.GreaterThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with data value and greater-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd[:len(vd)-1], - Comparator: readers.GreaterThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with from", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(messages[0:21])), - From: messages[20].Time, - }, - page: readers.MessagesPage{ - Total: uint64(len(messages[0:21])), - Messages: fromSenml(messages[0:21]), - }, - }, - { - desc: "read message with to", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(messages[21:])), - To: messages[20].Time, - }, - page: readers.MessagesPage{ - Total: uint64(len(messages[21:])), - Messages: fromSenml(messages[21:]), - }, - }, - { - desc: "read message with from/to", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - From: messages[5].Time, - To: messages[0].Time, - }, - page: readers.MessagesPage{ - Total: 5, - Messages: fromSenml(messages[1:6]), - }, - }, - } - - for _, tc := range cases { - result, err := reader.ReadAll(tc.chanID, tc.pageMeta) - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %s", tc.desc, err)) - assert.ElementsMatch(t, tc.page.Messages, result.Messages, fmt.Sprintf("%s: got incorrect list of senml Messages from ReadAll()", tc.desc)) - assert.Equal(t, tc.page.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.page.Total, result.Total)) - } -} - -func TestReadJSON(t *testing.T) { - writer := pwriter.New(db) - - id1 := testsutil.GenerateUUID(t) - m := json.Message{ - Channel: id1, - Publisher: id1, - Created: time.Now().Unix(), - Subtopic: "subtopic/format/some_json", - Protocol: "coap", - Payload: map[string]interface{}{ - "field_1": 123.0, - "field_2": "value", - "field_3": false, - "field_4": 12.344, - "field_5": map[string]interface{}{ - "field_1": "value", - "field_2": 42.0, - }, - }, - } - messages1 := json.Messages{ - Format: format1, - } - msgs1 := []map[string]interface{}{} - for i := 0; i < msgsNum; i++ { - msg := m - messages1.Data = append(messages1.Data, msg) - m := toMap(msg) - msgs1 = append(msgs1, m) - } - - err := writer.ConsumeBlocking(context.TODO(), messages1) - require.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) - - id2 := testsutil.GenerateUUID(t) - m = json.Message{ - Channel: id2, - Publisher: id2, - Created: time.Now().Unix(), - Subtopic: "subtopic/other_format/some_other_json", - Protocol: "udp", - Payload: map[string]interface{}{ - "field_1": "other_value", - "false_value": false, - "field_pi": 3.14159265, - }, - } - messages2 := json.Messages{ - Format: format2, - } - msgs2 := []map[string]interface{}{} - for i := 0; i < msgsNum; i++ { - msg := m - if i%2 == 0 { - msg.Protocol = httpProt - } - messages2.Data = append(messages2.Data, msg) - m := toMap(msg) - msgs2 = append(msgs2, m) - } - - err = writer.ConsumeBlocking(context.TODO(), messages2) - require.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) - - httpMsgs := []map[string]interface{}{} - for i := 0; i < msgsNum; i += 2 { - httpMsgs = append(httpMsgs, msgs2[i]) - } - - reader := preader.New(db) - - cases := map[string]struct { - chanID string - pageMeta readers.PageMetadata - page readers.MessagesPage - }{ - "read message page for existing channel": { - chanID: id1, - pageMeta: readers.PageMetadata{ - Format: messages1.Format, - Offset: 0, - Limit: 10, - }, - page: readers.MessagesPage{ - Total: 100, - Messages: fromJSON(msgs1[:10]), - }, - }, - "read message page for non-existent channel": { - chanID: wrongID, - pageMeta: readers.PageMetadata{ - Format: messages1.Format, - Offset: 0, - Limit: 10, - }, - page: readers.MessagesPage{ - Messages: []readers.Message{}, - }, - }, - "read message last page": { - chanID: id2, - pageMeta: readers.PageMetadata{ - Format: messages2.Format, - Offset: msgsNum - 20, - Limit: msgsNum, - }, - page: readers.MessagesPage{ - Total: msgsNum, - Messages: fromJSON(msgs2[msgsNum-20 : msgsNum]), - }, - }, - "read message with protocol": { - chanID: id2, - pageMeta: readers.PageMetadata{ - Format: messages2.Format, - Offset: 0, - Limit: uint64(msgsNum / 2), - Protocol: httpProt, - }, - page: readers.MessagesPage{ - Total: uint64(msgsNum / 2), - Messages: fromJSON(httpMsgs), - }, - }, - } - - for desc, tc := range cases { - result, err := reader.ReadAll(tc.chanID, tc.pageMeta) - for i := 0; i < len(result.Messages); i++ { - m := result.Messages[i] - // Remove id as it is not sent by the client. - delete(m.(map[string]interface{}), "id") - result.Messages[i] = m - } - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %s", desc, err)) - assert.ElementsMatch(t, tc.page.Messages, result.Messages, fmt.Sprintf("%s: got incorrect list of json Messages from ReadAll()", desc)) - assert.Equal(t, tc.page.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", desc, tc.page.Total, result.Total)) - } -} - -func fromSenml(msg []senml.Message) []readers.Message { - var ret []readers.Message - for _, m := range msg { - ret = append(ret, m) - } - return ret -} - -func fromJSON(msg []map[string]interface{}) []readers.Message { - var ret []readers.Message - for _, m := range msg { - ret = append(ret, m) - } - return ret -} - -func toMap(msg json.Message) map[string]interface{} { - return map[string]interface{}{ - "channel": msg.Channel, - "created": msg.Created, - "subtopic": msg.Subtopic, - "publisher": msg.Publisher, - "protocol": msg.Protocol, - "payload": map[string]interface{}(msg.Payload), - } -} diff --git a/docker/addons/vault/scripts/readers/postgres/setup_test.go b/docker/addons/vault/scripts/readers/postgres/setup_test.go deleted file mode 100644 index 4e3bb0e4..00000000 --- a/docker/addons/vault/scripts/readers/postgres/setup_test.go +++ /dev/null @@ -1,83 +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 ( - "fmt" - "log" - "os" - "testing" - - "github.com/absmach/magistrala/readers/postgres" - _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access - "github.com/jmoiron/sqlx" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" -) - -var db *sqlx.DB - -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") - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - - if err = pool.Retry(func() error { - db, err = sqlx.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := postgres.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = postgres.Connect(dbConfig); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - 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/docker/addons/vault/scripts/readers/timescale/README.md b/docker/addons/vault/scripts/readers/timescale/README.md deleted file mode 100644 index 7ce7db3b..00000000 --- a/docker/addons/vault/scripts/readers/timescale/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# Timescale reader - -Timescale reader provides message repository implementation for Timescale. - -## 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_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 | "" | - -## 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. - -To start the service, execute the following shell script: - -```bash -# download the latest version of the service -git clone https://github.com/absmach/magistrala - -cd magistrala - -# compile the timescale writer -make timescale-writer - -# copy binary to bin -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 -``` - -## Usage - -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). diff --git a/docker/addons/vault/scripts/readers/timescale/doc.go b/docker/addons/vault/scripts/readers/timescale/doc.go deleted file mode 100644 index 302be6ea..00000000 --- a/docker/addons/vault/scripts/readers/timescale/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package timescale contains repository implementations using Timescale as -// the underlying database. -package timescale diff --git a/docker/addons/vault/scripts/readers/timescale/init.go b/docker/addons/vault/scripts/readers/timescale/init.go deleted file mode 100644 index 9513df15..00000000 --- a/docker/addons/vault/scripts/readers/timescale/init.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package timescale - -import ( - "fmt" - - "github.com/jmoiron/sqlx" - migrate "github.com/rubenv/sql-migrate" -) - -// Table for SenML messages. -const defTable = "messages" - -// Config defines the options that are used when connecting to a TimescaleSQL instance. -type Config struct { - Host string - Port string - User string - Pass string - Name string - SSLMode string - SSLCert string - SSLKey string - SSLRootCert string -} - -// Connect creates a connection to the TimescaleSQL instance and applies any -// unapplied database migrations. A non-nil error is returned to indicate -// failure. -func Connect(cfg Config) (*sqlx.DB, error) { - url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert) - - db, err := sqlx.Open("pgx", url) - if err != nil { - return nil, err - } - - if err := migrateDB(db); err != nil { - return nil, err - } - - return db, nil -} - -func migrateDB(db *sqlx.DB) error { - migrations := &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "messages_1", - Up: []string{ - `CREATE TABLE IF NOT EXISTS messages ( - time BIGINT NOT NULL, - channel UUID, - subtopic VARCHAR(254), - publisher UUID, - protocol TEXT, - name VARCHAR(254), - unit TEXT, - value FLOAT, - string_value TEXT, - bool_value BOOL, - data_value BYTEA, - sum FLOAT, - update_time FLOAT, - PRIMARY KEY (time, publisher, subtopic, name) - ); - SELECT create_hypertable('messages', 'time', create_default_indexes => FALSE, chunk_time_interval => 86400000, if_not_exists => TRUE);`, - }, - Down: []string{ - "DROP TABLE messages", - }, - }, - }, - } - - _, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up) - return err -} diff --git a/docker/addons/vault/scripts/readers/timescale/messages.go b/docker/addons/vault/scripts/readers/timescale/messages.go deleted file mode 100644 index a6a844fa..00000000 --- a/docker/addons/vault/scripts/readers/timescale/messages.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package timescale - -import ( - "encoding/json" - "fmt" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/absmach/magistrala/pkg/transformers/senml" - "github.com/absmach/magistrala/readers" - "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jmoiron/sqlx" // required for DB access -) - -var _ readers.MessageRepository = (*timescaleRepository)(nil) - -type timescaleRepository struct { - db *sqlx.DB -} - -// New returns new TimescaleSQL writer. -func New(db *sqlx.DB) readers.MessageRepository { - return ×caleRepository{ - db: db, - } -} - -func (tr timescaleRepository) ReadAll(chanID string, rpm readers.PageMetadata) (readers.MessagesPage, error) { - order := "time" - format := defTable - - if rpm.Format != "" && rpm.Format != defTable { - order = "created" - format = rpm.Format - } - - q := fmt.Sprintf(`SELECT * FROM %s WHERE %s ORDER BY %s DESC LIMIT :limit OFFSET :offset;`, format, fmtCondition(rpm), order) - totalQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE %s;`, format, fmtCondition(rpm)) - - // If aggregation is provided, add time_bucket and aggregation to the query - const timeDivisor = 1000000000 - - if rpm.Aggregation != "" { - q = fmt.Sprintf(`SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/%d))) *%d AS time, %s(value) AS value, FIRST(publisher, time) AS publisher, FIRST(protocol, time) AS protocol, FIRST(subtopic, time) AS subtopic, FIRST(name,time) AS name, FIRST(unit, time) AS unit FROM %s WHERE %s GROUP BY 1 ORDER BY time DESC LIMIT :limit OFFSET :offset;`, rpm.Interval, timeDivisor, timeDivisor, rpm.Aggregation, format, fmtCondition(rpm)) - - totalQuery = fmt.Sprintf(`SELECT COUNT(*) FROM (SELECT EXTRACT(epoch FROM time_bucket('%s', to_timestamp(time/%d))) AS time, %s(value) AS value FROM %s WHERE %s GROUP BY 1) AS subquery;`, rpm.Interval, timeDivisor, rpm.Aggregation, format, fmtCondition(rpm)) - } - - params := map[string]interface{}{ - "channel": chanID, - "limit": rpm.Limit, - "offset": rpm.Offset, - "subtopic": rpm.Subtopic, - "publisher": rpm.Publisher, - "name": rpm.Name, - "protocol": rpm.Protocol, - "value": rpm.Value, - "bool_value": rpm.BoolValue, - "string_value": rpm.StringValue, - "data_value": rpm.DataValue, - "from": rpm.From, - "to": rpm.To, - } - - rows, err := tr.db.NamedQuery(q, params) - if err != nil { - if pgErr, ok := err.(*pgconn.PgError); ok { - if pgErr.Code == pgerrcode.UndefinedTable { - return readers.MessagesPage{}, nil - } - } - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - defer rows.Close() - - page := readers.MessagesPage{ - PageMetadata: rpm, - Messages: []readers.Message{}, - } - switch format { - case defTable: - for rows.Next() { - msg := senmlMessage{Message: senml.Message{}} - if err := rows.StructScan(&msg); err != nil { - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - - page.Messages = append(page.Messages, msg.Message) - } - default: - for rows.Next() { - msg := jsonMessage{} - if err := rows.StructScan(&msg); err != nil { - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - m, err := msg.toMap() - if err != nil { - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - page.Messages = append(page.Messages, m) - } - } - - rows, err = tr.db.NamedQuery(totalQuery, params) - if err != nil { - return readers.MessagesPage{}, errors.Wrap(readers.ErrReadMessages, err) - } - defer rows.Close() - - total := uint64(0) - if rows.Next() { - if err := rows.Scan(&total); err != nil { - return page, err - } - } - page.Total = total - - return page, nil -} - -func fmtCondition(rpm readers.PageMetadata) string { - condition := `channel = :channel` - - var query map[string]interface{} - meta, err := json.Marshal(rpm) - if err != nil { - return condition - } - if err := json.Unmarshal(meta, &query); err != nil { - return condition - } - - for name := range query { - switch name { - case - "subtopic", - "publisher", - "name", - "protocol": - condition = fmt.Sprintf(`%s AND %s = :%s`, condition, name, name) - case "v": - comparator := readers.ParseValueComparator(query) - condition = fmt.Sprintf(`%s AND value %s :value`, condition, comparator) - case "vb": - condition = fmt.Sprintf(`%s AND bool_value = :bool_value`, condition) - case "vs": - comparator := readers.ParseValueComparator(query) - switch comparator { - case "=": - condition = fmt.Sprintf("%s AND string_value = :string_value ", condition) - case ">": - condition = fmt.Sprintf("%s AND string_value LIKE '%%' || :string_value || '%%' AND string_value <> :string_value", condition) - case ">=": - condition = fmt.Sprintf("%s AND string_value LIKE '%%' || :string_value || '%%'", condition) - case "<=": - condition = fmt.Sprintf("%s AND :string_value LIKE '%%' || string_value || '%%'", condition) - case "<": - condition = fmt.Sprintf("%s AND :string_value LIKE '%%' || string_value || '%%' AND string_value <> :string_value", condition) - } - case "vd": - comparator := readers.ParseValueComparator(query) - condition = fmt.Sprintf(`%s AND data_value %s :data_value`, condition, comparator) - case "from": - condition = fmt.Sprintf(`%s AND time >= :from`, condition) - case "to": - condition = fmt.Sprintf(`%s AND time < :to`, condition) - } - } - return condition -} - -type senmlMessage struct { - ID string `db:"id"` - senml.Message -} - -type jsonMessage struct { - Channel string `db:"channel"` - Created int64 `db:"created"` - Subtopic string `db:"subtopic"` - Publisher string `db:"publisher"` - Protocol string `db:"protocol"` - Payload []byte `db:"payload"` -} - -func (msg jsonMessage) toMap() (map[string]interface{}, error) { - ret := map[string]interface{}{ - "channel": msg.Channel, - "created": msg.Created, - "subtopic": msg.Subtopic, - "publisher": msg.Publisher, - "protocol": msg.Protocol, - "payload": map[string]interface{}{}, - } - pld := make(map[string]interface{}) - if err := json.Unmarshal(msg.Payload, &pld); err != nil { - return nil, err - } - ret["payload"] = pld - return ret, nil -} diff --git a/docker/addons/vault/scripts/readers/timescale/messages_test.go b/docker/addons/vault/scripts/readers/timescale/messages_test.go deleted file mode 100644 index 439a3942..00000000 --- a/docker/addons/vault/scripts/readers/timescale/messages_test.go +++ /dev/null @@ -1,810 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package timescale_test - -import ( - "context" - "fmt" - "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" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - subtopic = "subtopic" - msgsNum = 100 - limit = 10 - valueFields = 5 - mqttProt = "mqtt" - httpProt = "http" - msgName = "temperature" - format1 = "format1" - format2 = "format2" - wrongID = "0" -) - -var ( - v float64 = 5 - vs = "stringValue" - vb = true - vd = "dataValue" - sum float64 = 42 -) - -func TestReadSenml(t *testing.T) { - writer := twriter.New(db) - - chanID := testsutil.GenerateUUID(t) - pubID := testsutil.GenerateUUID(t) - pubID2 := testsutil.GenerateUUID(t) - wrongID := testsutil.GenerateUUID(t) - - m := senml.Message{ - Channel: chanID, - Publisher: pubID, - Protocol: mqttProt, - } - - messages := []senml.Message{} - valueMsgs := []senml.Message{} - boolMsgs := []senml.Message{} - stringMsgs := []senml.Message{} - dataMsgs := []senml.Message{} - queryMsgs := []senml.Message{} - - now := float64(time.Now().Unix()) - for i := 0; i < msgsNum; i++ { - // Mix possible values as well as value sum. - msg := m - msg.Time = now - float64(i) - - count := i % valueFields - switch count { - case 0: - msg.Value = &v - valueMsgs = append(valueMsgs, msg) - case 1: - msg.BoolValue = &vb - boolMsgs = append(boolMsgs, msg) - case 2: - msg.StringValue = &vs - stringMsgs = append(stringMsgs, msg) - case 3: - msg.DataValue = &vd - dataMsgs = append(dataMsgs, msg) - case 4: - msg.Sum = &sum - msg.Subtopic = subtopic - msg.Protocol = httpProt - msg.Publisher = pubID2 - msg.Name = msgName - queryMsgs = append(queryMsgs, msg) - } - - messages = append(messages, msg) - } - - err := writer.ConsumeBlocking(context.TODO(), messages) - require.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) - - reader := treader.New(db) - - // Since messages are not saved in natural order, - // cases that return subset of messages are only - // checking data result set size, but not content. - cases := []struct { - desc string - chanID string - pageMeta readers.PageMetadata - page readers.MessagesPage - }{ - { - desc: "read message page for existing channel", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: msgsNum, - }, - page: readers.MessagesPage{ - Total: msgsNum, - Messages: fromSenml(messages), - }, - }, - { - desc: "read message page for non-existent channel", - chanID: wrongID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: msgsNum, - }, - page: readers.MessagesPage{ - Messages: []readers.Message{}, - }, - }, - { - desc: "read message last page", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: msgsNum - 20, - Limit: msgsNum, - }, - page: readers.MessagesPage{ - Total: msgsNum, - Messages: fromSenml(messages[msgsNum-20 : msgsNum]), - }, - }, - { - desc: "read message with non-existent subtopic", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: msgsNum, - Subtopic: "not-present", - }, - page: readers.MessagesPage{ - Messages: []readers.Message{}, - }, - }, - { - desc: "read message with subtopic", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(queryMsgs)), - Subtopic: subtopic, - }, - page: readers.MessagesPage{ - Total: uint64(len(queryMsgs)), - Messages: fromSenml(queryMsgs), - }, - }, - { - desc: "read message with publisher", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(queryMsgs)), - Publisher: pubID2, - }, - page: readers.MessagesPage{ - Total: uint64(len(queryMsgs)), - Messages: fromSenml(queryMsgs), - }, - }, - { - desc: "read message with wrong format", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Format: "messagess", - Offset: 0, - Limit: uint64(len(queryMsgs)), - Publisher: pubID2, - }, - page: readers.MessagesPage{ - Total: 0, - Messages: []readers.Message{}, - }, - }, - { - desc: "read message with protocol", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(queryMsgs)), - Protocol: httpProt, - }, - page: readers.MessagesPage{ - Total: uint64(len(queryMsgs)), - Messages: fromSenml(queryMsgs), - }, - }, - { - desc: "read message with name", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Name: msgName, - }, - page: readers.MessagesPage{ - Total: uint64(len(queryMsgs)), - Messages: fromSenml(queryMsgs[0:limit]), - }, - }, - { - desc: "read message with value", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v, - Comparator: readers.EqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and lower-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v + 1, - Comparator: readers.LowerThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and lower-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v + 1, - Comparator: readers.LowerThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and greater-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v - 1, - Comparator: readers.GreaterThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with value and greater-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - Value: v - 1, - Comparator: readers.GreaterThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(valueMsgs)), - Messages: fromSenml(valueMsgs[0:limit]), - }, - }, - { - desc: "read message with boolean value", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - BoolValue: vb, - }, - page: readers.MessagesPage{ - Total: uint64(len(boolMsgs)), - Messages: fromSenml(boolMsgs[0:limit]), - }, - }, - { - desc: "read message with string value", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: vs, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: vs, - Comparator: readers.EqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and lower-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: "a stringValues b", - Comparator: readers.LowerThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and lower-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: vs, - Comparator: readers.LowerThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and greater-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: "alu", - Comparator: readers.GreaterThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with string value and greater-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - StringValue: vs, - Comparator: readers.GreaterThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(stringMsgs)), - Messages: fromSenml(stringMsgs[0:limit]), - }, - }, - { - desc: "read message with data value", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with data value and lower-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd + string(rune(1)), - Comparator: readers.LowerThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with data value and lower-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd + string(rune(1)), - Comparator: readers.LowerThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with data value and greater-than comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd[:len(vd)-1] + string(rune(1)), - Comparator: readers.GreaterThanKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with data value and greater-than-or-equal comparator", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - DataValue: vd[:len(vd)-1] + string(rune(1)), - Comparator: readers.GreaterThanEqualKey, - }, - page: readers.MessagesPage{ - Total: uint64(len(dataMsgs)), - Messages: fromSenml(dataMsgs[0:limit]), - }, - }, - { - desc: "read message with from", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(messages[0:21])), - From: messages[20].Time, - }, - page: readers.MessagesPage{ - Total: uint64(len(messages[0:21])), - Messages: fromSenml(messages[0:21]), - }, - }, - { - desc: "read message with to", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: uint64(len(messages[21:])), - To: messages[20].Time, - }, - page: readers.MessagesPage{ - Total: uint64(len(messages[21:])), - Messages: fromSenml(messages[21:]), - }, - }, - { - desc: "read message with from/to", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Offset: 0, - Limit: limit, - From: messages[5].Time, - To: messages[0].Time, - }, - page: readers.MessagesPage{ - Total: 5, - Messages: fromSenml(messages[1:6]), - }, - }, - } - - for _, tc := range cases { - result, err := reader.ReadAll(tc.chanID, tc.pageMeta) - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %s", tc.desc, err)) - assert.ElementsMatch(t, tc.page.Messages, result.Messages, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.page.Messages, result.Messages)) - assert.Equal(t, tc.page.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.page.Total, result.Total)) - } -} - -func TestReadMessagesWithAggregation(t *testing.T) { - writer := twriter.New(db) - - chanID := testsutil.GenerateUUID(t) - pubID := testsutil.GenerateUUID(t) - messages := []senml.Message{} - - now := float64(time.Now().UnixNano()) - value := 10.0 - for i := 0; i < 100; i++ { - if i%10 == 0 { - value += 10.0 - } - v := value - msg := senml.Message{ - Channel: chanID, - Publisher: pubID, - Time: now - float64(i*1000000000), // over 100 seconds - Value: &v, - Protocol: mqttProt, - } - messages = append(messages, msg) - } - - err := writer.ConsumeBlocking(context.TODO(), messages) - require.Nil(t, err, "expected no error got %s\n", err) - - reader := treader.New(db) - - // Set up cases for aggregation readAll - cases := []struct { - desc string - chanID string - pageMeta readers.PageMetadata - page readers.MessagesPage - }{ - { - desc: "read message page for existing channel with AVG aggregation over an hour", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Limit: 100, - Offset: 0, - Aggregation: "AVG", - Interval: "1 hour", - From: now - float64(100000000000), - To: now, - }, - page: readers.MessagesPage{ - Messages: fromSenml(messages), - }, - }, - { - desc: "read message page for existing channel with MAX aggregation over an hour", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Limit: 100, - Offset: 0, - Aggregation: "MAX", - Interval: "1 hour", - From: now - float64(100000000000), - To: now, - }, - page: readers.MessagesPage{ - Messages: fromSenml(messages), - }, - }, - { - desc: "read message page for existing channel with MIN aggregation over an hour", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Limit: 100, - Offset: 0, - Aggregation: "MIN", - Interval: "1 hour", - From: now - float64(100000000000), - To: now, - }, - page: readers.MessagesPage{ - Messages: fromSenml(messages), - }, - }, - { - desc: "read message page for existing channel with SUM aggregation over an hour", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Limit: 100, - Offset: 0, - Aggregation: "SUM", - Interval: "1 hour", - From: now - float64(100000000000), - To: now, - }, - page: readers.MessagesPage{ - Messages: fromSenml(messages), - }, - }, - { - desc: "read message page for existing channel with COUNT aggregation over an hour", - chanID: chanID, - pageMeta: readers.PageMetadata{ - Limit: 100, - Offset: 0, - Aggregation: "COUNT", - Interval: "1 hour", - From: now - float64(100000000000), - To: now, - }, - page: readers.MessagesPage{ - Messages: fromSenml(messages), - }, - }, - } - - for _, tc := range cases { - resultPage, err := reader.ReadAll(tc.chanID, tc.pageMeta) - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %s", tc.desc, err)) - assert.NotEmpty(t, resultPage.Messages, "expected non-empty result set") - for i := range resultPage.Messages { - msg, ok := resultPage.Messages[i].(senml.Message) - if ok && msg.Value != nil { - assert.GreaterOrEqual(t, *msg.Value, resultPage.Value, "expected aggregated value to be greater or equal to the expected value") - } - } - } -} - -func TestReadJSON(t *testing.T) { - writer := twriter.New(db) - - id1 := testsutil.GenerateUUID(t) - messages1 := json.Messages{ - Format: format1, - } - msgs1 := []map[string]interface{}{} - timeNow := time.Now().UnixMilli() - for i := 0; i < msgsNum; i++ { - m := json.Message{ - Channel: id1, - Publisher: id1, - Created: timeNow - int64(i), - Subtopic: "subtopic/format/some_json", - Protocol: "coap", - Payload: map[string]interface{}{ - "field_1": 123.0, - "field_2": "value", - "field_3": false, - "field_4": 12.344, - "field_5": map[string]interface{}{ - "field_1": "value", - "field_2": 42.0, - }, - }, - } - - msg := m - messages1.Data = append(messages1.Data, msg) - mapped := toMap(msg) - msgs1 = append(msgs1, mapped) - } - - err := writer.ConsumeBlocking(context.TODO(), messages1) - require.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) - - id2 := testsutil.GenerateUUID(t) - messages2 := json.Messages{ - Format: format2, - } - msgs2 := []map[string]interface{}{} - for i := 0; i < msgsNum; i++ { - m := json.Message{ - Channel: id2, - Publisher: id2, - Created: timeNow - int64(i), - Subtopic: "subtopic/other_format/some_other_json", - Protocol: "udp", - Payload: map[string]interface{}{ - "field_1": "other_value", - "false_value": false, - "field_pi": 3.14159265, - }, - } - - msg := m - if i%2 == 0 { - msg.Protocol = httpProt - } - messages2.Data = append(messages2.Data, msg) - mapped := toMap(msg) - msgs2 = append(msgs2, mapped) - } - - err = writer.ConsumeBlocking(context.TODO(), messages2) - require.Nil(t, err, fmt.Sprintf("expected no error got %s\n", err)) - - httpMsgs := []map[string]interface{}{} - for i := 0; i < msgsNum; i += 2 { - httpMsgs = append(httpMsgs, msgs2[i]) - } - - reader := treader.New(db) - - cases := map[string]struct { - chanID string - pageMeta readers.PageMetadata - page readers.MessagesPage - }{ - "read message page for existing channel": { - chanID: id1, - pageMeta: readers.PageMetadata{ - Format: messages1.Format, - Offset: 0, - Limit: 10, - }, - page: readers.MessagesPage{ - Total: 100, - Messages: fromJSON(msgs1[:10]), - }, - }, - "read message page for non-existent channel": { - chanID: wrongID, - pageMeta: readers.PageMetadata{ - Format: messages1.Format, - Offset: 0, - Limit: 10, - }, - page: readers.MessagesPage{ - Messages: []readers.Message{}, - }, - }, - "read message last page": { - chanID: id2, - pageMeta: readers.PageMetadata{ - Format: messages2.Format, - Offset: msgsNum - 20, - Limit: msgsNum, - }, - page: readers.MessagesPage{ - Total: msgsNum, - Messages: fromJSON(msgs2[msgsNum-20 : msgsNum]), - }, - }, - "read message with protocol": { - chanID: id2, - pageMeta: readers.PageMetadata{ - Format: messages2.Format, - Offset: 0, - Limit: uint64(msgsNum / 2), - Protocol: httpProt, - }, - page: readers.MessagesPage{ - Total: uint64(msgsNum / 2), - Messages: fromJSON(httpMsgs), - }, - }, - } - - for desc, tc := range cases { - result, err := reader.ReadAll(tc.chanID, tc.pageMeta) - assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %s", desc, err)) - assert.ElementsMatch(t, tc.page.Messages, result.Messages, fmt.Sprintf("%s: got incorrect list of json Messages from ReadAll()", desc)) - assert.Equal(t, tc.page.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", desc, tc.page.Total, result.Total)) - } -} - -func fromSenml(msg []senml.Message) []readers.Message { - var ret []readers.Message - for _, m := range msg { - ret = append(ret, m) - } - return ret -} - -func fromJSON(msg []map[string]interface{}) []readers.Message { - var ret []readers.Message - for _, m := range msg { - ret = append(ret, m) - } - return ret -} - -func toMap(msg json.Message) map[string]interface{} { - return map[string]interface{}{ - "channel": msg.Channel, - "created": msg.Created, - "subtopic": msg.Subtopic, - "publisher": msg.Publisher, - "protocol": msg.Protocol, - "payload": map[string]interface{}(msg.Payload), - } -} diff --git a/docker/addons/vault/scripts/readers/timescale/setup_test.go b/docker/addons/vault/scripts/readers/timescale/setup_test.go deleted file mode 100644 index b4d14da5..00000000 --- a/docker/addons/vault/scripts/readers/timescale/setup_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package timescale_test contains tests for PostgreSQL repository -// implementations. -package timescale_test - -import ( - "fmt" - "log" - "os" - "testing" - - "github.com/absmach/magistrala/readers/timescale" - _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access - "github.com/jmoiron/sqlx" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" -) - -var db *sqlx.DB - -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: "timescale/timescaledb", - Tag: "2.13.1-pg16", - 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") - url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) - - if err = pool.Retry(func() error { - db, err = sqlx.Open("pgx", url) - if err != nil { - return err - } - return db.Ping() - }); err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - dbConfig := timescale.Config{ - Host: "localhost", - Port: port, - User: "test", - Pass: "test", - Name: "test", - SSLMode: "disable", - SSLCert: "", - SSLKey: "", - SSLRootCert: "", - } - - if db, err = timescale.Connect(dbConfig); err != nil { - log.Fatalf("Could not setup test DB connection: %s", err) - } - - 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/docker/addons/vault/scripts/scripts/ci.sh b/docker/addons/vault/scripts/scripts/ci.sh deleted file mode 100755 index 48097ea4..00000000 --- a/docker/addons/vault/scripts/scripts/ci.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# This script contains commands to be executed by the CI tool. -NPROC=$(nproc) -GO_VERSION=1.22.4 -PROTOC_VERSION=27.1 -PROTOC_GEN_VERSION=v1.34.2 -PROTOC_GRPC_VERSION=v1.4.0 -GOLANGCI_LINT_VERSION=v1.60.3 - -function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; } - -update_go() { - CURRENT_GO_VERSION=$(go version | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') - if version_gt $GO_VERSION $CURRENT_GO_VERSION; then - echo "Updating go version from $CURRENT_GO_VERSION to $GO_VERSION ..." - # remove other Go version from path - sudo rm -rf /usr/bin/go - sudo rm -rf /usr/local/go - sudo rm -rf /usr/local/bin/go - sudo rm -rf /usr/local/golang - sudo rm -rf $GOROOT $GOPAT $GOBIN - wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz - sudo tar -C /usr/local -xzf go$GO_VERSION.linux-amd64.tar.gz - export GOROOT=/usr/local/go - export PATH=$PATH:/usr/local/go/bin - fi - export GOBIN=$HOME/go/bin - export PATH=$PATH:$GOBIN - go version -} - -setup_protoc() { - # Execute `go get` for protoc dependencies outside of project dir. - echo "Setting up protoc..." - PROTOC_ZIP=protoc-$PROTOC_VERSION-linux-x86_64.zip - curl -0L https://github.com/google/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP -o $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 - - 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 - - export PATH=$PATH:/usr/local/bin/protoc -} - -setup_mg() { - echo "Setting up Magistrala..." - for p in $(ls *.pb.go); do - mv $p $p.tmp - done - for p in $(ls pkg/*/*.pb.go); do - mv $p $p.tmp - done - make proto - for p in $(ls *.pb.go); do - if ! cmp -s $p $p.tmp; then - echo "Proto file and generated Go file $p are out of sync!" - exit 1 - fi - done - for p in $(ls pkg/*/*.pb.go); do - if ! cmp -s $p $p.tmp; then - echo "Proto file and generated Go file $p are out of sync!" - exit 1 - fi - done - echo "Compile check for rabbitmq..." - MG_MESSAGE_BROKER_TYPE=rabbitmq make http - echo "Compile check for redis..." - MG_ES_TYPE=redis make http - make -j$NPROC -} - -setup_lint() { - # binary will be $(go env GOBIN)/golangci-lint - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOBIN) $GOLANGCI_LINT_VERSION -} - -setup() { - echo "Setting up..." - update_go - setup_protoc - setup_mg - setup_lint -} - -run_test() { - echo "Running lint..." - golangci-lint run - echo "Running tests..." - echo "" > coverage.txt - for d in $(go list ./... | grep -v 'vendor\|cmd'); do - GOCACHE=off - go test -mod=vendor -v -race -tags test -coverprofile=profile.out -covermode=atomic $d - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi - done -} - -push() { - if test -n "$BRANCH_NAME" && test "$BRANCH_NAME" = "master"; then - echo "Pushing Docker images..." - make -j$NPROC latest - fi -} - -set -e -setup -run_test -push diff --git a/docker/addons/vault/scripts/scripts/csv/channels.csv b/docker/addons/vault/scripts/scripts/csv/channels.csv deleted file mode 100644 index 9b367f7c..00000000 --- a/docker/addons/vault/scripts/scripts/csv/channels.csv +++ /dev/null @@ -1,3 +0,0 @@ -channel_1 -channel_2 -channel_3 diff --git a/docker/addons/vault/scripts/scripts/csv/things.csv b/docker/addons/vault/scripts/scripts/csv/things.csv deleted file mode 100644 index 4636a476..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/scripts/provision-dev.sh b/docker/addons/vault/scripts/scripts/provision-dev.sh deleted file mode 100755 index 49b50808..00000000 --- a/docker/addons/vault/scripts/scripts/provision-dev.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 -# - -### -# Provisions example user, thing and channel on a clean Magistrala installation. -# -# Expects a running Magistrala installation. -# -# -### - -if [ $# -lt 4 ] -then - echo "Usage: $0 user_email user_password device_name channel_name" - exit 1 -fi - -EMAIL=$1 -PASSWORD=$2 -DEVICE=$3 -CHANNEL=$4 - -#provision user: -printf "Provisoning user with email $EMAIL and password $PASSWORD \n" -curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H "Content-Type: application/json" https://localhost/users -d '{"credentials": {"identity": "'"$EMAIL"'","secret": "'"$PASSWORD"'"}, "status": "enabled", "role": "admin" }' - -#get jwt token -JWTTOKEN=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H "Content-Type: application/json" https://localhost/users/tokens/issue -d '{"identity":"'"$EMAIL"'", "secret":"'"$PASSWORD"'"}' | grep -oP '"access_token":"\K[^"]+' ) -printf "JWT TOKEN for user is $JWTTOKEN \n" - -#provision thing -printf "Provisioning thing with name $DEVICE \n" -DEVICEID=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/things -d '{"name":"'"$DEVICE"'", "status": "enabled"}' | grep -oP '"id":"\K[^"]+' ) -curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/things/$DEVICEID - -#get thing token -DEVICETOKEN=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -H "Authorization: Bearer $JWTTOKEN" https://localhost/things/$DEVICEID | grep -oP '"secret":"\K[^"]+' ) -printf "Device token is $DEVICETOKEN \n" - -#provision channel -printf "Provisioning channel with name $CHANNEL \n" -CHANNELID=$(curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/channels -d '{"name":"'"$CHANNEL"'", "status": "enabled"}' | grep -oP '"id":"\K[^"]+' ) -curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $JWTTOKEN" https://localhost/channels/$CHANNELID - -#connect thing to channel -printf "Connecting thing of id $DEVICEID to channel of id $CHANNELID \n" -curl -s -S --cacert docker/ssl/certs/magistrala-server.crt --insecure -X PUT -H "Authorization: Bearer $JWTTOKEN" https://localhost/channels/$CHANNELID/things/$DEVICEID diff --git a/docker/addons/vault/scripts/scripts/run.sh b/docker/addons/vault/scripts/scripts/run.sh deleted file mode 100755 index 0cdd52ca..00000000 --- a/docker/addons/vault/scripts/scripts/run.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -### -# Runs all Magistrala microservices (must be previously built and installed). -# -# Expects that PostgreSQL and needed messaging DB are alredy running. -# Additionally, MQTT microservice demands that Redis is up and running. -# -### - -BUILD_DIR=../build - -# Kill all magistrala-* stuff -function cleanup { - pkill magistrala - pkill nats -} - -### -# NATS -### -nats-server & -counter=1 -until fuser 4222/tcp 1>/dev/null 2>&1; -do - sleep 0.5 - ((counter++)) - if [ ${counter} -gt 10 ] - then - echo "NATS failed to start in 5 sec, exiting" - exit 1 - fi - echo "Waiting for NATS server" -done - -### -# Users -### -MG_USERS_LOG_LEVEL=info MG_USERS_HTTP_PORT=9002 MG_USERS_GRPC_PORT=7001 MG_USERS_ADMIN_EMAIL=admin@magistrala.com MG_USERS_ADMIN_PASSWORD=12345678 MG_USERS_ADMIN_USERNAME=admin MG_EMAIL_TEMPLATE=../docker/templates/users.tmpl $BUILD_DIR/magistrala-users & - -### -# Things -### -MG_THINGS_LOG_LEVEL=info MG_THINGS_HTTP_PORT=9000 MG_THINGS_AUTH_GRPC_PORT=7000 MG_THINGS_AUTH_HTTP_PORT=9002 $BUILD_DIR/magistrala-things & - -### -# HTTP -### -MG_HTTP_ADAPTER_LOG_LEVEL=info MG_HTTP_ADAPTER_PORT=8008 MG_THINGS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-http & - -### -# WS -### -MG_WS_ADAPTER_LOG_LEVEL=info MG_WS_ADAPTER_HTTP_PORT=8190 MG_THINGS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-ws & - -### -# MQTT -### -MG_MQTT_ADAPTER_LOG_LEVEL=info MG_THINGS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-mqtt & - -### -# CoAP -### -MG_COAP_ADAPTER_LOG_LEVEL=info MG_COAP_ADAPTER_PORT=5683 MG_THINGS_AUTH_GRPC_URL=localhost:7000 $BUILD_DIR/magistrala-coap & - -trap cleanup EXIT - -while : ; do sleep 1 ; done diff --git a/docker/addons/vault/scripts/things/README.md b/docker/addons/vault/scripts/things/README.md deleted file mode 100644 index f570b0ff..00000000 --- a/docker/addons/vault/scripts/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 | <redis://localhost:6379/0> | -| MG_THINGS_CACHE_KEY_DURATION | Cache key duration in seconds | 3600 | -| MG_THINGS_ES_URL | Event store URL | <localhost:6379> | -| 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 | <http://jaeger:4318/v1/traces> | -| 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/docker/addons/vault/scripts/things/api/doc.go b/docker/addons/vault/scripts/things/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/grpc/client.go b/docker/addons/vault/scripts/things/api/grpc/client.go deleted file mode 100644 index f48ecd63..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/grpc/doc.go b/docker/addons/vault/scripts/things/api/grpc/doc.go deleted file mode 100644 index 20956ee5..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/grpc/endpoint.go b/docker/addons/vault/scripts/things/api/grpc/endpoint.go deleted file mode 100644 index 0c00c38a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/grpc/endpoint_test.go b/docker/addons/vault/scripts/things/api/grpc/endpoint_test.go deleted file mode 100644 index 5feb8943..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/grpc/request.go b/docker/addons/vault/scripts/things/api/grpc/request.go deleted file mode 100644 index 890335ec..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/grpc/responses.go b/docker/addons/vault/scripts/things/api/grpc/responses.go deleted file mode 100644 index 8e11f127..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/grpc/server.go b/docker/addons/vault/scripts/things/api/grpc/server.go deleted file mode 100644 index 5dfe4584..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/http/channels.go b/docker/addons/vault/scripts/things/api/http/channels.go deleted file mode 100644 index 7efd4685..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/http/clients.go b/docker/addons/vault/scripts/things/api/http/clients.go deleted file mode 100644 index 285f5c43..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/http/endpoints.go b/docker/addons/vault/scripts/things/api/http/endpoints.go deleted file mode 100644 index 10b9abc6..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/http/endpoints_test.go b/docker/addons/vault/scripts/things/api/http/endpoints_test.go deleted file mode 100644 index 3c16c92e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/http/requests.go b/docker/addons/vault/scripts/things/api/http/requests.go deleted file mode 100644 index 8c644cd9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/http/requests_test.go b/docker/addons/vault/scripts/things/api/http/requests_test.go deleted file mode 100644 index a4529a9b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/http/responses.go b/docker/addons/vault/scripts/things/api/http/responses.go deleted file mode 100644 index c998bb05..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/api/http/transport.go b/docker/addons/vault/scripts/things/api/http/transport.go deleted file mode 100644 index 415e463d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/cache/doc.go b/docker/addons/vault/scripts/things/cache/doc.go deleted file mode 100644 index c73f0c04..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/cache/setup_test.go b/docker/addons/vault/scripts/things/cache/setup_test.go deleted file mode 100644 index 716f0672..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/cache/things.go b/docker/addons/vault/scripts/things/cache/things.go deleted file mode 100644 index b09aa6ef..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/cache/things_test.go b/docker/addons/vault/scripts/things/cache/things_test.go deleted file mode 100644 index 8fa34e22..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/clients.go b/docker/addons/vault/scripts/things/clients.go deleted file mode 100644 index 8894c171..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/doc.go b/docker/addons/vault/scripts/things/doc.go deleted file mode 100644 index c22b9303..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/errors.go b/docker/addons/vault/scripts/things/errors.go deleted file mode 100644 index 901dcfa7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/events/doc.go b/docker/addons/vault/scripts/things/events/doc.go deleted file mode 100644 index cb8cccbf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/events/events.go b/docker/addons/vault/scripts/things/events/events.go deleted file mode 100644 index 5ec7e8e9..00000000 --- a/docker/addons/vault/scripts/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 = "client." - 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/docker/addons/vault/scripts/things/events/streams.go b/docker/addons/vault/scripts/things/events/streams.go deleted file mode 100644 index 295fb37b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/middleware/authorization.go b/docker/addons/vault/scripts/things/middleware/authorization.go deleted file mode 100644 index 85a3af5d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/middleware/doc.go b/docker/addons/vault/scripts/things/middleware/doc.go deleted file mode 100644 index 253c8358..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/middleware/logging.go b/docker/addons/vault/scripts/things/middleware/logging.go deleted file mode 100644 index a176159c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/middleware/metrics.go b/docker/addons/vault/scripts/things/middleware/metrics.go deleted file mode 100644 index 6b6ecd2d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/mocks/cache.go b/docker/addons/vault/scripts/things/mocks/cache.go deleted file mode 100644 index 9e729c2c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/mocks/doc.go b/docker/addons/vault/scripts/things/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/mocks/repository.go b/docker/addons/vault/scripts/things/mocks/repository.go deleted file mode 100644 index 2917461b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/mocks/service.go b/docker/addons/vault/scripts/things/mocks/service.go deleted file mode 100644 index 9719334d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/mocks/things_client.go b/docker/addons/vault/scripts/things/mocks/things_client.go deleted file mode 100644 index 136280a8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/postgres/clients.go b/docker/addons/vault/scripts/things/postgres/clients.go deleted file mode 100644 index 150f9c9d..00000000 --- a/docker/addons/vault/scripts/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 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/docker/addons/vault/scripts/things/postgres/clients_test.go b/docker/addons/vault/scripts/things/postgres/clients_test.go deleted file mode 100644 index b03b7d4f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/postgres/doc.go b/docker/addons/vault/scripts/things/postgres/doc.go deleted file mode 100644 index 6e834635..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/postgres/init.go b/docker/addons/vault/scripts/things/postgres/init.go deleted file mode 100644 index 28e07a2c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/postgres/setup_test.go b/docker/addons/vault/scripts/things/postgres/setup_test.go deleted file mode 100644 index a167f643..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/roles.go b/docker/addons/vault/scripts/things/roles.go deleted file mode 100644 index 390ebbc9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/roles_test.go b/docker/addons/vault/scripts/things/roles_test.go deleted file mode 100644 index 2d50aeaa..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/service.go b/docker/addons/vault/scripts/things/service.go deleted file mode 100644 index 47590208..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/service_test.go b/docker/addons/vault/scripts/things/service_test.go deleted file mode 100644 index 79aa727e..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/standalone/doc.go b/docker/addons/vault/scripts/things/standalone/doc.go deleted file mode 100644 index 68ca6a78..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/standalone/standalone.go b/docker/addons/vault/scripts/things/standalone/standalone.go deleted file mode 100644 index 5d14ffba..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/status.go b/docker/addons/vault/scripts/things/status.go deleted file mode 100644 index f34ed99b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/status_test.go b/docker/addons/vault/scripts/things/status_test.go deleted file mode 100644 index 9df845bf..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/tracing/doc.go b/docker/addons/vault/scripts/things/tracing/doc.go deleted file mode 100644 index 1d803bec..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/things/tracing/tracing.go b/docker/addons/vault/scripts/things/tracing/tracing.go deleted file mode 100644 index 20fe07b5..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/tools/config/boilerplate.txt b/docker/addons/vault/scripts/tools/config/boilerplate.txt deleted file mode 100644 index b3f5a643..00000000 --- a/docker/addons/vault/scripts/tools/config/boilerplate.txt +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright (c) Abstract Machines - -// SPDX-License-Identifier: Apache-2.0 diff --git a/docker/addons/vault/scripts/tools/config/codecov.yml b/docker/addons/vault/scripts/tools/config/codecov.yml deleted file mode 100644 index a4010677..00000000 --- a/docker/addons/vault/scripts/tools/config/codecov.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# CoAP is temporarily ignored since we don't have tests for it yet. -coverage: - ignore: - - "tools/*" - - "coap/*" - - "**/mocks*" - - "*/middleware/*" diff --git a/docker/addons/vault/scripts/tools/config/golangci.yml b/docker/addons/vault/scripts/tools/config/golangci.yml deleted file mode 100644 index d38b122e..00000000 --- a/docker/addons/vault/scripts/tools/config/golangci.yml +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -run: - timeout: 10m - build-tags: - - "nats" - -issues: - max-issues-per-linter: 100 - max-same-issues: 100 - exclude: - - "string `Usage:\n` has (\\d+) occurrences, make it a constant" - - "string `For example:\n` has (\\d+) occurrences, make it a constant" - exclude-rules: - - path: cli/commands_test.go - linters: - - godot - -linters-settings: - importas: - no-unaliased: true - no-extra-aliases: false - 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 - alias: svcerr - - pkg: github.com/absmach/magistrala/pkg/errors/repository - alias: repoerr - - pkg: github.com/absmach/magistrala/pkg/sdk/mocks - alias: sdkmocks - - gocritic: - enabled-checks: - - importShadow - - httpNoBody - - paramTypeCombine - - emptyStringTest - - builtinShadow - - exposedSyncMutex - disabled-checks: - - appendAssign - enabled-tags: - - diagnostic - disabled-tags: - - performance - - style - - experimental - - opinionated - misspell: - ignore-words: - - "mosquitto" - stylecheck: - checks: ["-ST1000", "-ST1003", "-ST1020", "-ST1021", "-ST1022"] - goheader: - template: |- - Copyright (c) Abstract Machines - SPDX-License-Identifier: Apache-2.0 - -linters: - disable-all: true - enable: - - gocritic - - gosimple - - errcheck - - govet - - unused - - goconst - - godot - - godox - - ineffassign - - misspell - - stylecheck - - whitespace - - gci - - gofmt - - goimports - - loggercheck - - goheader - - asasalint - - asciicheck - - bidichk - - contextcheck - - decorder - - dogsled - - errchkjson - - errname - - copyloopvar - - ginkgolinter - - gocheckcompilerdirectives - - gofumpt - - goprintffuncname - - importas - - makezero - - mirror - - nakedret - - dupword diff --git a/docker/addons/vault/scripts/tools/config/mockery.yaml b/docker/addons/vault/scripts/tools/config/mockery.yaml deleted file mode 100644 index 69e23165..00000000 --- a/docker/addons/vault/scripts/tools/config/mockery.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -with-expecter: true -filename: "{{.InterfaceName}}.go" -outpkg: "mocks" -boilerplate-file: "./tools/config/boilerplate.txt" -packages: - github.com/absmach/magistrala: - interfaces: - ThingsServiceClient: - config: - dir: "./things/mocks" - mockname: "ThingsServiceClient" - filename: "things_client.go" - DomainsServiceClient: - config: - dir: "./auth/mocks" - mockname: "DomainsServiceClient" - filename: "domains_client.go" - TokenServiceClient: - config: - dir: "./auth/mocks" - mockname: "TokenServiceClient" - filename: "token_client.go" - - github.com/absmach/magistrala/certs/pki/amcerts: - interfaces: - Agent: - config: - dir: "./certs/mocks" - mockname: "Agent" - filename: "pki.go" diff --git a/docker/addons/vault/scripts/tools/doc.go b/docker/addons/vault/scripts/tools/doc.go deleted file mode 100644 index 296a4b2b..00000000 --- a/docker/addons/vault/scripts/tools/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package tools contains tools for Magistrala. -package tools diff --git a/docker/addons/vault/scripts/tools/e2e/Makefile b/docker/addons/vault/scripts/tools/e2e/Makefile deleted file mode 100644 index fd27a8a2..00000000 --- a/docker/addons/vault/scripts/tools/e2e/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -PROGRAM = e2e -SOURCES = $(wildcard *.go) cmd/main.go - -all: $(PROGRAM) - -.PHONY: all clean - -$(PROGRAM): $(SOURCES) - go build -ldflags "-s -w" -o $@ cmd/main.go - -clean: - rm -rf $(PROGRAM) diff --git a/docker/addons/vault/scripts/tools/e2e/README.md b/docker/addons/vault/scripts/tools/e2e/README.md deleted file mode 100644 index 6e358451..00000000 --- a/docker/addons/vault/scripts/tools/e2e/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# Magistrala Users Groups Things 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. - -## Installation - -```bash -cd tools/e2e -make -``` - -### Usage - -```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 - - -Usage: - - e2e [flags] - - -Examples: - -Here is a simple example of using e2e tool. -Use the following commands from the root Magistrala directory: - -go run tools/e2e/cmd/main.go -go run tools/e2e/cmd/main.go --host 142.93.118.47 -go run tools/e2e/cmd/main.go --host localhost --num 10 --num_of_messages 100 --prefix e2e - - -Flags: - - -h, --help help for e2e - -H, --host string address for a running Magistrala instance (default "localhost") - -n, --num uint number of users, groups, channels and things to create and connect (default 10) - -N, --num_of_messages uint number of messages to send (default 10) - -p, --prefix string name prefix for users, groups, things 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: - -```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. - -If you want to create a list of channels with certificates: - -```bash -go run tools/e2e/cmd/main.go --host localhost --num 10 --num_of_messages 100 --prefix e2e -``` - -Example of output: - -```bash -created user with token eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODEyMDYwMjMsImlhdCI6MTY4MTIwNTEyMywiaWRlbnRpdHkiOiJlMmUtbGF0ZS1zaWxlbmNlQGVtYWlsLmNvbSIsImlzcyI6ImNsaWVudHMuYXV0aCIsInN1YiI6IjdlZDIyY2IyLTRlMzQtNDhiZi04Y2RlLTIxMjZiYzYyYzY4MyIsInR5cGUiOiJhY2Nlc3MifQ.AdExNYs5mVQNpo_ejJDq7KTC5dKkZWmgM9FJvTM2T_GM2LE9ASQv0ymC4wS3PDXKWf-OcaR8DJIxE6WiG3fztQ -created users of ids: -9e87bc1d-0889-4252-a3df-36e02edfc859 -c1e4901a-fb7f-45e9-b934-c55194b1d028 -c341a9cb-542b-4c3b-afd6-c98e04ed5e7e -8cfc886b-21fa-4205-80b4-3601827b94ff -334984d7-30eb-4b06-92b8-5ec182bebac5 -created groups of ids: -7744ec55-c767-4137-be96-0d79699772a4 -c8fe4d9d-3ad6-4687-83c0-171356f3e4f6 -513f7295-0923-4e21-b41a-3cfd1cb7b9b9 -54bd71ea-3c22-401e-89ea-d58162b983c0 -ae91b327-4c40-4e68-91fe-cd6223ee4e99 -created things of ids: -5909a907-7413-47d4-b793-e1eb36988a5f -f9b6bc18-1862-4a24-8973-adde11cb3303 -c2bd6eed-6f38-464c-989c-fe8ec8c084ba -8c76702c-0534-4246-8ed7-21816b4f91cf -25005ca8-e886-465f-9cd1-4f3c4a95c6c1 -created channels of ids: -ebb0e5f3-2241-4770-a7cc-f4bbd06134ca -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 -sent messages to channels -``` diff --git a/docker/addons/vault/scripts/tools/e2e/cmd/main.go b/docker/addons/vault/scripts/tools/e2e/cmd/main.go deleted file mode 100644 index 5574382a..00000000 --- a/docker/addons/vault/scripts/tools/e2e/cmd/main.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains e2e tool for testing Magistrala. -package main - -import ( - "log" - - "github.com/absmach/magistrala/tools/e2e" - cc "github.com/ivanpirog/coloredcobra" - "github.com/spf13/cobra" -) - -const defNum = uint64(10) - -func main() { - econf := e2e.Config{} - - 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", - Example: "Here is a simple example of using e2e tool.\n" + - "Use the following commands from the root magistrala 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", - Run: func(_ *cobra.Command, _ []string) { - e2e.Test(econf) - }, - } - - cc.Init(&cc.Config{ - RootCmd: rootCmd, - Headings: cc.HiCyan + cc.Bold + cc.Underline, - CmdShortDescr: cc.Magenta, - Example: cc.Italic + cc.Magenta, - ExecName: cc.Bold, - Flags: cc.HiGreen + cc.Bold, - FlagsDescr: cc.Green, - FlagsDataType: cc.White + cc.Italic, - }) - - // 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().Uint64VarP(&econf.NumOfMsg, "num_of_messages", "N", defNum, "number of messages to send") - - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } -} diff --git a/docker/addons/vault/scripts/tools/e2e/doc.go b/docker/addons/vault/scripts/tools/e2e/doc.go deleted file mode 100644 index eb7fb081..00000000 --- a/docker/addons/vault/scripts/tools/e2e/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package e2e contains entry point for end-to-end tests. -package e2e diff --git a/docker/addons/vault/scripts/tools/e2e/e2e.go b/docker/addons/vault/scripts/tools/e2e/e2e.go deleted file mode 100644 index e7bf3540..00000000 --- a/docker/addons/vault/scripts/tools/e2e/e2e.go +++ /dev/null @@ -1,639 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package e2e - -import ( - "fmt" - "math/rand" - "net/http" - "os" - "os/exec" - "reflect" - "strings" - "time" - - "github.com/0x6flab/namegenerator" - sdk "github.com/absmach/magistrala/pkg/sdk/go" - "github.com/gookit/color" - "github.com/gorilla/websocket" - "golang.org/x/sync/errgroup" -) - -const ( - defPass = "12345678" - defWSPort = "8186" - numAdapters = 4 - batchSize = 99 - usersPort = "9002" - thingsPort = "9000" - domainsPort = "8189" -) - -var ( - namesgenerator = namegenerator.NewGenerator() - msgFormat = `[{"bn":"demo", "bu":"V", "t": %d, "bver":5, "n":"voltage", "u":"V", "v":%d}]` -) - -// Config - test configuration. -type Config struct { - Host string - Num uint64 - NumOfMsg uint64 - SSL bool - CA string - CAKey string - Prefix string -} - -// Test - function that does actual end to end testing. -// The operations are: -// - Create a user -// - Create other users -// - Do Read, Update and Change of Status operations on users. - -// - 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 channels -// - Do Read, Update and Change of Status operations on channels. - -// - Connect thing 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), - 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), - MsgContentType: sdk.CTJSONSenML, - TLSVerification: false, - } - - s := sdk.NewSDK(sdkConf) - - magenta := color.FgLightMagenta.Render - - domainID, token, err := createUser(s, conf) - if err != nil { - errExit(fmt.Errorf("unable to create user: %w", err)) - } - color.Success.Printf("created user with token %s\n", magenta(token)) - - users, err := createUsers(s, conf, token) - if err != nil { - errExit(fmt.Errorf("unable to create users: %w", err)) - } - color.Success.Printf("created users of ids:\n%s\n", magenta(getIDS(users))) - - groups, err := createGroups(s, conf, domainID, token) - if err != nil { - errExit(fmt.Errorf("unable to create groups: %w", err)) - } - color.Success.Printf("created groups of ids:\n%s\n", magenta(getIDS(groups))) - - things, err := createThings(s, conf, domainID, token) - if err != nil { - errExit(fmt.Errorf("unable to create things: %w", err)) - } - color.Success.Printf("created things of ids:\n%s\n", magenta(getIDS(things))) - - channels, err := createChannels(s, conf, domainID, token) - if err != nil { - errExit(fmt.Errorf("unable to create channels: %w", err)) - } - 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)) - } - color.Success.Println("viewed users, groups, things 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)) - } - color.Success.Println("updated users, groups, things and channels") - - // Send messages to channels - if err := messaging(s, conf, domainID, token, things, channels); err != nil { - errExit(fmt.Errorf("unable to send messages to channels: %w", err)) - } - color.Success.Println("sent messages to channels") -} - -func errExit(err error) { - color.Error.Println(err.Error()) - os.Exit(1) -} - -func createUser(s sdk.SDK, conf Config) (string, string, error) { - user := sdk.User{ - FirstName: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - LastName: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - Email: fmt.Sprintf("%s%s@email.com", conf.Prefix, namesgenerator.Generate()), - Credentials: sdk.Credentials{ - Username: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - Secret: defPass, - }, - Status: sdk.EnabledStatus, - Role: "admin", - } - - if _, err := s.CreateUser(user, ""); err != nil { - return "", "", fmt.Errorf("unable to create user: %w", err) - } - - login := sdk.Login{ - Identity: user.Credentials.Username, - Secret: user.Credentials.Secret, - } - token, err := s.CreateToken(login) - if err != nil { - return "", "", fmt.Errorf("unable to login user: %w", err) - } - - dname := fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()) - domain := sdk.Domain{ - Name: dname, - Alias: strings.ToLower(dname), - Permission: "admin", - } - - domain, err = s.CreateDomain(domain, token.AccessToken) - if err != nil { - return "", "", fmt.Errorf("unable to create domain: %w", err) - } - - login = sdk.Login{ - Identity: user.Credentials.Username, - Secret: user.Credentials.Secret, - } - token, err = s.CreateToken(login) - if err != nil { - return "", "", fmt.Errorf("unable to login user: %w", err) - } - - return domain.ID, token.AccessToken, nil -} - -func createUsers(s sdk.SDK, conf Config, token string) ([]sdk.User, error) { - var err error - users := []sdk.User{} - - for i := uint64(0); i < conf.Num; i++ { - user := sdk.User{ - FirstName: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - LastName: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - Email: fmt.Sprintf("%s%s@email.com", conf.Prefix, namesgenerator.Generate()), - Credentials: sdk.Credentials{ - Username: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - Secret: defPass, - }, - Status: sdk.EnabledStatus, - } - - user, err = s.CreateUser(user, token) - if err != nil { - return []sdk.User{}, fmt.Errorf("failed to create the users: %w", err) - } - users = append(users, user) - } - - return users, nil -} - -func createGroups(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Group, error) { - var err error - groups := []sdk.Group{} - - for i := uint64(0); i < conf.Num; i++ { - group := sdk.Group{ - Name: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - Status: sdk.EnabledStatus, - } - - group, err = s.CreateGroup(group, domainID, token) - if err != nil { - return []sdk.Group{}, fmt.Errorf("failed to create the group: %w", err) - } - groups = append(groups, group) - } - - return groups, nil -} - -func createThingsInBatch(s sdk.SDK, conf Config, domainID, token string, num uint64) ([]sdk.Thing, error) { - var err error - things := make([]sdk.Thing, num) - - for i := uint64(0); i < num; i++ { - things[i] = sdk.Thing{ - Name: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - } - } - - things, err = s.CreateThings(things, domainID, token) - if err != nil { - return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err) - } - - return things, nil -} - -func createThings(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Thing, error) { - things := []sdk.Thing{} - - if conf.Num > batchSize { - batches := int(conf.Num) / batchSize - for i := 0; i < batches; i++ { - ths, err := createThingsInBatch(s, conf, domainID, token, batchSize) - if err != nil { - return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err) - } - things = append(things, ths...) - } - ths, err := createThingsInBatch(s, conf, domainID, token, conf.Num%uint64(batchSize)) - if err != nil { - return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err) - } - things = append(things, ths...) - } else { - ths, err := createThingsInBatch(s, conf, domainID, token, conf.Num) - if err != nil { - return []sdk.Thing{}, fmt.Errorf("failed to create the things: %w", err) - } - things = append(things, ths...) - } - - return things, nil -} - -func createChannelsInBatch(s sdk.SDK, conf Config, domainID, token string, num uint64) ([]sdk.Channel, error) { - var err error - channels := make([]sdk.Channel, num) - - for i := uint64(0); i < num; i++ { - channels[i] = sdk.Channel{ - Name: fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()), - } - channels[i], err = s.CreateChannel(channels[i], domainID, token) - if err != nil { - return []sdk.Channel{}, fmt.Errorf("failed to create the channels: %w", err) - } - } - - return channels, nil -} - -func createChannels(s sdk.SDK, conf Config, domainID, token string) ([]sdk.Channel, error) { - channels := []sdk.Channel{} - - if conf.Num > batchSize { - batches := int(conf.Num) / batchSize - for i := 0; i < batches; i++ { - chs, err := createChannelsInBatch(s, conf, token, domainID, batchSize) - if err != nil { - return []sdk.Channel{}, fmt.Errorf("failed to create the channels: %w", err) - } - channels = append(channels, chs...) - } - chs, err := createChannelsInBatch(s, conf, domainID, token, conf.Num%uint64(batchSize)) - if err != nil { - return []sdk.Channel{}, fmt.Errorf("failed to create the channels: %w", err) - } - channels = append(channels, chs...) - } else { - chs, err := createChannelsInBatch(s, conf, domainID, token, conf.Num) - if err != nil { - return []sdk.Channel{}, fmt.Errorf("failed to create the channels: %w", err) - } - channels = append(channels, chs...) - } - - 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 { - for _, user := range users { - if _, err := s.User(user.ID, token); err != nil { - return fmt.Errorf("failed to get user %w", err) - } - } - up, err := s.Users(sdk.PageMetadata{}, token) - if err != nil { - return fmt.Errorf("failed to get users %w", err) - } - if up.Total < conf.Num { - return fmt.Errorf("returned users %d less than created users %d", up.Total, conf.Num) - } - for _, group := range groups { - if _, err := s.Group(group.ID, domainID, token); err != nil { - return fmt.Errorf("failed to get group %w", err) - } - } - gp, err := s.Groups(sdk.PageMetadata{}, domainID, token) - if err != nil { - return fmt.Errorf("failed to get groups %w", err) - } - 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) - } - } - tp, err := s.Things(sdk.PageMetadata{}, domainID, token) - if err != nil { - return fmt.Errorf("failed to get things %w", err) - } - if tp.Total < conf.Num { - return fmt.Errorf("returned things %d less than created things %d", tp.Total, conf.Num) - } - for _, channel := range channels { - if _, err := s.Channel(channel.ID, domainID, token); err != nil { - return fmt.Errorf("failed to get channel %w", err) - } - } - cp, err := s.Channels(sdk.PageMetadata{}, domainID, token) - if err != nil { - return fmt.Errorf("failed to get channels %w", err) - } - if cp.Total < conf.Num { - return fmt.Errorf("returned channels %d less than created channels %d", cp.Total, conf.Num) - } - - return nil -} - -func update(s sdk.SDK, domainID, token string, users []sdk.User, groups []sdk.Group, things []sdk.Thing, channels []sdk.Channel) error { - for _, user := range users { - user.FirstName = namesgenerator.Generate() - user.Metadata = sdk.Metadata{"Update": namesgenerator.Generate()} - rUser, err := s.UpdateUser(user, token) - if err != nil { - return fmt.Errorf("failed to update user %w", err) - } - if rUser.FirstName != user.FirstName { - return fmt.Errorf("failed to update user name before %s after %s", user.FirstName, rUser.FirstName) - } - if rUser.Metadata["Update"] != user.Metadata["Update"] { - return fmt.Errorf("failed to update user metadata before %s after %s", user.Metadata["Update"], rUser.Metadata["Update"]) - } - user = rUser - user.Credentials.Username = namesgenerator.Generate() - rUser, err = s.UpdateUsername(user, token) - if err != nil { - return fmt.Errorf("failed to update username %w", err) - } - if rUser.Credentials.Username != user.Credentials.Username { - return fmt.Errorf("failed to update user name before %s after %s", user.Credentials.Username, rUser.Credentials.Username) - } - user = rUser - rUser, err = s.UpdateUserEmail(user, token) - if err != nil { - return fmt.Errorf("failed to update user identity %w", err) - } - if rUser.Email != user.Email { - return fmt.Errorf("failed to update user identity before %s after %s", user.Email, rUser.Email) - } - user = rUser - user.Tags = []string{namesgenerator.Generate()} - rUser, err = s.UpdateUserTags(user, token) - if err != nil { - return fmt.Errorf("failed to update user tags %w", err) - } - if rUser.Tags[0] != user.Tags[0] { - return fmt.Errorf("failed to update user tags before %s after %s", user.Tags[0], rUser.Tags[0]) - } - user = rUser - rUser, err = s.DisableUser(user.ID, token) - if err != nil { - return fmt.Errorf("failed to disable user %w", err) - } - if rUser.Status != sdk.DisabledStatus { - return fmt.Errorf("failed to disable user before %s after %s", user.Status, rUser.Status) - } - user = rUser - rUser, err = s.EnableUser(user.ID, token) - if err != nil { - return fmt.Errorf("failed to enable user %w", err) - } - if rUser.Status != sdk.EnabledStatus { - return fmt.Errorf("failed to enable user before %s after %s", user.Status, rUser.Status) - } - } - for _, group := range groups { - group.Name = namesgenerator.Generate() - group.Metadata = sdk.Metadata{"Update": namesgenerator.Generate()} - rGroup, err := s.UpdateGroup(group, domainID, token) - if err != nil { - return fmt.Errorf("failed to update group %w", err) - } - if rGroup.Name != group.Name { - return fmt.Errorf("failed to update group name before %s after %s", group.Name, rGroup.Name) - } - if rGroup.Metadata["Update"] != group.Metadata["Update"] { - return fmt.Errorf("failed to update group metadata before %s after %s", group.Metadata["Update"], rGroup.Metadata["Update"]) - } - group = rGroup - rGroup, err = s.DisableGroup(group.ID, domainID, token) - if err != nil { - return fmt.Errorf("failed to disable group %w", err) - } - if rGroup.Status != sdk.DisabledStatus { - return fmt.Errorf("failed to disable group before %s after %s", group.Status, rGroup.Status) - } - group = rGroup - rGroup, err = s.EnableGroup(group.ID, domainID, token) - if err != nil { - return fmt.Errorf("failed to enable group %w", err) - } - if rGroup.Status != sdk.EnabledStatus { - 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) - if err != nil { - return fmt.Errorf("failed to update thing %w", err) - } - if rThing.Name != thing.Name { - return fmt.Errorf("failed to update thing name before %s after %s", thing.Name, rThing.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"]) - } - thing = rThing - rThing, err = s.UpdateThingSecret(thing.ID, thing.Credentials.Secret, domainID, token) - if err != nil { - return fmt.Errorf("failed to update thing secret %w", err) - } - thing = rThing - thing.Tags = []string{namesgenerator.Generate()} - rThing, err = s.UpdateThingTags(thing, domainID, token) - if err != nil { - return fmt.Errorf("failed to update thing 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]) - } - thing = rThing - rThing, err = s.DisableThing(thing.ID, domainID, token) - if err != nil { - return fmt.Errorf("failed to disable thing %w", err) - } - if rThing.Status != sdk.DisabledStatus { - return fmt.Errorf("failed to disable thing before %s after %s", thing.Status, rThing.Status) - } - thing = rThing - rThing, err = s.EnableThing(thing.ID, domainID, token) - if err != nil { - return fmt.Errorf("failed to enable thing %w", err) - } - if rThing.Status != sdk.EnabledStatus { - return fmt.Errorf("failed to enable thing before %s after %s", thing.Status, rThing.Status) - } - } - for _, channel := range channels { - channel.Name = namesgenerator.Generate() - channel.Metadata = sdk.Metadata{"Update": namesgenerator.Generate()} - rChannel, err := s.UpdateChannel(channel, domainID, token) - if err != nil { - return fmt.Errorf("failed to update channel %w", err) - } - if rChannel.Name != channel.Name { - return fmt.Errorf("failed to update channel name before %s after %s", channel.Name, rChannel.Name) - } - if rChannel.Metadata["Update"] != channel.Metadata["Update"] { - return fmt.Errorf("failed to update channel metadata before %s after %s", channel.Metadata["Update"], rChannel.Metadata["Update"]) - } - channel = rChannel - rChannel, err = s.DisableChannel(channel.ID, domainID, token) - if err != nil { - return fmt.Errorf("failed to disable channel %w", err) - } - if rChannel.Status != sdk.DisabledStatus { - return fmt.Errorf("failed to disable channel before %s after %s", channel.Status, rChannel.Status) - } - channel = rChannel - rChannel, err = s.EnableChannel(channel.ID, domainID, token) - if err != nil { - return fmt.Errorf("failed to enable channel %w", err) - } - if rChannel.Status != sdk.EnabledStatus { - return fmt.Errorf("failed to enable channel before %s after %s", channel.Status, rChannel.Status) - } - } - - return nil -} - -func messaging(s sdk.SDK, conf Config, domainID, token string, things []sdk.Thing, channels []sdk.Channel) error { - for _, thing := range things { - for _, channel := range channels { - conn := sdk.Connection{ - ThingID: thing.ID, - ChannelID: channel.ID, - } - if err := s.Connect(conn, domainID, token); err != nil { - return fmt.Errorf("failed to connect thing %s to channel %s", thing.ID, channel.ID) - } - } - } - - g := new(errgroup.Group) - - bt := time.Now().Unix() - for i := uint64(0); i < conf.NumOfMsg; i++ { - for _, thing := range things { - for _, channel := range channels { - func(num int64, thing sdk.Thing, channel sdk.Channel) { - g.Go(func() error { - msg := fmt.Sprintf(msgFormat, num+1, rand.Int()) - return sendHTTPMessage(s, msg, thing, channel.ID) - }) - g.Go(func() error { - msg := fmt.Sprintf(msgFormat, num+2, rand.Int()) - return sendCoAPMessage(msg, thing, channel.ID) - }) - g.Go(func() error { - msg := fmt.Sprintf(msgFormat, num+3, rand.Int()) - return sendMQTTMessage(msg, thing, channel.ID) - }) - g.Go(func() error { - msg := fmt.Sprintf(msgFormat, num+4, rand.Int()) - return sendWSMessage(conf, msg, thing, channel.ID) - }) - }(bt, thing, channel) - bt += numAdapters - } - } - } - - 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) - } - - 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) - 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 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) - 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 nil -} - -func sendWSMessage(conf Config, msg string, thing sdk.Thing, chanID string) error { - socketURL := fmt.Sprintf("ws://%s:%s/channels/%s/messages", conf.Host, defWSPort, chanID) - header := http.Header{"Authorization": []string{thing.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 nil -} - -// getIDS returns a list of IDs of the given objects. -func getIDS(objects interface{}) string { - v := reflect.ValueOf(objects) - if v.Kind() != reflect.Slice { - panic("objects argument must be a slice") - } - ids := make([]string, v.Len()) - for i := 0; i < v.Len(); i++ { - id := v.Index(i).FieldByName("ID").String() - ids[i] = id - } - idList := strings.Join(ids, "\n") - - return idList -} diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/Makefile b/docker/addons/vault/scripts/tools/mqtt-bench/Makefile deleted file mode 100644 index f2b3bed0..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -PROGRAM = mqtt-bench -SOURCES = $(wildcard *.go) cmd/main.go - -all: $(PROGRAM) - -.PHONY: all clean - -$(PROGRAM): $(SOURCES) - go build -ldflags "-s -w" -o $@ cmd/main.go - -clean: - rm -rf $(PROGRAM) diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/README.md b/docker/addons/vault/scripts/tools/mqtt-bench/README.md deleted file mode 100644 index f94eb4d2..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# MQTT Benchmarking Tool - -A simple MQTT benchmarking tool for Magistrala platform. - -It connects Magistrala things as subscribers over a number of channels and -uses other Magistrala things to publish messages and create MQTT load. - -Magistrala things used must be pre-provisioned first, and Magistrala `provision` tool can be used for this purpose. - -## Installation - -``` -cd tools/mqtt-bench -make -``` - -## Usage - -The tool supports multiple concurrent clients, publishers and subscribers configurable message size, etc: - -``` -./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 - -Usage: - mqtt-bench [flags] - -Flags: - -b, --broker string address for mqtt broker, for secure use tcps and 8883 (default "tcp://localhost:1883") - --ca string CA file (default "ca.crt") - -c, --config string config file for mqtt-bench (default "config.toml") - -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") - --mtls Use mtls for connection - -p, --pubs int Number of publishers (default 10) - -q, --qos int QoS for published messages, values 0 1 2 - --quiet Supress messages - -r, --retain Retain mqtt messages - -z, --size int Size of message payload bytes (default 100) - -t, --skipTLSVer Skip tls verification - -t, --timeout Timeout mqtt messages (default 10000) -``` - -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). -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 -``` - -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 -``` - -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 - -``` - -You can use `config.toml` to create tests with this tool: - -``` - -go run tools/mqtt-bench/cmd/main.go --config tools/mqtt-bench/config.toml - -``` - -Example of `config.toml`: - -``` - -[mqtt] -[mqtt.broker] -url = "tcp://localhost:1883" - -[mqtt.message] -size = 100 -format = "text" -qos = 2 -retain = true - -[mqtt.tls] -mtls = false -skiptlsver = true -ca = "ca.crt" - -[test] -pubs = 3 -count = 100 - -[log] -quiet = false - -[magistrala] -connections_file = "mgconn.toml" - -``` - -Based on this, a test scenario is provided in `templates/reference.toml` file. -``` diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/bench.go b/docker/addons/vault/scripts/tools/mqtt-bench/bench.go deleted file mode 100644 index b79f7a3d..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/bench.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bench - -import ( - "crypto/rand" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "os" - "strconv" - "sync" - "time" - - mglog "github.com/absmach/magistrala/logger" - "github.com/pelletier/go-toml" -) - -// Benchmark - main benchmarking function. -func Benchmark(cfg Config) error { - if err := checkConnection(cfg.MQTT.Broker.URL, 1); err != nil { - return err - } - logger, err := mglog.New(os.Stdout, "debug") - if err != nil { - return err - } - - subsResults := map[string](*[]float64){} - var caByte []byte - if cfg.MQTT.TLS.MTLS { - caFile, err := os.Open(cfg.MQTT.TLS.CA) - - defer func() { - if err = caFile.Close(); err != nil { - logger.Warn(fmt.Sprintf("Could not close file: %s", err)) - } - }() - if err != nil { - logger.Warn(err.Error()) - } - caByte, _ = io.ReadAll(caFile) - } - - data, err := os.ReadFile(cfg.Mg.ConnFile) - if err != nil { - return fmt.Errorf("error loading connections file: %s", err) - } - - mg := magistrala{} - 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) - } - - resCh := make(chan *runResults) - finishedPub := make(chan bool) - - startStamp := time.Now() - - n := len(mg.Channels) - var cert tls.Certificate - - start := time.Now() - - var wg sync.WaitGroup - errorChan := make(chan error, cfg.Test.Pubs) - - for i := 0; i < cfg.Test.Pubs; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - mgChan := mg.Channels[i%n] - mgThing := mg.Things[i%n] - - if cfg.MQTT.TLS.MTLS { - cert, err = tls.X509KeyPair([]byte(mgThing.MTLSCert), []byte(mgThing.MTLSKey)) - if err != nil { - errorChan <- err - return - } - } - c, err := makeClient(i, cfg, mgChan, mgThing, startStamp, caByte, cert) - if err != nil { - errorChan <- fmt.Errorf("unable to create message payload %s", err.Error()) - return - } - - c.publish(resCh, errorChan) - }(i) - } - - go func() { - wg.Wait() - close(errorChan) - }() - - for err := range errorChan { - if err != nil { - return err - } - } - - // Collect the results - var results []*runResults - if cfg.Test.Pubs > 0 { - results = make([]*runResults, cfg.Test.Pubs) - } - - // Wait for publishers to finish - go func() { - for i := 0; i < cfg.Test.Pubs; i++ { - results[i] = <-resCh - } - finishedPub <- true - }() - - <-finishedPub - - totalTime := time.Since(start) - totals := calculateTotalResults(results, totalTime, subsResults) - if totals == nil { - return fmt.Errorf("totals not assigned") - } - - printResults(results, totals, cfg.MQTT.Message.Format, cfg.Log.Quiet) - return nil -} - -func getBytePayload(size int, m message) (handler, error) { - // Calculate payload size. - var b []byte - s, err := json.Marshal(&m) - if err != nil { - return nil, err - } - n := len(s) - if n < size { - sz := size - n - for { - b = make([]byte, sz) - if _, err = rand.Read(b); err != nil { - return nil, err - } - m.Payload = b - content, err := json.Marshal(&m) - if err != nil { - return nil, err - } - l := len(content) - // Use range because the size of generated JSON - // depends on current time and random byte array. - if l <= size+5 && l >= size-5 { - break - } - if l > size { - sz-- - } - if l < size { - sz++ - } - } - } - - ret := func(m *message) ([]byte, error) { - m.Payload = b - m.Sent = time.Now() - return json.Marshal(m) - } - return ret, nil -} - -func makeClient(i int, cfg Config, mgChan mgChannel, mgThing mgThing, 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, - MsgTopic: fmt.Sprintf("channels/%s/messages/%d/test", mgChan.ChannelID, start.UnixNano()), - MsgSize: cfg.MQTT.Message.Size, - MsgCount: cfg.Test.Count, - MsgQoS: byte(cfg.MQTT.Message.QoS), - Quiet: cfg.Log.Quiet, - MTLS: cfg.MQTT.TLS.MTLS, - SkipTLSVer: cfg.MQTT.TLS.SkipTLSVer, - CA: caCert, - timeout: cfg.MQTT.Timeout, - ClientCert: clientCert, - Retain: cfg.MQTT.Message.Retain, - } - msg := message{ - Topic: c.MsgTopic, - QoS: c.MsgQoS, - ID: c.ID, - Sent: time.Now(), - } - h, err := getBytePayload(cfg.MQTT.Message.Size, msg) - if err != nil { - return nil, err - } - - c.SendMsg = h - return c, nil -} diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/client.go b/docker/addons/vault/scripts/tools/mqtt-bench/client.go deleted file mode 100644 index 1372990c..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/client.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bench - -import ( - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "log" - "net" - "strings" - "sync" - "time" - - mqtt "github.com/eclipse/paho.mqtt.golang" -) - -// Set default ping timeout to large value, so that ping -// won't fail in the case of broker pingresp delay. -const pingTimeout = 10000 - -// Client - represents mqtt client. -type Client struct { - ID string - BrokerURL string - BrokerUser string - BrokerPass string - MsgTopic string - MsgSize int - MsgCount int - MsgQoS byte - Quiet bool - timeout int - mqttClient *mqtt.Client - MTLS bool - SkipTLSVer bool - Retain bool - CA []byte - ClientCert tls.Certificate - ClientKey *rsa.PrivateKey - SendMsg handler -} - -type message struct { - ID string `json:"id"` - Topic string `json:"topic"` - QoS byte `json:"qos"` - Payload []byte `json:"payload"` - Sent time.Time `json:"sent"` - Delivered time.Time `json:"delivered"` - Error bool `json:"error"` -} - -type handler func(*message) ([]byte, error) - -func (c *Client) publish(r chan *runResults, errChan chan<- error) { - res := &runResults{} - times := make([]*float64, c.MsgCount) - - start := time.Now() - if c.connect() != nil { - flushMessages := make([]message, c.MsgCount) - for i, m := range flushMessages { - m.Error = true - times[i] = calcMsgRes(&m, res) - } - r <- calcRes(res, start, arr(times)) - } - if !c.Quiet { - log.Printf("Client %v is connected to the broker %v\n", c.ID, c.BrokerURL) - } - wg := sync.WaitGroup{} - mu := sync.Mutex{} - // Use a single message. - m := message{ - Topic: c.MsgTopic, - QoS: c.MsgQoS, - ID: c.ID, - Sent: time.Now(), - } - payload, err := c.SendMsg(&m) - if err != nil { - errChan <- fmt.Errorf("failed to marshal payload - %s", err.Error()) - } - - for i := 0; i < c.MsgCount; i++ { - wg.Add(1) - go func(mut *sync.Mutex, wg *sync.WaitGroup, i int, m message) { - defer wg.Done() - m.Sent = time.Now() - - token := (*c.mqttClient).Publish(m.Topic, m.QoS, c.Retain, payload) - if !token.WaitTimeout(time.Second*time.Duration(c.timeout)) || token.Error() != nil || !(*c.mqttClient).IsConnectionOpen() { - m.Error = true - mu.Lock() - times[i] = calcMsgRes(&m, res) - mu.Unlock() - return - } - - m.Delivered = time.Now() - m.Error = false - mu.Lock() - times[i] = calcMsgRes(&m, res) - mu.Unlock() - - if !c.Quiet && i > 0 && i%100 == 0 { - log.Printf("Client %v published %v messages and keeps publishing...\n", c.ID, i) - } - }(&mu, &wg, i, m) - } - wg.Wait() - - r <- calcRes(res, start, arr(times)) -} - -func (c *Client) connect() error { - opts := mqtt.NewClientOptions(). - AddBroker(c.BrokerURL). - SetClientID(c.ID). - SetCleanSession(false). - SetAutoReconnect(false). - SetOnConnectHandler(c.connected). - SetConnectionLostHandler(c.connLost). - SetPingTimeout(time.Second * pingTimeout). - SetAutoReconnect(true). - SetCleanSession(false) - - if c.BrokerUser != "" && c.BrokerPass != "" { - opts.SetUsername(c.BrokerUser) - opts.SetPassword(c.BrokerPass) - } - - if c.MTLS { - cfg := &tls.Config{ - InsecureSkipVerify: c.SkipTLSVer, - } - - if c.CA != nil { - cfg.RootCAs = x509.NewCertPool() - cfg.RootCAs.AppendCertsFromPEM(c.CA) - } - if c.ClientCert.Certificate != nil { - cfg.Certificates = []tls.Certificate{c.ClientCert} - } - - opts.SetTLSConfig(cfg) - opts.SetProtocolVersion(4) - } - - client := mqtt.NewClient(opts) - token := client.Connect() - token.Wait() - - c.mqttClient = &client - - if token.Error() != nil { - log.Printf("Client %v had error connecting to the broker: %s\n", c.ID, token.Error().Error()) - return token.Error() - } - - return nil -} - -func checkConnection(broker string, timeoutSecs int) error { - s := strings.Split(broker, ":") - if len(s) != 3 { - return errors.New("wrong host address format") - } - - network := s[0] - host := strings.Trim(s[1], "/") - port := s[2] - - log.Println("Testing connection...") - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), time.Duration(timeoutSecs)*time.Second) - conClose := func() { - if conn != nil { - log.Println("Closing testing connection...") - conn.Close() - } - } - - defer conClose() - if err, ok := err.(*net.OpError); ok && err.Timeout() { - return fmt.Errorf("timeout error: %s", err.Error()) - } - - if err != nil { - return fmt.Errorf("error: %s", err.Error()) - } - - log.Printf("Connection to %s://%s:%s looks OK\n", network, host, port) - return nil -} - -func arr(a []*float64) []float64 { - ret := []float64{} - for _, v := range a { - if v != nil { - ret = append(ret, *v) - } - } - if len(ret) == 0 { - ret = append(ret, 0) - } - return ret -} - -func (c *Client) connected(client mqtt.Client) { - if !c.Quiet { - log.Printf("Client %v is connected to the broker %v\n", c.ID, c.BrokerURL) - } -} - -func (c *Client) connLost(client mqtt.Client, reason error) { - log.Printf("Client %v had lost connection to the broker: %s\n", c.ID, reason.Error()) -} diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/cmd/main.go b/docker/addons/vault/scripts/tools/mqtt-bench/cmd/main.go deleted file mode 100644 index f3edf7d3..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/cmd/main.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains the entry point of the mqtt-bench tool. -package main - -import ( - "log" - - bench "github.com/absmach/magistrala/tools/mqtt-bench" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func main() { - confFile := "" - bconf := bench.Config{} - - // 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`, - Run: func(cmd *cobra.Command, args []string) { - if confFile != "" { - viper.SetConfigFile(confFile) - - if err := viper.ReadInConfig(); err != nil { - log.Printf("Failed to load config - %s", err) - } - - if err := viper.Unmarshal(&bconf); err != nil { - log.Printf("Unable to decode into struct, %v", err) - } - } - - if err := bench.Benchmark(bconf); err != nil { - log.Fatal(err) - } - }, - } - - // Flags - // MQTT Broker - rootCmd.PersistentFlags().StringVarP(&bconf.MQTT.Broker.URL, "broker", "b", "tcp://localhost:1883", - "address for mqtt broker, for secure use tcps and 8883") - - // MQTT Message - rootCmd.PersistentFlags().IntVarP(&bconf.MQTT.Message.Size, "size", "z", 100, "Size of message payload bytes") - rootCmd.PersistentFlags().StringVarP(&bconf.MQTT.Message.Payload, "payload", "l", "", "Template message") - rootCmd.PersistentFlags().StringVarP(&bconf.MQTT.Message.Format, "format", "f", "text", "Output format: text|json") - rootCmd.PersistentFlags().IntVarP(&bconf.MQTT.Message.QoS, "qos", "q", 0, "QoS for published messages, values 0 1 2") - rootCmd.PersistentFlags().BoolVarP(&bconf.MQTT.Message.Retain, "retain", "r", false, "Retain mqtt messages") - rootCmd.PersistentFlags().IntVarP(&bconf.MQTT.Timeout, "timeout", "o", 10000, "Timeout mqtt messages") - - // MQTT TLS - rootCmd.PersistentFlags().BoolVarP(&bconf.MQTT.TLS.MTLS, "mtls", "", false, "Use mtls for connection") - rootCmd.PersistentFlags().BoolVarP(&bconf.MQTT.TLS.SkipTLSVer, "skipTLSVer", "t", false, "Skip tls verification") - rootCmd.PersistentFlags().StringVarP(&bconf.MQTT.TLS.CA, "ca", "", "ca.crt", "CA file") - - // Test params - rootCmd.PersistentFlags().IntVarP(&bconf.Test.Count, "count", "n", 100, "Number of messages sent per publisher") - rootCmd.PersistentFlags().IntVarP(&bconf.Test.Subs, "subs", "s", 10, "Number of subscribers") - rootCmd.PersistentFlags().IntVarP(&bconf.Test.Pubs, "pubs", "p", 10, "Number of publishers") - - // Log params - rootCmd.PersistentFlags().BoolVarP(&bconf.Log.Quiet, "quiet", "", false, "Suppress messages") - - // 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") - - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } -} diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/config.go b/docker/addons/vault/scripts/tools/mqtt-bench/config.go deleted file mode 100644 index a67a12c3..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/config.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bench - -// Keep struct names exported, otherwise Viper unmarshalling won't work. -type mqttBrokerConfig struct { - URL string `toml:"url" mapstructure:"url"` -} - -type mqttMessageConfig struct { - Size int `toml:"size" mapstructure:"size"` - Payload string `toml:"payload" mapstructure:"payload"` - Format string `toml:"format" mapstructure:"format"` - QoS int `toml:"qos" mapstructure:"qos"` - Retain bool `toml:"retain" mapstructure:"retain"` -} - -type mqttTLSConfig struct { - MTLS bool `toml:"mtls" mapstructure:"mtls"` - SkipTLSVer bool `toml:"skiptlsver" mapstructure:"skiptlsver"` - CA string `toml:"ca" mapstructure:"ca"` -} - -type mqttConfig struct { - Broker mqttBrokerConfig `toml:"broker" mapstructure:"broker"` - Message mqttMessageConfig `toml:"message" mapstructure:"message"` - Timeout int `toml:"timeout" mapstructure:"timeout"` - TLS mqttTLSConfig `toml:"tls" mapstructure:"tls"` -} - -type testConfig struct { - Count int `toml:"count" mapstructure:"count"` - Pubs int `toml:"pubs" mapstructure:"pubs"` - Subs int `toml:"subs" mapstructure:"subs"` -} - -type logConfig struct { - Quiet bool `toml:"quiet" mapstructure:"quiet"` -} - -type magistralaFile struct { - ConnFile string `toml:"connections_file" mapstructure:"connections_file"` -} - -type mgThing struct { - ThingID string `toml:"thing_id" mapstructure:"thing_id"` - ThingKey string `toml:"thing_key" mapstructure:"thing_key"` - MTLSCert string `toml:"mtls_cert" mapstructure:"mtls_cert"` - MTLSKey string `toml:"mtls_key" mapstructure:"mtls_key"` -} - -type mgChannel struct { - ChannelID string `toml:"channel_id" mapstructure:"channel_id"` -} - -type magistrala struct { - Things []mgThing `toml:"things" mapstructure:"things"` - Channels []mgChannel `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"` -} diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/doc.go b/docker/addons/vault/scripts/tools/mqtt-bench/doc.go deleted file mode 100644 index 62465147..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package bench contains benchmarking tool for MQTT broker. -package bench diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/results.go b/docker/addons/vault/scripts/tools/mqtt-bench/results.go deleted file mode 100644 index 6d397e0f..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/results.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package bench - -import ( - "bytes" - "encoding/json" - "fmt" - "log" - "time" - - "gonum.org/v1/gonum/mat" - "gonum.org/v1/gonum/stat" -) - -type subsResults map[string](*[]float64) - -type runResults struct { - ID string `json:"id"` - Successes int64 `json:"successes"` - Failures int64 `json:"failures"` - RunTime float64 `json:"run_time"` - MsgTimeMin float64 `json:"msg_time_min"` - MsgTimeMax float64 `json:"msg_time_max"` - MsgTimeMean float64 `json:"msg_time_mean"` - MsgTimeStd float64 `json:"msg_time_std"` - MsgDelTimeMin float64 `json:"msg_del_time_min"` - MsgDelTimeMax float64 `json:"msg_del_time_max"` - MsgDelTimeMean float64 `json:"msg_del_time_mean"` - MsgDelTimeStd float64 `json:"msg_del_time_std"` - MsgsPerSec float64 `json:"msgs_per_sec"` -} - -type totalResults struct { - Ratio float64 `json:"ratio"` - Successes int64 `json:"successes"` - Failures int64 `json:"failures"` - TotalRunTime float64 `json:"total_run_time"` - AvgRunTime float64 `json:"avg_run_time"` - MsgTimeMin float64 `json:"msg_time_min"` - MsgTimeMax float64 `json:"msg_time_max"` - MsgDelTimeMin float64 `json:"msg_del_time_min"` - MsgDelTimeMax float64 `json:"msg_del_time_max"` - MsgTimeMeanAvg float64 `json:"msg_time_mean_avg"` - MsgTimeMeanStd float64 `json:"msg_time_mean_std"` - MsgDelTimeMeanAvg float64 `json:"msg_del_time_mean_avg"` - MsgDelTimeMeanStd float64 `json:"msg_del_time_mean_std"` - TotalMsgsPerSec float64 `json:"total_msgs_per_sec"` - AvgMsgsPerSec float64 `json:"avg_msgs_per_sec"` -} - -// JSONResults are used to export results as a JSON document. -type JSONResults struct { - Runs []*runResults `json:"runs"` - Totals *totalResults `json:"totals"` -} - -func calcMsgRes(m *message, res *runResults) *float64 { - if m.Error { - res.Failures++ - return nil - } - res.Successes++ - diff := float64(m.Delivered.Sub(m.Sent).Nanoseconds() / 1000) // in microseconds - return &diff -} - -func calcRes(r *runResults, start time.Time, times []float64) *runResults { - duration := time.Since(start) - timeMatrix := mat.NewDense(1, len(times), times) - r.MsgTimeMin = mat.Min(timeMatrix) - r.MsgTimeMax = mat.Max(timeMatrix) - r.MsgTimeMean = stat.Mean(times, nil) - r.MsgTimeStd = stat.StdDev(times, nil) - r.RunTime = duration.Seconds() - r.MsgsPerSec = float64(r.Successes) / duration.Seconds() - return r -} - -func calculateTotalResults(results []*runResults, totalTime time.Duration, sr subsResults) *totalResults { - if results == nil || len(results) < 1 { - return nil - } - totals := new(totalResults) - msgTimeMeans := make([]float64, len(results)) - msgTimeMeansDelivered := make([]float64, len(results)) - msgsPerSecs := make([]float64, len(results)) - runTimes := make([]float64, len(results)) - bws := make([]float64, len(results)) - - totals.TotalRunTime = totalTime.Seconds() - - totals.MsgTimeMin = results[0].MsgTimeMin - for i, res := range results { - totals.Successes += res.Successes - totals.Failures += res.Failures - totals.TotalMsgsPerSec += res.MsgsPerSec - - // Don't count those client that sent no messages. - if res.MsgsPerSec == 0 { - continue - } - - if res.MsgTimeMin < totals.MsgTimeMin { - totals.MsgTimeMin = res.MsgTimeMin - } - - if res.MsgTimeMax > totals.MsgTimeMax { - totals.MsgTimeMax = res.MsgTimeMax - } - - if res.MsgDelTimeMin < totals.MsgDelTimeMin { - totals.MsgDelTimeMin = res.MsgDelTimeMin - } - - if res.MsgDelTimeMax > totals.MsgDelTimeMax { - totals.MsgDelTimeMax = res.MsgDelTimeMax - } - - msgTimeMeansDelivered[i] = res.MsgDelTimeMean - msgTimeMeans[i] = res.MsgTimeMean - msgsPerSecs[i] = res.MsgsPerSec - runTimes[i] = res.RunTime - bws[i] = res.MsgsPerSec - } - - for _, v := range sr { - times := mat.NewDense(1, len(*v), *v) - totals.MsgDelTimeMin = mat.Min(times) / 1000 - totals.MsgDelTimeMax = mat.Max(times) / 1000 - totals.MsgDelTimeMeanAvg = stat.Mean(*v, nil) / 1000 - totals.MsgDelTimeMeanStd = stat.StdDev(*v, nil) / 1000 - } - - totals.Ratio = float64(totals.Successes) / float64(totals.Successes+totals.Failures) - totals.AvgMsgsPerSec = stat.Mean(msgsPerSecs, nil) - totals.AvgRunTime = stat.Mean(runTimes, nil) - totals.MsgDelTimeMeanAvg = stat.Mean(msgTimeMeansDelivered, nil) - totals.MsgDelTimeMeanStd = stat.StdDev(msgTimeMeansDelivered, nil) - totals.MsgTimeMeanAvg = stat.Mean(msgTimeMeans, nil) - totals.MsgTimeMeanStd = stat.StdDev(msgTimeMeans, nil) - - return totals -} - -func printResults(results []*runResults, totals *totalResults, format string, quiet bool) { - switch format { - case "json": - jr := JSONResults{ - Runs: results, - Totals: totals, - } - data, err := json.Marshal(jr) - if err != nil { - log.Printf("Failed to prepare results for printing - %s\n", err.Error()) - } - var out bytes.Buffer - if err = json.Indent(&out, data, "", "\t"); err != nil { - return - } - - fmt.Println(out.String()) - default: - if !quiet { - for _, res := range results { - fmt.Printf("======= CLIENT %s =======\n", res.ID) - fmt.Printf("Ratio: %.6f (%d/%d)\n", float64(res.Successes)/float64(res.Successes+res.Failures), res.Successes, res.Successes+res.Failures) - fmt.Printf("Succeeded: %d\n", res.Successes) - fmt.Printf("Failed: %d\n", res.Failures) - fmt.Printf("Runtime (s): %.3f\n", res.RunTime) - fmt.Printf("Msg time min (µs): %.3f\n", res.MsgTimeMin) - fmt.Printf("Msg time max (µs): %.3f\n", res.MsgTimeMax) - fmt.Printf("Msg time mean (µs): %.3f\n", res.MsgTimeMean) - fmt.Printf("Msg time std (µs): %.3f\n\n", res.MsgTimeStd) - - fmt.Printf("Bandwidth (msg/sec): %.3f\n\n", res.MsgsPerSec) - } - } - fmt.Printf("========= TOTAL (%d) =========\n", len(results)) - fmt.Printf("Total Ratio: %.3f (%d/%d)\n", totals.Ratio, totals.Successes, totals.Successes+totals.Failures) - fmt.Printf("Succeeded: %d\n", totals.Successes) - fmt.Printf("Failed: %d\n", totals.Failures) - fmt.Printf("Total Runtime (sec): %.3f\n", totals.TotalRunTime) - fmt.Printf("Average Runtime (sec): %.3f\n", totals.AvgRunTime) - fmt.Printf("Msg time min (µs): %.3f\n", totals.MsgTimeMin) - fmt.Printf("Msg time max (µs): %.3f\n", totals.MsgTimeMax) - fmt.Printf("Msg time mean (µs): %.3f\n", totals.MsgTimeMeanAvg) - fmt.Printf("Msg time mean std (µs): %.3f\n", totals.MsgTimeMeanStd) - - fmt.Printf("Average Bandwidth (msg/sec): %.3f\n", totals.AvgMsgsPerSec) - fmt.Printf("Total Bandwidth (msg/sec): %.3f\n", totals.TotalMsgsPerSec) - } -} diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/scripts/mqtt-bench.sh b/docker/addons/vault/scripts/tools/mqtt-bench/scripts/mqtt-bench.sh deleted file mode 100755 index 5142b7bf..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/scripts/mqtt-bench.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -i=0 -echo "BEGIN TEST " > result.$1.out -for mtls in true -do - for ret in false true - do - for qos in 0 1 2 - do - for pub in 1 10 100 - do - for sub in 1 10 - do - for message in 100 1000 - do - if [[ $pub -eq 100 && $message -eq 1000 ]]; - then - continue - fi - - for size in 100 500 - do - let "i += 1" - echo "=================================TEST $i=========================================" >> $1-$i.out - echo "MTLS: $mtls RETAIN: $ret, QOS $qos" >> $1-$i.out - echo "Pub:" $pub ", Sub:" $sub ", MsgSize:" $size ", MsgPerPub:" $message >> $1-$i.out - echo "=================================================================================" >> $1-$i.out - if [ "$mtls" = true ]; - then - echo "| " >> $1-$i.out - echo "| ./mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -m=true -b tcps://$2:8883 --quiet=true --ca ../../../docker/ssl/certs/ca.crt -t=true" >> $1-$i.out - echo "| " >> $1-$i.out - ../cmd/mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -m=true -b tcps://$2:8883 --quiet=true --ca ../../../docker/ssl/certs/ca.crt -t=true >> $1-$i.out - else - echo "| " >> $1-$i.out - echo "| ./mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -b tcp://$2:1883 --quiet=true" >> $1-$i.out - echo "| " >> $1-$i.out - ../cmd/mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -b tcp://$2:1883 --quiet=true >> $1-$i.out - fi - sleep 2 - done - done - done - done - done - - done -done -files=`ls test*.out | sort --version-sort ` -for file in $files -do - cat $file >> result.$1.out -done -echo "END TEST " >> result.$1.out diff --git a/docker/addons/vault/scripts/tools/mqtt-bench/templates/reference.toml b/docker/addons/vault/scripts/tools/mqtt-bench/templates/reference.toml deleted file mode 100644 index 5a60e8a6..00000000 --- a/docker/addons/vault/scripts/tools/mqtt-bench/templates/reference.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -[mqtt] - timeout = 1000 - [mqtt.broker] - url = "tcp://localhost:1883" - - [mqtt.message] - size = 1000 - format = "text" - qos = 2 - retain = true - payload = "{\"bn\":\"some-base-name\",\"bt\":1.276020076001e+09, \"bu\":\"A\",\"bver\":5, \"n\":\"voltage\",\"u\":\"V\",\"v\":120.1}" - - [mqtt.tls] - mtls = false - skiptlsver = true - ca = "ca.crt" - -[test] -pubs = 2000 -count = 70 - -[log] -quiet = true - -[magistrala] -connections_file = "../provision/mgconn.toml" diff --git a/docker/addons/vault/scripts/tools/provision/Makefile b/docker/addons/vault/scripts/tools/provision/Makefile deleted file mode 100644 index 7b8abc56..00000000 --- a/docker/addons/vault/scripts/tools/provision/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -PROGRAM = provision -SOURCES = $(wildcard *.go) cmd/main.go - -all: $(PROGRAM) - -.PHONY: all clean - -$(PROGRAM): $(SOURCES) - go build -ldflags "-s -w" -o $@ cmd/main.go - -clean: - rm -rf $(PROGRAM) diff --git a/docker/addons/vault/scripts/tools/provision/README.md b/docker/addons/vault/scripts/tools/provision/README.md deleted file mode 100644 index 77d70683..00000000 --- a/docker/addons/vault/scripts/tools/provision/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# Magistrala Things 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. - -This tool is useful for testing, and it creates a TOML format output (on stdout, can be redirected into the file as needed) -that can be used by Magistrala MQTT benchmarking tool (`mqtt-bench`). - -## Installation -``` -cd tools/provision -make -``` - -### Usage -``` -./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 - -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") - -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 - --ssl create certificates for mTLS access - -u, --username string magistrala user - --prefix string name prefix for things and channels -``` - -Example: -``` -go run tools/provision/cmd/main.go -u test@magistrala.com -p test1234 --host https://142.93.118.47 -``` - -If you want to create a list of channels with certificates: - -``` -go run tools/provision/cmd/main.go --host http://localhost --num 10 -u test@magistrala.com -p test1234 --ssl true --ca docker/ssl/certs/ca.crt --cakey docker/ssl/certs/ca.key - -``` - ->`ca.crt` and `ca.key` are used for creating things certificate and for HTTPS, -> 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 - - -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" -mtls_cert = """-----BEGIN CERTIFICATE----- -MIIEmTCCA4GgAwIBAgIRAO50qOfXsU+cHm/QY2NYu+0wDQYJKoZIhvcNAQELBQAw -VzESMBAGA1UEAwwJbG9jYWxob3N0MREwDwYDVQQKDAhNYWluZmx1eDEMMAoGA1UE -CwwDSW9UMSAwHgYJKoZIhvcNAQkBFhFpbmZvQG1haW5mbHV4LmNvbTAeFw0xOTEx -MTUxNzU2MzhaFw0yMDAyMjMxNzU2MzhaMFUxETAPBgNVBAoTCE1haW5mbHV4MREw -DwYDVQQLEwhtYWluZmx1eDEtMCsGA1UEAxMkMDc3MTMxMDMtNTEzZi00M2M3LWI3 -ZmUtNTAwYzFhZjIzZDdkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA -zsIYoovZJGJxfu7e4X3P3wnHDi9/wvRMhGW1EZEB5vNvfxvmmt4PhiE1c73mCypT -AUdui0j+hrCx8P90v12LEcJqty3yBnw+ge2/xCLNLKZh2/MjBQ7A7PMQpmOo31LR -hxFSthW41C296iwVYyvRa19y7g5mcUrzWvI2EVZbbGEDym1U/PI4aKhdQ3a7fF6B -GfvXYbGOa4/8VUIj8KHTRg2Z6/iLhxYgUnHd3xMCjihQkwLvB7/avVr9Ih9oLEe+ -h7H9Pl5hMEpHP4BvHokUFhtbzqofuHNBKuEUf5r/cQ1oVAl6F77Fs5vZbQ59bLxw -etclDxW7nvOgIxEIUcJAkdd+nOxhpfbDM8QFsPXGSfb9vWUTaoQDIeWx9pPY5tsY -tbtW2HeKRGHO9jGFSzonY6sbTiaIzQ0F2PNPS1BoBIo2A95YNwt2ScfuRTs5ZK62 -2+RNWbs+pDXJ5ZGcWDfjSxEYXy+jGUyvDExGCtryUu5Ufp7XuZ4O767iDzaj7dFG -rXSXfXrqwm8u2CMwucNzdVqikNG2gDToHDyIjLRd62m2pHk9gXbk3FGI+5x52pBs -+xdRaddMY8+DJ2R88PFoq3kqexxs2HJathCu6RfoP452zH9iU0gvPLR7fXuPoZ6Y -5NqE1CebZ6IiwwivD7kU1LxmhmQUY9DaHdHNYd66bd0CAwEAAaNiMGAwDgYDVR0P -AQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAOBgNVHQ4E -BwQFAQIDBAYwHwYDVR0jBBgwFoAUbOMUfdahIzURpsN/dcUu8ek3PvIwDQYJKoZI -hvcNAQELBQADggEBAI+DdKYKKPVi4CPUbl+R81dq+Otd8L9i/RxM7G89XU0aGkSO -GSJzURKYbmLGgWdVWcdYMUfbpiE8vH1dLuDQdRywpDDjSMx7h0PwpYvk25HHKMSs -OIKpxvI1DyuNcwxrPuH863zw1Mo1hpGGin7yZc8VBf6nbR3RMNbQ2elMH1m7no4v -YM4HrTeR9n1bakIVw9OLnFpB03sT3keBdWsLDbAZ0yZfvxqdn6Hr7NRnab3vyrOz -GrYPJ51B/FGZC9n0ZR+SWzipen15vaG46SvoCv9HfDZ9cbSVR4eyPy/OIx+5CBVY -uGpJ+kN8jH5tuoxrmHZOsPMA+a6CZD2cKTaRu+Y= ------END CERTIFICATE----- -""" -mtls_key = """-----BEGIN RSA PRIVATE KEY----- -MIIJKQIBAAKCAgEAzsIYoovZJGJxfu7e4X3P3wnHDi9/wvRMhGW1EZEB5vNvfxvm -mt4PhiE1c73mCypTAUdui0j+hrCx8P90v12LEcJqty3yBnw+ge2/xCLNLKZh2/Mj -BQ7A7PMQpmOo31LRhxFSthW41C296iwVYyvRa19y7g5mcUrzWvI2EVZbbGEDym1U -/PI4aKhdQ3a7fF6BGfvXYbGOa4/8VUIj8KHTRg2Z6/iLhxYgUnHd3xMCjihQkwLv -B7/avVr9Ih9oLEe+h7H9Pl5hMEpHP4BvHokUFhtbzqofuHNBKuEUf5r/cQ1oVAl6 -F77Fs5vZbQ59bLxwetclDxW7nvOgIxEIUcJAkdd+nOxhpfbDM8QFsPXGSfb9vWUT -aoQDIeWx9pPY5tsYtbtW2HeKRGHO9jGFSzonY6sbTiaIzQ0F2PNPS1BoBIo2A95Y -Nwt2ScfuRTs5ZK622+RNWbs+pDXJ5ZGcWDfjSxEYXy+jGUyvDExGCtryUu5Ufp7X -uZ4O767iDzaj7dFGrXSXfXrqwm8u2CMwucNzdVqikNG2gDToHDyIjLRd62m2pHk9 -gXbk3FGI+5x52pBs+xdRaddMY8+DJ2R88PFoq3kqexxs2HJathCu6RfoP452zH9i -U0gvPLR7fXuPoZ6Y5NqE1CebZ6IiwwivD7kU1LxmhmQUY9DaHdHNYd66bd0CAwEA -AQKCAgAj2sr03TWhtqSh84CZL/0tW3+2eQw53a2rRAv7aN8gktSiAU+jSaD9jKK9 -WJAdHZDZZu7Hnrfs2ZVyCorPaMRmJwXkkEYpU8BvPbCErdhQxuWvg+FtzhosvRYF -FMFDQRRuzNVAGFI+EVSe2Fg5I28kpJ/EoqCnQu0it2Ai74vZJpXGs+EKIGMh2xiZ -S2zF64mN3PuDyIu/IXALxPWAlD+UJWWs4yQnH/Io+fAU8DIAPwOCCv8yo9WmArJl -CXdCPorO81HMUAegnTDv1TDv5aujDcmE9EGd9fa2HeQ1IMbtbvrJn/8ZQQ79z6gL -3nhns+H5m3ekvwsTTIJXsmtz6jDSCek5C78gKJ6fIH/urKkgG0Pcw4HdOtt5PYQS -KnAKN9KuPEqwxJCDpwKcENDxBul9Huc9i4m1J8hq4qtEBk8k1rqfjWAxigBmhdQV -jY0q//ou/VYgD07RIqezCovVZwJDqvEKg2A5e2YmUXIbYmG1BTCN5NIDcnwqO65C -gD4V9vgn2+ek7z8rBr5VHJ/3LNqc+XFzQW+GjzVFLUfzkgipMGt4DVQdseXWKaiz -v6LV7Nn4hPKETZ5pYzNll4SH+PkVG0Pwc9g8yZF0CcvQt/4wry78LdihgXUBtI7G -+5cH/DXOCd1itaauggHQwEm6GF4VR3uPthoU++QvPKqSAvWnQQKCAQEA7n6xDE2J -iWEBCj8gDYcKKgMUlwWmnWc7MprOU2oCR4DXLcDNcmJLKwb2UC1Z4dxQy5pJs6Yk -5f6rOFwQ0sMM36PcmRJcBNeMTsj2ilZ79TbVYl4pgtjZLJl4JptwXFZFeVdTx1Sa -QoZasqlyO44Uw5D3+ztddHpnOVPCLd36xV6R3e1scKuXCrE4Pl/+YmkYG8NrRKoe -vHUhmmtcukxsEPhGJhQqpbMhm75hBFfHJw2gMu1bBGDGYzfX9bBkF1ZRq+7X6/g0 -Zvr5Gh1tZhkHDR9JwRMNbTSQgVvJD0eToBo5kZbWF4+giAhNkV+wGiCMJgdGWJQo -4Cz5rY+Nv2Rz7QKCAQEA3e8SzLm4Gvft9AZUy96kuk5uKckAXW/FnDKfa+zFoT7w -KyEz9yOZRFXoPdrReZLzgk8GDZVbYAyXmONx9Sjq1GmZ/fDkXpUtdr6PmDR19Hea -CVqUfkBYmMTmA0zFpS6rsI+dIwCP2h7slJQ4eUESYVRiXWyOKEhQVGM0t9liUfrr -lfRnVj6q9I3vqCcqgBuODoAS/iFaFpSfh05XSKdl9XW2t/sd33acPqh9zKBczlsR -H6dyrO02znbbOgrBCBbxtFdq4YLuHKsBB2umz/NKfpnoOUHLeTU2VaqyOtDK9BIA -XtCPu6KJNZ86eFAbtHwBpHn7u7iQZtcaWK9LuESDsQKCAQEAiMV/I18UEQTgY8/v -wdI/sfgyRqmm833QJSVCTfPterQYstRu/boBAZvshe58LVr7usewnKYbYwq5hojF -3RieuWJvkBlHTD+Q5124hX0zeV0I4nC9vZw+b6VTklByD4IqNXwvP5D1JlGGkg86 -w4ynu7/XduyEm9fWerneEg/LUIT7gho2pibBaBBaAOtsJ2O9v65CRg6Jseo6ayRG -+U/6aYD4Ob429u/Txk1XtfXg8DSQOqSEHe6h1ySfZPbTb87A56kBiwG8i5JCaQeX -RYX01UGsOl2Cxa3vcUAB/hE+SALCIQwvmzNzDJA2a7hEdbdUqDpjzUiqaGViinZZ -A/nHwQKCAQAkTxLCT7ghIWLaw5Zn7DsDCAXZ7DqVDs5DqbyPSaNjqApe5AW+byKK -HYvrYrtWqoYQUaFp43+ZjTXYG43vUAxrSAObmieimcFgZfjUK/EIV/Dpito0dY6J -H92JuKu1RJduQXCx40ulod2OyVkb7Vt2dPnK0xHG4V3TEI/1bCk7xFN6qwuk/oe1 -jusglZfMcbWiBa4VyZsViqc22chJ6KkzqViFbR4MCzmwvpwmOC42zItWpGyMghqv -WJ6xNkUyb56HpK2ly2ftZMS8VA5sgx8y6zck9vC1GdGT3mNeX/50Q+WvnWuGhSbx -kOVd/a0qsAcMw7A9nApz6Mk0rSk0MnFhAoIBAQCI6dU5c1sTp/LNp+z6yQmcJD3Z -HNYdVhf8pxHpRWZ8r5otFwi1lr5vk15Zh59B5nMLQHP3UWJ7R66HUjXCtFe86ojV -xngL3lXJNtLcCWXQHM/nkWZ1TVCeZ6mS8aJndcy4sY0lPUqRtYaXSV/EyzpQJUmf -xcEeQuOhBZ4s8uSyuLgEPYbeYyi7Vpujm7UpplTN55dIZrQ7tMefRNgHjybFfC8P -QsxPR4lWoFpr9xFvtBORlP+In8LjD3Z2EDm2guIRAWebEJGsY7ftAv7CEFrLOJd5 -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 -[[channels]] -channel_id = "1f18afa1-29c4-4634-99d1-68dfa1b74e6a" - -``` diff --git a/docker/addons/vault/scripts/tools/provision/cmd/main.go b/docker/addons/vault/scripts/tools/provision/cmd/main.go deleted file mode 100644 index 1b7461e1..00000000 --- a/docker/addons/vault/scripts/tools/provision/cmd/main.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains entry point for provisioning tool. -package main - -import ( - "log" - - "github.com/absmach/magistrala/tools/provision" - "github.com/spf13/cobra" -) - -func main() { - pconf := provision.Config{} - - 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`, - Run: func(_ *cobra.Command, _ []string) { - if err := provision.Provision(pconf); err != nil { - log.Fatal(err) - } - }, - } - - // 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().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") - - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } -} diff --git a/docker/addons/vault/scripts/tools/provision/doc.go b/docker/addons/vault/scripts/tools/provision/doc.go deleted file mode 100644 index 342b0abe..00000000 --- a/docker/addons/vault/scripts/tools/provision/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package provision is a simple utility to create -// a list of channels and things connected to these channels -// with possibility to create certificates for mTLS use case. -package provision diff --git a/docker/addons/vault/scripts/tools/provision/provision.go b/docker/addons/vault/scripts/tools/provision/provision.go deleted file mode 100644 index d0316a07..00000000 --- a/docker/addons/vault/scripts/tools/provision/provision.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package provision - -import ( - "bufio" - "bytes" - "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "log" - "math/big" - "os" - "strings" - "time" - - "github.com/0x6flab/namegenerator" - sdk "github.com/absmach/magistrala/pkg/sdk/go" -) - -const ( - defPass = "12345678" - defReaderURL = "http://localhost:9005" -) - -var namesgenerator = namegenerator.NewGenerator() - -// MgConn - structure describing Magistrala connection set. -type MgConn struct { - ChannelID string - ThingID string - ThingKey string - MTLSCert string - MTLSKey string -} - -// Config - provisioning configuration. -type Config struct { - Host string - Username string - Email string - Password string - Num int - SSL bool - CA string - CAKey string - Prefix string -} - -// Provision - function that does actual provisiong. -func Provision(conf Config) error { - const ( - rsaBits = 4096 - ttl = "2400h" - ) - - msgContentType := string(sdk.CTJSONSenML) - sdkConf := sdk.Config{ - ThingsURL: conf.Host, - UsersURL: conf.Host, - ReaderURL: defReaderURL, - HTTPAdapterURL: fmt.Sprintf("%s/http", conf.Host), - BootstrapURL: conf.Host, - CertsURL: conf.Host, - MsgContentType: sdk.ContentType(msgContentType), - TLSVerification: false, - } - - s := sdk.NewSDK(sdkConf) - - user := sdk.User{ - Email: conf.Email, - Credentials: sdk.Credentials{ - Username: conf.Username, - Secret: conf.Password, - }, - } - - if user.Email == "" { - user.Email = fmt.Sprintf("%s@email.com", namesgenerator.Generate()) - user.Credentials.Secret = defPass - } - - // Create new user - if _, err := s.CreateUser(user, ""); err != nil { - return fmt.Errorf("unable to create new user: %s", err.Error()) - } - - var err error - - // Login user - token, err := s.CreateToken(sdk.Login{Identity: user.Credentials.Username, Secret: user.Credentials.Secret}) - if err != nil { - return fmt.Errorf("unable to login user: %s", err.Error()) - } - - // Create new domain - dname := fmt.Sprintf("%s%s", conf.Prefix, namesgenerator.Generate()) - domain := sdk.Domain{ - Name: dname, - Alias: strings.ToLower(dname), - Permission: "admin", - } - - domain, err = s.CreateDomain(domain, token.AccessToken) - if err != nil { - return fmt.Errorf("unable to create domain: %w", err) - } - // Login to domain - token, err = s.CreateToken(sdk.Login{ - Identity: user.Credentials.Username, - Secret: user.Credentials.Secret, - }) - if err != nil { - return fmt.Errorf("unable to login user: %w", err) - } - - var tlsCert tls.Certificate - var caCert *x509.Certificate - - if conf.SSL { - tlsCert, err = tls.LoadX509KeyPair(conf.CA, conf.CAKey) - if err != nil { - return fmt.Errorf("failed to load CA cert") - } - - b, err := os.ReadFile(conf.CA) - if err != nil { - return fmt.Errorf("failed to load CA cert") - } - - block, _ := pem.Decode(b) - if block == nil { - return fmt.Errorf("no PEM data found, failed to decode CA") - } - - caCert, err = x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("failed to decode certificate - %s", err.Error()) - } - } - - // Create things and channels - things := make([]sdk.Thing, conf.Num) - channels := make([]sdk.Channel, conf.Num) - cIDs := []string{} - tIDs := []string{} - - fmt.Println("# List of things 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)} - channels[i] = sdk.Channel{Name: fmt.Sprintf("%s-channel-%d", conf.Prefix, i)} - } - - things, err = s.CreateThings(things, domain.ID, token.AccessToken) - if err != nil { - return fmt.Errorf("failed to create the things: %s", err.Error()) - } - - var chs []sdk.Channel - for _, c := range channels { - c, err = s.CreateChannel(c, domain.ID, token.AccessToken) - if err != nil { - return fmt.Errorf("failed to create the chennels: %s", err.Error()) - } - chs = append(chs, c) - } - channels = chs - - for _, t := range things { - tIDs = append(tIDs, t.ID) - } - - for _, c := range channels { - cIDs = append(cIDs, c.ID) - } - - for i := 0; i < conf.Num; i++ { - cert := "" - key := "" - - if conf.SSL { - var priv interface{} - priv, _ = rsa.GenerateKey(rand.Reader, rsaBits) - - notBefore := time.Now() - validFor, err := time.ParseDuration(ttl) - if err != nil { - return fmt.Errorf("failed to set date %v", validFor) - } - notAfter := notBefore.Add(validFor) - - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return fmt.Errorf("failed to generate serial number: %s", err) - } - - tmpl := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{"Magistrala"}, - CommonName: things[i].Credentials.Secret, - OrganizationalUnit: []string{"magistrala"}, - }, - NotBefore: notBefore, - NotAfter: notAfter, - - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - SubjectKeyId: []byte{1, 2, 3, 4, 6}, - } - - derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, caCert, publicKey(priv), tlsCert.PrivateKey) - if err != nil { - return fmt.Errorf("failed to create certificate: %s", err) - } - - var bw, keyOut bytes.Buffer - buffWriter := bufio.NewWriter(&bw) - buffKeyOut := bufio.NewWriter(&keyOut) - - if err := pem.Encode(buffWriter, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return fmt.Errorf("failed to write cert pem data: %s", err) - } - buffWriter.Flush() - cert = bw.String() - - if err := pem.Encode(buffKeyOut, pemBlockForKey(priv)); err != nil { - return fmt.Errorf("failed to write key pem data: %s", err) - } - buffKeyOut.Flush() - key = keyOut.String() - } - - // Print output - fmt.Printf("[[things]]\nthing_id = \"%s\"\nthing_key = \"%s\"\n", things[i].ID, things[i].Credentials.Secret) - if conf.SSL { - fmt.Printf("mtls_cert = \"\"\"%s\"\"\"\n", cert) - fmt.Printf("mtls_key = \"\"\"%s\"\"\"\n", key) - } - fmt.Println("") - } - - fmt.Printf("# List of channels that things can publish to\n" + - "# each channel is connected to each thing from things list\n") - for i := 0; i < conf.Num; i++ { - fmt.Printf("[[channels]]\nchannel_id = \"%s\"\n\n", cIDs[i]) - } - - for _, cID := range cIDs { - for _, tID := range tIDs { - conIDs := sdk.Connection{ - ThingID: tID, - ChannelID: cID, - } - if err := s.Connect(conIDs, domain.ID, token.AccessToken); err != nil { - log.Fatalf("Failed to connect things %s to channels %s: %s", tID, cID, err) - } - } - } - - return nil -} - -func publicKey(priv interface{}) interface{} { - switch k := priv.(type) { - case *rsa.PrivateKey: - return &k.PublicKey - case *ecdsa.PrivateKey: - return &k.PublicKey - default: - return nil - } -} - -func pemBlockForKey(priv interface{}) *pem.Block { - switch k := priv.(type) { - case *rsa.PrivateKey: - return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} - case *ecdsa.PrivateKey: - b, err := x509.MarshalECPrivateKey(k) - if err != nil { - fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) - os.Exit(2) - } - return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} - default: - return nil - } -} diff --git a/docker/addons/vault/scripts/users/README.md b/docker/addons/vault/scripts/users/README.md deleted file mode 100644 index cdcfce87..00000000 --- a/docker/addons/vault/scripts/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 | <admin@example.com> | -| 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 | <nats://localhost:4222> | -| MG_JAEGER_URL | Jaeger server URL | <http://localhost:4318/v1/traces> | -| MG_OAUTH_UI_REDIRECT_URL | OAuth UI redirect URL | <http://localhost:9095/domains> | -| MG_OAUTH_UI_ERROR_URL | OAuth UI error URL | <http://localhost:9095/error> | -| 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/docker/addons/vault/scripts/users/api/doc.go b/docker/addons/vault/scripts/users/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/api/endpoint_test.go b/docker/addons/vault/scripts/users/api/endpoint_test.go deleted file mode 100644 index 32d219cb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/api/endpoints.go b/docker/addons/vault/scripts/users/api/endpoints.go deleted file mode 100644 index dcb8986f..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/api/groups.go b/docker/addons/vault/scripts/users/api/groups.go deleted file mode 100644 index 72cb478c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/api/requests.go b/docker/addons/vault/scripts/users/api/requests.go deleted file mode 100644 index 5fb97978..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/api/requests_test.go b/docker/addons/vault/scripts/users/api/requests_test.go deleted file mode 100644 index 462ecebe..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/api/responses.go b/docker/addons/vault/scripts/users/api/responses.go deleted file mode 100644 index 21df78d3..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/api/transport.go b/docker/addons/vault/scripts/users/api/transport.go deleted file mode 100644 index e3334b2a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/api/users.go b/docker/addons/vault/scripts/users/api/users.go deleted file mode 100644 index c712034d..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/delete_handler.go b/docker/addons/vault/scripts/users/delete_handler.go deleted file mode 100644 index cbe623b6..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/doc.go b/docker/addons/vault/scripts/users/doc.go deleted file mode 100644 index 24207115..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/emailer.go b/docker/addons/vault/scripts/users/emailer.go deleted file mode 100644 index 9f0c5396..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/emailer/doc.go b/docker/addons/vault/scripts/users/emailer/doc.go deleted file mode 100644 index 4db3fb1c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/emailer/emailer.go b/docker/addons/vault/scripts/users/emailer/emailer.go deleted file mode 100644 index 030a74ab..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/errors.go b/docker/addons/vault/scripts/users/errors.go deleted file mode 100644 index 7dc6b0a9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/events/doc.go b/docker/addons/vault/scripts/users/events/doc.go deleted file mode 100644 index 86f9918a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/events/events.go b/docker/addons/vault/scripts/users/events/events.go deleted file mode 100644 index 844fe77b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/events/streams.go b/docker/addons/vault/scripts/users/events/streams.go deleted file mode 100644 index 0820a0e2..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/hasher.go b/docker/addons/vault/scripts/users/hasher.go deleted file mode 100644 index c8fa2a87..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/hasher/doc.go b/docker/addons/vault/scripts/users/hasher/doc.go deleted file mode 100644 index 98be9922..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/hasher/hasher.go b/docker/addons/vault/scripts/users/hasher/hasher.go deleted file mode 100644 index 698acf70..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/middleware/authorization.go b/docker/addons/vault/scripts/users/middleware/authorization.go deleted file mode 100644 index 53c552ff..00000000 --- a/docker/addons/vault/scripts/users/middleware/authorization.go +++ /dev/null @@ -1,234 +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 - } - 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/docker/addons/vault/scripts/users/middleware/doc.go b/docker/addons/vault/scripts/users/middleware/doc.go deleted file mode 100644 index ce2aef48..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/middleware/logging.go b/docker/addons/vault/scripts/users/middleware/logging.go deleted file mode 100644 index d261b722..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/middleware/metrics.go b/docker/addons/vault/scripts/users/middleware/metrics.go deleted file mode 100644 index ab6321ac..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/mocks/doc.go b/docker/addons/vault/scripts/users/mocks/doc.go deleted file mode 100644 index 16ed198a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/mocks/emailer.go b/docker/addons/vault/scripts/users/mocks/emailer.go deleted file mode 100644 index 77e226a6..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/mocks/hasher.go b/docker/addons/vault/scripts/users/mocks/hasher.go deleted file mode 100644 index 4c4425b2..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/mocks/repository.go b/docker/addons/vault/scripts/users/mocks/repository.go deleted file mode 100644 index 739c96ca..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/mocks/service.go b/docker/addons/vault/scripts/users/mocks/service.go deleted file mode 100644 index 83dfe9e6..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/postgres/doc.go b/docker/addons/vault/scripts/users/postgres/doc.go deleted file mode 100644 index b4f616d7..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/postgres/init.go b/docker/addons/vault/scripts/users/postgres/init.go deleted file mode 100644 index 99e5c380..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/postgres/setup_test.go b/docker/addons/vault/scripts/users/postgres/setup_test.go deleted file mode 100644 index a8cd27f5..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/postgres/users.go b/docker/addons/vault/scripts/users/postgres/users.go deleted file mode 100644 index 37b23a43..00000000 --- a/docker/addons/vault/scripts/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, 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/docker/addons/vault/scripts/users/postgres/users_test.go b/docker/addons/vault/scripts/users/postgres/users_test.go deleted file mode 100644 index 671512ad..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/roles.go b/docker/addons/vault/scripts/users/roles.go deleted file mode 100644 index 4cb493d1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/service.go b/docker/addons/vault/scripts/users/service.go deleted file mode 100644 index f6318f87..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/service_test.go b/docker/addons/vault/scripts/users/service_test.go deleted file mode 100644 index 8c891afc..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/status.go b/docker/addons/vault/scripts/users/status.go deleted file mode 100644 index 974cec22..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/tracing/doc.go b/docker/addons/vault/scripts/users/tracing/doc.go deleted file mode 100644 index 5aa1b44b..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/tracing/tracing.go b/docker/addons/vault/scripts/users/tracing/tracing.go deleted file mode 100644 index 81ad0dcb..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/users/users.go b/docker/addons/vault/scripts/users/users.go deleted file mode 100644 index 8fe96042..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/uuid.go b/docker/addons/vault/scripts/uuid.go deleted file mode 100644 index 29c5b294..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/README.md b/docker/addons/vault/scripts/ws/README.md deleted file mode 100644 index 61784314..00000000 --- a/docker/addons/vault/scripts/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 | <localhost:7000> | -| 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 | <nats://localhost:4222> | -| MG_JAEGER_URL | Jaeger server URL | <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_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/docker/addons/vault/scripts/ws/adapter.go b/docker/addons/vault/scripts/ws/adapter.go deleted file mode 100644 index e92b0412..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/adapter_test.go b/docker/addons/vault/scripts/ws/adapter_test.go deleted file mode 100644 index 40323a2a..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/api/doc.go b/docker/addons/vault/scripts/ws/api/doc.go deleted file mode 100644 index 2424852c..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/api/endpoint_test.go b/docker/addons/vault/scripts/ws/api/endpoint_test.go deleted file mode 100644 index 1bc1faf1..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/api/endpoints.go b/docker/addons/vault/scripts/ws/api/endpoints.go deleted file mode 100644 index 040133a9..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/api/logging.go b/docker/addons/vault/scripts/ws/api/logging.go deleted file mode 100644 index 5c693a45..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/api/metrics.go b/docker/addons/vault/scripts/ws/api/metrics.go deleted file mode 100644 index a1a8d593..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/api/requests.go b/docker/addons/vault/scripts/ws/api/requests.go deleted file mode 100644 index cc3f50dc..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/api/transport.go b/docker/addons/vault/scripts/ws/api/transport.go deleted file mode 100644 index 1398d206..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/client.go b/docker/addons/vault/scripts/ws/client.go deleted file mode 100644 index cf33a105..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/client_test.go b/docker/addons/vault/scripts/ws/client_test.go deleted file mode 100644 index 7e6dbce8..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/doc.go b/docker/addons/vault/scripts/ws/doc.go deleted file mode 100644 index 67c9b3ca..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/handler.go b/docker/addons/vault/scripts/ws/handler.go deleted file mode 100644 index 56a39da8..00000000 --- a/docker/addons/vault/scripts/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/<channel_id>/messages/<subtopic>/.../ct/<content_type> - 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/<channel_id>/messages/<subtopic>/.../ct/<content_type> - 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/docker/addons/vault/scripts/ws/tracing/doc.go b/docker/addons/vault/scripts/ws/tracing/doc.go deleted file mode 100644 index 2d65dbe4..00000000 --- a/docker/addons/vault/scripts/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/docker/addons/vault/scripts/ws/tracing/tracing.go b/docker/addons/vault/scripts/ws/tracing/tracing.go deleted file mode 100644 index ed7e62c9..00000000 --- a/docker/addons/vault/scripts/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) -}