mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 04:00:27 +00:00
NOISSUE - Remove SuperMQ duplicates (#23)
* Update docker-compose to use SuperMQ Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Remove duplicate services Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Update Bootstrap Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Update other services to use SMQ Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Switch config prefix to SMQ Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Remove leftovers Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Remove duplicate interface definitions Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Remove unused actions Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Remove unused API docs Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Resolve linter comments Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> * Fix provision Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com> --------- Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com>
This commit is contained in:
@@ -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"
|
||||
@@ -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"
|
||||
@@ -23,14 +23,6 @@ jobs:
|
||||
go-version: 1.22.x
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Install protolint
|
||||
run: |
|
||||
go install github.com/yoheimuta/protolint/cmd/protolint@latest
|
||||
|
||||
- name: Lint Protobuf Files
|
||||
run: |
|
||||
protolint .
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
@@ -41,14 +33,6 @@ jobs:
|
||||
run: |
|
||||
make all -j $(nproc)
|
||||
|
||||
- name: Compile check for rabbitmq
|
||||
run: |
|
||||
MG_MESSAGE_BROKER_TYPE=rabbitmq make mqtt
|
||||
|
||||
- name: Compile check for redis
|
||||
run: |
|
||||
MG_ES_TYPE=redis make mqtt
|
||||
|
||||
run-tests:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
@@ -75,15 +59,6 @@ jobs:
|
||||
workflow:
|
||||
- ".github/workflows/tests.yml"
|
||||
|
||||
auth:
|
||||
- "auth/**"
|
||||
- "cmd/auth/**"
|
||||
- "auth.proto"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "pkg/ulid/**"
|
||||
- "pkg/uuid/**"
|
||||
|
||||
bootstrap:
|
||||
- "bootstrap/**"
|
||||
- "cmd/bootstrap/**"
|
||||
@@ -93,81 +68,16 @@ jobs:
|
||||
- "pkg/sdk/**"
|
||||
- "pkg/events/**"
|
||||
|
||||
certs:
|
||||
- "certs/**"
|
||||
- "cmd/certs/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "auth/**"
|
||||
- "pkg/sdk/**"
|
||||
|
||||
cli:
|
||||
- "cli/**"
|
||||
- "cmd/cli/**"
|
||||
- "pkg/sdk/**"
|
||||
|
||||
coap:
|
||||
- "coap/**"
|
||||
- "cmd/coap/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "things/**"
|
||||
- "pkg/messaging/**"
|
||||
|
||||
consumers:
|
||||
- "consumers/**"
|
||||
- "cmd/postgres-writer/**"
|
||||
- "cmd/timescale-writer/**"
|
||||
- "cmd/smpp-notifier/**"
|
||||
- "cmd/smtp-notifier/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "auth/**"
|
||||
- "pkg/ulid/**"
|
||||
- "pkg/uuid/**"
|
||||
- "pkg/messaging/**"
|
||||
|
||||
journal:
|
||||
- "journal/**"
|
||||
- "cmd/journal/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "auth/**"
|
||||
- "pkg/events/**"
|
||||
|
||||
http:
|
||||
- "http/**"
|
||||
- "cmd/http/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "things/**"
|
||||
- "pkg/messaging/**"
|
||||
- "logger/**"
|
||||
|
||||
internal:
|
||||
- "internal/**"
|
||||
|
||||
invitations:
|
||||
- "invitations/**"
|
||||
- "cmd/invitations/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "auth/**"
|
||||
- "pkg/sdk/**"
|
||||
|
||||
logger:
|
||||
- "logger/**"
|
||||
|
||||
mqtt:
|
||||
- "mqtt/**"
|
||||
- "cmd/mqtt/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "things/**"
|
||||
- "pkg/messaging/**"
|
||||
- "logger/**"
|
||||
- "pkg/events/**"
|
||||
|
||||
pkg-errors:
|
||||
- "pkg/errors/**"
|
||||
|
||||
@@ -175,40 +85,6 @@ jobs:
|
||||
- "pkg/events/**"
|
||||
- "pkg/messaging/**"
|
||||
|
||||
pkg-grpcclient:
|
||||
- "pkg/grpcclient/**"
|
||||
|
||||
pkg-messaging:
|
||||
- "pkg/messaging/**"
|
||||
|
||||
pkg-sdk:
|
||||
- "pkg/sdk/**"
|
||||
- "pkg/errors/**"
|
||||
- "pkg/groups/**"
|
||||
- "auth/**"
|
||||
- "bootstrap/**"
|
||||
- "certs/**"
|
||||
- "consumers/**"
|
||||
- "http/**"
|
||||
- "internal/*"
|
||||
- "internal/api/**"
|
||||
- "internal/apiutil/**"
|
||||
- "internal/groups/**"
|
||||
- "invitations/**"
|
||||
- "provision/**"
|
||||
- "readers/**"
|
||||
- "things/**"
|
||||
- "users/**"
|
||||
|
||||
pkg-transformers:
|
||||
- "pkg/transformers/**"
|
||||
|
||||
pkg-ulid:
|
||||
- "pkg/ulid/**"
|
||||
|
||||
pkg-uuid:
|
||||
- "pkg/uuid/**"
|
||||
|
||||
provision:
|
||||
- "provision/**"
|
||||
- "cmd/provision/**"
|
||||
@@ -224,138 +100,30 @@ jobs:
|
||||
- "things/**"
|
||||
- "auth/**"
|
||||
|
||||
things:
|
||||
- "things/**"
|
||||
- "cmd/things/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "auth/**"
|
||||
- "pkg/ulid/**"
|
||||
- "pkg/uuid/**"
|
||||
- "pkg/events/**"
|
||||
|
||||
users:
|
||||
- "users/**"
|
||||
- "cmd/users/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "auth/**"
|
||||
- "pkg/ulid/**"
|
||||
- "pkg/uuid/**"
|
||||
- "pkg/events/**"
|
||||
|
||||
ws:
|
||||
- "ws/**"
|
||||
- "cmd/ws/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "things/**"
|
||||
- "pkg/messaging/**"
|
||||
|
||||
- name: Create coverage directory
|
||||
run: |
|
||||
mkdir coverage
|
||||
|
||||
- name: Run Journal tests
|
||||
if: steps.changes.outputs.journal == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/journal.out ./journal/...
|
||||
|
||||
- name: Run auth tests
|
||||
if: steps.changes.outputs.auth == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/auth.out ./auth/...
|
||||
|
||||
- name: Run bootstrap tests
|
||||
if: steps.changes.outputs.bootstrap == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/bootstrap.out ./bootstrap/...
|
||||
|
||||
- name: Run certs tests
|
||||
if: steps.changes.outputs.certs == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/certs.out ./certs/...
|
||||
|
||||
- name: Run cli tests
|
||||
if: steps.changes.outputs.cli == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/cli.out ./cli/...
|
||||
|
||||
- name: Run CoAP tests
|
||||
if: steps.changes.outputs.coap == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/coap.out ./coap/...
|
||||
|
||||
- name: Run consumers tests
|
||||
if: steps.changes.outputs.consumers == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/consumers.out ./consumers/...
|
||||
|
||||
- name: Run HTTP tests
|
||||
if: steps.changes.outputs.http == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/http.out ./http/...
|
||||
|
||||
- name: Run internal tests
|
||||
if: steps.changes.outputs.internal == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/internal.out ./internal/...
|
||||
|
||||
- name: Run invitations tests
|
||||
if: steps.changes.outputs.invitations == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/invitations.out ./invitations/...
|
||||
|
||||
- name: Run logger tests
|
||||
if: steps.changes.outputs.logger == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/logger.out ./logger/...
|
||||
|
||||
- name: Run MQTT tests
|
||||
if: steps.changes.outputs.mqtt == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/mqtt.out ./mqtt/...
|
||||
|
||||
- name: Run pkg errors tests
|
||||
if: steps.changes.outputs.pkg-errors == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/pkg-errors.out ./pkg/errors/...
|
||||
|
||||
- name: Run pkg events tests
|
||||
if: steps.changes.outputs.pkg-events == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/pkg-events.out ./pkg/events/...
|
||||
|
||||
- name: Run pkg grpcclient tests
|
||||
if: steps.changes.outputs.pkg-grpcclient == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/pkg-grpcclient.out ./pkg/grpcclient/...
|
||||
|
||||
- name: Run pkg messaging tests
|
||||
if: steps.changes.outputs.pkg-messaging == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/pkg-messaging.out ./pkg/messaging/...
|
||||
|
||||
- name: Run pkg sdk tests
|
||||
if: steps.changes.outputs.pkg-sdk == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/pkg-sdk.out ./pkg/sdk/...
|
||||
|
||||
- name: Run pkg transformers tests
|
||||
if: steps.changes.outputs.pkg-transformers == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/pkg-transformers.out ./pkg/transformers/...
|
||||
|
||||
- name: Run pkg ulid tests
|
||||
if: steps.changes.outputs.pkg-ulid == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/pkg-ulid.out ./pkg/ulid/...
|
||||
|
||||
- name: Run pkg uuid tests
|
||||
if: steps.changes.outputs.pkg-uuid == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/pkg-uuid.out ./pkg/uuid/...
|
||||
|
||||
- name: Run provision tests
|
||||
if: steps.changes.outputs.provision == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
@@ -366,21 +134,6 @@ jobs:
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/readers.out ./readers/...
|
||||
|
||||
- name: Run things tests
|
||||
if: steps.changes.outputs.things == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/things.out ./things/...
|
||||
|
||||
- name: Run users tests
|
||||
if: steps.changes.outputs.users == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/users.out ./users/...
|
||||
|
||||
- name: Run WebSocket tests
|
||||
if: steps.changes.outputs.ws == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/ws.out ./ws/...
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
|
||||
MG_DOCKER_IMAGE_NAME_PREFIX ?= ghcr.io/absmach/magistrala
|
||||
BUILD_DIR = build
|
||||
SERVICES = auth users things http coap ws postgres-writer postgres-reader timescale-writer \
|
||||
timescale-reader cli bootstrap mqtt provision certs invitations journal re
|
||||
TEST_API_SERVICES = journal auth bootstrap certs http invitations notifiers provision readers things users
|
||||
TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES))
|
||||
SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-writer timescale-reader
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
|
||||
CGO_ENABLED ?= 0
|
||||
@@ -18,7 +15,7 @@ USER_REPO ?= $(shell git remote get-url origin | sed -e 's/.*\/\([^/]*\)\/\([^/]
|
||||
empty:=
|
||||
space:= $(empty) $(empty)
|
||||
# Docker compose project name should follow this guidelines: https://docs.docker.com/compose/reference/#use--p-to-specify-a-project-name
|
||||
DOCKER_PROJECT ?= $(shell echo $(subst $(space),,$(USER_REPO)) | tr -c -s '[:alnum:][=-=]' '_' | tr '[:upper:]' '[:lower:]')
|
||||
DOCKER_PROJECT ?= test #$(shell echo $(subst $(space),,$(USER_REPO)) | tr -c -s '[:alnum:][=-=]' '_' | tr '[:upper:]' '[:lower:]')
|
||||
DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config
|
||||
DEFAULT_DOCKER_COMPOSE_COMMAND := up
|
||||
GRPC_MTLS_CERT_FILES_EXISTS = 0
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,833 +0,0 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Magistrala Auth Service
|
||||
description: |
|
||||
This is the Auth Server based on the OpenAPI 3.0 specification. It is the HTTP API for managing platform users. You can now help us improve the API whether it's by making changes to the definition itself or to the code.
|
||||
Some useful links:
|
||||
- [The Magistrala repository](https://github.com/absmach/magistrala)
|
||||
contact:
|
||||
email: info@abstractmachines.fr
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/main/LICENSE
|
||||
version: 0.14.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8189
|
||||
- url: https://localhost:8189
|
||||
|
||||
tags:
|
||||
- name: Keys
|
||||
description: Everything about your Keys.
|
||||
externalDocs:
|
||||
description: Find out more about keys
|
||||
url: https://docs.magistrala.abstractmachines.fr/
|
||||
|
||||
- name: Domains
|
||||
description: Everything about your Domains.
|
||||
externalDocs:
|
||||
description: Find out more about domains
|
||||
url: https://docs.magistrala.abstractmachines.fr/
|
||||
|
||||
- name: Health
|
||||
description: Service health check endpoint.
|
||||
externalDocs:
|
||||
description: Find out more about health check
|
||||
url: https://docs.magistrala.abstractmachines.fr/
|
||||
|
||||
|
||||
paths:
|
||||
/domains:
|
||||
post:
|
||||
tags:
|
||||
- Domains
|
||||
summary: Adds new domain
|
||||
description: |
|
||||
Adds new domain.
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/DomainCreateReq"
|
||||
responses:
|
||||
"201":
|
||||
$ref: "#/components/responses/DomainCreateRes"
|
||||
"400":
|
||||
description: Failed due to malformed JSON.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"409":
|
||||
description: Failed due to using an existing alias.
|
||||
"415":
|
||||
description: Missing or invalid content type.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
get:
|
||||
summary: Retrieves list of domains.
|
||||
description: |
|
||||
Retrieves list of domains that the user have access.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
- $ref: "#/components/parameters/Offset"
|
||||
- $ref: "#/components/parameters/Metadata"
|
||||
- $ref: "#/components/parameters/Status"
|
||||
- $ref: "#/components/parameters/DomainName"
|
||||
- $ref: "#/components/parameters/Permission"
|
||||
tags:
|
||||
- Domains
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/DomainsPageRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/domains/{domainID}:
|
||||
get:
|
||||
summary: Retrieves domain information
|
||||
description: |
|
||||
Retrieves a specific domain that is identified by the domain ID.
|
||||
tags:
|
||||
- Domains
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/DomainRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
patch:
|
||||
summary: Updates name, metadata, tags and alias of the domain.
|
||||
description: |
|
||||
Updates name, metadata, tags and alias of the domain.
|
||||
tags:
|
||||
- Domains
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/DomainUpdateReq"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/DomainRes"
|
||||
"400":
|
||||
description: Failed due to malformed JSON.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Unauthorized access to domain id.
|
||||
"404":
|
||||
description: Failed due to non existing domain.
|
||||
"415":
|
||||
description: Missing or invalid content type.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/domains/{domainID}/permissions:
|
||||
get:
|
||||
summary: Retrieves user permissions on domain.
|
||||
description: |
|
||||
Retrieves user permissions on domain that is identified by the domain ID.
|
||||
tags:
|
||||
- Domains
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/DomainPermissionRes"
|
||||
"400":
|
||||
description: Malformed entity specification.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Failed authorization over the domain.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
/domains/{domainID}/enable:
|
||||
post:
|
||||
summary: Enables a domain
|
||||
description: |
|
||||
Enables a specific domain that is identified by the domain ID.
|
||||
tags:
|
||||
- Domains
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Successfully enabled domain.
|
||||
"400":
|
||||
description: Failed due to malformed domain's ID.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Unauthorized access the domain ID.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/domains/{domainID}/disable:
|
||||
post:
|
||||
summary: Disable a domain
|
||||
description: |
|
||||
Disable a specific domain that is identified by the domain ID.
|
||||
tags:
|
||||
- Domains
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Successfully disabled domain.
|
||||
"400":
|
||||
description: Failed due to malformed domain's ID.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Unauthorized access the domain ID.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/domains/{domainID}/freeze:
|
||||
post:
|
||||
summary: Freeze a domain
|
||||
description: |
|
||||
Freeze a specific domain that is identified by the domain ID.
|
||||
tags:
|
||||
- Domains
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Successfully freezed domain.
|
||||
"400":
|
||||
description: Failed due to malformed domain's ID.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Unauthorized access the domain ID.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/domains/{domainID}/users/assign:
|
||||
post:
|
||||
summary: Assign users to domain
|
||||
description: |
|
||||
Assign users to domain that is identified by the domain ID.
|
||||
tags:
|
||||
- Domains
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/AssignUserReq"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Users successfully assigned to domain.
|
||||
"400":
|
||||
description: Failed due to malformed domain's ID.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Unauthorized access the domain ID.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"409":
|
||||
description: Conflict of data.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/domains/{domainID}/users/unassign:
|
||||
post:
|
||||
summary: Unassign user from domain
|
||||
description: |
|
||||
Unassign user from domain that is identified by the domain ID.
|
||||
tags:
|
||||
- Domains
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/UnassignUsersReq"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"204":
|
||||
description: Users successfully unassigned from domain.
|
||||
"400":
|
||||
description: Failed due to malformed domain's ID.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Unauthorized access the domain ID.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"409":
|
||||
description: Conflict of data.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
/keys:
|
||||
post:
|
||||
operationId: issueKey
|
||||
tags:
|
||||
- Keys
|
||||
summary: Issue API key
|
||||
description: |
|
||||
Generates a new API key. Thew new API key will
|
||||
be uniquely identified by its ID.
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/KeyRequest"
|
||||
responses:
|
||||
"201":
|
||||
description: Issued new key.
|
||||
"400":
|
||||
description: Failed due to malformed JSON.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"409":
|
||||
description: Failed due to using already existing ID.
|
||||
"415":
|
||||
description: Missing or invalid content type.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/keys/{keyID}:
|
||||
get:
|
||||
operationId: getKey
|
||||
summary: Gets API key details.
|
||||
description: |
|
||||
Gets API key details for the given key.
|
||||
tags:
|
||||
- Keys
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ApiKeyId"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/KeyRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
delete:
|
||||
operationId: revokeKey
|
||||
summary: Revoke API key
|
||||
description: |
|
||||
Revoke API key identified by the given ID.
|
||||
tags:
|
||||
- Keys
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ApiKeyId"
|
||||
responses:
|
||||
"204":
|
||||
description: Key revoked.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/users/{userID}/domains:
|
||||
get:
|
||||
tags:
|
||||
- Domains
|
||||
summary: Lists domains associated with a user.
|
||||
description: |
|
||||
Retrieves a list of domains associated with a user. Due to performance concerns, data
|
||||
is retrieved in subsets. The API must ensure that the entire
|
||||
dataset is consumed either by making subsequent requests, or by
|
||||
increasing the subset size of the initial request.
|
||||
parameters:
|
||||
- $ref: "users.yml#/components/parameters/UserID"
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
- $ref: "#/components/parameters/Offset"
|
||||
- $ref: "#/components/parameters/Metadata"
|
||||
- $ref: "#/components/parameters/Status"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/DomainsPageRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: |
|
||||
Missing or invalid access token provided.
|
||||
This endpoint is available only for administrators.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/health:
|
||||
get:
|
||||
summary: Retrieves service health check info.
|
||||
tags:
|
||||
- Health
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/HealthRes"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
DomainReqObj:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: domainName
|
||||
description: Domain name.
|
||||
tags:
|
||||
type: array
|
||||
minItems: 0
|
||||
items:
|
||||
type: string
|
||||
example: ["tag1", "tag2"]
|
||||
description: domain tags.
|
||||
metadata:
|
||||
type: object
|
||||
example: { "domain": "example.com" }
|
||||
description: Arbitrary, object-encoded domain's data.
|
||||
alias:
|
||||
type: string
|
||||
example: domain alias
|
||||
description: Domain alias.
|
||||
required:
|
||||
- name
|
||||
- alias
|
||||
Domain:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
description: Domain unique identifier.
|
||||
name:
|
||||
type: string
|
||||
example: domainName
|
||||
description: Domain name.
|
||||
tags:
|
||||
type: array
|
||||
minItems: 0
|
||||
items:
|
||||
type: string
|
||||
example: ["tag1", "tag2"]
|
||||
description: domain tags.
|
||||
metadata:
|
||||
type: object
|
||||
example: { "domain": "example.com" }
|
||||
description: Arbitrary, object-encoded domain's data.
|
||||
alias:
|
||||
type: string
|
||||
example: domain alias
|
||||
description: Domain alias.
|
||||
status:
|
||||
type: string
|
||||
description: Domain Status
|
||||
format: string
|
||||
example: enabled
|
||||
created_by:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "0d837f56-3f8a-4e2a-9359-6347d0fc9f06 "
|
||||
description: User ID of the user who created the domain.
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2019-11-26 13:31:52"
|
||||
description: Time when the domain was created.
|
||||
updated_by:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "80f66b77-ed74-4e74-9f88-6cce9a0a3049"
|
||||
description: User ID of the user who last updated the domain.
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2019-11-26 13:31:52"
|
||||
description: Time when the domain was last updated.
|
||||
xml:
|
||||
name: domain
|
||||
|
||||
DomainsPage:
|
||||
type: object
|
||||
properties:
|
||||
domains:
|
||||
type: array
|
||||
minItems: 0
|
||||
uniqueItems: true
|
||||
items:
|
||||
$ref: "#/components/schemas/Domain"
|
||||
total:
|
||||
type: integer
|
||||
example: 1
|
||||
description: Total number of items.
|
||||
offset:
|
||||
type: integer
|
||||
description: Number of items to skip during retrieval.
|
||||
limit:
|
||||
type: integer
|
||||
example: 10
|
||||
description: Maximum number of items to return in one page.
|
||||
required:
|
||||
- domains
|
||||
- total
|
||||
- offset
|
||||
DomainUpdate:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: domainName
|
||||
description: Domain name.
|
||||
tags:
|
||||
type: array
|
||||
minItems: 0
|
||||
items:
|
||||
type: string
|
||||
example: ["tag1", "tag2"]
|
||||
description: domain tags.
|
||||
metadata:
|
||||
type: object
|
||||
example: { "domain": "example.com" }
|
||||
description: Arbitrary, object-encoded thing's data.
|
||||
alias:
|
||||
type: string
|
||||
example: domain alias
|
||||
description: Domain alias.
|
||||
Permissions:
|
||||
type: object
|
||||
properties:
|
||||
permissions:
|
||||
type: array
|
||||
minItems: 0
|
||||
items:
|
||||
type: string
|
||||
description: Permissions
|
||||
|
||||
AssignUserDomainRelationReq:
|
||||
type: object
|
||||
properties:
|
||||
user_ids:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
description: Users IDs
|
||||
example:
|
||||
[
|
||||
"5dc1ce4b-7cc9-4f12-98a6-9d74cc4980bb",
|
||||
"c01ed106-e52d-4aa4-bed3-39f360177cfa",
|
||||
]
|
||||
relation:
|
||||
type: string
|
||||
enum: ["administrator", "editor", "contributor", "member", "guest"]
|
||||
example: "administrator"
|
||||
description: Policy relations.
|
||||
required:
|
||||
- user_ids
|
||||
- relation
|
||||
UnassignUserDomainRelationReq:
|
||||
type: object
|
||||
properties:
|
||||
user_id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
description: User unique identifier.
|
||||
required:
|
||||
- user_id
|
||||
Key:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "c5747f2f-2a7c-4fe1-b41a-51a5ae290945"
|
||||
description: API key unique identifier
|
||||
issuer_id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "9118de62-c680-46b7-ad0a-21748a52833a"
|
||||
description: In ID of the entity that issued the token.
|
||||
type:
|
||||
type: integer
|
||||
example: 0
|
||||
description: API key type. Keys of different type are processed differently.
|
||||
subject:
|
||||
type: string
|
||||
format: string
|
||||
example: "test@example.com"
|
||||
description: User's email or service identifier of API key subject.
|
||||
issued_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2019-11-26 13:31:52"
|
||||
description: Time when the key is generated.
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2019-11-26 13:31:52"
|
||||
description: Time when the Key expires. If this field is missing,
|
||||
that means that Key is valid indefinitely.
|
||||
|
||||
parameters:
|
||||
DomainID:
|
||||
name: domainID
|
||||
description: Unique domain identifier.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
Status:
|
||||
name: status
|
||||
description: Domain status.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
default: enabled
|
||||
required: false
|
||||
example: enabled
|
||||
DomainName:
|
||||
name: name
|
||||
description: Domain's name.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: "domainName"
|
||||
Permission:
|
||||
name: permission
|
||||
description: permission.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: "edit"
|
||||
ApiKeyId:
|
||||
name: keyID
|
||||
description: API Key ID.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
Limit:
|
||||
name: limit
|
||||
description: Size of the subset to retrieve.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
required: false
|
||||
Offset:
|
||||
name: offset
|
||||
description: Number of items to skip during retrieval.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
minimum: 0
|
||||
required: false
|
||||
Metadata:
|
||||
name: metadata
|
||||
description: Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json.
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
Type:
|
||||
name: type
|
||||
description: The type of the API Key.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
minimum: 0
|
||||
required: false
|
||||
Subject:
|
||||
name: subject
|
||||
description: The subject of an API Key
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
|
||||
requestBodies:
|
||||
DomainCreateReq:
|
||||
description: JSON-formatted document describing the new domain to be registered
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DomainReqObj"
|
||||
DomainUpdateReq:
|
||||
description: JSON-formated document describing the name, alias, tags, and metadata of the domain to be updated
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DomainUpdate"
|
||||
AssignUserReq:
|
||||
description: JSON-formated document describing the policy related to assigning users to a domain
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AssignUserDomainRelationReq"
|
||||
|
||||
UnassignUsersReq:
|
||||
description: JSON-formated document describing the policy related to unassigning user from a domain
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UnassignUserDomainRelationReq"
|
||||
|
||||
KeyRequest:
|
||||
description: JSON-formatted document describing key request.
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: integer
|
||||
example: 0
|
||||
description: API key type. Keys of different type are processed differently.
|
||||
duration:
|
||||
type: number
|
||||
format: integer
|
||||
example: 23456
|
||||
description: Number of seconds issued token is valid for.
|
||||
|
||||
responses:
|
||||
ServiceError:
|
||||
description: Unexpected server-side error occurred.
|
||||
|
||||
DomainCreateRes:
|
||||
description: Create new domain.
|
||||
headers:
|
||||
Location:
|
||||
schema:
|
||||
type: string
|
||||
format: url
|
||||
description: Registered domain relative URL in the format `/domains/<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: []
|
||||
@@ -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: []
|
||||
@@ -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: []
|
||||
@@ -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: []
|
||||
@@ -1,344 +0,0 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Magistrala Journal Log Service
|
||||
description: |
|
||||
This is the Journal Log Server based on the OpenAPI 3.0 specification. It is the HTTP API for viewing journal log history. You can now help us improve the API whether it's by making changes to the definition itself or to the code.
|
||||
Some useful links:
|
||||
- [The Magistrala repository](https://github.com/absmach/magistrala)
|
||||
contact:
|
||||
email: info@mainflux.com
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/master/LICENSE
|
||||
version: 0.14.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9021
|
||||
- url: https://localhost:9021
|
||||
|
||||
tags:
|
||||
- name: journal-log
|
||||
description: Everything about your Journal Log
|
||||
externalDocs:
|
||||
description: Find out more about Journal Log
|
||||
url: http://docs.mainflux.io/
|
||||
|
||||
paths:
|
||||
/journal/user/{userID}:
|
||||
get:
|
||||
tags:
|
||||
- journal-log
|
||||
summary: List user journal log
|
||||
description: |
|
||||
Retrieves a list of journal. Due to performance concerns, data
|
||||
is retrieved in subsets. The API must ensure that the entire
|
||||
dataset is consumed either by making subsequent requests, or by
|
||||
increasing the subset size of the initial request.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/user_id"
|
||||
- $ref: "#/components/parameters/offset"
|
||||
- $ref: "#/components/parameters/limit"
|
||||
- $ref: "#/components/parameters/operation"
|
||||
- $ref: "#/components/parameters/with_attributes"
|
||||
- $ref: "#/components/parameters/with_metadata"
|
||||
- $ref: "#/components/parameters/from"
|
||||
- $ref: "#/components/parameters/to"
|
||||
- $ref: "#/components/parameters/dir"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/JournalsPageRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Failed to perform authorization over the entity.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/{domainID}/journal/{entityType}/{id}:
|
||||
get:
|
||||
tags:
|
||||
- journal-log
|
||||
summary: List entity journal log
|
||||
description: |
|
||||
Retrieves a list of journal. Due to performance concerns, data
|
||||
is retrieved in subsets. The API must ensure that the entire
|
||||
dataset is consumed either by making subsequent requests, or by
|
||||
increasing the subset size of the initial request.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/domain_id"
|
||||
- $ref: "#/components/parameters/entity_type"
|
||||
- $ref: "#/components/parameters/id"
|
||||
- $ref: "#/components/parameters/offset"
|
||||
- $ref: "#/components/parameters/limit"
|
||||
- $ref: "#/components/parameters/operation"
|
||||
- $ref: "#/components/parameters/with_attributes"
|
||||
- $ref: "#/components/parameters/with_metadata"
|
||||
- $ref: "#/components/parameters/from"
|
||||
- $ref: "#/components/parameters/to"
|
||||
- $ref: "#/components/parameters/dir"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/JournalsPageRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Failed to perform authorization over the entity.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/health:
|
||||
get:
|
||||
summary: Retrieves service health check info.
|
||||
tags:
|
||||
- health
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/HealthRes"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Journal:
|
||||
type: object
|
||||
properties:
|
||||
operation:
|
||||
type: string
|
||||
example: user.create
|
||||
description: Journal operation.
|
||||
occurred_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2024-01-11T12:05:07.449053Z"
|
||||
description: Time when the journal occurred.
|
||||
attributes:
|
||||
type: object
|
||||
description: Journal attributes.
|
||||
example:
|
||||
{
|
||||
"created_at": "2024-06-12T11:34:32.991591Z",
|
||||
"id": "29d425c8-542b-4614-8a4d-a5951945d720",
|
||||
"identity": "Gawne-Havlicek@email.com",
|
||||
"name": "Newgard-Frisina",
|
||||
"status": "enabled",
|
||||
"updated_at": "2024-06-12T11:34:33.116795Z",
|
||||
"updated_by": "ad228f20-4741-47c5-bef7-d871b541c019",
|
||||
}
|
||||
metadata:
|
||||
type: object
|
||||
description: Journal payload.
|
||||
example: { "Update": "Calvo-Felkins" }
|
||||
xml:
|
||||
name: journal
|
||||
|
||||
JournalPage:
|
||||
type: object
|
||||
properties:
|
||||
journals:
|
||||
type: array
|
||||
minItems: 0
|
||||
uniqueItems: true
|
||||
items:
|
||||
$ref: "#/components/schemas/Journal"
|
||||
total:
|
||||
type: integer
|
||||
example: 1
|
||||
description: Total number of items.
|
||||
offset:
|
||||
type: integer
|
||||
description: Number of items to skip during retrieval.
|
||||
limit:
|
||||
type: integer
|
||||
example: 10
|
||||
description: Maximum number of items to return in one page.
|
||||
required:
|
||||
- journals
|
||||
- total
|
||||
- offset
|
||||
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Error message
|
||||
example: { "error": "malformed entity specification" }
|
||||
|
||||
parameters:
|
||||
domain_id:
|
||||
name: domainID
|
||||
description: Unique identifier for a domain.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
entity_type:
|
||||
name: entityType
|
||||
description: Type of entity, e.g. user, group, thing, etc.entityType
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- group
|
||||
- thing
|
||||
- channel
|
||||
required: true
|
||||
example: group
|
||||
|
||||
user_id:
|
||||
name: userID
|
||||
description: Unique identifier for a user.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
id:
|
||||
name: id
|
||||
description: Unique identifier for an entity, e.g. group, channel or thing. Used together with entity_type.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
offset:
|
||||
name: offset
|
||||
description: Number of items to skip during retrieval.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
minimum: 0
|
||||
required: false
|
||||
example: "0"
|
||||
|
||||
limit:
|
||||
name: limit
|
||||
description: Size of the subset to retrieve.
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
maximum: 10
|
||||
minimum: 1
|
||||
required: false
|
||||
example: "10"
|
||||
|
||||
operation:
|
||||
name: operation
|
||||
description: Journal operation.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: user.create
|
||||
|
||||
with_attributes:
|
||||
name: with_attributes
|
||||
description: Include journal attributes.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
required: false
|
||||
example: true
|
||||
|
||||
with_metadata:
|
||||
name: with_metadata
|
||||
description: Include journal metadata.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
required: false
|
||||
example: true
|
||||
|
||||
from:
|
||||
name: from
|
||||
description: Start date in unix time.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: int64
|
||||
required: false
|
||||
example: 1966777289
|
||||
|
||||
to:
|
||||
name: to
|
||||
description: End date in unix time.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: int64
|
||||
required: false
|
||||
example: 1966777289
|
||||
|
||||
dir:
|
||||
name: dir
|
||||
description: Sort direction.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
required: false
|
||||
example: desc
|
||||
|
||||
responses:
|
||||
JournalsPageRes:
|
||||
description: Data retrieved.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/JournalPage"
|
||||
|
||||
HealthRes:
|
||||
description: Service Health Check.
|
||||
content:
|
||||
application/health+json:
|
||||
schema:
|
||||
$ref: "./schemas/HealthInfo.yml"
|
||||
|
||||
ServiceError:
|
||||
description: Unexpected server-side error occurred.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
* User access: "Authorization: Bearer <user_access_token>"
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
@@ -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: []
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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: []
|
||||
File diff suppressed because it is too large
Load Diff
-993
@@ -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
|
||||
}
|
||||
-98
@@ -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;
|
||||
}
|
||||
-159
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package domains
|
||||
|
||||
type deleteUserRes struct {
|
||||
deleted bool
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package http
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
-209
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/absmach/magistrala/auth"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
// errInvalidIssuer is returned when the issuer is not magistrala.auth.
|
||||
errInvalidIssuer = errors.New("invalid token issuer value")
|
||||
// errInvalidType is returned when there is no type field.
|
||||
errInvalidType = errors.New("invalid token type")
|
||||
// errJWTExpiryKey is used to check if the token is expired.
|
||||
errJWTExpiryKey = errors.New(`"exp" not satisfied`)
|
||||
// ErrSignJWT indicates an error in signing jwt token.
|
||||
ErrSignJWT = errors.New("failed to sign jwt token")
|
||||
// ErrValidateJWTToken indicates a failure to validate JWT token.
|
||||
ErrValidateJWTToken = errors.New("failed to validate jwt token")
|
||||
// ErrJSONHandle indicates an error in handling JSON.
|
||||
ErrJSONHandle = errors.New("failed to perform operation JSON")
|
||||
)
|
||||
|
||||
const (
|
||||
issuerName = "magistrala.auth"
|
||||
tokenType = "type"
|
||||
userField = "user"
|
||||
oauthProviderField = "oauth_provider"
|
||||
oauthAccessTokenField = "access_token"
|
||||
oauthRefreshTokenField = "refresh_token"
|
||||
)
|
||||
|
||||
type tokenizer struct {
|
||||
secret []byte
|
||||
}
|
||||
|
||||
var _ auth.Tokenizer = (*tokenizer)(nil)
|
||||
|
||||
// NewRepository instantiates an implementation of Token repository.
|
||||
func New(secret []byte) auth.Tokenizer {
|
||||
return &tokenizer{
|
||||
secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
func (tok *tokenizer) Issue(key auth.Key) (string, error) {
|
||||
builder := jwt.NewBuilder()
|
||||
builder.
|
||||
Issuer(issuerName).
|
||||
IssuedAt(key.IssuedAt).
|
||||
Claim(tokenType, key.Type).
|
||||
Expiration(key.ExpiresAt)
|
||||
builder.Claim(userField, key.User)
|
||||
if key.Subject != "" {
|
||||
builder.Subject(key.Subject)
|
||||
}
|
||||
if key.ID != "" {
|
||||
builder.JwtID(key.ID)
|
||||
}
|
||||
tkn, err := builder.Build()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, tok.secret))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(ErrSignJWT, err)
|
||||
}
|
||||
return string(signedTkn), nil
|
||||
}
|
||||
|
||||
func (tok *tokenizer) Parse(token string) (auth.Key, error) {
|
||||
tkn, err := tok.validateToken(token)
|
||||
if err != nil {
|
||||
return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
|
||||
key, err := toKey(tkn)
|
||||
if err != nil {
|
||||
return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (tok *tokenizer) validateToken(token string) (jwt.Token, error) {
|
||||
tkn, err := jwt.Parse(
|
||||
[]byte(token),
|
||||
jwt.WithValidate(true),
|
||||
jwt.WithKey(jwa.HS512, tok.secret),
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Contains(err, errJWTExpiryKey) {
|
||||
return nil, auth.ErrExpiry
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) jwt.ValidationError {
|
||||
if t.Issuer() != issuerName {
|
||||
return jwt.NewValidationError(errInvalidIssuer)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err := jwt.Validate(tkn, jwt.WithValidator(validator)); err != nil {
|
||||
return nil, errors.Wrap(ErrValidateJWTToken, err)
|
||||
}
|
||||
|
||||
return tkn, nil
|
||||
}
|
||||
|
||||
func toKey(tkn jwt.Token) (auth.Key, error) {
|
||||
data, err := json.Marshal(tkn.PrivateClaims())
|
||||
if err != nil {
|
||||
return auth.Key{}, errors.Wrap(ErrJSONHandle, err)
|
||||
}
|
||||
var key auth.Key
|
||||
if err := json.Unmarshal(data, &key); err != nil {
|
||||
return auth.Key{}, errors.Wrap(ErrJSONHandle, err)
|
||||
}
|
||||
|
||||
tType, ok := tkn.Get(tokenType)
|
||||
if !ok {
|
||||
return auth.Key{}, errInvalidType
|
||||
}
|
||||
ktype, err := strconv.ParseInt(fmt.Sprintf("%v", tType), 10, 64)
|
||||
if err != nil {
|
||||
return auth.Key{}, err
|
||||
}
|
||||
|
||||
key.ID = tkn.JwtID()
|
||||
key.Type = auth.KeyType(ktype)
|
||||
key.Issuer = tkn.Issuer()
|
||||
key.Subject = tkn.Subject()
|
||||
key.IssuedAt = tkn.IssuedAt()
|
||||
key.ExpiresAt = tkn.Expiration()
|
||||
|
||||
return key, nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
-924
@@ -1,924 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/absmach/magistrala/pkg/policies"
|
||||
)
|
||||
|
||||
const (
|
||||
recoveryDuration = 5 * time.Minute
|
||||
defLimit = 100
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrExpiry indicates that the token is expired.
|
||||
ErrExpiry = errors.New("token is expired")
|
||||
|
||||
errIssueUser = errors.New("failed to issue new login key")
|
||||
errIssueTmp = errors.New("failed to issue new temporary key")
|
||||
errRevoke = errors.New("failed to remove key")
|
||||
errRetrieve = errors.New("failed to retrieve key data")
|
||||
errIdentify = errors.New("failed to validate token")
|
||||
errPlatform = errors.New("invalid platform id")
|
||||
errCreateDomainPolicy = errors.New("failed to create domain policy")
|
||||
errAddPolicies = errors.New("failed to add policies")
|
||||
errRemovePolicies = errors.New("failed to remove the policies")
|
||||
errRollbackPolicy = errors.New("failed to rollback policy")
|
||||
errRemoveLocalPolicy = errors.New("failed to remove from local policy copy")
|
||||
errRemovePolicyEngine = errors.New("failed to remove from policy engine")
|
||||
)
|
||||
|
||||
// Authz represents a authorization service. It exposes
|
||||
// functionalities through `auth` to perform authorization.
|
||||
//
|
||||
//go:generate mockery --name Authz --output=./mocks --filename authz.go --quiet --note "Copyright (c) Abstract Machines"
|
||||
type Authz interface {
|
||||
// Authorize checks authorization of the given `subject`. Basically,
|
||||
// Authorize verifies that Is `subject` allowed to `relation` on
|
||||
// `object`. Authorize returns a non-nil error if the subject has
|
||||
// no relation on the object (which simply means the operation is
|
||||
// denied).
|
||||
Authorize(ctx context.Context, pr policies.Policy) error
|
||||
}
|
||||
|
||||
// Authn specifies an API that must be fulfilled by the domain service
|
||||
// implementation, and all of its decorators (e.g. logging & metrics).
|
||||
// Token is a string value of the actual Key and is used to authenticate
|
||||
// an Auth service request.
|
||||
type Authn interface {
|
||||
// Issue issues a new Key, returning its token value alongside.
|
||||
Issue(ctx context.Context, token string, key Key) (Token, error)
|
||||
|
||||
// Revoke removes the Key with the provided id that is
|
||||
// issued by the user identified by the provided key.
|
||||
Revoke(ctx context.Context, token, id string) error
|
||||
|
||||
// RetrieveKey retrieves data for the Key identified by the provided
|
||||
// ID, that is issued by the user identified by the provided key.
|
||||
RetrieveKey(ctx context.Context, token, id string) (Key, error)
|
||||
|
||||
// Identify validates token token. If token is valid, content
|
||||
// is returned. If token is invalid, or invocation failed for some
|
||||
// other reason, non-nil error value is returned in response.
|
||||
Identify(ctx context.Context, token string) (Key, error)
|
||||
}
|
||||
|
||||
// Service specifies an API that must be fulfilled by the domain service
|
||||
// implementation, and all of its decorators (e.g. logging & metrics).
|
||||
// Token is a string value of the actual Key and is used to authenticate
|
||||
// an Auth service request.
|
||||
|
||||
//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines"
|
||||
type Service interface {
|
||||
Authn
|
||||
Authz
|
||||
Domains
|
||||
}
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
type service struct {
|
||||
keys KeyRepository
|
||||
domains DomainsRepository
|
||||
idProvider magistrala.IDProvider
|
||||
evaluator policies.Evaluator
|
||||
policysvc policies.Service
|
||||
tokenizer Tokenizer
|
||||
loginDuration time.Duration
|
||||
refreshDuration time.Duration
|
||||
invitationDuration time.Duration
|
||||
}
|
||||
|
||||
// New instantiates the auth service implementation.
|
||||
func New(keys KeyRepository, domains DomainsRepository, idp magistrala.IDProvider, tokenizer Tokenizer, policyEvaluator policies.Evaluator, policyService policies.Service, loginDuration, refreshDuration, invitationDuration time.Duration) Service {
|
||||
return &service{
|
||||
tokenizer: tokenizer,
|
||||
domains: domains,
|
||||
keys: keys,
|
||||
idProvider: idp,
|
||||
evaluator: policyEvaluator,
|
||||
policysvc: policyService,
|
||||
loginDuration: loginDuration,
|
||||
refreshDuration: refreshDuration,
|
||||
invitationDuration: invitationDuration,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) Issue(ctx context.Context, token string, key Key) (Token, error) {
|
||||
key.IssuedAt = time.Now().UTC()
|
||||
switch key.Type {
|
||||
case APIKey:
|
||||
return svc.userKey(ctx, token, key)
|
||||
case RefreshKey:
|
||||
return svc.refreshKey(ctx, token, key)
|
||||
case RecoveryKey:
|
||||
return svc.tmpKey(recoveryDuration, key)
|
||||
case InvitationKey:
|
||||
return svc.invitationKey(ctx, key)
|
||||
default:
|
||||
return svc.accessKey(ctx, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) Revoke(ctx context.Context, token, id string) error {
|
||||
issuerID, _, err := svc.authenticate(token)
|
||||
if err != nil {
|
||||
return errors.Wrap(errRevoke, err)
|
||||
}
|
||||
if err := svc.keys.Remove(ctx, issuerID, id); err != nil {
|
||||
return errors.Wrap(errRevoke, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) RetrieveKey(ctx context.Context, token, id string) (Key, error) {
|
||||
issuerID, _, err := svc.authenticate(token)
|
||||
if err != nil {
|
||||
return Key{}, errors.Wrap(errRetrieve, err)
|
||||
}
|
||||
|
||||
key, err := svc.keys.Retrieve(ctx, issuerID, id)
|
||||
if err != nil {
|
||||
return Key{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (svc service) Identify(ctx context.Context, token string) (Key, error) {
|
||||
key, err := svc.tokenizer.Parse(token)
|
||||
if errors.Contains(err, ErrExpiry) {
|
||||
err = svc.keys.Remove(ctx, key.Issuer, key.ID)
|
||||
return Key{}, errors.Wrap(svcerr.ErrAuthentication, errors.Wrap(ErrKeyExpired, err))
|
||||
}
|
||||
if err != nil {
|
||||
return Key{}, errors.Wrap(svcerr.ErrAuthentication, errors.Wrap(errIdentify, err))
|
||||
}
|
||||
|
||||
switch key.Type {
|
||||
case RecoveryKey, AccessKey, InvitationKey, RefreshKey:
|
||||
return key, nil
|
||||
case APIKey:
|
||||
_, err := svc.keys.Retrieve(ctx, key.Issuer, key.ID)
|
||||
if err != nil {
|
||||
return Key{}, svcerr.ErrAuthentication
|
||||
}
|
||||
return key, nil
|
||||
default:
|
||||
return Key{}, svcerr.ErrAuthentication
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) Authorize(ctx context.Context, pr policies.Policy) error {
|
||||
if err := svc.PolicyValidation(pr); err != nil {
|
||||
return errors.Wrap(svcerr.ErrMalformedEntity, err)
|
||||
}
|
||||
if pr.SubjectKind == policies.TokenKind {
|
||||
key, err := svc.Identify(ctx, pr.Subject)
|
||||
if err != nil {
|
||||
return errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
if key.Subject == "" {
|
||||
if pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ThingType || pr.ObjectType == policies.DomainType {
|
||||
return svcerr.ErrDomainAuthorization
|
||||
}
|
||||
return svcerr.ErrAuthentication
|
||||
}
|
||||
pr.Subject = key.Subject
|
||||
pr.Domain = key.Domain
|
||||
}
|
||||
if err := svc.checkPolicy(ctx, pr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) checkPolicy(ctx context.Context, pr policies.Policy) error {
|
||||
// Domain status is required for if user sent authorization request on things, channels, groups and domains
|
||||
if pr.SubjectType == policies.UserType && (pr.ObjectType == policies.GroupType || pr.ObjectType == policies.ThingType || pr.ObjectType == policies.DomainType) {
|
||||
domainID := pr.Domain
|
||||
if domainID == "" {
|
||||
if pr.ObjectType != policies.DomainType {
|
||||
return svcerr.ErrDomainAuthorization
|
||||
}
|
||||
domainID = pr.Object
|
||||
}
|
||||
if err := svc.checkDomain(ctx, pr.SubjectType, pr.Subject, domainID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := svc.evaluator.CheckPolicy(ctx, pr); err != nil {
|
||||
return errors.Wrap(svcerr.ErrAuthorization, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) checkDomain(ctx context.Context, subjectType, subject, domainID string) error {
|
||||
if err := svc.evaluator.CheckPolicy(ctx, policies.Policy{
|
||||
Subject: subject,
|
||||
SubjectType: subjectType,
|
||||
Permission: policies.MembershipPermission,
|
||||
Object: domainID,
|
||||
ObjectType: policies.DomainType,
|
||||
}); err != nil {
|
||||
return svcerr.ErrDomainAuthorization
|
||||
}
|
||||
|
||||
d, err := svc.domains.RetrieveByID(ctx, domainID)
|
||||
if err != nil {
|
||||
return errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
switch d.Status {
|
||||
case EnabledStatus:
|
||||
case DisabledStatus:
|
||||
if err := svc.evaluator.CheckPolicy(ctx, policies.Policy{
|
||||
Subject: subject,
|
||||
SubjectType: subjectType,
|
||||
Permission: policies.AdminPermission,
|
||||
Object: domainID,
|
||||
ObjectType: policies.DomainType,
|
||||
}); err != nil {
|
||||
return svcerr.ErrDomainAuthorization
|
||||
}
|
||||
case FreezeStatus:
|
||||
if err := svc.evaluator.CheckPolicy(ctx, policies.Policy{
|
||||
Subject: subject,
|
||||
SubjectType: subjectType,
|
||||
Permission: policies.AdminPermission,
|
||||
Object: policies.MagistralaObject,
|
||||
ObjectType: policies.PlatformType,
|
||||
}); err != nil {
|
||||
return svcerr.ErrDomainAuthorization
|
||||
}
|
||||
default:
|
||||
return svcerr.ErrDomainAuthorization
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) PolicyValidation(pr policies.Policy) error {
|
||||
if pr.ObjectType == policies.PlatformType && pr.Object != policies.MagistralaObject {
|
||||
return errPlatform
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) tmpKey(duration time.Duration, key Key) (Token, error) {
|
||||
key.ExpiresAt = time.Now().Add(duration)
|
||||
value, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueTmp, err)
|
||||
}
|
||||
|
||||
return Token{AccessToken: value}, nil
|
||||
}
|
||||
|
||||
func (svc service) accessKey(ctx context.Context, key Key) (Token, error) {
|
||||
var err error
|
||||
key.Type = AccessKey
|
||||
key.ExpiresAt = time.Now().Add(svc.loginDuration)
|
||||
|
||||
key.Subject, err = svc.checkUserDomain(ctx, key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(svcerr.ErrAuthorization, err)
|
||||
}
|
||||
|
||||
access, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueTmp, err)
|
||||
}
|
||||
|
||||
key.ExpiresAt = time.Now().Add(svc.refreshDuration)
|
||||
key.Type = RefreshKey
|
||||
refresh, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueTmp, err)
|
||||
}
|
||||
|
||||
return Token{AccessToken: access, RefreshToken: refresh}, nil
|
||||
}
|
||||
|
||||
func (svc service) invitationKey(ctx context.Context, key Key) (Token, error) {
|
||||
var err error
|
||||
key.Type = InvitationKey
|
||||
key.ExpiresAt = time.Now().Add(svc.invitationDuration)
|
||||
|
||||
key.Subject, err = svc.checkUserDomain(ctx, key)
|
||||
if err != nil {
|
||||
return Token{}, err
|
||||
}
|
||||
|
||||
access, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueTmp, err)
|
||||
}
|
||||
|
||||
return Token{AccessToken: access}, nil
|
||||
}
|
||||
|
||||
func (svc service) refreshKey(ctx context.Context, token string, key Key) (Token, error) {
|
||||
k, err := svc.tokenizer.Parse(token)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errRetrieve, err)
|
||||
}
|
||||
if k.Type != RefreshKey {
|
||||
return Token{}, errIssueUser
|
||||
}
|
||||
key.ID = k.ID
|
||||
if key.Domain == "" {
|
||||
key.Domain = k.Domain
|
||||
}
|
||||
key.User = k.User
|
||||
key.Type = AccessKey
|
||||
|
||||
key.Subject, err = svc.checkUserDomain(ctx, key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(svcerr.ErrAuthorization, err)
|
||||
}
|
||||
|
||||
key.ExpiresAt = time.Now().Add(svc.loginDuration)
|
||||
access, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueTmp, err)
|
||||
}
|
||||
|
||||
key.ExpiresAt = time.Now().Add(svc.refreshDuration)
|
||||
key.Type = RefreshKey
|
||||
refresh, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueTmp, err)
|
||||
}
|
||||
|
||||
return Token{AccessToken: access, RefreshToken: refresh}, nil
|
||||
}
|
||||
|
||||
func (svc service) checkUserDomain(ctx context.Context, key Key) (subject string, err error) {
|
||||
if key.Domain != "" {
|
||||
// Check user is platform admin.
|
||||
if err = svc.Authorize(ctx, policies.Policy{
|
||||
Subject: key.User,
|
||||
SubjectType: policies.UserType,
|
||||
Permission: policies.AdminPermission,
|
||||
Object: policies.MagistralaObject,
|
||||
ObjectType: policies.PlatformType,
|
||||
}); err == nil {
|
||||
return key.User, nil
|
||||
}
|
||||
// Check user is domain member.
|
||||
domainUserSubject := EncodeDomainUserID(key.Domain, key.User)
|
||||
if err = svc.Authorize(ctx, policies.Policy{
|
||||
Subject: domainUserSubject,
|
||||
SubjectType: policies.UserType,
|
||||
Permission: policies.MembershipPermission,
|
||||
Object: key.Domain,
|
||||
ObjectType: policies.DomainType,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return domainUserSubject, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (svc service) userKey(ctx context.Context, token string, key Key) (Token, error) {
|
||||
id, sub, err := svc.authenticate(token)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueUser, err)
|
||||
}
|
||||
|
||||
key.Issuer = id
|
||||
if key.Subject == "" {
|
||||
key.Subject = sub
|
||||
}
|
||||
|
||||
keyID, err := svc.idProvider.ID()
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueUser, err)
|
||||
}
|
||||
key.ID = keyID
|
||||
|
||||
if _, err := svc.keys.Save(ctx, key); err != nil {
|
||||
return Token{}, errors.Wrap(errIssueUser, err)
|
||||
}
|
||||
|
||||
tkn, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Token{}, errors.Wrap(errIssueUser, err)
|
||||
}
|
||||
|
||||
return Token{AccessToken: tkn}, nil
|
||||
}
|
||||
|
||||
func (svc service) authenticate(token string) (string, string, error) {
|
||||
key, err := svc.tokenizer.Parse(token)
|
||||
if err != nil {
|
||||
return "", "", errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
// Only login key token is valid for login.
|
||||
if key.Type != AccessKey || key.Issuer == "" {
|
||||
return "", "", svcerr.ErrAuthentication
|
||||
}
|
||||
|
||||
return key.Issuer, key.Subject, nil
|
||||
}
|
||||
|
||||
// Switch the relative permission for the relation.
|
||||
func SwitchToPermission(relation string) string {
|
||||
switch relation {
|
||||
case policies.AdministratorRelation:
|
||||
return policies.AdminPermission
|
||||
case policies.EditorRelation:
|
||||
return policies.EditPermission
|
||||
case policies.ContributorRelation:
|
||||
return policies.ViewPermission
|
||||
case policies.MemberRelation:
|
||||
return policies.MembershipPermission
|
||||
case policies.GuestRelation:
|
||||
return policies.ViewPermission
|
||||
default:
|
||||
return relation
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) CreateDomain(ctx context.Context, token string, d Domain) (do Domain, err error) {
|
||||
key, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
d.CreatedBy = key.User
|
||||
|
||||
domainID, err := svc.idProvider.ID()
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
d.ID = domainID
|
||||
|
||||
if d.Status != DisabledStatus && d.Status != EnabledStatus {
|
||||
return Domain{}, svcerr.ErrInvalidStatus
|
||||
}
|
||||
|
||||
d.CreatedAt = time.Now()
|
||||
|
||||
if err := svc.createDomainPolicy(ctx, key.User, domainID, policies.AdministratorRelation); err != nil {
|
||||
return Domain{}, errors.Wrap(errCreateDomainPolicy, err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if errRollBack := svc.createDomainPolicyRollback(ctx, key.User, domainID, policies.AdministratorRelation); errRollBack != nil {
|
||||
err = errors.Wrap(err, errors.Wrap(errRollbackPolicy, errRollBack))
|
||||
}
|
||||
}
|
||||
}()
|
||||
dom, err := svc.domains.Save(ctx, d)
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
return dom, nil
|
||||
}
|
||||
|
||||
func (svc service) RetrieveDomain(ctx context.Context, token, id string) (Domain, error) {
|
||||
res, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
domain, err := svc.domains.RetrieveByID(ctx, id)
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
if err := svc.checkSuperAdmin(ctx, res.User); err != nil {
|
||||
if err = svc.Authorize(ctx, policies.Policy{
|
||||
Subject: EncodeDomainUserID(id, res.User),
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Object: id,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return Domain{ID: domain.ID, Name: domain.Name, Alias: domain.Alias}, nil
|
||||
}
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
func (svc service) RetrieveDomainPermissions(ctx context.Context, token, id string) (policies.Permissions, error) {
|
||||
res, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
subject := res.User
|
||||
if err := svc.checkSuperAdmin(ctx, res.User); err != nil {
|
||||
domainUserSubject := EncodeDomainUserID(id, res.User)
|
||||
if err := svc.Authorize(ctx, policies.Policy{
|
||||
Subject: domainUserSubject,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Object: id,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
subject = domainUserSubject
|
||||
}
|
||||
|
||||
lp, err := svc.policysvc.ListPermissions(ctx, policies.Policy{
|
||||
SubjectType: policies.UserType,
|
||||
Subject: subject,
|
||||
Object: id,
|
||||
ObjectType: policies.DomainType,
|
||||
}, []string{policies.AdminPermission, policies.EditPermission, policies.ViewPermission, policies.MembershipPermission, policies.CreatePermission})
|
||||
if err != nil {
|
||||
return []string{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
return lp, nil
|
||||
}
|
||||
|
||||
func (svc service) UpdateDomain(ctx context.Context, token, id string, d DomainReq) (Domain, error) {
|
||||
key, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return Domain{}, err
|
||||
}
|
||||
if err := svc.checkSuperAdmin(ctx, key.User); err != nil {
|
||||
if err := svc.Authorize(ctx, policies.Policy{
|
||||
Subject: EncodeDomainUserID(id, key.User),
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Object: id,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.EditPermission,
|
||||
}); err != nil {
|
||||
return Domain{}, err
|
||||
}
|
||||
}
|
||||
|
||||
dom, err := svc.domains.Update(ctx, id, key.User, d)
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
return dom, nil
|
||||
}
|
||||
|
||||
func (svc service) ChangeDomainStatus(ctx context.Context, token, id string, d DomainReq) (Domain, error) {
|
||||
key, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
if err := svc.checkSuperAdmin(ctx, key.User); err != nil {
|
||||
if err := svc.Authorize(ctx, policies.Policy{
|
||||
Subject: EncodeDomainUserID(id, key.User),
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Object: id,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.AdminPermission,
|
||||
}); err != nil {
|
||||
return Domain{}, err
|
||||
}
|
||||
}
|
||||
|
||||
dom, err := svc.domains.Update(ctx, id, key.User, d)
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
return dom, nil
|
||||
}
|
||||
|
||||
func (svc service) ListDomains(ctx context.Context, token string, p Page) (DomainsPage, error) {
|
||||
key, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return DomainsPage{}, errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
p.SubjectID = key.User
|
||||
if err := svc.checkSuperAdmin(ctx, key.User); err == nil {
|
||||
p.SubjectID = ""
|
||||
}
|
||||
dp, err := svc.domains.ListDomains(ctx, p)
|
||||
if err != nil {
|
||||
return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
if p.SubjectID == "" {
|
||||
for i := range dp.Domains {
|
||||
dp.Domains[i].Permission = policies.AdministratorRelation
|
||||
}
|
||||
}
|
||||
return dp, nil
|
||||
}
|
||||
|
||||
func (svc service) AssignUsers(ctx context.Context, token, id string, userIds []string, relation string) error {
|
||||
res, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
|
||||
if err := svc.checkSuperAdmin(ctx, res.User); err != nil {
|
||||
domainUserID := EncodeDomainUserID(id, res.User)
|
||||
if err := svc.Authorize(ctx, policies.Policy{
|
||||
Subject: domainUserID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Object: id,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.SharePermission,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := svc.Authorize(ctx, policies.Policy{
|
||||
Subject: domainUserID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Object: id,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: SwitchToPermission(relation),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, userID := range userIds {
|
||||
if err := svc.Authorize(ctx, policies.Policy{
|
||||
Subject: userID,
|
||||
SubjectType: policies.UserType,
|
||||
Permission: policies.MembershipPermission,
|
||||
Object: policies.MagistralaObject,
|
||||
ObjectType: policies.PlatformType,
|
||||
}); err != nil {
|
||||
return errors.Wrap(svcerr.ErrMalformedEntity, fmt.Errorf("invalid user id : %s ", userID))
|
||||
}
|
||||
}
|
||||
|
||||
return svc.addDomainPolicies(ctx, id, relation, userIds...)
|
||||
}
|
||||
|
||||
func (svc service) UnassignUser(ctx context.Context, token, id, userID string) error {
|
||||
res, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
|
||||
if err := svc.checkSuperAdmin(ctx, res.User); err != nil {
|
||||
domainUserID := EncodeDomainUserID(id, res.User)
|
||||
pr := policies.Policy{
|
||||
Subject: domainUserID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Object: id,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.SharePermission,
|
||||
}
|
||||
if err := svc.Authorize(ctx, pr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr.Permission = policies.AdminPermission
|
||||
if err := svc.Authorize(ctx, pr); err != nil {
|
||||
pr.SubjectKind = policies.UsersKind
|
||||
// User is not admin.
|
||||
pr.Subject = userID
|
||||
if err := svc.Authorize(ctx, pr); err == nil {
|
||||
// Non admin attempts to remove admin.
|
||||
return errors.Wrap(svcerr.ErrAuthorization, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := svc.policysvc.DeletePolicyFilter(ctx, policies.Policy{
|
||||
Subject: EncodeDomainUserID(id, userID),
|
||||
SubjectType: policies.UserType,
|
||||
}); err != nil {
|
||||
return errors.Wrap(errRemovePolicies, err)
|
||||
}
|
||||
|
||||
pc := Policy{
|
||||
SubjectType: policies.UserType,
|
||||
SubjectID: userID,
|
||||
ObjectType: policies.DomainType,
|
||||
ObjectID: id,
|
||||
}
|
||||
|
||||
if err := svc.domains.DeletePolicies(ctx, pc); err != nil {
|
||||
return errors.Wrap(errRemovePolicies, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IMPROVEMENT NOTE: Take decision: Only Patform admin or both Patform and domain admins can see others users domain.
|
||||
func (svc service) ListUserDomains(ctx context.Context, token, userID string, p Page) (DomainsPage, error) {
|
||||
res, err := svc.Identify(ctx, token)
|
||||
if err != nil {
|
||||
return DomainsPage{}, errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
if err := svc.checkSuperAdmin(ctx, res.User); err != nil {
|
||||
return DomainsPage{}, errors.Wrap(svcerr.ErrAuthorization, err)
|
||||
}
|
||||
if userID != "" && res.User != userID {
|
||||
p.SubjectID = userID
|
||||
} else {
|
||||
p.SubjectID = res.User
|
||||
}
|
||||
dp, err := svc.domains.ListDomains(ctx, p)
|
||||
if err != nil {
|
||||
return DomainsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
return dp, nil
|
||||
}
|
||||
|
||||
func (svc service) addDomainPolicies(ctx context.Context, domainID, relation string, userIDs ...string) (err error) {
|
||||
var prs []policies.Policy
|
||||
var pcs []Policy
|
||||
|
||||
for _, userID := range userIDs {
|
||||
prs = append(prs, policies.Policy{
|
||||
Subject: EncodeDomainUserID(domainID, userID),
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Relation: relation,
|
||||
Object: domainID,
|
||||
ObjectType: policies.DomainType,
|
||||
})
|
||||
pcs = append(pcs, Policy{
|
||||
SubjectType: policies.UserType,
|
||||
SubjectID: userID,
|
||||
Relation: relation,
|
||||
ObjectType: policies.DomainType,
|
||||
ObjectID: domainID,
|
||||
})
|
||||
}
|
||||
if err := svc.policysvc.AddPolicies(ctx, prs); err != nil {
|
||||
return errors.Wrap(errAddPolicies, err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if errDel := svc.policysvc.DeletePolicies(ctx, prs); errDel != nil {
|
||||
err = errors.Wrap(err, errors.Wrap(errRollbackPolicy, errDel))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err = svc.domains.SavePolicies(ctx, pcs...); err != nil {
|
||||
return errors.Wrap(errAddPolicies, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) createDomainPolicy(ctx context.Context, userID, domainID, relation string) (err error) {
|
||||
prs := []policies.Policy{
|
||||
{
|
||||
Subject: EncodeDomainUserID(domainID, userID),
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Relation: relation,
|
||||
Object: domainID,
|
||||
ObjectType: policies.DomainType,
|
||||
},
|
||||
{
|
||||
Subject: policies.MagistralaObject,
|
||||
SubjectType: policies.PlatformType,
|
||||
Relation: policies.PlatformRelation,
|
||||
Object: domainID,
|
||||
ObjectType: policies.DomainType,
|
||||
},
|
||||
}
|
||||
if err := svc.policysvc.AddPolicies(ctx, prs); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if errDel := svc.policysvc.DeletePolicies(ctx, prs); errDel != nil {
|
||||
err = errors.Wrap(err, errors.Wrap(errRollbackPolicy, errDel))
|
||||
}
|
||||
}
|
||||
}()
|
||||
err = svc.domains.SavePolicies(ctx, Policy{
|
||||
SubjectType: policies.UserType,
|
||||
SubjectID: userID,
|
||||
Relation: relation,
|
||||
ObjectType: policies.DomainType,
|
||||
ObjectID: domainID,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(errCreateDomainPolicy, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (svc service) createDomainPolicyRollback(ctx context.Context, userID, domainID, relation string) error {
|
||||
var err error
|
||||
prs := []policies.Policy{
|
||||
{
|
||||
Subject: EncodeDomainUserID(domainID, userID),
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Relation: relation,
|
||||
Object: domainID,
|
||||
ObjectType: policies.DomainType,
|
||||
},
|
||||
{
|
||||
Subject: policies.MagistralaObject,
|
||||
SubjectType: policies.PlatformType,
|
||||
Relation: policies.PlatformRelation,
|
||||
Object: domainID,
|
||||
ObjectType: policies.DomainType,
|
||||
},
|
||||
}
|
||||
if errPolicy := svc.policysvc.DeletePolicies(ctx, prs); errPolicy != nil {
|
||||
err = errors.Wrap(errRemovePolicyEngine, errPolicy)
|
||||
}
|
||||
errPolicyCopy := svc.domains.DeletePolicies(ctx, Policy{
|
||||
SubjectType: policies.UserType,
|
||||
SubjectID: userID,
|
||||
Relation: relation,
|
||||
ObjectType: policies.DomainType,
|
||||
ObjectID: domainID,
|
||||
})
|
||||
if errPolicyCopy != nil {
|
||||
err = errors.Wrap(err, errors.Wrap(errRemoveLocalPolicy, errPolicyCopy))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func EncodeDomainUserID(domainID, userID string) string {
|
||||
if domainID == "" || userID == "" {
|
||||
return ""
|
||||
}
|
||||
return domainID + "_" + userID
|
||||
}
|
||||
|
||||
func DecodeDomainUserID(domainUserID string) (string, string) {
|
||||
if domainUserID == "" {
|
||||
return domainUserID, domainUserID
|
||||
}
|
||||
duid := strings.Split(domainUserID, "_")
|
||||
|
||||
switch {
|
||||
case len(duid) == 2:
|
||||
return duid[0], duid[1]
|
||||
case len(duid) == 1:
|
||||
return duid[0], ""
|
||||
case len(duid) == 0 || len(duid) > 2:
|
||||
fallthrough
|
||||
default:
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) DeleteUserFromDomains(ctx context.Context, id string) (err error) {
|
||||
domainsPage, err := svc.domains.ListDomains(ctx, Page{SubjectID: id, Limit: defLimit})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if domainsPage.Total > defLimit {
|
||||
for i := defLimit; i < int(domainsPage.Total); i += defLimit {
|
||||
page := Page{SubjectID: id, Offset: uint64(i), Limit: defLimit}
|
||||
dp, err := svc.domains.ListDomains(ctx, page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsPage.Domains = append(domainsPage.Domains, dp.Domains...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, domain := range domainsPage.Domains {
|
||||
req := policies.Policy{
|
||||
Subject: EncodeDomainUserID(domain.ID, id),
|
||||
SubjectType: policies.UserType,
|
||||
}
|
||||
if err := svc.policysvc.DeletePolicyFilter(ctx, req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := svc.domains.DeleteUserPolicies(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) checkSuperAdmin(ctx context.Context, userID string) error {
|
||||
if err := svc.evaluator.CheckPolicy(ctx, policies.Policy{
|
||||
Subject: userID,
|
||||
SubjectType: policies.UserType,
|
||||
Permission: policies.AdminPermission,
|
||||
Object: policies.MagistralaObject,
|
||||
ObjectType: policies.PlatformType,
|
||||
}); err != nil {
|
||||
return svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
-484
@@ -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",
|
||||
}
|
||||
+75
-75
@@ -2,68 +2,68 @@
|
||||
|
||||
New devices need to be configured properly and connected to the Magistrala. Bootstrap service is used in order to accomplish that. This service provides the following features:
|
||||
|
||||
1. Creating new Magistrala Things
|
||||
2. Providing basic configuration for the newly created Things
|
||||
3. Enabling/disabling Things
|
||||
1. Creating new Magistrala Clients
|
||||
2. Providing basic configuration for the newly created Clients
|
||||
3. Enabling/disabling Clients
|
||||
|
||||
Pre-provisioning a new Thing is as simple as sending Configuration data to the Bootstrap service. Once the Thing is online, it sends a request for initial config to Bootstrap service. Bootstrap service provides an API for enabling and disabling Things. Only enabled Things can exchange messages over Magistrala. Bootstrapping does not implicitly enable Things, it has to be done manually.
|
||||
Pre-provisioning a new Client is as simple as sending Configuration data to the Bootstrap service. Once the Client is online, it sends a request for initial config to Bootstrap service. Bootstrap service provides an API for enabling and disabling Clients. Only enabled Clients can exchange messages over Magistrala. Bootstrapping does not implicitly enable Clients, it has to be done manually.
|
||||
|
||||
In order to bootstrap successfully, the Thing needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during the manufacturing process. If the Thing is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Thing will be saved so that it can be provisioned later.
|
||||
In order to bootstrap successfully, the Client needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during the manufacturing process. If the Client is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Client will be saved so that it can be provisioned later.
|
||||
|
||||
## Thing Configuration Entity
|
||||
## Client Configuration Entity
|
||||
|
||||
Thing Configuration consists of two logical parts: the custom configuration that can be interpreted by the Thing itself and Magistrala-related configuration. Magistrala config contains:
|
||||
Client Configuration consists of two logical parts: the custom configuration that can be interpreted by the Client itself and Magistrala-related configuration. Magistrala config contains:
|
||||
|
||||
1. corresponding Magistrala Thing ID
|
||||
2. corresponding Magistrala Thing key
|
||||
3. list of the Magistrala channels the Thing is connected to
|
||||
1. corresponding Magistrala Client ID
|
||||
2. corresponding Magistrala Client key
|
||||
3. list of the Magistrala channels the Client is connected to
|
||||
|
||||
> Note: list of channels contains IDs of the Magistrala channels. These channels are _pre-provisioned_ on the Magistrala side and, unlike corresponding Magistrala Thing, Bootstrap service is not able to create Magistrala Channels.
|
||||
> Note: list of channels contains IDs of the Magistrala channels. These channels are _pre-provisioned_ on the Magistrala side and, unlike corresponding Magistrala Client, Bootstrap service is not able to create Magistrala Channels.
|
||||
|
||||
Enabling and disabling Thing (adding Thing to/from whitelist) is as simple as connecting corresponding Magistrala Thing to the given list of Channels. Configuration keeps _state_ of the Thing:
|
||||
Enabling and disabling Client (adding Client to/from whitelist) is as simple as connecting corresponding Magistrala Client to the given list of Channels. Configuration keeps _state_ of the Client:
|
||||
|
||||
| State | What it means |
|
||||
| -------- | --------------------------------------------- |
|
||||
| Inactive | Thing is created, but isn't enabled |
|
||||
| Active | Thing is able to communicate using Magistrala |
|
||||
| State | What it means |
|
||||
| -------- | ---------------------------------------------- |
|
||||
| Inactive | Client is created, but isn't enabled |
|
||||
| Active | Client is able to communicate using Magistrala |
|
||||
|
||||
Switching between states `Active` and `Inactive` enables and disables Thing, respectively.
|
||||
Switching between states `Active` and `Inactive` enables and disables Client, respectively.
|
||||
|
||||
Thing configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Thing. For example, a device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure.
|
||||
Client configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Client. For example, a device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure.
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ----------------------------- | -------------------------------------------------------------------------------- | -------------------------------- |
|
||||
| MG_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | info |
|
||||
| MG_BOOTSTRAP_DB_HOST | Database host address | localhost |
|
||||
| MG_BOOTSTRAP_DB_PORT | Database host port | 5432 |
|
||||
| MG_BOOTSTRAP_DB_USER | Database user | magistrala |
|
||||
| MG_BOOTSTRAP_DB_PASS | Database password | magistrala |
|
||||
| MG_BOOTSTRAP_DB_NAME | Name of the database used by the service | bootstrap |
|
||||
| MG_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
|
||||
| MG_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | "" |
|
||||
| MG_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | "" |
|
||||
| MG_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" |
|
||||
| MG_BOOTSTRAP_ENCRYPT_KEY | Secret key for secure bootstrapping encryption | 12345678910111213141516171819202 |
|
||||
| MG_BOOTSTRAP_HTTP_HOST | Bootstrap service HTTP host | "" |
|
||||
| MG_BOOTSTRAP_HTTP_PORT | Bootstrap service HTTP port | 9013 |
|
||||
| MG_BOOTSTRAP_HTTP_SERVER_CERT | Path to server certificate in pem format | "" |
|
||||
| MG_BOOTSTRAP_HTTP_SERVER_KEY | Path to server key in pem format | "" |
|
||||
| MG_BOOTSTRAP_EVENT_CONSUMER | Bootstrap service event source consumer name | bootstrap |
|
||||
| MG_ES_URL | Event store URL | <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 | "" |
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------- | --------------------------------- |
|
||||
| SMQ_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | info |
|
||||
| SMQ_BOOTSTRAP_DB_HOST | Database host address | localhost |
|
||||
| SMQ_BOOTSTRAP_DB_PORT | Database host port | 5432 |
|
||||
| SMQ_BOOTSTRAP_DB_USER | Database user | magistrala |
|
||||
| SMQ_BOOTSTRAP_DB_PASS | Database password | magistrala |
|
||||
| SMQ_BOOTSTRAP_DB_NAME | Name of the database used by the service | bootstrap |
|
||||
| SMQ_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
|
||||
| SMQ_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | "" |
|
||||
| SMQ_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | "" |
|
||||
| SMQ_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" |
|
||||
| SMQ_BOOTSTRAP_ENCRYPT_KEY | Secret key for secure bootstrapping encryption | 12345678910111213141516171819202 |
|
||||
| SMQ_BOOTSTRAP_HTTP_HOST | Bootstrap service HTTP host | "" |
|
||||
| SMQ_BOOTSTRAP_HTTP_PORT | Bootstrap service HTTP port | 9013 |
|
||||
| SMQ_BOOTSTRAP_HTTP_SERVER_CERT | Path to server certificate in pem format | "" |
|
||||
| SMQ_BOOTSTRAP_HTTP_SERVER_KEY | Path to server key in pem format | "" |
|
||||
| SMQ_BOOTSTRAP_EVENT_CONSUMER | Bootstrap service event source consumer name | bootstrap |
|
||||
| SMQ_ES_URL | Event store URL | <nats://localhost:4222> |
|
||||
| SMQ_AUTH_GRPC_URL | Auth service Auth gRPC URL | <localhost:8181> |
|
||||
| SMQ_AUTH_GRPC_TIMEOUT | Auth service Auth gRPC request timeout in seconds | 1s |
|
||||
| SMQ_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service Auth gRPC client certificate file | "" |
|
||||
| SMQ_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service Auth gRPC client key file | "" |
|
||||
| SMQ_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server Auth gRPC server trusted CA certificate file | "" |
|
||||
| SMQ_CLIENTS_URL | Base URL for Magistrala Clients | <http://localhost:9000> |
|
||||
| SMQ_JAEGER_URL | Jaeger server URL | <http://localhost:4318/v1/traces> |
|
||||
| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 |
|
||||
| SMQ_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
|
||||
| SMQ_BOOTSTRAP_INSTANCE_ID | Bootstrap service instance ID | "" |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -84,38 +84,38 @@ make bootstrap
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MG_BOOTSTRAP_LOG_LEVEL=info \
|
||||
MG_BOOTSTRAP_DB_HOST=localhost \
|
||||
MG_BOOTSTRAP_DB_PORT=5432 \
|
||||
MG_BOOTSTRAP_DB_USER=magistrala \
|
||||
MG_BOOTSTRAP_DB_PASS=magistrala \
|
||||
MG_BOOTSTRAP_DB_NAME=bootstrap \
|
||||
MG_BOOTSTRAP_DB_SSL_MODE=disable \
|
||||
MG_BOOTSTRAP_DB_SSL_CERT="" \
|
||||
MG_BOOTSTRAP_DB_SSL_KEY="" \
|
||||
MG_BOOTSTRAP_DB_SSL_ROOT_CERT="" \
|
||||
MG_BOOTSTRAP_HTTP_HOST=localhost \
|
||||
MG_BOOTSTRAP_HTTP_PORT=9013 \
|
||||
MG_BOOTSTRAP_HTTP_SERVER_CERT="" \
|
||||
MG_BOOTSTRAP_HTTP_SERVER_KEY="" \
|
||||
MG_BOOTSTRAP_EVENT_CONSUMER=bootstrap \
|
||||
MG_ES_URL=nats://localhost:4222 \
|
||||
MG_AUTH_GRPC_URL=localhost:8181 \
|
||||
MG_AUTH_GRPC_TIMEOUT=1s \
|
||||
MG_AUTH_GRPC_CLIENT_CERT="" \
|
||||
MG_AUTH_GRPC_CLIENT_KEY="" \
|
||||
MG_AUTH_GRPC_SERVER_CERTS="" \
|
||||
MG_THINGS_URL=http://localhost:9000 \
|
||||
MG_JAEGER_URL=http://localhost:14268/api/traces \
|
||||
MG_JAEGER_TRACE_RATIO=1.0 \
|
||||
MG_SEND_TELEMETRY=true \
|
||||
MG_BOOTSTRAP_INSTANCE_ID="" \
|
||||
SMQ_BOOTSTRAP_LOG_LEVEL=info \
|
||||
SMQ_BOOTSTRAP_DB_HOST=localhost \
|
||||
SMQ_BOOTSTRAP_DB_PORT=5432 \
|
||||
SMQ_BOOTSTRAP_DB_USER=magistrala \
|
||||
SMQ_BOOTSTRAP_DB_PASS=magistrala \
|
||||
SMQ_BOOTSTRAP_DB_NAME=bootstrap \
|
||||
SMQ_BOOTSTRAP_DB_SSL_MODE=disable \
|
||||
SMQ_BOOTSTRAP_DB_SSL_CERT="" \
|
||||
SMQ_BOOTSTRAP_DB_SSL_KEY="" \
|
||||
SMQ_BOOTSTRAP_DB_SSL_ROOT_CERT="" \
|
||||
SMQ_BOOTSTRAP_HTTP_HOST=localhost \
|
||||
SMQ_BOOTSTRAP_HTTP_PORT=9013 \
|
||||
SMQ_BOOTSTRAP_HTTP_SERVER_CERT="" \
|
||||
SMQ_BOOTSTRAP_HTTP_SERVER_KEY="" \
|
||||
SMQ_BOOTSTRAP_EVENT_CONSUMER=bootstrap \
|
||||
SMQ_ES_URL=nats://localhost:4222 \
|
||||
SMQ_AUTH_GRPC_URL=localhost:8181 \
|
||||
SMQ_AUTH_GRPC_TIMEOUT=1s \
|
||||
SMQ_AUTH_GRPC_CLIENT_CERT="" \
|
||||
SMQ_AUTH_GRPC_CLIENT_KEY="" \
|
||||
SMQ_AUTH_GRPC_SERVER_CERTS="" \
|
||||
SMQ_CLIENTS_URL=http://localhost:9000 \
|
||||
SMQ_JAEGER_URL=http://localhost:14268/api/traces \
|
||||
SMQ_JAEGER_TRACE_RATIO=1.0 \
|
||||
SMQ_SEND_TELEMETRY=true \
|
||||
SMQ_BOOTSTRAP_INSTANCE_ID="" \
|
||||
$GOBIN/magistrala-bootstrap
|
||||
```
|
||||
|
||||
Setting `MG_BOOTSTRAP_HTTP_SERVER_CERT` and `MG_BOOTSTRAP_HTTP_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key.
|
||||
Setting `SMQ_BOOTSTRAP_HTTP_SERVER_CERT` and `SMQ_BOOTSTRAP_HTTP_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key.
|
||||
|
||||
Setting `MG_AUTH_GRPC_CLIENT_CERT` and `MG_AUTH_GRPC_CLIENT_KEY` will enable TLS against the auth service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_AUTH_GRPC_SERVER_CERTS` will enable TLS against the auth service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs.
|
||||
Setting `SMQ_AUTH_GRPC_CLIENT_CERT` and `SMQ_AUTH_GRPC_CLIENT_KEY` will enable TLS against the auth service. The service expects a file in PEM format for both the certificate and the key. Setting `SMQ_AUTH_GRPC_SERVER_CERTS` will enable TLS against the auth service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
+30
-30
@@ -6,12 +6,12 @@ package api
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
"github.com/absmach/magistrala/internal/api"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
config := bootstrap.Config{
|
||||
ThingID: req.ThingID,
|
||||
ClientID: req.ClientID,
|
||||
ExternalID: req.ExternalID,
|
||||
ExternalKey: req.ExternalKey,
|
||||
Channels: channels,
|
||||
@@ -50,7 +50,7 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
res := configRes{
|
||||
id: saved.ThingID,
|
||||
id: saved.ClientID,
|
||||
created: true,
|
||||
}
|
||||
|
||||
@@ -70,13 +70,13 @@ func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
cfg, err := svc.UpdateCert(ctx, session, req.thingID, req.ClientCert, req.ClientKey, req.CACert)
|
||||
cfg, err := svc.UpdateCert(ctx, session, req.clientID, req.ClientCert, req.ClientKey, req.CACert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := updateConfigRes{
|
||||
ThingID: cfg.ThingID,
|
||||
ClientID: cfg.ClientID,
|
||||
ClientCert: cfg.ClientCert,
|
||||
CACert: cfg.CACert,
|
||||
ClientKey: cfg.ClientKey,
|
||||
@@ -113,14 +113,14 @@ func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
res := viewRes{
|
||||
ThingID: config.ThingID,
|
||||
ThingKey: config.ThingKey,
|
||||
Channels: channels,
|
||||
ExternalID: config.ExternalID,
|
||||
ExternalKey: config.ExternalKey,
|
||||
Name: config.Name,
|
||||
Content: config.Content,
|
||||
State: config.State,
|
||||
ClientID: config.ClientID,
|
||||
CLientSecret: config.ClientSecret,
|
||||
Channels: channels,
|
||||
ExternalID: config.ExternalID,
|
||||
ExternalKey: config.ExternalKey,
|
||||
Name: config.Name,
|
||||
Content: config.Content,
|
||||
State: config.State,
|
||||
}
|
||||
|
||||
return res, nil
|
||||
@@ -140,9 +140,9 @@ func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
config := bootstrap.Config{
|
||||
ThingID: req.id,
|
||||
Name: req.Name,
|
||||
Content: req.Content,
|
||||
ClientID: req.id,
|
||||
Name: req.Name,
|
||||
Content: req.Content,
|
||||
}
|
||||
|
||||
if err := svc.Update(ctx, session, config); err != nil {
|
||||
@@ -150,7 +150,7 @@ func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
res := configRes{
|
||||
id: config.ThingID,
|
||||
id: config.ClientID,
|
||||
created: false,
|
||||
}
|
||||
|
||||
@@ -217,14 +217,14 @@ func listEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
view := viewRes{
|
||||
ThingID: cfg.ThingID,
|
||||
ThingKey: cfg.ThingKey,
|
||||
Channels: channels,
|
||||
ExternalID: cfg.ExternalID,
|
||||
ExternalKey: cfg.ExternalKey,
|
||||
Name: cfg.Name,
|
||||
Content: cfg.Content,
|
||||
State: cfg.State,
|
||||
ClientID: cfg.ClientID,
|
||||
CLientSecret: cfg.ClientSecret,
|
||||
Channels: channels,
|
||||
ExternalID: cfg.ExternalID,
|
||||
ExternalKey: cfg.ExternalKey,
|
||||
Name: cfg.Name,
|
||||
Content: cfg.Content,
|
||||
State: cfg.State,
|
||||
}
|
||||
res.Configs = append(res.Configs, view)
|
||||
}
|
||||
|
||||
+153
-153
@@ -18,16 +18,16 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
bsapi "github.com/absmach/magistrala/bootstrap/api"
|
||||
"github.com/absmach/magistrala/bootstrap/mocks"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
authnmocks "github.com/absmach/magistrala/pkg/authn/mocks"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
bsapi "github.com/absmach/supermq/bootstrap/api"
|
||||
"github.com/absmach/supermq/bootstrap/mocks"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
@@ -49,44 +49,44 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
encKey = []byte("1234567891011121")
|
||||
metadata = map[string]interface{}{"meta": "data"}
|
||||
addExternalID = testsutil.GenerateUUID(&testing.T{})
|
||||
addExternalKey = testsutil.GenerateUUID(&testing.T{})
|
||||
addThingID = testsutil.GenerateUUID(&testing.T{})
|
||||
addThingKey = testsutil.GenerateUUID(&testing.T{})
|
||||
addReq = struct {
|
||||
ThingID string `json:"thing_id"`
|
||||
ThingKey string `json:"thing_key"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key"`
|
||||
Channels []string `json:"channels"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
encKey = []byte("1234567891011121")
|
||||
metadata = map[string]interface{}{"meta": "data"}
|
||||
addExternalID = testsutil.GenerateUUID(&testing.T{})
|
||||
addExternalKey = testsutil.GenerateUUID(&testing.T{})
|
||||
addClientID = testsutil.GenerateUUID(&testing.T{})
|
||||
addClientSecret = testsutil.GenerateUUID(&testing.T{})
|
||||
addReq = struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key"`
|
||||
Channels []string `json:"channels"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
}{
|
||||
ThingID: addThingID,
|
||||
ThingKey: addThingKey,
|
||||
ExternalID: addExternalID,
|
||||
ExternalKey: addExternalKey,
|
||||
Channels: []string{"1"},
|
||||
Name: "name",
|
||||
Content: "config",
|
||||
ClientID: addClientID,
|
||||
ClientSecret: addClientSecret,
|
||||
ExternalID: addExternalID,
|
||||
ExternalKey: addExternalKey,
|
||||
Channels: []string{"1"},
|
||||
Name: "name",
|
||||
Content: "config",
|
||||
}
|
||||
|
||||
updateReq = struct {
|
||||
Channels []string `json:"channels,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
State bootstrap.State `json:"state,omitempty"`
|
||||
ClientCert string `json:"client_cert,omitempty"`
|
||||
ClientKey string `json:"client_key,omitempty"`
|
||||
CACert string `json:"ca_cert,omitempty"`
|
||||
Channels []string `json:"channels,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
State bootstrap.State `json:"state,omitempty"`
|
||||
ClientCert string `json:"client_cert,omitempty"`
|
||||
ClientSecret string `json:"client_secret,omitempty"`
|
||||
CACert string `json:"ca_cert,omitempty"`
|
||||
}{
|
||||
Channels: []string{"1"},
|
||||
Content: "config update",
|
||||
State: 1,
|
||||
ClientCert: "newcert",
|
||||
ClientKey: "newkey",
|
||||
CACert: "newca",
|
||||
Channels: []string{"1"},
|
||||
Content: "config update",
|
||||
State: 1,
|
||||
ClientCert: "newcert",
|
||||
ClientSecret: "newkey",
|
||||
CACert: "newca",
|
||||
}
|
||||
|
||||
missingIDRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrMissingID.Error(), Msg: apiutil.ErrValidation.Error()})
|
||||
@@ -108,10 +108,10 @@ type testRequest struct {
|
||||
|
||||
func newConfig() bootstrap.Config {
|
||||
return bootstrap.Config{
|
||||
ThingID: addThingID,
|
||||
ThingKey: addThingKey,
|
||||
ExternalID: addExternalID,
|
||||
ExternalKey: addExternalKey,
|
||||
ClientID: addClientID,
|
||||
ClientSecret: addClientSecret,
|
||||
ExternalID: addExternalID,
|
||||
ExternalKey: addExternalKey,
|
||||
Channels: []bootstrap.Channel{
|
||||
{
|
||||
ID: "1",
|
||||
@@ -136,7 +136,7 @@ func (tr testRequest) make() (*http.Response, error) {
|
||||
req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token)
|
||||
}
|
||||
if tr.key != "" {
|
||||
req.Header.Set("Authorization", apiutil.ThingPrefix+tr.key)
|
||||
req.Header.Set("Authorization", apiutil.ClientPrefix+tr.key)
|
||||
}
|
||||
|
||||
if tr.contentType != "" {
|
||||
@@ -177,7 +177,7 @@ func dec(in []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
func newBootstrapServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
|
||||
logger := mglog.NewMock()
|
||||
logger := smqlog.NewMock()
|
||||
svc := new(mocks.Service)
|
||||
authn := new(authnmocks.Authentication)
|
||||
mux := bsapi.MakeHandler(svc, authn, bootstrap.NewConfigReader(encKey), logger, instanceID)
|
||||
@@ -200,7 +200,7 @@ func TestAdd(t *testing.T) {
|
||||
data := toJSON(addReq)
|
||||
|
||||
neID := addReq
|
||||
neID.ThingID = testsutil.GenerateUUID(t)
|
||||
neID.ClientID = testsutil.GenerateUUID(t)
|
||||
neData := toJSON(neID)
|
||||
|
||||
invalidChannels := addReq
|
||||
@@ -212,7 +212,7 @@ func TestAdd(t *testing.T) {
|
||||
req string
|
||||
domainID string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
session smqauthn.Session
|
||||
contentType string
|
||||
status int
|
||||
location string
|
||||
@@ -237,7 +237,7 @@ func TestAdd(t *testing.T) {
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusCreated,
|
||||
location: "/things/configs/" + c.ThingID,
|
||||
location: "/clients/configs/" + c.ClientID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
@@ -324,7 +324,7 @@ func TestAdd(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
|
||||
@@ -332,7 +332,7 @@ func TestAdd(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: bs.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/things/configs", bs.URL, tc.domainID),
|
||||
url: fmt.Sprintf("%s/%s/clients/configs", bs.URL, tc.domainID),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(tc.req),
|
||||
@@ -359,20 +359,20 @@ func TestView(t *testing.T) {
|
||||
}
|
||||
|
||||
data := config{
|
||||
ThingID: c.ThingID,
|
||||
ThingKey: c.ThingKey,
|
||||
State: c.State,
|
||||
Channels: channels,
|
||||
ExternalID: c.ExternalID,
|
||||
ExternalKey: c.ExternalKey,
|
||||
Name: c.Name,
|
||||
Content: c.Content,
|
||||
ClientID: c.ClientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
State: c.State,
|
||||
Channels: channels,
|
||||
ExternalID: c.ExternalID,
|
||||
ExternalKey: c.ExternalKey,
|
||||
Name: c.Name,
|
||||
Content: c.Content,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
session smqauthn.Session
|
||||
id string
|
||||
status int
|
||||
res config
|
||||
@@ -382,7 +382,7 @@ func TestView(t *testing.T) {
|
||||
{
|
||||
desc: "view a config with invalid token",
|
||||
token: invalidToken,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
status: http.StatusUnauthorized,
|
||||
res: config{},
|
||||
authenticateErr: svcerr.ErrAuthentication,
|
||||
@@ -391,7 +391,7 @@ func TestView(t *testing.T) {
|
||||
{
|
||||
desc: "view a config",
|
||||
token: validToken,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
status: http.StatusOK,
|
||||
res: data,
|
||||
err: nil,
|
||||
@@ -407,7 +407,7 @@ func TestView(t *testing.T) {
|
||||
{
|
||||
desc: "view a config with an empty token",
|
||||
token: "",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
status: http.StatusUnauthorized,
|
||||
res: config{},
|
||||
err: apiutil.ErrBearerToken,
|
||||
@@ -415,7 +415,7 @@ func TestView(t *testing.T) {
|
||||
{
|
||||
desc: "view config without authorization",
|
||||
token: validToken,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
status: http.StatusForbidden,
|
||||
res: config{},
|
||||
err: svcerr.ErrAuthorization,
|
||||
@@ -425,14 +425,14 @@ func TestView(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("View", mock.Anything, tc.session, tc.id).Return(c, tc.err)
|
||||
req := testRequest{
|
||||
client: bs.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%s/things/configs/%s", bs.URL, domainID, tc.id),
|
||||
url: fmt.Sprintf("%s/%s/clients/configs/%s", bs.URL, domainID, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -467,7 +467,7 @@ func TestUpdate(t *testing.T) {
|
||||
req string
|
||||
id string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
session smqauthn.Session
|
||||
contentType string
|
||||
status int
|
||||
authenticateErr error
|
||||
@@ -476,7 +476,7 @@ func TestUpdate(t *testing.T) {
|
||||
{
|
||||
desc: "update with invalid token",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: invalidToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
@@ -486,7 +486,7 @@ func TestUpdate(t *testing.T) {
|
||||
{
|
||||
desc: "update with an empty token",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: "",
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
@@ -495,7 +495,7 @@ func TestUpdate(t *testing.T) {
|
||||
{
|
||||
desc: "update a valid config",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusOK,
|
||||
@@ -504,7 +504,7 @@ func TestUpdate(t *testing.T) {
|
||||
{
|
||||
desc: "update a config with wrong content type",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: "",
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
@@ -522,7 +522,7 @@ func TestUpdate(t *testing.T) {
|
||||
{
|
||||
desc: "update a config with invalid request format",
|
||||
req: "}",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -530,7 +530,7 @@ func TestUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "update a config with an empty request",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
req: "",
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
@@ -542,14 +542,14 @@ func TestUpdate(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("Update", mock.Anything, tc.session, mock.Anything).Return(tc.err)
|
||||
req := testRequest{
|
||||
client: bs.Client(),
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%s/things/configs/%s", bs.URL, domainID, tc.id),
|
||||
url: fmt.Sprintf("%s/%s/clients/configs/%s", bs.URL, domainID, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(tc.req),
|
||||
@@ -575,7 +575,7 @@ func TestUpdateCert(t *testing.T) {
|
||||
req string
|
||||
id string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
session smqauthn.Session
|
||||
contentType string
|
||||
status int
|
||||
authenticateErr error
|
||||
@@ -584,7 +584,7 @@ func TestUpdateCert(t *testing.T) {
|
||||
{
|
||||
desc: "update with invalid token",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: invalidToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
@@ -594,7 +594,7 @@ func TestUpdateCert(t *testing.T) {
|
||||
{
|
||||
desc: "update with an empty token",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: "",
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
@@ -603,7 +603,7 @@ func TestUpdateCert(t *testing.T) {
|
||||
{
|
||||
desc: "update a valid config",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusOK,
|
||||
@@ -612,7 +612,7 @@ func TestUpdateCert(t *testing.T) {
|
||||
{
|
||||
desc: "update a config with wrong content type",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: "",
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
@@ -630,7 +630,7 @@ func TestUpdateCert(t *testing.T) {
|
||||
{
|
||||
desc: "update a config with invalid request format",
|
||||
req: "}",
|
||||
id: c.ThingKey,
|
||||
id: c.ClientSecret,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -638,7 +638,7 @@ func TestUpdateCert(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "update a config with an empty request",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
req: "",
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
@@ -650,14 +650,14 @@ func TestUpdateCert(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("UpdateCert", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(c, tc.err)
|
||||
req := testRequest{
|
||||
client: bs.Client(),
|
||||
method: http.MethodPatch,
|
||||
url: fmt.Sprintf("%s/%s/things/configs/certs/%s", bs.URL, domainID, tc.id),
|
||||
url: fmt.Sprintf("%s/%s/clients/configs/certs/%s", bs.URL, domainID, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(tc.req),
|
||||
@@ -687,7 +687,7 @@ func TestUpdateConnections(t *testing.T) {
|
||||
req string
|
||||
id string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
session smqauthn.Session
|
||||
contentType string
|
||||
status int
|
||||
authenticateErr error
|
||||
@@ -696,7 +696,7 @@ func TestUpdateConnections(t *testing.T) {
|
||||
{
|
||||
desc: "update connections with invalid token",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: invalidToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
@@ -706,7 +706,7 @@ func TestUpdateConnections(t *testing.T) {
|
||||
{
|
||||
desc: "update connections with an empty token",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: "",
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
@@ -715,7 +715,7 @@ func TestUpdateConnections(t *testing.T) {
|
||||
{
|
||||
desc: "update connections valid config",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusOK,
|
||||
@@ -724,7 +724,7 @@ func TestUpdateConnections(t *testing.T) {
|
||||
{
|
||||
desc: "update connections with wrong content type",
|
||||
req: data,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: "",
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
@@ -742,7 +742,7 @@ func TestUpdateConnections(t *testing.T) {
|
||||
{
|
||||
desc: "update connections with invalid channels",
|
||||
req: wrongData,
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusNotFound,
|
||||
@@ -751,7 +751,7 @@ func TestUpdateConnections(t *testing.T) {
|
||||
{
|
||||
desc: "update a config with invalid request format",
|
||||
req: "}",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -759,7 +759,7 @@ func TestUpdateConnections(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "update a config with an empty request",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
req: "",
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
@@ -771,14 +771,14 @@ func TestUpdateConnections(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
repoCall := svc.On("UpdateConnections", mock.Anything, tc.session, tc.token, mock.Anything, mock.Anything).Return(tc.err)
|
||||
req := testRequest{
|
||||
client: bs.Client(),
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%s/things/configs/connections/%s", bs.URL, domainID, tc.id),
|
||||
url: fmt.Sprintf("%s/%s/clients/configs/connections/%s", bs.URL, domainID, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(tc.req),
|
||||
@@ -800,13 +800,13 @@ func TestList(t *testing.T) {
|
||||
|
||||
bs, svc, auth := newBootstrapServer()
|
||||
defer bs.Close()
|
||||
path := fmt.Sprintf("%s/%s/%s", bs.URL, domainID, "things/configs")
|
||||
path := fmt.Sprintf("%s/%s/%s", bs.URL, domainID, "clients/configs")
|
||||
|
||||
c := newConfig()
|
||||
|
||||
for i := 0; i < configNum; i++ {
|
||||
c.ExternalID = strconv.Itoa(i)
|
||||
c.ThingKey = c.ExternalID
|
||||
c.ClientSecret = c.ExternalID
|
||||
c.Name = fmt.Sprintf("%s-%d", addName, i)
|
||||
c.ExternalKey = fmt.Sprintf("%s%s", addExternalKey, strconv.Itoa(i))
|
||||
|
||||
@@ -815,14 +815,14 @@ func TestList(t *testing.T) {
|
||||
channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
|
||||
}
|
||||
s := config{
|
||||
ThingID: c.ThingID,
|
||||
ThingKey: c.ThingKey,
|
||||
Channels: channels,
|
||||
ExternalID: c.ExternalID,
|
||||
ExternalKey: c.ExternalKey,
|
||||
Name: c.Name,
|
||||
Content: c.Content,
|
||||
State: c.State,
|
||||
ClientID: c.ClientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
Channels: channels,
|
||||
ExternalID: c.ExternalID,
|
||||
ExternalKey: c.ExternalKey,
|
||||
Name: c.Name,
|
||||
Content: c.Content,
|
||||
State: c.State,
|
||||
}
|
||||
list[i] = s
|
||||
}
|
||||
@@ -833,7 +833,7 @@ func TestList(t *testing.T) {
|
||||
state = bootstrap.Inactive
|
||||
}
|
||||
svcCall := svc.On("ChangeState", context.Background(), mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
err := svc.ChangeState(context.Background(), mgauthn.Session{}, validToken, list[i].ThingID, state)
|
||||
err := svc.ChangeState(context.Background(), smqauthn.Session{}, validToken, list[i].ClientID, state)
|
||||
assert.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err))
|
||||
|
||||
svcCall.Unset()
|
||||
@@ -849,7 +849,7 @@ func TestList(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
session smqauthn.Session
|
||||
url string
|
||||
status int
|
||||
res configPage
|
||||
@@ -1040,7 +1040,7 @@ func TestList(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("List", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(bootstrap.ConfigsPage{Total: tc.res.Total, Offset: tc.res.Offset, Limit: tc.res.Limit}, tc.err)
|
||||
@@ -1077,14 +1077,14 @@ func TestRemove(t *testing.T) {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
session smqauthn.Session
|
||||
status int
|
||||
authenticateErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "remove with invalid token",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: invalidToken,
|
||||
status: http.StatusUnauthorized,
|
||||
authenticateErr: svcerr.ErrAuthentication,
|
||||
@@ -1092,7 +1092,7 @@ func TestRemove(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "remove with an empty token",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: "",
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
@@ -1106,7 +1106,7 @@ func TestRemove(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "remove config",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
status: http.StatusNoContent,
|
||||
err: nil,
|
||||
@@ -1123,14 +1123,14 @@ func TestRemove(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(tc.err)
|
||||
req := testRequest{
|
||||
client: bs.Client(),
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%s/things/configs/%s", bs.URL, domainID, tc.id),
|
||||
url: fmt.Sprintf("%s/%s/clients/configs/%s", bs.URL, domainID, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -1156,21 +1156,21 @@ func TestBootstrap(t *testing.T) {
|
||||
}
|
||||
|
||||
s := struct {
|
||||
ThingID string `json:"thing_id"`
|
||||
ThingKey string `json:"thing_key"`
|
||||
Channels []channel `json:"channels"`
|
||||
Content string `json:"content"`
|
||||
ClientCert string `json:"client_cert"`
|
||||
ClientKey string `json:"client_key"`
|
||||
CACert string `json:"ca_cert"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
Channels []channel `json:"channels"`
|
||||
Content string `json:"content"`
|
||||
ClientCert string `json:"client_cert"`
|
||||
ClientKey string `json:"client_key"`
|
||||
CACert string `json:"ca_cert"`
|
||||
}{
|
||||
ThingID: c.ThingID,
|
||||
ThingKey: c.ThingKey,
|
||||
Channels: channels,
|
||||
Content: c.Content,
|
||||
ClientCert: c.ClientCert,
|
||||
ClientKey: c.ClientKey,
|
||||
CACert: c.CACert,
|
||||
ClientID: c.ClientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
Channels: channels,
|
||||
Content: c.Content,
|
||||
ClientCert: c.ClientCert,
|
||||
ClientKey: c.ClientKey,
|
||||
CACert: c.CACert,
|
||||
}
|
||||
|
||||
data := toJSON(s)
|
||||
@@ -1185,7 +1185,7 @@ func TestBootstrap(t *testing.T) {
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "bootstrap a Thing with unknown ID",
|
||||
desc: "bootstrap a Client with unknown ID",
|
||||
externalID: unknown,
|
||||
externalKey: c.ExternalKey,
|
||||
status: http.StatusNotFound,
|
||||
@@ -1194,7 +1194,7 @@ func TestBootstrap(t *testing.T) {
|
||||
err: bootstrap.ErrBootstrap,
|
||||
},
|
||||
{
|
||||
desc: "bootstrap a Thing with an empty ID",
|
||||
desc: "bootstrap a Client with an empty ID",
|
||||
externalID: "",
|
||||
externalKey: c.ExternalKey,
|
||||
status: http.StatusBadRequest,
|
||||
@@ -1203,7 +1203,7 @@ func TestBootstrap(t *testing.T) {
|
||||
err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrMalformedEntity),
|
||||
},
|
||||
{
|
||||
desc: "bootstrap a Thing with unknown key",
|
||||
desc: "bootstrap a Client with unknown key",
|
||||
externalID: c.ExternalID,
|
||||
externalKey: unknown,
|
||||
status: http.StatusForbidden,
|
||||
@@ -1212,7 +1212,7 @@ func TestBootstrap(t *testing.T) {
|
||||
err: errors.Wrap(bootstrap.ErrExternalKey, errors.New("")),
|
||||
},
|
||||
{
|
||||
desc: "bootstrap a Thing with an empty key",
|
||||
desc: "bootstrap a Client with an empty key",
|
||||
externalID: c.ExternalID,
|
||||
externalKey: "",
|
||||
status: http.StatusBadRequest,
|
||||
@@ -1221,7 +1221,7 @@ func TestBootstrap(t *testing.T) {
|
||||
err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrAuthentication),
|
||||
},
|
||||
{
|
||||
desc: "bootstrap known Thing",
|
||||
desc: "bootstrap known Client",
|
||||
externalID: c.ExternalID,
|
||||
externalKey: c.ExternalKey,
|
||||
status: http.StatusOK,
|
||||
@@ -1255,7 +1255,7 @@ func TestBootstrap(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: bs.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/things/bootstrap/%s", bs.URL, tc.externalID),
|
||||
url: fmt.Sprintf("%s/clients/bootstrap/%s", bs.URL, tc.externalID),
|
||||
key: tc.externalKey,
|
||||
}
|
||||
res, err := req.make()
|
||||
@@ -1287,7 +1287,7 @@ func TestChangeState(t *testing.T) {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
session smqauthn.Session
|
||||
state string
|
||||
contentType string
|
||||
status int
|
||||
@@ -1296,7 +1296,7 @@ func TestChangeState(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
desc: "change state with invalid token",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: invalidToken,
|
||||
state: active,
|
||||
contentType: contentType,
|
||||
@@ -1306,7 +1306,7 @@ func TestChangeState(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "change state with an empty token",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: "",
|
||||
state: active,
|
||||
contentType: contentType,
|
||||
@@ -1315,7 +1315,7 @@ func TestChangeState(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "change state with invalid content type",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
state: active,
|
||||
contentType: "",
|
||||
@@ -1324,7 +1324,7 @@ func TestChangeState(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "change state to active",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
state: active,
|
||||
contentType: contentType,
|
||||
@@ -1333,7 +1333,7 @@ func TestChangeState(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "change state to inactive",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
state: inactive,
|
||||
contentType: contentType,
|
||||
@@ -1351,7 +1351,7 @@ func TestChangeState(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "change state to invalid value",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
state: fmt.Sprintf("{\"state\": %d}", -3),
|
||||
contentType: contentType,
|
||||
@@ -1360,7 +1360,7 @@ func TestChangeState(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "change state with invalid data",
|
||||
id: c.ThingID,
|
||||
id: c.ClientID,
|
||||
token: validToken,
|
||||
state: "",
|
||||
contentType: contentType,
|
||||
@@ -1372,14 +1372,14 @@ func TestChangeState(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("ChangeState", mock.Anything, tc.session, tc.token, mock.Anything, mock.Anything).Return(tc.err)
|
||||
req := testRequest{
|
||||
client: bs.Client(),
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%s/things/state/%s", bs.URL, domainID, tc.id),
|
||||
url: fmt.Sprintf("%s/%s/clients/state/%s", bs.URL, domainID, tc.id),
|
||||
token: tc.token,
|
||||
contentType: tc.contentType,
|
||||
body: strings.NewReader(tc.state),
|
||||
@@ -1400,14 +1400,14 @@ type channel struct {
|
||||
}
|
||||
|
||||
type config struct {
|
||||
ThingID string `json:"thing_id,omitempty"`
|
||||
ThingKey string `json:"thing_key,omitempty"`
|
||||
Channels []channel `json:"channels,omitempty"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Name string `json:"name"`
|
||||
State bootstrap.State `json:"state"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
ClientSecret string `json:"client_secret,omitempty"`
|
||||
Channels []channel `json:"channels,omitempty"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Name string `json:"name"`
|
||||
State bootstrap.State `json:"state"`
|
||||
}
|
||||
|
||||
type configPage struct {
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
)
|
||||
|
||||
const maxLimitSize = 100
|
||||
|
||||
type addReq struct {
|
||||
token string
|
||||
ThingID string `json:"thing_id"`
|
||||
ClientID string `json:"client_id"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key"`
|
||||
Channels []string `json:"channels"`
|
||||
@@ -76,14 +76,14 @@ func (req updateReq) validate() error {
|
||||
}
|
||||
|
||||
type updateCertReq struct {
|
||||
thingID string
|
||||
clientID string
|
||||
ClientCert string `json:"client_cert"`
|
||||
ClientKey string `json:"client_key"`
|
||||
CACert string `json:"ca_cert"`
|
||||
}
|
||||
|
||||
func (req updateCertReq) validate() error {
|
||||
if req.thingID == "" {
|
||||
if req.clientID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -151,20 +151,20 @@ func TestUpdateReqValidation(t *testing.T) {
|
||||
|
||||
func TestUpdateCertReqValidation(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
thingID string
|
||||
err error
|
||||
desc string
|
||||
clientID string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "empty thing id",
|
||||
thingID: "",
|
||||
err: apiutil.ErrMissingID,
|
||||
desc: "empty client id",
|
||||
clientID: "",
|
||||
err: apiutil.ErrMissingID,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := updateCertReq{
|
||||
thingID: tc.thingID,
|
||||
clientID: tc.clientID,
|
||||
}
|
||||
|
||||
err := req.validate()
|
||||
|
||||
+20
-20
@@ -7,16 +7,16 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
"github.com/absmach/supermq"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
)
|
||||
|
||||
var (
|
||||
_ magistrala.Response = (*removeRes)(nil)
|
||||
_ magistrala.Response = (*configRes)(nil)
|
||||
_ magistrala.Response = (*stateRes)(nil)
|
||||
_ magistrala.Response = (*viewRes)(nil)
|
||||
_ magistrala.Response = (*listRes)(nil)
|
||||
_ supermq.Response = (*removeRes)(nil)
|
||||
_ supermq.Response = (*configRes)(nil)
|
||||
_ supermq.Response = (*stateRes)(nil)
|
||||
_ supermq.Response = (*viewRes)(nil)
|
||||
_ supermq.Response = (*listRes)(nil)
|
||||
)
|
||||
|
||||
type removeRes struct{}
|
||||
@@ -49,7 +49,7 @@ func (res configRes) Code() int {
|
||||
func (res configRes) Headers() map[string]string {
|
||||
if res.created {
|
||||
return map[string]string{
|
||||
"Location": fmt.Sprintf("/things/configs/%s", res.id),
|
||||
"Location": fmt.Sprintf("/clients/configs/%s", res.id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,16 +67,16 @@ type channelRes struct {
|
||||
}
|
||||
|
||||
type viewRes struct {
|
||||
ThingID string `json:"thing_id,omitempty"`
|
||||
ThingKey string `json:"thing_key,omitempty"`
|
||||
Channels []channelRes `json:"channels,omitempty"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
State bootstrap.State `json:"state"`
|
||||
ClientCert string `json:"client_cert,omitempty"`
|
||||
CACert string `json:"ca_cert,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
CLientSecret string `json:"client_secret,omitempty"`
|
||||
Channels []channelRes `json:"channels,omitempty"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
State bootstrap.State `json:"state"`
|
||||
ClientCert string `json:"client_cert,omitempty"`
|
||||
CACert string `json:"ca_cert,omitempty"`
|
||||
}
|
||||
|
||||
func (res viewRes) Code() int {
|
||||
@@ -125,9 +125,9 @@ func (res stateRes) Empty() bool {
|
||||
}
|
||||
|
||||
type updateConfigRes struct {
|
||||
ThingID string `json:"thing_id,omitempty"`
|
||||
ClientCert string `json:"client_cert,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
CACert string `json:"ca_cert,omitempty"`
|
||||
ClientCert string `json:"client_cert,omitempty"`
|
||||
ClientKey string `json:"client_key,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -11,12 +11,12 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
"github.com/absmach/magistrala/internal/api"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/go-chi/chi/v5"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
@@ -33,21 +33,21 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
fullMatch = []string{"state", "external_id", "thing_id", "thing_key"}
|
||||
fullMatch = []string{"state", "external_id", "client_id", "client_key"}
|
||||
partialMatch = []string{"name"}
|
||||
// ErrBootstrap indicates error in getting bootstrap configuration.
|
||||
ErrBootstrap = errors.New("failed to read bootstrap configuration")
|
||||
)
|
||||
|
||||
// MakeHandler returns a HTTP handler for API endpoints.
|
||||
func MakeHandler(svc bootstrap.Service, authn mgauthn.Authentication, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler {
|
||||
func MakeHandler(svc bootstrap.Service, authn smqauthn.Authentication, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
|
||||
}
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Route("/{domainID}/things", func(r chi.Router) {
|
||||
r.Route("/{domainID}/clients", func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(api.AuthenticateMiddleware(authn, true))
|
||||
|
||||
@@ -96,14 +96,14 @@ func MakeHandler(svc bootstrap.Service, authn mgauthn.Authentication, reader boo
|
||||
})
|
||||
})
|
||||
|
||||
r.With(api.AuthenticateMiddleware(authn, true)).Put("/state/{thingID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
r.With(api.AuthenticateMiddleware(authn, true)).Put("/state/{clientID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
stateEndpoint(svc),
|
||||
decodeStateRequest,
|
||||
api.EncodeResponse,
|
||||
opts...), "update_state").ServeHTTP)
|
||||
})
|
||||
|
||||
r.Route("/things/bootstrap", func(r chi.Router) {
|
||||
r.Route("/clients/bootstrap", func(r chi.Router) {
|
||||
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
bootstrapEndpoint(svc, reader, false),
|
||||
decodeBootstrapRequest,
|
||||
@@ -121,7 +121,7 @@ func MakeHandler(svc bootstrap.Service, authn mgauthn.Authentication, reader boo
|
||||
opts...), "bootstrap_secure").ServeHTTP)
|
||||
})
|
||||
|
||||
r.Get("/health", magistrala.Health("bootstrap", instanceID))
|
||||
r.Get("/health", supermq.Health("bootstrap", instanceID))
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
return r
|
||||
@@ -163,7 +163,7 @@ func decodeUpdateCertRequest(_ context.Context, r *http.Request) (interface{}, e
|
||||
}
|
||||
|
||||
req := updateCertReq{
|
||||
thingID: chi.URLParam(r, "certID"),
|
||||
clientID: chi.URLParam(r, "certID"),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
|
||||
@@ -216,7 +216,7 @@ func decodeListRequest(_ context.Context, r *http.Request) (interface{}, error)
|
||||
func decodeBootstrapRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
req := bootstrapReq{
|
||||
id: chi.URLParam(r, "externalID"),
|
||||
key: apiutil.ExtractThingKey(r),
|
||||
key: apiutil.ExtractClientSecret(r),
|
||||
}
|
||||
|
||||
return req, nil
|
||||
@@ -229,7 +229,7 @@ func decodeStateRequest(_ context.Context, r *http.Request) (interface{}, error)
|
||||
|
||||
req := changeStateReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
id: chi.URLParam(r, "thingID"),
|
||||
id: chi.URLParam(r, "clientID"),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity))
|
||||
|
||||
+28
-28
@@ -7,30 +7,30 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/things"
|
||||
"github.com/absmach/supermq/clients"
|
||||
)
|
||||
|
||||
// Config represents Configuration entity. It wraps information about external entity
|
||||
// as well as info about corresponding Magistrala entities.
|
||||
// MGThing represents corresponding Magistrala Thing ID.
|
||||
// MGKey is key of corresponding Magistrala Thing.
|
||||
// MGChannels is a list of Magistrala Channels corresponding Magistrala Thing connects to.
|
||||
// as well as info about corresponding SuperMQ entities.
|
||||
// MGClient represents corresponding SuperMQ Client ID.
|
||||
// MGKey is key of corresponding SuperMQ Client.
|
||||
// MGChannels is a list of SuperMQ Channels corresponding SuperMQ Client connects to.
|
||||
type Config struct {
|
||||
ThingID string `json:"thing_id"`
|
||||
DomainID string `json:"domain_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ClientCert string `json:"client_cert,omitempty"`
|
||||
ClientKey string `json:"client_key,omitempty"`
|
||||
CACert string `json:"ca_cert,omitempty"`
|
||||
ThingKey string `json:"thing_key"`
|
||||
Channels []Channel `json:"channels,omitempty"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key"`
|
||||
Content string `json:"content,omitempty"`
|
||||
State State `json:"state"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
DomainID string `json:"domain_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ClientCert string `json:"client_cert,omitempty"`
|
||||
ClientKey string `json:"client_key,omitempty"`
|
||||
CACert string `json:"ca_cert,omitempty"`
|
||||
Channels []Channel `json:"channels,omitempty"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalKey string `json:"external_key"`
|
||||
Content string `json:"content,omitempty"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
// Channel represents Magistrala channel corresponding Magistrala Thing is connected to.
|
||||
// Channel represents SuperMQ channel corresponding SuperMQ Client is connected to.
|
||||
type Channel struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
@@ -41,7 +41,7 @@ type Channel struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
Status things.Status `json:"status"`
|
||||
Status clients.Status `json:"status"`
|
||||
}
|
||||
|
||||
// Filter is used for the search filters.
|
||||
@@ -73,7 +73,7 @@ type ConfigRepository interface {
|
||||
|
||||
// RetrieveAll retrieves a subset of Configs that are owned
|
||||
// by the specific user, with given filter parameters.
|
||||
RetrieveAll(ctx context.Context, domainID string, thingIDs []string, filter Filter, offset, limit uint64) ConfigsPage
|
||||
RetrieveAll(ctx context.Context, domainID string, clientIDs []string, filter Filter, offset, limit uint64) ConfigsPage
|
||||
|
||||
// RetrieveByExternalID returns Config for given external ID.
|
||||
RetrieveByExternalID(ctx context.Context, externalID string) (Config, error)
|
||||
@@ -84,7 +84,7 @@ type ConfigRepository interface {
|
||||
|
||||
// UpdateCerts updates and returns an existing Config certificate and domainID.
|
||||
// A non-nil error is returned to indicate operation failure.
|
||||
UpdateCert(ctx context.Context, domainID, thingID, clientCert, clientKey, caCert string) (Config, error)
|
||||
UpdateCert(ctx context.Context, domainID, clientID, clientCert, clientKey, caCert string) (Config, error)
|
||||
|
||||
// UpdateConnections updates a list of Channels the Config is connected to
|
||||
// adding new Channels if needed.
|
||||
@@ -100,11 +100,11 @@ type ConfigRepository interface {
|
||||
// ListExisting retrieves those channels from the given list that exist in DB.
|
||||
ListExisting(ctx context.Context, domainID string, ids []string) ([]Channel, error)
|
||||
|
||||
// Methods RemoveThing, UpdateChannel, and RemoveChannel are related to
|
||||
// Methods RemoveClient, UpdateChannel, and RemoveChannel are related to
|
||||
// event sourcing. That's why these methods surpass ownership check.
|
||||
|
||||
// RemoveThing removes Config of the Thing with the given ID.
|
||||
RemoveThing(ctx context.Context, id string) error
|
||||
// RemoveClient removes Config of the Client with the given ID.
|
||||
RemoveClient(ctx context.Context, id string) error
|
||||
|
||||
// UpdateChannel updates channel with the given ID.
|
||||
UpdateChannel(ctx context.Context, c Channel) error
|
||||
@@ -112,9 +112,9 @@ type ConfigRepository interface {
|
||||
// RemoveChannel removes channel with the given ID.
|
||||
RemoveChannel(ctx context.Context, id string) error
|
||||
|
||||
// ConnectThing changes state of the Config when the corresponding Thing is connected to the Channel.
|
||||
ConnectThing(ctx context.Context, channelID, thingID string) error
|
||||
// ConnectClient changes state of the Config when the corresponding Client is connected to the Channel.
|
||||
ConnectClient(ctx context.Context, channelID, clientID string) error
|
||||
|
||||
// DisconnectThing changes state of the Config when the corresponding Thing is disconnected from the Channel.
|
||||
DisconnectThing(ctx context.Context, channelID, thingID string) error
|
||||
// DisconnectClient changes state of the Config when the corresponding Client is disconnected from the Channel.
|
||||
DisconnectClient(ctx context.Context, channelID, clientID string) error
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package bootstrap contains the domain concept definitions needed to support
|
||||
// Magistrala bootstrap service functionality.
|
||||
// SuperMQ bootstrap service functionality.
|
||||
package bootstrap
|
||||
|
||||
@@ -19,6 +19,6 @@ type updateChannelEvent struct {
|
||||
|
||||
// Connection event is either connect or disconnect event.
|
||||
type connectionEvent struct {
|
||||
thingIDs []string
|
||||
clientIDs []string
|
||||
channelID string
|
||||
}
|
||||
|
||||
@@ -7,21 +7,21 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/absmach/magistrala/pkg/events"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/absmach/supermq/pkg/events"
|
||||
)
|
||||
|
||||
const (
|
||||
thingRemove = "thing.remove"
|
||||
thingConnect = "group.assign"
|
||||
thingDisconnect = "group.unassign"
|
||||
clientRemove = "client.remove"
|
||||
clientConnect = "group.assign"
|
||||
clientDisconnect = "group.unassign"
|
||||
|
||||
channelPrefix = "group."
|
||||
channelPrefix = "channels."
|
||||
channelUpdate = channelPrefix + "update"
|
||||
channelRemove = channelPrefix + "remove"
|
||||
|
||||
memberKind = "things"
|
||||
memberKind = "client"
|
||||
relation = "group"
|
||||
)
|
||||
|
||||
@@ -43,35 +43,35 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
|
||||
}
|
||||
|
||||
switch msg["operation"] {
|
||||
case thingRemove:
|
||||
rte := decodeRemoveThing(msg)
|
||||
case clientRemove:
|
||||
rte := decodeRemoveClient(msg)
|
||||
err = es.svc.RemoveConfigHandler(ctx, rte.id)
|
||||
case thingConnect:
|
||||
cte := decodeConnectThing(msg)
|
||||
if cte.channelID == "" || len(cte.thingIDs) == 0 {
|
||||
case clientConnect:
|
||||
cte := decodeConnectClient(msg)
|
||||
if cte.channelID == "" || len(cte.clientIDs) == 0 {
|
||||
return svcerr.ErrMalformedEntity
|
||||
}
|
||||
for _, thingID := range cte.thingIDs {
|
||||
if thingID == "" {
|
||||
for _, clientID := range cte.clientIDs {
|
||||
if clientID == "" {
|
||||
return svcerr.ErrMalformedEntity
|
||||
}
|
||||
if err := es.svc.ConnectThingHandler(ctx, cte.channelID, thingID); err != nil {
|
||||
if err := es.svc.ConnectClientHandler(ctx, cte.channelID, clientID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case thingDisconnect:
|
||||
dte := decodeDisconnectThing(msg)
|
||||
if dte.channelID == "" || len(dte.thingIDs) == 0 {
|
||||
case clientDisconnect:
|
||||
dte := decodeDisconnectClient(msg)
|
||||
if dte.channelID == "" || len(dte.clientIDs) == 0 {
|
||||
return svcerr.ErrMalformedEntity
|
||||
}
|
||||
for _, thingID := range dte.thingIDs {
|
||||
if thingID == "" {
|
||||
for _, clientID := range dte.clientIDs {
|
||||
if clientID == "" {
|
||||
return svcerr.ErrMalformedEntity
|
||||
}
|
||||
}
|
||||
|
||||
for _, thingID := range dte.thingIDs {
|
||||
if err = es.svc.DisconnectThingHandler(ctx, dte.channelID, thingID); err != nil {
|
||||
for _, c := range dte.clientIDs {
|
||||
if err = es.svc.DisconnectClientHandler(ctx, dte.channelID, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeRemoveThing(event map[string]interface{}) removeEvent {
|
||||
func decodeRemoveClient(event map[string]interface{}) removeEvent {
|
||||
return removeEvent{
|
||||
id: events.Read(event, "id", ""),
|
||||
}
|
||||
@@ -113,25 +113,25 @@ func decodeRemoveChannel(event map[string]interface{}) removeEvent {
|
||||
}
|
||||
}
|
||||
|
||||
func decodeConnectThing(event map[string]interface{}) connectionEvent {
|
||||
func decodeConnectClient(event map[string]interface{}) connectionEvent {
|
||||
if events.Read(event, "memberKind", "") != memberKind && events.Read(event, "relation", "") != relation {
|
||||
return connectionEvent{}
|
||||
}
|
||||
|
||||
return connectionEvent{
|
||||
channelID: events.Read(event, "group_id", ""),
|
||||
thingIDs: events.ReadStringSlice(event, "member_ids"),
|
||||
clientIDs: events.ReadStringSlice(event, "member_ids"),
|
||||
}
|
||||
}
|
||||
|
||||
func decodeDisconnectThing(event map[string]interface{}) connectionEvent {
|
||||
func decodeDisconnectClient(event map[string]interface{}) connectionEvent {
|
||||
if events.Read(event, "memberKind", "") != memberKind && events.Read(event, "relation", "") != relation {
|
||||
return connectionEvent{}
|
||||
}
|
||||
|
||||
return connectionEvent{
|
||||
channelID: events.Read(event, "group_id", ""),
|
||||
thingIDs: events.ReadStringSlice(event, "member_ids"),
|
||||
clientIDs: events.ReadStringSlice(event, "member_ids"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package producer
|
||||
|
||||
import (
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
"github.com/absmach/magistrala/pkg/events"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
"github.com/absmach/supermq/pkg/events"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -17,12 +17,12 @@ const (
|
||||
configList = configPrefix + "list"
|
||||
configHandlerRemove = configPrefix + "remove_handler"
|
||||
|
||||
thingPrefix = "bootstrap.thing."
|
||||
thingBootstrap = thingPrefix + "bootstrap"
|
||||
thingStateChange = thingPrefix + "change_state"
|
||||
thingUpdateConnections = thingPrefix + "update_connections"
|
||||
thingConnect = thingPrefix + "connect"
|
||||
thingDisconnect = thingPrefix + "disconnect"
|
||||
clientPrefix = "bootstrap.client."
|
||||
clientBootstrap = clientPrefix + "bootstrap"
|
||||
clientStateChange = clientPrefix + "change_state"
|
||||
clientUpdateConnections = clientPrefix + "update_connections"
|
||||
clientConnect = clientPrefix + "connect"
|
||||
clientDisconnect = clientPrefix + "disconnect"
|
||||
|
||||
channelPrefix = "bootstrap.channel."
|
||||
channelHandlerRemove = channelPrefix + "remove_handler"
|
||||
@@ -52,8 +52,8 @@ func (ce configEvent) Encode() (map[string]interface{}, error) {
|
||||
"state": ce.State.String(),
|
||||
"operation": ce.operation,
|
||||
}
|
||||
if ce.ThingID != "" {
|
||||
val["thing_id"] = ce.ThingID
|
||||
if ce.ClientID != "" {
|
||||
val["client_id"] = ce.ClientID
|
||||
}
|
||||
if ce.Content != "" {
|
||||
val["content"] = ce.Content
|
||||
@@ -91,12 +91,12 @@ func (ce configEvent) Encode() (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
type removeConfigEvent struct {
|
||||
mgThing string
|
||||
client string
|
||||
}
|
||||
|
||||
func (rce removeConfigEvent) Encode() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"thing_id": rce.mgThing,
|
||||
"client_id": rce.client,
|
||||
"operation": configRemove,
|
||||
}, nil
|
||||
}
|
||||
@@ -134,11 +134,11 @@ func (be bootstrapEvent) Encode() (map[string]interface{}, error) {
|
||||
val := map[string]interface{}{
|
||||
"external_id": be.externalID,
|
||||
"success": be.success,
|
||||
"operation": thingBootstrap,
|
||||
"operation": clientBootstrap,
|
||||
}
|
||||
|
||||
if be.ThingID != "" {
|
||||
val["thing_id"] = be.ThingID
|
||||
if be.ClientID != "" {
|
||||
val["client_id"] = be.ClientID
|
||||
}
|
||||
if be.Content != "" {
|
||||
val["content"] = be.Content
|
||||
@@ -175,38 +175,41 @@ func (be bootstrapEvent) Encode() (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
type changeStateEvent struct {
|
||||
mgThing string
|
||||
state bootstrap.State
|
||||
mgClient string
|
||||
state bootstrap.State
|
||||
}
|
||||
|
||||
func (cse changeStateEvent) Encode() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"thing_id": cse.mgThing,
|
||||
"client_id": cse.mgClient,
|
||||
"state": cse.state.String(),
|
||||
"operation": thingStateChange,
|
||||
"operation": clientStateChange,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type updateConnectionsEvent struct {
|
||||
mgThing string
|
||||
mgClient string
|
||||
mgChannels []string
|
||||
}
|
||||
|
||||
func (uce updateConnectionsEvent) Encode() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"thing_id": uce.mgThing,
|
||||
"client_id": uce.mgClient,
|
||||
"channels": uce.mgChannels,
|
||||
"operation": thingUpdateConnections,
|
||||
"operation": clientUpdateConnections,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type updateCertEvent struct {
|
||||
thingKey, clientCert, clientKey, caCert string
|
||||
clientID string
|
||||
clientCert string
|
||||
clientKey string
|
||||
caCert string
|
||||
}
|
||||
|
||||
func (uce updateCertEvent) Encode() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"thing_key": uce.thingKey,
|
||||
"client_id": uce.clientID,
|
||||
"client_cert": uce.clientCert,
|
||||
"client_key": uce.clientKey,
|
||||
"ca_cert": uce.caCert,
|
||||
@@ -247,28 +250,28 @@ func (uche updateChannelHandlerEvent) Encode() (map[string]interface{}, error) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
type connectThingEvent struct {
|
||||
thingID string
|
||||
type connectClientEvent struct {
|
||||
clientID string
|
||||
channelID string
|
||||
}
|
||||
|
||||
func (cte connectThingEvent) Encode() (map[string]interface{}, error) {
|
||||
func (cte connectClientEvent) Encode() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"thing_id": cte.thingID,
|
||||
"client_id": cte.clientID,
|
||||
"channel_id": cte.channelID,
|
||||
"operation": thingConnect,
|
||||
"operation": clientConnect,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type disconnectThingEvent struct {
|
||||
thingID string
|
||||
type disconnectClientEvent struct {
|
||||
clientID string
|
||||
channelID string
|
||||
}
|
||||
|
||||
func (dte disconnectThingEvent) Encode() (map[string]interface{}, error) {
|
||||
func (dte disconnectClientEvent) Encode() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"thing_id": dte.thingID,
|
||||
"client_id": dte.clientID,
|
||||
"channel_id": dte.channelID,
|
||||
"operation": thingDisconnect,
|
||||
"operation": clientDisconnect,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ package producer
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/events"
|
||||
"github.com/absmach/supermq/bootstrap"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/events"
|
||||
)
|
||||
|
||||
var _ bootstrap.Service = (*eventStore)(nil)
|
||||
@@ -27,7 +27,7 @@ func NewEventStoreMiddleware(svc bootstrap.Service, publisher events.Publisher)
|
||||
}
|
||||
}
|
||||
|
||||
func (es *eventStore) Add(ctx context.Context, session mgauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) {
|
||||
func (es *eventStore) Add(ctx context.Context, session smqauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) {
|
||||
saved, err := es.svc.Add(ctx, session, token, cfg)
|
||||
if err != nil {
|
||||
return saved, err
|
||||
@@ -44,7 +44,7 @@ func (es *eventStore) Add(ctx context.Context, session mgauthn.Session, token st
|
||||
return saved, err
|
||||
}
|
||||
|
||||
func (es *eventStore) View(ctx context.Context, session mgauthn.Session, id string) (bootstrap.Config, error) {
|
||||
func (es *eventStore) View(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||
cfg, err := es.svc.View(ctx, session, id)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
@@ -60,7 +60,7 @@ func (es *eventStore) View(ctx context.Context, session mgauthn.Session, id stri
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func (es *eventStore) Update(ctx context.Context, session mgauthn.Session, cfg bootstrap.Config) error {
|
||||
func (es *eventStore) Update(ctx context.Context, session smqauthn.Session, cfg bootstrap.Config) error {
|
||||
if err := es.svc.Update(ctx, session, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -72,14 +72,14 @@ func (es *eventStore) Update(ctx context.Context, session mgauthn.Session, cfg b
|
||||
return es.Publish(ctx, ev)
|
||||
}
|
||||
|
||||
func (es eventStore) UpdateCert(ctx context.Context, session mgauthn.Session, thingKey, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
||||
cfg, err := es.svc.UpdateCert(ctx, session, thingKey, clientCert, clientKey, caCert)
|
||||
func (es eventStore) UpdateCert(ctx context.Context, session smqauthn.Session, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
||||
cfg, err := es.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
ev := updateCertEvent{
|
||||
thingKey: thingKey,
|
||||
clientID: clientID,
|
||||
clientCert: clientCert,
|
||||
clientKey: clientKey,
|
||||
caCert: caCert,
|
||||
@@ -92,20 +92,20 @@ func (es eventStore) UpdateCert(ctx context.Context, session mgauthn.Session, th
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (es *eventStore) UpdateConnections(ctx context.Context, session mgauthn.Session, token, id string, connections []string) error {
|
||||
func (es *eventStore) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error {
|
||||
if err := es.svc.UpdateConnections(ctx, session, token, id, connections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ev := updateConnectionsEvent{
|
||||
mgThing: id,
|
||||
mgClient: id,
|
||||
mgChannels: connections,
|
||||
}
|
||||
|
||||
return es.Publish(ctx, ev)
|
||||
}
|
||||
|
||||
func (es *eventStore) List(ctx context.Context, session mgauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
|
||||
func (es *eventStore) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
|
||||
bp, err := es.svc.List(ctx, session, filter, offset, limit)
|
||||
if err != nil {
|
||||
return bp, err
|
||||
@@ -125,13 +125,13 @@ func (es *eventStore) List(ctx context.Context, session mgauthn.Session, filter
|
||||
return bp, nil
|
||||
}
|
||||
|
||||
func (es *eventStore) Remove(ctx context.Context, session mgauthn.Session, id string) error {
|
||||
func (es *eventStore) Remove(ctx context.Context, session smqauthn.Session, id string) error {
|
||||
if err := es.svc.Remove(ctx, session, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ev := removeConfigEvent{
|
||||
mgThing: id,
|
||||
client: id,
|
||||
}
|
||||
|
||||
return es.Publish(ctx, ev)
|
||||
@@ -157,14 +157,14 @@ func (es *eventStore) Bootstrap(ctx context.Context, externalKey, externalID str
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func (es *eventStore) ChangeState(ctx context.Context, session mgauthn.Session, token, id string, state bootstrap.State) error {
|
||||
func (es *eventStore) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) error {
|
||||
if err := es.svc.ChangeState(ctx, session, token, id, state); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ev := changeStateEvent{
|
||||
mgThing: id,
|
||||
state: state,
|
||||
mgClient: id,
|
||||
state: state,
|
||||
}
|
||||
|
||||
return es.Publish(ctx, ev)
|
||||
@@ -208,26 +208,26 @@ func (es *eventStore) UpdateChannelHandler(ctx context.Context, channel bootstra
|
||||
return es.Publish(ctx, ev)
|
||||
}
|
||||
|
||||
func (es *eventStore) ConnectThingHandler(ctx context.Context, channelID, thingID string) error {
|
||||
if err := es.svc.ConnectThingHandler(ctx, channelID, thingID); err != nil {
|
||||
func (es *eventStore) ConnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
||||
if err := es.svc.ConnectClientHandler(ctx, channelID, clientID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ev := connectThingEvent{
|
||||
thingID: thingID,
|
||||
ev := connectClientEvent{
|
||||
clientID: clientID,
|
||||
channelID: channelID,
|
||||
}
|
||||
|
||||
return es.Publish(ctx, ev)
|
||||
}
|
||||
|
||||
func (es *eventStore) DisconnectThingHandler(ctx context.Context, channelID, thingID string) error {
|
||||
if err := es.svc.DisconnectThingHandler(ctx, channelID, thingID); err != nil {
|
||||
func (es *eventStore) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
||||
if err := es.svc.DisconnectClientHandler(ctx, channelID, clientID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ev := disconnectThingEvent{
|
||||
thingID: thingID,
|
||||
ev := disconnectClientEvent{
|
||||
clientID: clientID,
|
||||
channelID: channelID,
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user