mirror of
https://github.com/absmach/magistrala.git
synced 2026-08-07 23:32:14 +00:00
Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65c6a4d8f4 | |||
| ec223e7467 | |||
| aa3cc3d7ee | |||
| 9650dde716 | |||
| d02747a56f | |||
| dc0df1c955 | |||
| 64b4497ad4 | |||
| 1b26264495 | |||
| 69366d77cc | |||
| fa8862cbc8 | |||
| 15b8901853 | |||
| 9258237cb6 | |||
| 2adeecd9ec | |||
| c5eee6d8fd | |||
| 648edbd386 | |||
| 35fb296318 | |||
| a98777b191 | |||
| 3c693a7b41 | |||
| c230a24b7d | |||
| 42b2a8c166 | |||
| 6468ebe749 | |||
| 1adbe1ff24 | |||
| 27995cb093 | |||
| 5833bafbee | |||
| 6394908b2c | |||
| 0e0598c6d8 | |||
| 3e400da5ad | |||
| 2112281171 | |||
| 6f40c7fa1e | |||
| b0e9e43b9e | |||
| bc7727c275 | |||
| 8d1c3c41b5 | |||
| d84df8604f | |||
| d4d8bc5351 | |||
| ac9bc9aad6 | |||
| 4b83301f69 | |||
| b746a8679f | |||
| e6022542b9 | |||
| 6c77e1b738 | |||
| 04774ae972 | |||
| ae7c93020a | |||
| b031dc08b8 | |||
| 0465ceb7a3 | |||
| af424ee2cb | |||
| 96a15b32dd | |||
| 61f1e9aca2 | |||
| 29f7063c15 | |||
| 1f48a73677 | |||
| cd9cb9e1d5 | |||
| 8d9e92f228 | |||
| ad685c27ad | |||
| 44485cacec | |||
| 7fac6ff4c4 | |||
| 1930a0c8f2 | |||
| 9eac9366ba | |||
| ad191c4609 | |||
| 1d935f0519 | |||
| bc1ccace4a | |||
| 89fdb4e6c7 | |||
| 44e44639aa | |||
| d31c66174e | |||
| e6d7a07277 | |||
| a2ea92a5ba | |||
| 3320a716f3 | |||
| a70b838f1c | |||
| c50221a042 | |||
| 1efa8b5bce | |||
| e57ad79cd4 | |||
| 22616911d2 | |||
| 766bb5519a | |||
| fbcdef9693 | |||
| 9aacde7c5b | |||
| cdf26051f6 | |||
| a9a412e909 | |||
| 0754fa1126 | |||
| ec75f4eadb | |||
| af35396601 | |||
| 6a27594751 | |||
| 555d89d358 | |||
| c3c9dcf318 | |||
| 5a75772f62 | |||
| b9ac94f058 | |||
| 8ab1f4c408 | |||
| e842bfaab6 | |||
| 886cd63415 | |||
| 9cd22910d8 | |||
| 906e365adf | |||
| 8e7e897906 | |||
| 4b50ef93a2 | |||
| 73c2d76b58 | |||
| 473338527a | |||
| 12d1a0de32 | |||
| b4e0e89c38 | |||
| 6f5e535b30 | |||
| fb1062a98a | |||
| c56a5ae577 | |||
| a19cd9c97b | |||
| 0e1a090f16 | |||
| ef712443e1 | |||
| 248b708909 | |||
| c63c936b36 | |||
| 4927333691 | |||
| 5acc8f96a6 | |||
| 9d91740063 | |||
| 1951f206cb | |||
| 441feb8578 | |||
| 7d11f16988 | |||
| 2e3f52fdbf | |||
| b942a42271 | |||
| 0178594c25 | |||
| 3dbd617ed0 | |||
| 8188da16b3 | |||
| bc7b988098 | |||
| a0b3970ad6 | |||
| 0f20663cfb | |||
| 4dc98763a6 |
@@ -11,7 +11,6 @@ on:
|
||||
- ".github/workflows/api-tests.yaml"
|
||||
- "api/**"
|
||||
- "auth/api/http/**"
|
||||
- "certs/api/**"
|
||||
- "channels/api/http/**"
|
||||
- "clients/api/http/**"
|
||||
- "domains/api/http/**"
|
||||
@@ -33,7 +32,6 @@ env:
|
||||
GROUPS_URL: http://localhost:9004
|
||||
HTTP_ADAPTER_URL: http://localhost:8008
|
||||
AUTH_URL: http://localhost:9001
|
||||
CERTS_URL: http://localhost:9019
|
||||
JOURNAL_URL: http://localhost:9021
|
||||
|
||||
jobs:
|
||||
@@ -41,12 +39,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
go-version: 1.25.x
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Build images
|
||||
@@ -78,11 +76,6 @@ jobs:
|
||||
- "apidocs/openapi/auth.yaml"
|
||||
- "auth/api/http/**"
|
||||
|
||||
certs:
|
||||
- ".github/workflows/api-tests.yaml"
|
||||
- "apidocs/openapi/certs.yaml"
|
||||
- "certs/api/**"
|
||||
|
||||
domains:
|
||||
- ".github/workflows/api-tests.yaml"
|
||||
- "apidocs/openapi/domains.yaml"
|
||||
@@ -120,7 +113,7 @@ jobs:
|
||||
schema: apidocs/openapi/users.yaml
|
||||
base-url: ${{ env.USERS_URL }}
|
||||
checks: all
|
||||
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --suppress-health-check=filter_too_much --exclude-checks=positive_data_acceptance --phases=examples,stateful'
|
||||
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --suppress-health-check=filter_too_much --exclude-checks=positive_data_acceptance --exclude-operation-id=requestPasswordReset --phases=examples,stateful'
|
||||
|
||||
- name: Run Groups API tests
|
||||
if: steps.changes.outputs.groups == 'true'
|
||||
@@ -185,15 +178,6 @@ jobs:
|
||||
checks: all
|
||||
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --suppress-health-check=filter_too_much --exclude-checks=positive_data_acceptance --phases=examples'
|
||||
|
||||
- name: Run Certs API tests
|
||||
if: steps.changes.outputs.certs == 'true'
|
||||
uses: schemathesis/action@v2.1.0
|
||||
with:
|
||||
schema: apidocs/openapi/certs.yaml
|
||||
base-url: ${{ env.CERTS_URL }}
|
||||
checks: all
|
||||
args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --suppress-health-check=filter_too_much --exclude-checks=positive_data_acceptance --phases=examples'
|
||||
|
||||
- name: Stop containers
|
||||
if: always()
|
||||
run: make run down args="-v" && make run_addons down args="-v"
|
||||
|
||||
@@ -17,18 +17,24 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Fetch tags for the build
|
||||
run: |
|
||||
git fetch --prune --unshallow --tags
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
go-version: 1.25.x
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Set GOBIN
|
||||
run: echo "GOBIN=$HOME/.local/bin" >> $GITHUB_ENV
|
||||
|
||||
- name: Add GOBIN to PATH
|
||||
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
make test
|
||||
@@ -64,7 +70,7 @@ jobs:
|
||||
|
||||
- name: Trigger Helm Chart Deployment
|
||||
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
uses: peter-evans/repository-dispatch@v4
|
||||
with:
|
||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||
repository: absmach/amdm
|
||||
|
||||
@@ -16,14 +16,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
go-version: 1.25.x
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Set GOBIN
|
||||
run: echo "GOBIN=$HOME/.local/bin" >> $GITHUB_ENV
|
||||
|
||||
- name: Add GOBIN to PATH
|
||||
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Check for changes in go.mod
|
||||
run: |
|
||||
go mod tidy
|
||||
@@ -60,17 +66,14 @@ jobs:
|
||||
- "pkg/groups/groups.go"
|
||||
- "users/emailer.go"
|
||||
- "users/hasher.go"
|
||||
- "certs/certs.go"
|
||||
- "certs/pki/openbao/openbao.go"
|
||||
- "certs/service.go"
|
||||
- "journal/journal.go"
|
||||
- "consumers/notifier.go"
|
||||
|
||||
- name: Set up protoc
|
||||
if: steps.changes.outputs.proto == 'true'
|
||||
run: |
|
||||
PROTOC_VERSION=30.2
|
||||
PROTOC_GEN_VERSION=v1.36.6
|
||||
PROTOC_VERSION=33.0
|
||||
PROTOC_GEN_VERSION=v1.36.10
|
||||
PROTOC_GRPC_VERSION=v1.5.1
|
||||
|
||||
# Export the variables so they are available in future steps
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Check License Header
|
||||
run: |
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Swagger UI action
|
||||
id: swagger-ui-action
|
||||
@@ -28,4 +28,4 @@ jobs:
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: swagger-ui
|
||||
cname: docs.api.supermq.abstractmachines.fr
|
||||
cname: docs.api.supermq.absmach.eu
|
||||
|
||||
@@ -15,14 +15,25 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
go-version: 1.25.x
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Fetch Certs
|
||||
run: |
|
||||
make fetch_certs
|
||||
if [[ -n $(git status --porcelain docker/addons/certs) ]]; then
|
||||
echo "Certs docker file is not up to date. Please update it"
|
||||
git diff docker/addons/certs
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
- name: Install protolint
|
||||
run: |
|
||||
go install github.com/yoheimuta/protolint/cmd/protolint@latest
|
||||
@@ -34,7 +45,7 @@ jobs:
|
||||
- name: Run linters
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: v2.1.6
|
||||
version: v2.4.0
|
||||
args: --config ./tools/config/.golangci.yaml
|
||||
|
||||
- name: Build all Binaries
|
||||
@@ -56,14 +67,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
go-version: 1.25.x
|
||||
cache-dependency-path: "go.sum"
|
||||
|
||||
- name: Check for changes in specific paths
|
||||
@@ -84,14 +95,6 @@ jobs:
|
||||
- "pkg/ulid/**"
|
||||
- "pkg/uuid/**"
|
||||
|
||||
certs:
|
||||
- "certs/**"
|
||||
- "cmd/certs/**"
|
||||
- "auth.pb.go"
|
||||
- "auth_grpc.pb.go"
|
||||
- "auth/**"
|
||||
- "pkg/sdk/**"
|
||||
|
||||
channels:
|
||||
- "channels/**"
|
||||
- "cmd/channels/**"
|
||||
@@ -203,15 +206,15 @@ jobs:
|
||||
- "pkg/errors/**"
|
||||
- "pkg/groups/**"
|
||||
- "auth/**"
|
||||
- "certs/**"
|
||||
- "http/**"
|
||||
- "internal/*"
|
||||
- "internal/api/**"
|
||||
- "internal/apiutil/**"
|
||||
- "internal/groups/**"
|
||||
- "invitations/**"
|
||||
- "clients/**"
|
||||
- "users/**"
|
||||
- "channels/**"
|
||||
- "domains/**"
|
||||
- "groups/**"
|
||||
- "journal/**"
|
||||
- "api/http/**"
|
||||
|
||||
pkg-transformers:
|
||||
- "pkg/transformers/**"
|
||||
@@ -259,11 +262,6 @@ jobs:
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/domains.out ./domains/...
|
||||
|
||||
- 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: |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SMQ_DOCKER_IMAGE_NAME_PREFIX ?= supermq
|
||||
BUILD_DIR ?= build
|
||||
SERVICES = auth users clients groups channels domains http coap ws cli mqtt certs journal
|
||||
SERVICES = auth users clients groups channels domains http coap ws cli mqtt journal
|
||||
TEST_API_SERVICES = journal auth certs http clients users channels groups domains
|
||||
TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES))
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
@@ -21,7 +21,8 @@ DOCKER_PROJECT ?= $(shell echo $(subst $(space),,$(USER_REPO)) | sed -E 's/[^a-z
|
||||
DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config restart
|
||||
DEFAULT_DOCKER_COMPOSE_COMMAND := up
|
||||
GRPC_MTLS_CERT_FILES_EXISTS = 0
|
||||
MOCKERY_VERSION=v3.5.0
|
||||
MOCKERY = $(GOBIN)/mockery
|
||||
MOCKERY_VERSION=3.5.5
|
||||
PKG_PROTO_GEN_OUT_DIR=api/grpc
|
||||
INTERNAL_PROTO_DIR=internal/proto
|
||||
INTERNAL_PROTO_FILES := $(shell find $(INTERNAL_PROTO_DIR) -name "*.proto" | sed 's|$(INTERNAL_PROTO_DIR)/||')
|
||||
@@ -117,11 +118,16 @@ install:
|
||||
cp $$file $(GOBIN)/supermq-`basename $$file`; \
|
||||
done
|
||||
|
||||
mocks:
|
||||
@which mockery > /dev/null || go install github.com/vektra/mockery/v3@$(MOCKERY_VERSION)
|
||||
@unset MOCKERY_VERSION
|
||||
mockery --config ./tools/config/.mockery.yaml
|
||||
mocks: $(MOCKERY)
|
||||
@$(MOCKERY) --config ./tools/config/.mockery.yaml
|
||||
|
||||
$(MOCKERY):
|
||||
@mkdir -p $(GOBIN)
|
||||
@mkdir -p mockery
|
||||
@echo ">> downloading mockery $(MOCKERY_VERSION)..."
|
||||
@curl -sL https://github.com/vektra/mockery/releases/download/v$(MOCKERY_VERSION)/mockery_$(MOCKERY_VERSION)_Linux_x86_64.tar.gz | tar -xz -C mockery
|
||||
@mv mockery/mockery $(GOBIN)
|
||||
@rm -r mockery
|
||||
|
||||
DIRS = consumers readers postgres internal
|
||||
test: mocks
|
||||
@@ -133,7 +139,7 @@ test: mocks
|
||||
|
||||
define test_api_service
|
||||
$(eval svc=$(subst test_api_,,$(1)))
|
||||
@which st > /dev/null || (echo "schemathesis not found, please install it from https://github.com/schemathesis/schemathesis#getting-started" && exit 1)
|
||||
@which uv > /dev/null || (echo "uv not found, please install it from https://github.com/astral-sh/uv" && exit 1)
|
||||
|
||||
@if [ -z "$(USER_TOKEN)" ]; then \
|
||||
echo "USER_TOKEN is not set"; \
|
||||
@@ -148,7 +154,7 @@ define test_api_service
|
||||
fi
|
||||
|
||||
@if [ "$(svc)" = "http" ]; then \
|
||||
st run apidocs/openapi/$(svc).yaml \
|
||||
uvx schemathesis run apidocs/openapi/$(svc).yaml \
|
||||
--checks all \
|
||||
--url $(2) \
|
||||
--header "Authorization: Client $(CLIENT_SECRET)" \
|
||||
@@ -156,12 +162,13 @@ define test_api_service
|
||||
--exclude-checks=positive_data_acceptance \
|
||||
--phases=examples,stateful; \
|
||||
else \
|
||||
st run apidocs/openapi/$(svc).yaml \
|
||||
uvx schemathesis run apidocs/openapi/$(svc).yaml \
|
||||
--checks all \
|
||||
--url $(2) \
|
||||
--header "Authorization: Bearer $(USER_TOKEN)" \
|
||||
--suppress-health-check=filter_too_much \
|
||||
--exclude-checks=positive_data_acceptance \
|
||||
--exclude-operation-id=requestPasswordReset \
|
||||
--phases=examples,stateful; \
|
||||
fi
|
||||
endef
|
||||
@@ -248,13 +255,21 @@ endif
|
||||
endif
|
||||
endif
|
||||
|
||||
fetch_certs:
|
||||
@./scripts/certs.sh
|
||||
|
||||
run: check_certs
|
||||
docker compose -f docker/docker-compose.yaml --env-file docker/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args)
|
||||
|
||||
run_addons: check_certs
|
||||
$(foreach SVC,$(RUN_ADDON_ARGS),$(if $(filter $(SVC),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)),,$(error Invalid Service $(SVC))))
|
||||
@docker compose -f docker/docker-compose.yaml --env-file ./docker/.env -p $(DOCKER_PROJECT) up -d auth domains jaeger
|
||||
@for SVC in $(RUN_ADDON_ARGS); do \
|
||||
SMQ_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yaml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \
|
||||
if [ "$$SVC" = "certs" ]; then \
|
||||
docker compose -f docker/addons/$$SVC/docker-compose.yaml -f docker/certs-docker-compose-override.yaml --env-file ./docker/.env --env-file ./docker/addons/$$SVC/.env -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) & \
|
||||
else \
|
||||
SMQ_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yaml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \
|
||||
fi; \
|
||||
done
|
||||
|
||||
run_live: check_certs
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v6.30.2
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.0
|
||||
// source: auth/v1/auth.proto
|
||||
|
||||
package v1
|
||||
@@ -73,6 +73,7 @@ type AuthNRes struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // token id
|
||||
UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // user id
|
||||
UserRole uint32 `protobuf:"varint,3,opt,name=user_role,json=userRole,proto3" json:"user_role,omitempty"` // user role
|
||||
Verified bool `protobuf:"varint,4,opt,name=verified,proto3" json:"verified,omitempty"` // verified user
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -128,6 +129,13 @@ func (x *AuthNRes) GetUserRole() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *AuthNRes) GetVerified() bool {
|
||||
if x != nil {
|
||||
return x.Verified
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AuthZReq struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Domain string `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"` // Domain
|
||||
@@ -378,11 +386,12 @@ const file_auth_v1_auth_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x12auth/v1/auth.proto\x12\aauth.v1\" \n" +
|
||||
"\bAuthNReq\x12\x14\n" +
|
||||
"\x05token\x18\x01 \x01(\tR\x05token\"P\n" +
|
||||
"\x05token\x18\x01 \x01(\tR\x05token\"l\n" +
|
||||
"\bAuthNRes\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x17\n" +
|
||||
"\auser_id\x18\x02 \x01(\tR\x06userId\x12\x1b\n" +
|
||||
"\tuser_role\x18\x03 \x01(\rR\buserRole\"\xa2\x02\n" +
|
||||
"\tuser_role\x18\x03 \x01(\rR\buserRole\x12\x1a\n" +
|
||||
"\bverified\x18\x04 \x01(\bR\bverified\"\xa2\x02\n" +
|
||||
"\bAuthZReq\x12\x16\n" +
|
||||
"\x06domain\x18\x01 \x01(\tR\x06domain\x12!\n" +
|
||||
"\fsubject_type\x18\x02 \x01(\tR\vsubjectType\x12!\n" +
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.30.2
|
||||
// - protoc v6.33.0
|
||||
// source: auth/v1/auth.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v6.30.2
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.0
|
||||
// source: channels/v1/channels.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.30.2
|
||||
// - protoc v6.33.0
|
||||
// source: channels/v1/channels.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v6.30.2
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.0
|
||||
// source: clients/v1/clients.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.30.2
|
||||
// - protoc v6.33.0
|
||||
// source: clients/v1/clients.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v6.30.2
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.0
|
||||
// source: common/v1/common.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v6.30.2
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.0
|
||||
// source: domains/v1/domains.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.30.2
|
||||
// - protoc v6.33.0
|
||||
// source: domains/v1/domains.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v6.30.2
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.0
|
||||
// source: groups/v1/groups.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.30.2
|
||||
// - protoc v6.33.0
|
||||
// source: groups/v1/groups.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v6.30.2
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.0
|
||||
// source: token/v1/token.proto
|
||||
|
||||
package v1
|
||||
@@ -29,6 +29,7 @@ type IssueReq struct {
|
||||
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||
UserRole uint32 `protobuf:"varint,2,opt,name=user_role,json=userRole,proto3" json:"user_role,omitempty"`
|
||||
Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Verified bool `protobuf:"varint,4,opt,name=verified,proto3" json:"verified,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -84,9 +85,17 @@ func (x *IssueReq) GetType() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *IssueReq) GetVerified() bool {
|
||||
if x != nil {
|
||||
return x.Verified
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type RefreshReq struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
|
||||
Verified bool `protobuf:"varint,2,opt,name=verified,proto3" json:"verified,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -128,6 +137,13 @@ func (x *RefreshReq) GetRefreshToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RefreshReq) GetVerified() bool {
|
||||
if x != nil {
|
||||
return x.Verified
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -195,14 +211,16 @@ var File_token_v1_token_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_token_v1_token_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x14token/v1/token.proto\x12\btoken.v1\"T\n" +
|
||||
"\x14token/v1/token.proto\x12\btoken.v1\"p\n" +
|
||||
"\bIssueReq\x12\x17\n" +
|
||||
"\auser_id\x18\x01 \x01(\tR\x06userId\x12\x1b\n" +
|
||||
"\tuser_role\x18\x02 \x01(\rR\buserRole\x12\x12\n" +
|
||||
"\x04type\x18\x03 \x01(\rR\x04type\"1\n" +
|
||||
"\x04type\x18\x03 \x01(\rR\x04type\x12\x1a\n" +
|
||||
"\bverified\x18\x04 \x01(\bR\bverified\"M\n" +
|
||||
"\n" +
|
||||
"RefreshReq\x12#\n" +
|
||||
"\rrefresh_token\x18\x01 \x01(\tR\frefreshToken\"\x87\x01\n" +
|
||||
"\rrefresh_token\x18\x01 \x01(\tR\frefreshToken\x12\x1a\n" +
|
||||
"\bverified\x18\x02 \x01(\bR\bverified\"\x87\x01\n" +
|
||||
"\x05Token\x12!\n" +
|
||||
"\faccess_token\x18\x01 \x01(\tR\vaccessToken\x12(\n" +
|
||||
"\rrefresh_token\x18\x02 \x01(\tH\x00R\frefreshToken\x88\x01\x01\x12\x1f\n" +
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.30.2
|
||||
// - protoc v6.33.0
|
||||
// source: token/v1/token.proto
|
||||
|
||||
package v1
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/auth"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type sessionKeyType string
|
||||
|
||||
const SessionKey = sessionKeyType("session")
|
||||
|
||||
func AuthenticateMiddleware(authn smqauthn.Authentication, domainCheck bool) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
token := apiutil.ExtractBearerToken(r)
|
||||
if token == "" {
|
||||
EncodeError(r.Context(), apiutil.ErrBearerToken, w)
|
||||
return
|
||||
}
|
||||
resp, err := authn.Authenticate(r.Context(), token)
|
||||
if err != nil {
|
||||
EncodeError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if domainCheck {
|
||||
domain := chi.URLParam(r, "domainID")
|
||||
if domain == "" {
|
||||
EncodeError(r.Context(), apiutil.ErrMissingDomainID, w)
|
||||
return
|
||||
}
|
||||
resp.DomainID = domain
|
||||
switch resp.Role {
|
||||
case smqauthn.AdminRole:
|
||||
resp.DomainUserID = resp.UserID
|
||||
case smqauthn.UserRole:
|
||||
resp.DomainUserID = auth.EncodeDomainUserID(domain, resp.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), SessionKey, resp)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
+30
-9
@@ -7,12 +7,12 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/absmach/supermq"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/absmach/supermq/clients"
|
||||
"github.com/absmach/supermq/groups"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
@@ -28,10 +28,12 @@ const (
|
||||
LimitKey = "limit"
|
||||
OnlyTotal = "only_total"
|
||||
|
||||
NameOrder = "name"
|
||||
IDOrder = "id"
|
||||
AscDir = "asc"
|
||||
DescDir = "desc"
|
||||
NameOrder = "name"
|
||||
IDOrder = "id"
|
||||
AscDir = "asc"
|
||||
DescDir = "desc"
|
||||
UpdatedAtOrder = "updated_at"
|
||||
CreatedAtOrder = "created_at"
|
||||
|
||||
MetadataKey = "metadata"
|
||||
NameKey = "name"
|
||||
@@ -72,7 +74,7 @@ const (
|
||||
DefTotal = uint64(100)
|
||||
DefOffset = 0
|
||||
DefOrder = "updated_at"
|
||||
DefDir = "asc"
|
||||
DefDir = "desc"
|
||||
DefLimit = 10
|
||||
DefLevel = 0
|
||||
DefStartLevel = 1
|
||||
@@ -108,6 +110,13 @@ func ValidateUUID(extID string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateEmail(email string) (err error) {
|
||||
if _, err := mail.ParseAddress(email); err != nil {
|
||||
return apiutil.ErrInvalidEmail
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateName validates name format.
|
||||
func ValidateName(id string) error {
|
||||
if !nameRegExp.MatchString(id) {
|
||||
@@ -148,7 +157,7 @@ func ValidateUserName(name string) error {
|
||||
}
|
||||
|
||||
// EncodeResponse encodes successful response.
|
||||
func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
func EncodeResponse(_ context.Context, w http.ResponseWriter, response any) error {
|
||||
if ar, ok := response.(supermq.Response); ok {
|
||||
for k, v := range ar.Headers() {
|
||||
w.Header().Set(k, v)
|
||||
@@ -181,9 +190,21 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
|
||||
}
|
||||
|
||||
switch {
|
||||
case errors.Contains(err, errors.ErrTryAgain):
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
case errors.Contains(err, errors.ErrEmailAlreadyExists),
|
||||
errors.Contains(err, errors.ErrUsernameNotAvailable),
|
||||
errors.Contains(err, errors.ErrRouteNotAvailable),
|
||||
errors.Contains(err, errors.ErrChannelRouteNotAvailable),
|
||||
errors.Contains(err, errors.ErrDomainRouteNotAvailable),
|
||||
errors.Contains(err, svcerr.ErrExternalAuthProviderCouldNotChangePassword),
|
||||
errors.Contains(err, svcerr.ErrExternalAuthProviderCouldNotResetPassword),
|
||||
errors.Contains(err, svcerr.ErrExternalAuthProviderCouldNotUpdate):
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case errors.Contains(err, svcerr.ErrAuthorization),
|
||||
errors.Contains(err, svcerr.ErrDomainAuthorization),
|
||||
errors.Contains(err, svcerr.ErrUnauthorizedPAT):
|
||||
errors.Contains(err, svcerr.ErrUnauthorizedPAT),
|
||||
errors.Contains(err, svcerr.ErrSuperAdminAction):
|
||||
err = unwrap(err)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
|
||||
@@ -198,6 +219,7 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
|
||||
errors.Contains(err, apiutil.ErrMissingSecret),
|
||||
errors.Contains(err, errors.ErrMalformedEntity),
|
||||
errors.Contains(err, apiutil.ErrMissingID),
|
||||
errors.Contains(err, apiutil.ErrInvalidVerification),
|
||||
errors.Contains(err, apiutil.ErrMissingName),
|
||||
errors.Contains(err, apiutil.ErrMissingEmail),
|
||||
errors.Contains(err, apiutil.ErrInvalidEmail),
|
||||
@@ -236,7 +258,6 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
|
||||
errors.Contains(err, apiutil.ErrEmptySearchQuery),
|
||||
errors.Contains(err, apiutil.ErrLenSearchQuery),
|
||||
errors.Contains(err, apiutil.ErrMissingDomainID),
|
||||
errors.Contains(err, certs.ErrFailedReadFromPKI),
|
||||
errors.Contains(err, apiutil.ErrMissingUserID),
|
||||
errors.Contains(err, apiutil.ErrMissingPATID),
|
||||
errors.Contains(err, apiutil.ErrMissingUsername),
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestEncodeResponse(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
resp interface{}
|
||||
resp any
|
||||
header http.Header
|
||||
code int
|
||||
body []byte
|
||||
|
||||
@@ -268,4 +268,10 @@ var (
|
||||
|
||||
// ErrMissingUsernameEmail indicates missing user name / email.
|
||||
ErrMissingUsernameEmail = errors.New("missing username / email")
|
||||
|
||||
// ErrInvalidVerification indicates invalid email verification.
|
||||
ErrInvalidVerification = errors.New("invalid verification")
|
||||
|
||||
// ErrEmailNotVerified indicates invalid email not verified.
|
||||
ErrEmailNotVerified = errors.New("email not verified")
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ func ReadStringQuery(r *http.Request, key, def string) (string, error) {
|
||||
}
|
||||
|
||||
// ReadMetadataQuery reads the value of json http query parameters for a given key.
|
||||
func ReadMetadataQuery(r *http.Request, key string, def map[string]interface{}) (map[string]interface{}, error) {
|
||||
func ReadMetadataQuery(r *http.Request, key string, def map[string]any) (map[string]any, error) {
|
||||
vals := r.URL.Query()[key]
|
||||
if len(vals) > 1 {
|
||||
return nil, ErrInvalidQueryParams
|
||||
@@ -49,7 +49,7 @@ func ReadMetadataQuery(r *http.Request, key string, def map[string]interface{})
|
||||
return def, nil
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err := json.Unmarshal([]byte(vals[0]), &m)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrInvalidQueryParams, err)
|
||||
|
||||
@@ -67,14 +67,14 @@ func TestReadMetadataQuery(t *testing.T) {
|
||||
desc string
|
||||
url string
|
||||
key string
|
||||
ret map[string]interface{}
|
||||
ret map[string]any
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "valid metadata query",
|
||||
url: "http://localhost:8080/?key={\"test\":\"test\"}",
|
||||
key: "key",
|
||||
ret: map[string]interface{}{"test": "test"},
|
||||
ret: map[string]any{"test": "test"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
@@ -177,7 +177,7 @@ func TestReadNumQuery(t *testing.T) {
|
||||
url string
|
||||
key string
|
||||
numType string
|
||||
ret interface{}
|
||||
ret any
|
||||
err error
|
||||
}{
|
||||
{
|
||||
@@ -316,7 +316,7 @@ func TestReadNumQuery(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
r := &http.Request{URL: parsedURL}
|
||||
var ret interface{}
|
||||
var ret any
|
||||
switch c.numType {
|
||||
case "int64":
|
||||
ret, err = apiutil.ReadNumQuery[int64](r, c.key, 0)
|
||||
|
||||
@@ -5,7 +5,7 @@ asyncapi: '2.6.0'
|
||||
id: 'https://github.com/absmach/supermq/blob/main/api/asyncapi/mqtt.yaml'
|
||||
info:
|
||||
title: SuperMQ MQTT Adapter
|
||||
version: '1.0.0'
|
||||
version: '0.18.0'
|
||||
contact:
|
||||
name: SuperMQ Team
|
||||
url: 'https://github.com/absmach/supermq'
|
||||
|
||||
@@ -6,7 +6,7 @@ id: 'https://github.com/absmach/supermq/blob/main/api/asyncapi/websocket.yaml'
|
||||
info:
|
||||
title: SuperMQ WebSocket adapter
|
||||
description: WebSocket adapter provides a WebSocket API for sending messages through communication channels. WebSocket adapter uses [mProxy](https://github.com/absmach/mproxy) for proxying traffic between client and MQTT broker.
|
||||
version: '1.0.0'
|
||||
version: '0.18.0'
|
||||
contact:
|
||||
name: SuperMQ Team
|
||||
url: 'https://github.com/absmach/supermq'
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
This folder contains an OpenAPI specifications for SuperMQ API.
|
||||
|
||||
View specification in Swagger UI at [docs.api.magistrala.abstractmachines.fr](https://docs.api.supermq.abstractmachines.fr)
|
||||
View specification in Swagger UI at [docs.api.supermq.abstractmachines.fr](https://docs.api.supermq.abstractmachines.fr)
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9001
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: SuperMQ Certs service
|
||||
description: |
|
||||
HTTP API for Certs service
|
||||
Some useful links:
|
||||
- [The SuperMQ repository](https://github.com/absmach/supermq)
|
||||
contact:
|
||||
email: info@abstractmachines.fr
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
|
||||
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.supermq.abstractmachines.fr/
|
||||
|
||||
paths:
|
||||
/{domainID}/certs:
|
||||
post:
|
||||
operationId: createCert
|
||||
summary: Creates a certificate for client
|
||||
description: Creates a certificate for client
|
||||
tags:
|
||||
- certs
|
||||
parameters:
|
||||
- $ref: "auth.yaml#/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.yaml#/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"
|
||||
|
||||
/{domainID}/certs/{clientID}/revoke-all:
|
||||
post:
|
||||
operationId: revokeAllCerts
|
||||
summary: Revokes all certificates for a given client ID
|
||||
description: |
|
||||
Revokes all certificates for a given client ID.
|
||||
tags:
|
||||
- certs
|
||||
parameters:
|
||||
- $ref: "auth.yaml#/components/parameters/DomainID"
|
||||
- $ref: "#/components/parameters/ClientID"
|
||||
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}/certs/{certID}/revoke:
|
||||
post:
|
||||
operationId: revokeCertBySerial
|
||||
summary: Revokes a certificate by serial number
|
||||
description: |
|
||||
Revokes a certificate for a given certificate serial number.
|
||||
tags:
|
||||
- certs
|
||||
parameters:
|
||||
- $ref: "auth.yaml#/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/{clientID}:
|
||||
get:
|
||||
operationId: getSerials
|
||||
summary: Retrieves certificates' serial IDs
|
||||
description: |
|
||||
Retrieves a list of certificates' serial IDs for a given client ID.
|
||||
tags:
|
||||
- certs
|
||||
parameters:
|
||||
- $ref: "auth.yaml#/components/parameters/DomainID"
|
||||
- $ref: "#/components/parameters/ClientID"
|
||||
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:
|
||||
ClientID:
|
||||
name: clientID
|
||||
description: Client ID
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
CertID:
|
||||
name: certID
|
||||
description: Serial of certificate
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
example: "7c:a9:91:e7:13:e9:5c:6b:1d:16:cf:76:20:82:f3:01:c3:d5:a6:66"
|
||||
required: true
|
||||
|
||||
schemas:
|
||||
Cert:
|
||||
type: object
|
||||
properties:
|
||||
client_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Corresponding SuperMQ Client 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 client
|
||||
provide a client id, data identifying particular client will be embedded into the Certificate.
|
||||
x509 and ECC certificates are supported when using when Openbao is used as PKI.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- client_id
|
||||
- ttl
|
||||
properties:
|
||||
client_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:
|
||||
clientID: $response.body#/client_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/health_info.yaml"
|
||||
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
* Users access: "Authorization: Bearer <user_token>"
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9005
|
||||
@@ -87,8 +87,20 @@ paths:
|
||||
- $ref: "auth.yaml#/components/parameters/DomainID"
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
- $ref: "#/components/parameters/Offset"
|
||||
- $ref: "#/components/parameters/Order"
|
||||
- $ref: "#/components/parameters/Direction"
|
||||
- $ref: "#/components/parameters/Metadata"
|
||||
- $ref: "#/components/parameters/Status"
|
||||
- $ref: "#/components/parameters/ChannelName"
|
||||
- $ref: "#/components/parameters/ID"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/ActionsQuery"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/RoleIDQuery"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/RoleNameQuery"
|
||||
- $ref: "#/components/parameters/AccessType"
|
||||
- $ref: "#/components/parameters/OnlyTotal"
|
||||
- $ref: "#/components/parameters/Client"
|
||||
- $ref: "#/components/parameters/Group"
|
||||
- $ref: "#/components/parameters/User"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/ChannelPageRes"
|
||||
@@ -844,16 +856,94 @@ components:
|
||||
minimum: 0
|
||||
required: false
|
||||
example: "0"
|
||||
|
||||
Order:
|
||||
name: order
|
||||
description: Field by which to order the results
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: created_at
|
||||
|
||||
Connected:
|
||||
name: connected
|
||||
description: Connection state of the subset to retrieve.
|
||||
Direction:
|
||||
name: dir
|
||||
description: Direction of ordering the results.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
required: false
|
||||
example: desc
|
||||
|
||||
ID:
|
||||
name: id
|
||||
description: List channels with the given ID.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
AccessType:
|
||||
name: access_type
|
||||
description: Type of access the user has on the channel.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- direct
|
||||
- domain
|
||||
- indirect
|
||||
- indirect_group
|
||||
required: false
|
||||
example: direct
|
||||
|
||||
OnlyTotal:
|
||||
name: only_total
|
||||
description: If true, the response will contain only the total number of channels that match the query parameters.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
default: true
|
||||
default: false
|
||||
required: false
|
||||
|
||||
Client:
|
||||
name: client
|
||||
description: If provided lists channels that a client with the provided ID is connected to.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
minLength: 36
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
Group:
|
||||
name: group
|
||||
description: If provided lists channels belonging to a group with the provided ID.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
minLength: 36
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
User:
|
||||
name: user
|
||||
description: If provided lists channels associated with a user with the provided ID. Only available for admin users.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
minLength: 36
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
requestBodies:
|
||||
ChannelCreateReq:
|
||||
description: JSON-formatted document describing the new channel to be registered
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9006
|
||||
@@ -28,8 +28,8 @@ tags:
|
||||
- name: Roles
|
||||
description: All operations involving roles for clients
|
||||
externalDocs:
|
||||
description: Find out more about roles
|
||||
url: https://docs.supermq.abstractmachines.fr/
|
||||
description: Find out more about roles
|
||||
url: https://docs.supermq.abstractmachines.fr/
|
||||
- name: Health
|
||||
description: Health check operations
|
||||
externalDocs:
|
||||
@@ -84,10 +84,22 @@ paths:
|
||||
- $ref: "auth.yaml#/components/parameters/DomainID"
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
- $ref: "#/components/parameters/Offset"
|
||||
- $ref: "#/components/parameters/Order"
|
||||
- $ref: "#/components/parameters/Direction"
|
||||
- $ref: "#/components/parameters/Metadata"
|
||||
- $ref: "#/components/parameters/Status"
|
||||
- $ref: "#/components/parameters/ClientName"
|
||||
- $ref: "#/components/parameters/Tags"
|
||||
- $ref: "#/components/parameters/ID"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/ActionsQuery"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/RoleIDQuery"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/RoleNameQuery"
|
||||
- $ref: "#/components/parameters/AccessType"
|
||||
- $ref: "#/components/parameters/OnlyTotal"
|
||||
- $ref: "#/components/parameters/Channel"
|
||||
- $ref: "#/components/parameters/ConnectionType"
|
||||
- $ref: "#/components/parameters/Group"
|
||||
- $ref: "#/components/parameters/User"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
@@ -436,7 +448,7 @@ paths:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"201":
|
||||
$ref: "./schemas/roles.yaml#/components/responses/CreateRoleRes"
|
||||
$ref: "./schemas/roles.yaml#/components/responses/CreateRoleRes"
|
||||
"400":
|
||||
description: Failed due to malformed client's ID.
|
||||
"401":
|
||||
@@ -946,7 +958,7 @@ components:
|
||||
ParentGroupReqObj:
|
||||
type: object
|
||||
properties:
|
||||
parent_group_id:
|
||||
parent_group_id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
@@ -1089,9 +1101,7 @@ components:
|
||||
example: 10
|
||||
description: Maximum number of items to return in one page.
|
||||
required:
|
||||
- clients
|
||||
- total
|
||||
- offset
|
||||
|
||||
ClientUpdate:
|
||||
type: object
|
||||
@@ -1241,6 +1251,105 @@ components:
|
||||
required: false
|
||||
example: "0"
|
||||
|
||||
Order:
|
||||
name: order
|
||||
description: Field by which to order the results
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: created_at
|
||||
|
||||
Direction:
|
||||
name: dir
|
||||
description: Direction of ordering the results.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
required: false
|
||||
example: desc
|
||||
|
||||
ID:
|
||||
name: id
|
||||
description: List clients with the given ID.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
AccessType:
|
||||
name: access_type
|
||||
description: Type of access the user has on the client.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- direct
|
||||
- domain
|
||||
- indirect
|
||||
- indirect_group
|
||||
required: false
|
||||
example: direct
|
||||
|
||||
OnlyTotal:
|
||||
name: only_total
|
||||
description: If true, the response will contain only the total number of clients that match the query parameters.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
|
||||
Channel:
|
||||
name: channel
|
||||
description: If provided lists clients connected to a channel with the provided ID.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
minLength: 36
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
ConnectionType:
|
||||
name: connection_type
|
||||
description: If provided with channel parameter lists clients connected to the channel with the provided connection type.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- Publish
|
||||
- Subscribe
|
||||
required: false
|
||||
example: Publish
|
||||
|
||||
Group:
|
||||
name: group
|
||||
description: If provided lists clients belonging to a group with the provided ID.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
minLength: 36
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
User:
|
||||
name: user
|
||||
description: If provided lists clients associated with a user with the provided ID. Only available for admin users.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
minLength: 36
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
requestBodies:
|
||||
ClientCreateReq:
|
||||
description: JSON-formatted document describing the new client to be registered
|
||||
@@ -1337,7 +1446,7 @@ components:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Client"
|
||||
|
||||
|
||||
ClientPageRes:
|
||||
description: Data retrieved.
|
||||
content:
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Magistrala Domains Service
|
||||
title: SuperMQ Domains Service
|
||||
description: |
|
||||
This is the Domains Server based on the OpenAPI 3.0 specification. It is the HTTP API for managing platform domains. 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)
|
||||
- [The SuperMQ repository](https://github.com/absmach/supermq)
|
||||
contact:
|
||||
email: info@abstractmachines.fr
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/magistrala/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9003
|
||||
@@ -24,7 +24,7 @@ tags:
|
||||
description: CRUD operations for your domains
|
||||
externalDocs:
|
||||
description: Find out more about domains
|
||||
url: https://docs.magistrala.abstractmachines.fr/
|
||||
url: https://docs.supermq.abstractmachines.fr/
|
||||
- name: Roles
|
||||
description: All operations involving roles for domains
|
||||
externalDocs:
|
||||
@@ -39,7 +39,7 @@ tags:
|
||||
description: Service health check endpoint.
|
||||
externalDocs:
|
||||
description: Find out more about health check
|
||||
url: https://docs.magistrala.abstractmachines.fr/
|
||||
url: https://docs.supermq.abstractmachines.fr/
|
||||
|
||||
paths:
|
||||
/domains:
|
||||
@@ -73,10 +73,16 @@ paths:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
- $ref: "#/components/parameters/Offset"
|
||||
- $ref: "#/components/parameters/Order"
|
||||
- $ref: "#/components/parameters/Direction"
|
||||
- $ref: "#/components/parameters/Metadata"
|
||||
- $ref: "#/components/parameters/Status"
|
||||
- $ref: "#/components/parameters/DomainName"
|
||||
- $ref: "#/components/parameters/Permission"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/ActionsQuery"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/RoleIDQuery"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/RoleNameQuery"
|
||||
- $ref: "#/components/parameters/AccessType"
|
||||
- $ref: "#/components/parameters/OnlyTotal"
|
||||
tags:
|
||||
- Domains
|
||||
security:
|
||||
@@ -757,45 +763,18 @@ paths:
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/domains/{domainID}/invitations/{userID}:
|
||||
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/DomainID"
|
||||
- $ref: "#/components/parameters/UserID"
|
||||
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.
|
||||
Deletes a specific invitation that is identified by the user ID and domain ID.
|
||||
The user ID is provided in the request body.
|
||||
tags:
|
||||
- Invitations
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/DomainID"
|
||||
- $ref: "#/components/parameters/UserID"
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/DeleteInvitationReq"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
@@ -1017,9 +996,7 @@ components:
|
||||
example: 10
|
||||
description: Maximum number of items to return in one page.
|
||||
required:
|
||||
- domains
|
||||
- total
|
||||
- offset
|
||||
DomainUpdate:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1167,14 +1144,6 @@ components:
|
||||
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.
|
||||
@@ -1272,15 +1241,46 @@ components:
|
||||
- all
|
||||
required: false
|
||||
example: accepted
|
||||
RoleID:
|
||||
name: roleID
|
||||
description: Unique role identifier.
|
||||
Order:
|
||||
name: order
|
||||
description: Field by which to order the results
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
required: false
|
||||
example: created_at
|
||||
Direction:
|
||||
name: dir
|
||||
description: Direction of ordering the results.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
required: false
|
||||
example: desc
|
||||
AccessType:
|
||||
name: access_type
|
||||
description: Type of access the user has on the domain.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- direct
|
||||
- domain
|
||||
- indirect
|
||||
- indirect_group
|
||||
required: false
|
||||
example: direct
|
||||
OnlyTotal:
|
||||
name: only_total
|
||||
description: If true, the response will contain only the total number of domains that match the query parameters.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
|
||||
requestBodies:
|
||||
DomainCreateReq:
|
||||
@@ -1319,6 +1319,21 @@ components:
|
||||
description: Domain unique identifier.
|
||||
required:
|
||||
- domain_id
|
||||
DeleteInvitationReq:
|
||||
description: JSON-formatted document describing request for deleting invitation
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
user_id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
description: User unique identifier.
|
||||
required:
|
||||
- user_id
|
||||
|
||||
responses:
|
||||
ServiceError:
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9004
|
||||
@@ -88,11 +88,20 @@ paths:
|
||||
- $ref: "auth.yaml#/components/parameters/DomainID"
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
- $ref: "#/components/parameters/Offset"
|
||||
- $ref: "#/components/parameters/Order"
|
||||
- $ref: "#/components/parameters/DirectionOrder"
|
||||
- $ref: "#/components/parameters/Level"
|
||||
- $ref: "#/components/parameters/Tree"
|
||||
- $ref: "#/components/parameters/Metadata"
|
||||
- $ref: "#/components/parameters/GroupName"
|
||||
- $ref: "#/components/parameters/RootGroup"
|
||||
- $ref: "#/components/parameters/Status"
|
||||
- $ref: "#/components/parameters/ID"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/ActionsQuery"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/RoleIDQuery"
|
||||
- $ref: "./schemas/roles.yaml#/components/parameters/RoleNameQuery"
|
||||
- $ref: "#/components/parameters/AccessType"
|
||||
- $ref: "#/components/parameters/OnlyTotal"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/GroupPageRes"
|
||||
@@ -234,7 +243,6 @@ paths:
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
|
||||
/{domainID}/groups/{groupID}/enable:
|
||||
post:
|
||||
operationId: enableGroup
|
||||
@@ -1196,9 +1204,7 @@ components:
|
||||
example: 10
|
||||
description: Maximum number of items to return in one page.
|
||||
required:
|
||||
- groups
|
||||
- total
|
||||
- offset
|
||||
|
||||
GroupsHierarchyPage:
|
||||
type: object
|
||||
@@ -1358,11 +1364,14 @@ components:
|
||||
|
||||
Status:
|
||||
name: status
|
||||
description: User account status.
|
||||
description: Lists groups with the given status.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
default: enabled
|
||||
enum:
|
||||
- enabled
|
||||
- disabled
|
||||
- all
|
||||
required: false
|
||||
example: enabled
|
||||
|
||||
@@ -1529,6 +1538,71 @@ components:
|
||||
minimum: 0
|
||||
required: false
|
||||
example: "0"
|
||||
|
||||
Order:
|
||||
name: order
|
||||
description: Field by which to order the results
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: created_at
|
||||
|
||||
DirectionOrder:
|
||||
name: order
|
||||
description: Direction of ordering the results.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
required: false
|
||||
example: desc
|
||||
|
||||
ID:
|
||||
name: id
|
||||
description: List groups with the given ID.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
AccessType:
|
||||
name: access_type
|
||||
description: Type of access the user has on the group.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- direct
|
||||
- domain
|
||||
- indirect
|
||||
- indirect_group
|
||||
required: false
|
||||
example: direct
|
||||
|
||||
OnlyTotal:
|
||||
name: only_total
|
||||
description: If true, the response will contain only the total number of groups that match the query parameters.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
|
||||
User:
|
||||
name: user
|
||||
description: If provided lists groups associated with a user with the provided ID. Only available for admin users.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
minLength: 36
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
requestBodies:
|
||||
GroupCreateReq:
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8008
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9021
|
||||
|
||||
@@ -11,7 +11,7 @@ properties:
|
||||
version:
|
||||
type: string
|
||||
description: Service version.
|
||||
example: v0.14.0
|
||||
example: v0.18.0
|
||||
commit:
|
||||
type: string
|
||||
description: Service commit hash.
|
||||
|
||||
@@ -6,12 +6,12 @@ components:
|
||||
NewRole:
|
||||
type: object
|
||||
properties:
|
||||
role_id:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
description: Role unique identifier.
|
||||
role_name:
|
||||
name:
|
||||
type: string
|
||||
description: Role's name.
|
||||
example: "roleName"
|
||||
@@ -44,12 +44,12 @@ components:
|
||||
Role:
|
||||
type: object
|
||||
properties:
|
||||
role_id:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
description: Role unique identifier.
|
||||
role_name:
|
||||
name:
|
||||
type: string
|
||||
description: Role's name.
|
||||
example: "roleName"
|
||||
@@ -82,7 +82,7 @@ components:
|
||||
CreateRoleObj:
|
||||
type: object
|
||||
properties:
|
||||
role_name:
|
||||
name:
|
||||
type: string
|
||||
description: Role's name.
|
||||
example: "roleName"
|
||||
@@ -270,6 +270,31 @@ components:
|
||||
type: string
|
||||
required: true
|
||||
example: "roleID"
|
||||
RoleIDQuery:
|
||||
name: role_id
|
||||
description: Filter by role ID.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: false
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
RoleNameQuery:
|
||||
name: role_name
|
||||
description: Filter by role name.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: admin
|
||||
ActionsQuery:
|
||||
name: actions
|
||||
description: Filter by actions. Multiple actions can be specified separated by comma.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: "action1,action2"
|
||||
|
||||
requestBodies:
|
||||
CreateRoleReq:
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9018
|
||||
|
||||
@@ -13,7 +13,7 @@ info:
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://github.com/absmach/supermq/blob/main/LICENSE
|
||||
version: 0.15.1
|
||||
version: 0.18.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:9002
|
||||
@@ -75,6 +75,8 @@ paths:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
- $ref: "#/components/parameters/Offset"
|
||||
- $ref: "#/components/parameters/Order"
|
||||
- $ref: "#/components/parameters/Direction"
|
||||
- $ref: "#/components/parameters/Metadata"
|
||||
- $ref: "#/components/parameters/Status"
|
||||
- $ref: "#/components/parameters/FirstName"
|
||||
@@ -82,6 +84,7 @@ paths:
|
||||
- $ref: "#/components/parameters/Username"
|
||||
- $ref: "#/components/parameters/Email"
|
||||
- $ref: "#/components/parameters/Tag"
|
||||
- $ref: "#/components/parameters/OnlyTotal"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
@@ -607,6 +610,54 @@ paths:
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/users/send-verification:
|
||||
post:
|
||||
operationId: sendVerification
|
||||
tags:
|
||||
- Users
|
||||
summary: Sends a verification email
|
||||
description: |
|
||||
Sends a verification email to the user.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Sent verification email if registered.
|
||||
"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"
|
||||
|
||||
/verify-email:
|
||||
get:
|
||||
operationId: verifyEmail
|
||||
tags:
|
||||
- Users
|
||||
summary: Verify user's email
|
||||
description: |
|
||||
Verify user's email using the token from the verification link.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/VerificationToken"
|
||||
responses:
|
||||
"200":
|
||||
description: Email verified successfully.
|
||||
"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"
|
||||
|
||||
/health:
|
||||
get:
|
||||
operationId: health
|
||||
@@ -818,6 +869,7 @@ components:
|
||||
description: Total number of items.
|
||||
offset:
|
||||
type: integer
|
||||
example: 1
|
||||
description: Number of items to skip during retrieval.
|
||||
limit:
|
||||
type: integer
|
||||
@@ -826,7 +878,7 @@ components:
|
||||
required:
|
||||
- users
|
||||
- total
|
||||
- offset
|
||||
- limit
|
||||
|
||||
UserUpdate:
|
||||
type: object
|
||||
@@ -1239,6 +1291,44 @@ components:
|
||||
required: false
|
||||
example: "0"
|
||||
|
||||
Order:
|
||||
name: order
|
||||
description: Field by which to order the results
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
example: created_at
|
||||
|
||||
Direction:
|
||||
name: dir
|
||||
description: Direction of ordering the results.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
required: false
|
||||
example: desc
|
||||
|
||||
OnlyTotal:
|
||||
name: only_total
|
||||
description: If true, the response will contain only the total number of users that match the query parameters.
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
|
||||
VerificationToken:
|
||||
name: token
|
||||
description: Verification token.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
requestBodies:
|
||||
UserCreateReq:
|
||||
description: JSON-formatted document describing the new user to be registered
|
||||
|
||||
@@ -75,17 +75,17 @@ func (client authGrpcClient) Authenticate(ctx context.Context, token *grpcAuthV1
|
||||
return &grpcAuthV1.AuthNRes{}, grpcapi.DecodeError(err)
|
||||
}
|
||||
ir := res.(authenticateRes)
|
||||
return &grpcAuthV1.AuthNRes{Id: ir.id, UserId: ir.userID, UserRole: uint32(ir.userRole)}, nil
|
||||
return &grpcAuthV1.AuthNRes{Id: ir.id, UserId: ir.userID, UserRole: uint32(ir.userRole), Verified: ir.verified}, nil
|
||||
}
|
||||
|
||||
func encodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func encodeIdentifyRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(authenticateReq)
|
||||
return &grpcAuthV1.AuthNReq{Token: req.token}, nil
|
||||
}
|
||||
|
||||
func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
func decodeIdentifyResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
res := grpcRes.(*grpcAuthV1.AuthNRes)
|
||||
return authenticateRes{id: res.GetId(), userID: res.GetUserId(), userRole: auth.Role(res.UserRole)}, nil
|
||||
return authenticateRes{id: res.GetId(), userID: res.GetUserId(), userRole: auth.Role(res.UserRole), verified: res.GetVerified()}, nil
|
||||
}
|
||||
|
||||
func (client authGrpcClient) AuthenticatePAT(ctx context.Context, token *grpcAuthV1.AuthNReq, _ ...grpc.CallOption) (*grpcAuthV1.AuthNRes, error) {
|
||||
@@ -97,12 +97,12 @@ func (client authGrpcClient) AuthenticatePAT(ctx context.Context, token *grpcAut
|
||||
return &grpcAuthV1.AuthNRes{}, grpcapi.DecodeError(err)
|
||||
}
|
||||
ir := res.(authenticateRes)
|
||||
return &grpcAuthV1.AuthNRes{Id: ir.id, UserId: ir.userID}, nil
|
||||
return &grpcAuthV1.AuthNRes{Id: ir.id, UserId: ir.userID, UserRole: uint32(ir.userRole)}, nil
|
||||
}
|
||||
|
||||
func decodeIdentifyPATResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
func decodeIdentifyPATResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
res := grpcRes.(*grpcAuthV1.AuthNRes)
|
||||
return authenticateRes{id: res.GetId(), userID: res.GetUserId()}, nil
|
||||
return authenticateRes{id: res.GetId(), userID: res.GetUserId(), userRole: auth.Role(res.UserRole)}, nil
|
||||
}
|
||||
|
||||
func (client authGrpcClient) Authorize(ctx context.Context, req *grpcAuthV1.AuthZReq, _ ...grpc.CallOption) (r *grpcAuthV1.AuthZRes, err error) {
|
||||
@@ -127,12 +127,12 @@ func (client authGrpcClient) Authorize(ctx context.Context, req *grpcAuthV1.Auth
|
||||
return &grpcAuthV1.AuthZRes{Authorized: ar.authorized, Id: ar.id}, nil
|
||||
}
|
||||
|
||||
func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
func decodeAuthorizeResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
res := grpcRes.(*grpcAuthV1.AuthZRes)
|
||||
return authorizeRes{authorized: res.Authorized, id: res.Id}, nil
|
||||
}
|
||||
|
||||
func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func encodeAuthorizeRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(authReq)
|
||||
return &grpcAuthV1.AuthZReq{
|
||||
Domain: req.Domain,
|
||||
@@ -166,7 +166,7 @@ func (client authGrpcClient) AuthorizePAT(ctx context.Context, req *grpcAuthV1.A
|
||||
return &grpcAuthV1.AuthZRes{Authorized: ar.authorized, Id: ar.id}, nil
|
||||
}
|
||||
|
||||
func encodeAuthorizePATRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func encodeAuthorizePATRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(authPATReq)
|
||||
return &grpcAuthV1.AuthZPatReq{
|
||||
UserId: req.userID,
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func authenticateEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(authenticateReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return authenticateRes{}, err
|
||||
@@ -23,12 +23,12 @@ func authenticateEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return authenticateRes{}, err
|
||||
}
|
||||
|
||||
return authenticateRes{userID: key.Subject, userRole: key.Role}, nil
|
||||
return authenticateRes{userID: key.Subject, userRole: key.Role, verified: key.Verified}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func authenticatePATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(authenticateReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return authenticateRes{}, err
|
||||
@@ -39,12 +39,12 @@ func authenticatePATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return authenticateRes{}, err
|
||||
}
|
||||
|
||||
return authenticateRes{id: pat.ID, userID: pat.User}, nil
|
||||
return authenticateRes{id: pat.ID, userID: pat.User, userRole: pat.Role}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func authorizeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(authReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
@@ -68,7 +68,7 @@ func authorizeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func authorizePATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(authPATReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ type authenticateRes struct {
|
||||
id string
|
||||
userID string
|
||||
userRole smqauth.Role
|
||||
verified bool
|
||||
}
|
||||
|
||||
type authorizeRes struct {
|
||||
|
||||
@@ -75,22 +75,22 @@ func (s *authGrpcServer) Authorize(ctx context.Context, req *grpcAuthV1.AuthZReq
|
||||
return res.(*grpcAuthV1.AuthZRes), nil
|
||||
}
|
||||
|
||||
func decodeAuthenticateRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func decodeAuthenticateRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(*grpcAuthV1.AuthNReq)
|
||||
return authenticateReq{token: req.GetToken()}, nil
|
||||
}
|
||||
|
||||
func encodeAuthenticateResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
func encodeAuthenticateResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
res := grpcRes.(authenticateRes)
|
||||
return &grpcAuthV1.AuthNRes{Id: res.id, UserId: res.userID, UserRole: uint32(res.userRole), Verified: res.verified}, nil
|
||||
}
|
||||
|
||||
func encodeAuthenticatePATResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
res := grpcRes.(authenticateRes)
|
||||
return &grpcAuthV1.AuthNRes{Id: res.id, UserId: res.userID, UserRole: uint32(res.userRole)}, nil
|
||||
}
|
||||
|
||||
func encodeAuthenticatePATResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
res := grpcRes.(authenticateRes)
|
||||
return &grpcAuthV1.AuthNRes{Id: res.id, UserId: res.userID}, nil
|
||||
}
|
||||
|
||||
func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func decodeAuthorizeRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(*grpcAuthV1.AuthZReq)
|
||||
return authReq{
|
||||
Domain: req.GetDomain(),
|
||||
@@ -104,12 +104,12 @@ func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
func encodeAuthorizeResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
res := grpcRes.(authorizeRes)
|
||||
return &grpcAuthV1.AuthZRes{Authorized: res.authorized, Id: res.id}, nil
|
||||
}
|
||||
|
||||
func decodeAuthorizePATRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func decodeAuthorizePATRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(*grpcAuthV1.AuthZPatReq)
|
||||
return authPATReq{
|
||||
userID: req.GetUserId(),
|
||||
|
||||
@@ -56,6 +56,7 @@ func (client tokenGrpcClient) Issue(ctx context.Context, req *grpcTokenV1.IssueR
|
||||
userID: req.GetUserId(),
|
||||
userRole: auth.Role(req.GetUserRole()),
|
||||
keyType: auth.KeyType(req.GetType()),
|
||||
verified: req.GetVerified(),
|
||||
})
|
||||
if err != nil {
|
||||
return &grpcTokenV1.Token{}, grpcapi.DecodeError(err)
|
||||
@@ -63,16 +64,17 @@ func (client tokenGrpcClient) Issue(ctx context.Context, req *grpcTokenV1.IssueR
|
||||
return res.(*grpcTokenV1.Token), nil
|
||||
}
|
||||
|
||||
func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func encodeIssueRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(issueReq)
|
||||
return &grpcTokenV1.IssueReq{
|
||||
UserId: req.userID,
|
||||
UserRole: uint32(req.userRole),
|
||||
Type: uint32(req.keyType),
|
||||
Verified: req.verified,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
func decodeIssueResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
return grpcRes, nil
|
||||
}
|
||||
|
||||
@@ -80,18 +82,18 @@ func (client tokenGrpcClient) Refresh(ctx context.Context, req *grpcTokenV1.Refr
|
||||
ctx, cancel := context.WithTimeout(ctx, client.timeout)
|
||||
defer cancel()
|
||||
|
||||
res, err := client.refresh(ctx, refreshReq{refreshToken: req.GetRefreshToken()})
|
||||
res, err := client.refresh(ctx, refreshReq{refreshToken: req.GetRefreshToken(), verified: req.GetVerified()})
|
||||
if err != nil {
|
||||
return &grpcTokenV1.Token{}, grpcapi.DecodeError(err)
|
||||
}
|
||||
return res.(*grpcTokenV1.Token), nil
|
||||
}
|
||||
|
||||
func encodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func encodeRefreshRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(refreshReq)
|
||||
return &grpcTokenV1.RefreshReq{RefreshToken: req.refreshToken}, nil
|
||||
return &grpcTokenV1.RefreshReq{RefreshToken: req.refreshToken, Verified: req.verified}, nil
|
||||
}
|
||||
|
||||
func decodeRefreshResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
func decodeRefreshResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
return grpcRes, nil
|
||||
}
|
||||
|
||||
@@ -11,16 +11,17 @@ import (
|
||||
)
|
||||
|
||||
func issueEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(issueReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return issueRes{}, err
|
||||
}
|
||||
|
||||
key := auth.Key{
|
||||
Type: req.keyType,
|
||||
Subject: req.userID,
|
||||
Role: req.userRole,
|
||||
Type: req.keyType,
|
||||
Subject: req.userID,
|
||||
Role: req.userRole,
|
||||
Verified: req.verified,
|
||||
}
|
||||
tkn, err := svc.Issue(ctx, "", key)
|
||||
if err != nil {
|
||||
@@ -36,13 +37,13 @@ func issueEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func refreshEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(refreshReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return issueRes{}, err
|
||||
}
|
||||
|
||||
key := auth.Key{Type: auth.RefreshKey}
|
||||
key := auth.Key{Type: auth.RefreshKey, Verified: req.verified}
|
||||
tkn, err := svc.Issue(ctx, req.refreshToken, key)
|
||||
if err != nil {
|
||||
return issueRes{}, err
|
||||
|
||||
@@ -12,6 +12,7 @@ type issueReq struct {
|
||||
userID string
|
||||
userRole auth.Role
|
||||
keyType auth.KeyType
|
||||
verified bool
|
||||
}
|
||||
|
||||
func (req issueReq) validate() error {
|
||||
@@ -27,6 +28,7 @@ func (req issueReq) validate() error {
|
||||
|
||||
type refreshReq struct {
|
||||
refreshToken string
|
||||
verified bool
|
||||
}
|
||||
|
||||
func (req refreshReq) validate() error {
|
||||
|
||||
@@ -52,21 +52,22 @@ func (s *tokenGrpcServer) Refresh(ctx context.Context, req *grpcTokenV1.RefreshR
|
||||
return res.(*grpcTokenV1.Token), nil
|
||||
}
|
||||
|
||||
func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func decodeIssueRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(*grpcTokenV1.IssueReq)
|
||||
return issueReq{
|
||||
userID: req.GetUserId(),
|
||||
userRole: auth.Role(req.GetUserRole()),
|
||||
keyType: auth.KeyType(req.GetType()),
|
||||
verified: req.Verified,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
func decodeRefreshRequest(_ context.Context, grpcReq any) (any, error) {
|
||||
req := grpcReq.(*grpcTokenV1.RefreshReq)
|
||||
return refreshReq{refreshToken: req.GetRefreshToken()}, nil
|
||||
return refreshReq{refreshToken: req.GetRefreshToken(), verified: req.Verified}, nil
|
||||
}
|
||||
|
||||
func encodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
func encodeIssueResponse(_ context.Context, grpcRes any) (any, error) {
|
||||
res := grpcRes.(issueRes)
|
||||
|
||||
return &grpcTokenV1.Token{
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func issueEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(issueKeyReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -44,7 +44,7 @@ func issueEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func retrieveEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(keyReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
@@ -71,7 +71,7 @@ func retrieveEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func revokeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(keyReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
|
||||
@@ -90,7 +90,7 @@ func newServer(svc auth.Service) *httptest.Server {
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
func toJSON(data interface{}) string {
|
||||
func toJSON(data any) string {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return ""
|
||||
|
||||
@@ -50,7 +50,7 @@ func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux {
|
||||
return mux
|
||||
}
|
||||
|
||||
func decodeIssue(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeIssue(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func decodeIssue(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeKeyReq(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeKeyReq(_ context.Context, r *http.Request) (any, error) {
|
||||
req := keyReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
id: chi.URLParam(r, "id"),
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func createPATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(createPatReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -27,7 +27,7 @@ func createPATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func retrievePATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(retrievePatReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -43,7 +43,7 @@ func retrievePATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func updatePATNameEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(updatePatNameReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -59,7 +59,7 @@ func updatePATNameEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func updatePATDescriptionEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(updatePatDescriptionReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -75,7 +75,7 @@ func updatePATDescriptionEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func listPATSEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(listPatsReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -98,7 +98,7 @@ func listPATSEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func deletePATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(deletePatReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -113,7 +113,7 @@ func deletePATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func resetPATSecretEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(resetPatSecretReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -129,7 +129,7 @@ func resetPATSecretEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func revokePATSecretEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(revokePatSecretReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -144,7 +144,7 @@ func revokePATSecretEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func clearAllPATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(clearAllPATReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -159,7 +159,7 @@ func clearAllPATEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func addScopeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(addScopeReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -174,7 +174,7 @@ func addScopeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func removeScopeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(removeScopeReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -189,7 +189,7 @@ func removeScopeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func clearAllScopeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(clearAllScopeReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -204,7 +204,7 @@ func clearAllScopeEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func listScopesEndpoint(svc auth.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
req := request.(listScopesReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -130,7 +130,7 @@ func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux {
|
||||
return mux
|
||||
}
|
||||
|
||||
func decodeCreatePATRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeCreatePATRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
@@ -145,7 +145,7 @@ func decodeCreatePATRequest(_ context.Context, r *http.Request) (interface{}, er
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeRetrievePATRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeRetrievePATRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
token := apiutil.ExtractBearerToken(r)
|
||||
if strings.HasPrefix(token, patPrefix) {
|
||||
return nil, apiutil.ErrUnsupportedTokenType
|
||||
@@ -158,7 +158,7 @@ func decodeRetrievePATRequest(_ context.Context, r *http.Request) (interface{},
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeUpdatePATNameRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeUpdatePATNameRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
@@ -176,7 +176,7 @@ func decodeUpdatePATNameRequest(_ context.Context, r *http.Request) (interface{}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeUpdatePATDescriptionRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeUpdatePATDescriptionRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
@@ -195,7 +195,7 @@ func decodeUpdatePATDescriptionRequest(_ context.Context, r *http.Request) (inte
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeListPATSRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeListPATSRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
@@ -237,7 +237,7 @@ func decodeListPATSRequest(_ context.Context, r *http.Request) (interface{}, err
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeDeletePATRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeDeletePATRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
token := apiutil.ExtractBearerToken(r)
|
||||
if strings.HasPrefix(token, patPrefix) {
|
||||
return nil, apiutil.ErrUnsupportedTokenType
|
||||
@@ -248,7 +248,7 @@ func decodeDeletePATRequest(_ context.Context, r *http.Request) (interface{}, er
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeResetPATSecretRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeResetPATSecretRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
@@ -267,7 +267,7 @@ func decodeResetPATSecretRequest(_ context.Context, r *http.Request) (interface{
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeRevokePATSecretRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeRevokePATSecretRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
token := apiutil.ExtractBearerToken(r)
|
||||
if strings.HasPrefix(token, patPrefix) {
|
||||
return nil, apiutil.ErrUnsupportedTokenType
|
||||
@@ -278,7 +278,7 @@ func decodeRevokePATSecretRequest(_ context.Context, r *http.Request) (interface
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeClearAllPATRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeClearAllPATRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
token := apiutil.ExtractBearerToken(r)
|
||||
if strings.HasPrefix(token, patPrefix) {
|
||||
return nil, apiutil.ErrUnsupportedTokenType
|
||||
@@ -289,7 +289,7 @@ func decodeClearAllPATRequest(_ context.Context, r *http.Request) (interface{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeAddScopeRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeAddScopeRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
@@ -311,7 +311,7 @@ func decodeAddScopeRequest(_ context.Context, r *http.Request) (interface{}, err
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeListScopeRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeListScopeRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
l, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
@@ -333,7 +333,7 @@ func decodeListScopeRequest(_ context.Context, r *http.Request) (interface{}, er
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeRemoveScopeRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeRemoveScopeRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, apiutil.ErrUnsupportedContentType
|
||||
}
|
||||
@@ -353,7 +353,7 @@ func decodeRemoveScopeRequest(_ context.Context, r *http.Request) (interface{},
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeClearAllScopeRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
func decodeClearAllScopeRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
token := apiutil.ExtractBearerToken(r)
|
||||
if strings.HasPrefix(token, patPrefix) {
|
||||
return nil, apiutil.ErrUnsupportedTokenType
|
||||
|
||||
@@ -21,6 +21,8 @@ var (
|
||||
errInvalidType = errors.New("invalid token type")
|
||||
// errInvalidRole is returned when the role is invalid.
|
||||
errInvalidRole = errors.New("invalid role")
|
||||
// errInvalidVerified is returned when the verified is invalid.
|
||||
errInvalidVerified = errors.New("invalid verified")
|
||||
// errJWTExpiryKey is used to check if the token is expired.
|
||||
errJWTExpiryKey = errors.New(`"exp" not satisfied`)
|
||||
// ErrSignJWT indicates an error in signing jwt token.
|
||||
@@ -36,6 +38,7 @@ const (
|
||||
tokenType = "type"
|
||||
userField = "user"
|
||||
RoleField = "role"
|
||||
VerifiedField = "verified"
|
||||
oauthProviderField = "oauth_provider"
|
||||
oauthAccessTokenField = "access_token"
|
||||
oauthRefreshTokenField = "refresh_token"
|
||||
@@ -62,6 +65,7 @@ func (tok *tokenizer) Issue(key auth.Key) (string, error) {
|
||||
Claim(tokenType, key.Type).
|
||||
Expiration(key.ExpiresAt)
|
||||
builder.Claim(RoleField, key.Role)
|
||||
builder.Claim(VerifiedField, key.Verified)
|
||||
if key.Subject != "" {
|
||||
builder.Subject(key.Subject)
|
||||
}
|
||||
@@ -150,6 +154,16 @@ func toKey(tkn jwt.Token) (auth.Key, error) {
|
||||
if !ok {
|
||||
return auth.Key{}, errInvalidRole
|
||||
}
|
||||
|
||||
tVerified, ok := tkn.Get(VerifiedField)
|
||||
if !ok {
|
||||
return auth.Key{}, errInvalidVerified
|
||||
}
|
||||
kVerified, ok := tVerified.(bool)
|
||||
if !ok {
|
||||
return auth.Key{}, errInvalidVerified
|
||||
}
|
||||
|
||||
kr := auth.Role(kRole)
|
||||
if !kr.Validate() {
|
||||
return auth.Key{}, errInvalidRole
|
||||
@@ -162,6 +176,7 @@ func toKey(tkn jwt.Token) (auth.Key, error) {
|
||||
key.Subject = tkn.Subject()
|
||||
key.IssuedAt = tkn.IssuedAt()
|
||||
key.ExpiresAt = tkn.Expiration()
|
||||
key.Verified = kVerified
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ type Key struct {
|
||||
Role Role `json:"role,omitempty"`
|
||||
IssuedAt time.Time `json:"issued_at,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
}
|
||||
|
||||
func (key Key) String() string {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package middleware provides logging metrics and tracing middleware
|
||||
// for SuperMQ Auth service.
|
||||
//
|
||||
// For more details about tracing instrumentation for SuperMQ refer to the
|
||||
// documentation at https://docs.supermq.abstractmachines.fr/tracing/.
|
||||
package middleware
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
//go:build !test
|
||||
|
||||
package api
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -21,8 +21,8 @@ type loggingMiddleware struct {
|
||||
svc auth.Service
|
||||
}
|
||||
|
||||
// LoggingMiddleware adds logging facilities to the core service.
|
||||
func LoggingMiddleware(svc auth.Service, logger *slog.Logger) auth.Service {
|
||||
// NewLogging adds logging facilities to the core service.
|
||||
func NewLogging(svc auth.Service, logger *slog.Logger) auth.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func (lm *loggingMiddleware) Issue(ctx context.Context, token string, key auth.K
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.Group("key",
|
||||
slog.String("subject", key.Subject),
|
||||
slog.Any("type", key.Type),
|
||||
slog.String("type", key.Type.String()),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
@@ -86,7 +86,7 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id aut
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.Group("key",
|
||||
slog.String("subject", id.Subject),
|
||||
slog.Any("type", id.Type),
|
||||
slog.String("type", id.Type.String()),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
//go:build !test
|
||||
|
||||
package api
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -22,8 +22,8 @@ type metricsMiddleware struct {
|
||||
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 {
|
||||
// NewMetrics instruments core service by tracking request count and latency.
|
||||
func NewMetrics(svc auth.Service, counter metrics.Counter, latency metrics.Histogram) auth.Service {
|
||||
return &metricsMiddleware{
|
||||
counter: counter,
|
||||
latency: latency,
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package tracing
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -21,8 +21,8 @@ type tracingMiddleware struct {
|
||||
svc auth.Service
|
||||
}
|
||||
|
||||
// New returns a new group service with tracing capabilities.
|
||||
func New(svc auth.Service, tracer trace.Tracer) auth.Service {
|
||||
// NewTracing returns a new auth service with tracing capabilities.
|
||||
func NewTracing(svc auth.Service, tracer trace.Tracer) auth.Service {
|
||||
return &tracingMiddleware{tracer, svc}
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,10 +1,11 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
|
||||
+4
-3
@@ -1,10 +1,11 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
|
||||
+4
-3
@@ -1,10 +1,11 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
|
||||
+15
@@ -296,6 +296,7 @@ type PAT struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
Role Role `json:"role,omitempty"`
|
||||
IssuedAt time.Time `json:"issued_at,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
@@ -341,6 +342,20 @@ func (pat *PAT) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, pat)
|
||||
}
|
||||
|
||||
// Validate checks if the PAT has valid fields.
|
||||
func (pat *PAT) Validate() error {
|
||||
if pat == nil {
|
||||
return errors.New("PAT cannot be nil")
|
||||
}
|
||||
if pat.Name == "" {
|
||||
return errors.New("PAT name cannot be empty")
|
||||
}
|
||||
if pat.User == "" {
|
||||
return errors.New("PAT user cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pat *PAT) String() string {
|
||||
str, err := json.MarshalIndent(pat, "", " ")
|
||||
if err != nil {
|
||||
|
||||
+17
-8
@@ -45,10 +45,11 @@ func (pr *patRepo) Save(ctx context.Context, pat auth.PAT) error {
|
||||
return errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
_, err = pr.db.NamedQueryContext(ctx, q, dbPat)
|
||||
rows, err := pr.db.NamedQueryContext(ctx, q, dbPat)
|
||||
if err != nil {
|
||||
return postgres.HandleError(repoerr.ErrCreateEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -306,10 +307,11 @@ func (pr *patRepo) Revoke(ctx context.Context, userID, patID string) error {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := pr.db.NamedQueryContext(ctx, q, upm)
|
||||
rows, err := pr.db.NamedQueryContext(ctx, q, upm)
|
||||
if err != nil {
|
||||
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -325,10 +327,11 @@ func (pr *patRepo) Reactivate(ctx context.Context, userID, patID string) error {
|
||||
ID: patID,
|
||||
}
|
||||
|
||||
_, err := pr.db.NamedQueryContext(ctx, q, upm)
|
||||
rows, err := pr.db.NamedQueryContext(ctx, q, upm)
|
||||
if err != nil {
|
||||
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -340,10 +343,11 @@ func (pr *patRepo) Remove(ctx context.Context, userID, patID string) error {
|
||||
ID: patID,
|
||||
}
|
||||
|
||||
_, err := pr.db.NamedQueryContext(ctx, q, upm)
|
||||
rows, err := pr.db.NamedQueryContext(ctx, q, upm)
|
||||
if err != nil {
|
||||
return postgres.HandleError(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -355,10 +359,11 @@ func (pr *patRepo) RemoveAllPAT(ctx context.Context, userID string) error {
|
||||
User: userID,
|
||||
}
|
||||
|
||||
_, err := pr.db.NamedQueryContext(ctx, q, pm)
|
||||
rows, err := pr.db.NamedQueryContext(ctx, q, pm)
|
||||
if err != nil {
|
||||
return postgres.HandleError(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if err := pr.cache.RemoveUserAllScope(ctx, userID); err != nil {
|
||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||
@@ -385,10 +390,11 @@ func (pr *patRepo) AddScope(ctx context.Context, userID string, scopes []auth.Sc
|
||||
}
|
||||
|
||||
if len(newScopes) > 0 {
|
||||
_, err := pr.db.NamedQueryContext(ctx, q, toDBScope(newScopes))
|
||||
rows, err := pr.db.NamedQueryContext(ctx, q, toDBScope(newScopes))
|
||||
if err != nil {
|
||||
return postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
}
|
||||
|
||||
if err := pr.cache.Save(ctx, userID, scopes); err != nil {
|
||||
@@ -473,10 +479,12 @@ func (pr *patRepo) processScope(ctx context.Context, sc auth.Scope) (auth.Scope,
|
||||
AND optional_domain_id = :optional_domain_id
|
||||
AND operation = :operation`
|
||||
|
||||
_, err = pr.db.NamedQueryContext(ctx, updateWithWildcardQuery, params)
|
||||
rows, err = pr.db.NamedQueryContext(ctx, updateWithWildcardQuery, params)
|
||||
if err != nil {
|
||||
return auth.Scope{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return auth.Scope{}, nil
|
||||
}
|
||||
}
|
||||
@@ -575,10 +583,11 @@ func (pr *patRepo) RemoveAllScope(ctx context.Context, patID string) error {
|
||||
|
||||
q := `DELETE FROM pat_scopes WHERE pat_id = :pat_id`
|
||||
|
||||
_, err := pr.db.NamedQueryContext(ctx, q, pm)
|
||||
rows, err := pr.db.NamedQueryContext(ctx, q, pm)
|
||||
if err != nil {
|
||||
return postgres.HandleError(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if err := pr.cache.RemoveAllScope(ctx, pm.User, patID); err != nil {
|
||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||
|
||||
+21
-1
@@ -369,6 +369,21 @@ func (svc service) checkUserRole(ctx context.Context, key Key) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) getUserRole(ctx context.Context, userID string) (role Role) {
|
||||
rl := UserRole
|
||||
if err := svc.Authorize(ctx, policies.Policy{
|
||||
Subject: userID,
|
||||
SubjectType: policies.UserType,
|
||||
Permission: policies.AdminPermission,
|
||||
Object: policies.SuperMQObject,
|
||||
ObjectType: policies.PlatformType,
|
||||
}); err == nil {
|
||||
rl = AdminRole
|
||||
}
|
||||
|
||||
return rl
|
||||
}
|
||||
|
||||
func (svc service) userKey(ctx context.Context, token string, key Key) (Token, error) {
|
||||
id, sub, err := svc.authenticate(token)
|
||||
if err != nil {
|
||||
@@ -485,6 +500,10 @@ func (svc service) CreatePAT(ctx context.Context, token, name, description strin
|
||||
Revoked: false,
|
||||
}
|
||||
|
||||
if err := pat.Validate(); err != nil {
|
||||
return PAT{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
if err := svc.pats.Save(ctx, pat); err != nil {
|
||||
return PAT{}, errors.Wrap(errCreatePAT, err)
|
||||
}
|
||||
@@ -682,7 +701,8 @@ func (svc service) IdentifyPAT(ctx context.Context, secret string) (PAT, error)
|
||||
if err := svc.hasher.Compare(secret, secretHash); err != nil {
|
||||
return PAT{}, errors.Wrap(svcerr.ErrAuthentication, err)
|
||||
}
|
||||
return PAT{ID: patID.String(), User: userID.String()}, nil
|
||||
role := svc.getUserRole(ctx, userID.String())
|
||||
return PAT{ID: patID.String(), User: userID.String(), Role: role}, nil
|
||||
}
|
||||
|
||||
func (svc service) AuthorizePAT(ctx context.Context, userID, patID string, entityType EntityType, optionalDomainID string, operation Operation, entityID string) error {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package tracing provides tracing instrumentation for SuperMQ Users service.
|
||||
//
|
||||
// This package provides tracing middleware for SuperMQ Users service.
|
||||
// It can be used to trace incoming requests and add tracing capabilities to
|
||||
// SuperMQ Users service.
|
||||
//
|
||||
// For more details about tracing instrumentation for SuperMQ messaging refer
|
||||
// to the documentation at https://docs.supermq.abstractmachines.fr/tracing/.
|
||||
package tracing
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
# Certs Service
|
||||
|
||||
Issues certificates for clients. `Certs` service can create certificates to be used when `SuperMQ` is deployed to support mTLS.
|
||||
Certificate service can create certificates using PKI mode - where certificates issued by PKI, when you deploy `OpenBao` as PKI certificate management `cert` service will proxy requests to `OpenBao` previously checking access rights and saving info on successfully created certificate.
|
||||
|
||||
## PKI mode
|
||||
|
||||
When `SMQ_CERTS_OPENBAO_HOST` is set it is presumed that `OpenBao` is installed and `certs` service will issue certificates using `OpenBao` API.
|
||||
First you'll need to set up `OpenBao`.
|
||||
To setup `OpenBao` follow steps in the [OpenBao PKI Documentation](https://openbao.org/docs/secrets/pki/).
|
||||
|
||||
For lab purposes you can use docker-compose and script for setting up PKI in [https://github.com/absmach/supermq/blob/main/docker/addons/certs/README.md](https://github.com/absmach/supermq/blob/main/docker/addons/certs/README.md)
|
||||
|
||||
```bash
|
||||
SMQ_CERTS_OPENBAO_HOST=<https://openbao-domain:8200>
|
||||
SMQ_CERTS_OPENBAO_NAMESPACE=<openbao_namespace>
|
||||
SMQ_CERTS_OPENBAO_APP_ROLE=<openbao_app_role>
|
||||
SMQ_CERTS_OPENBAO_APP_SECRET=<openbao_app_secret>
|
||||
SMQ_CERTS_OPENBAO_PKI_PATH=<openbao_pki_path>
|
||||
SMQ_CERTS_OPENBAO_ROLE=<openbao_role_name>
|
||||
```
|
||||
|
||||
The certificates can also be revoked using `certs` service. To revoke a certificate you need to provide `client_id` of the client for which the certificate was issued.
|
||||
|
||||
```bash
|
||||
curl -s -S -X DELETE http://localhost:9019/certs/revoke -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"client_id":"c30b8842-507c-4bcd-973c-74008cef3be5"}'
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| :------------------------------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| SMQ_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info |
|
||||
| SMQ_CERTS_HTTP_HOST | Service Certs host | "" |
|
||||
| SMQ_CERTS_HTTP_PORT | Service Certs port | 9019 |
|
||||
| SMQ_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" |
|
||||
| SMQ_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" |
|
||||
| SMQ_AUTH_GRPC_URL | Auth service gRPC URL | [localhost:8181](localhost:8181) |
|
||||
| SMQ_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s |
|
||||
| SMQ_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" |
|
||||
| SMQ_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" |
|
||||
| SMQ_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" |
|
||||
| SMQ_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt |
|
||||
| SMQ_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key |
|
||||
| SMQ_CERTS_OPENBAO_HOST | OpenBao host | http://localhost:8200 |
|
||||
| SMQ_CERTS_OPENBAO_NAMESPACE | OpenBao namespace in which pki is present | "" |
|
||||
| SMQ_CERTS_OPENBAO_APP_ROLE | OpenBao AppRole auth RoleID | "" |
|
||||
| SMQ_CERTS_OPENBAO_APP_SECRET | OpenBao AppRole auth Secret | "" |
|
||||
| SMQ_CERTS_OPENBAO_PKI_PATH | OpenBao PKI path for issuing Clients Certificates | pki |
|
||||
| SMQ_CERTS_OPENBAO_ROLE | OpenBao PKI Role Name for issuing Clients Certificates | supermq |
|
||||
| SMQ_CERTS_DB_HOST | Database host | localhost |
|
||||
| SMQ_CERTS_DB_PORT | Database port | 5432 |
|
||||
| SMQ_CERTS_DB_PASS | Database password | supermq |
|
||||
| SMQ_CERTS_DB_USER | Database user | supermq |
|
||||
| SMQ_CERTS_DB_NAME | Database name | certs |
|
||||
| SMQ_CERTS_DB_SSL_MODE | Database SSL mode | disable |
|
||||
| SMQ_CERTS_DB_SSL_CERT | Database SSL certificate | "" |
|
||||
| SMQ_CERTS_DB_SSL_KEY | Database SSL key | "" |
|
||||
| SMQ_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" |
|
||||
| SMQ_CLIENTS_URL | Clients service URL | [localhost:9000](localhost:9000) |
|
||||
| SMQ_JAEGER_URL | Jaeger server URL | [http://localhost:4318/v1/traces](http://localhost:4318//v1/traces) |
|
||||
| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 |
|
||||
| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true |
|
||||
| SMQ_CERTS_INSTANCE_ID | Service instance ID | "" |
|
||||
|
||||
## Deployment
|
||||
|
||||
The service is distributed as Docker container. Check the [`certs`](https://github.com/absmach/supermq/blob/main/docker/addons/certs/docker-compose.yaml) service section in docker-compose file to see how the service is deployed.
|
||||
|
||||
Running this service outside of container requires working instance of the auth service, clients service, postgres database, OpenBao 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/supermq
|
||||
|
||||
cd supermq
|
||||
|
||||
# compile the certs
|
||||
make certs
|
||||
|
||||
# copy binary to bin
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
SMQ_CERTS_LOG_LEVEL=info \
|
||||
SMQ_CERTS_HTTP_HOST=localhost \
|
||||
SMQ_CERTS_HTTP_PORT=9019 \
|
||||
SMQ_CERTS_HTTP_SERVER_CERT="" \
|
||||
SMQ_CERTS_HTTP_SERVER_KEY="" \
|
||||
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_CERTS_SIGN_CA_PATH=ca.crt \
|
||||
SMQ_CERTS_SIGN_CA_KEY_PATH=ca.key \
|
||||
SMQ_CERTS_OPENBAO_HOST=http://localhost:8200 \
|
||||
SMQ_CERTS_OPENBAO_NAMESPACE="" \
|
||||
SMQ_CERTS_OPENBAO_APP_ROLE=supermq \
|
||||
SMQ_CERTS_OPENBAO_APP_SECRET=supermq \
|
||||
SMQ_CERTS_OPENBAO_PKI_PATH=pki \
|
||||
SMQ_CERTS_OPENBAO_ROLE=supermq \
|
||||
SMQ_CERTS_DB_HOST=localhost \
|
||||
SMQ_CERTS_DB_PORT=5432 \
|
||||
SMQ_CERTS_DB_PASS=supermq \
|
||||
SMQ_CERTS_DB_USER=supermq \
|
||||
SMQ_CERTS_DB_NAME=certs \
|
||||
SMQ_CERTS_DB_SSL_MODE=disable \
|
||||
SMQ_CERTS_DB_SSL_CERT="" \
|
||||
SMQ_CERTS_DB_SSL_KEY="" \
|
||||
SMQ_CERTS_DB_SSL_ROOT_CERT="" \
|
||||
SMQ_CLIENTS_URL=localhost:9000 \
|
||||
SMQ_JAEGER_URL=http://localhost:14268/api/traces \
|
||||
SMQ_JAEGER_TRACE_RATIO=1.0 \
|
||||
SMQ_SEND_TELEMETRY=true \
|
||||
SMQ_CERTS_INSTANCE_ID="" \
|
||||
$GOBIN/supermq-certs
|
||||
```
|
||||
|
||||
Setting `SMQ_CERTS_HTTP_SERVER_CERT` and `SMQ_CERTS_HTTP_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key.
|
||||
|
||||
Setting `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
|
||||
|
||||
For more information about service capabilities and its usage, please check out the [Certs section](https://docs.supermq.abstractmachines.fr/certs/).
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package api contains implementation of certs service HTTP API.
|
||||
package api
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
func issueCert(svc certs.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(addCertsReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
res, err := svc.IssueCert(ctx, req.domainID, req.token, req.ClientID, req.TTL)
|
||||
if err != nil {
|
||||
return certsRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
return certsRes{
|
||||
SerialNumber: res.SerialNumber,
|
||||
ClientID: res.ClientID,
|
||||
Certificate: res.Certificate,
|
||||
Key: res.Key,
|
||||
CAChain: res.CAChain,
|
||||
IssuingCA: res.IssuingCA,
|
||||
ExpiryTime: res.ExpiryTime,
|
||||
Revoked: res.Revoked,
|
||||
issued: true,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func listSerials(svc certs.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(listReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
page, err := svc.ListSerials(ctx, req.clientID, req.pm)
|
||||
if err != nil {
|
||||
return certsPageRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
res := certsPageRes{
|
||||
pageRes: pageRes{
|
||||
Total: page.Total,
|
||||
Offset: page.Offset,
|
||||
Limit: page.Limit,
|
||||
},
|
||||
Certs: []certsRes{},
|
||||
}
|
||||
|
||||
for _, cert := range page.Certificates {
|
||||
cr := certsRes{
|
||||
SerialNumber: cert.SerialNumber,
|
||||
ExpiryTime: cert.ExpiryTime,
|
||||
ClientID: cert.ClientID,
|
||||
Revoked: cert.Revoked,
|
||||
}
|
||||
res.Certs = append(res.Certs, cr)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
func viewCert(svc certs.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(viewReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return certsRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
cert, err := svc.ViewCert(ctx, req.serialID)
|
||||
if err != nil {
|
||||
return certsRes{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
return certsRes{
|
||||
ClientID: cert.ClientID,
|
||||
Certificate: cert.Certificate,
|
||||
Key: cert.Key,
|
||||
SerialNumber: cert.SerialNumber,
|
||||
ExpiryTime: cert.ExpiryTime,
|
||||
Revoked: cert.Revoked,
|
||||
issued: false,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func revokeAllCerts(svc certs.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(revokeAllReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
res, err := svc.RevokeCert(ctx, req.domainID, req.token, req.clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return revokeCertsRes{
|
||||
RevocationTime: res.RevocationTime,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func revokeBySerial(svc certs.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(revokeBySerialReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
res, err := svc.RevokeBySerial(ctx, req.serialID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return revokeCertsRes{
|
||||
RevocationTime: res.RevocationTime,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,787 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/absmach/supermq/certs/api"
|
||||
"github.com/absmach/supermq/certs/mocks"
|
||||
"github.com/absmach/supermq/internal/testsutil"
|
||||
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/absmach/supermq/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
contentType = "application/json"
|
||||
valid = "valid"
|
||||
invalid = "invalid"
|
||||
clientID = testsutil.GenerateUUID(&testing.T{})
|
||||
serial = testsutil.GenerateUUID(&testing.T{})
|
||||
ttl = "1h"
|
||||
cert = certs.Cert{
|
||||
ClientID: clientID,
|
||||
SerialNumber: serial,
|
||||
ExpiryTime: time.Now().Add(time.Hour),
|
||||
}
|
||||
validID = testsutil.GenerateUUID(&testing.T{})
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
client *http.Client
|
||||
method string
|
||||
url string
|
||||
contentType string
|
||||
token string
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
func (tr testRequest) make() (*http.Response, error) {
|
||||
req, err := http.NewRequest(tr.method, tr.url, tr.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tr.token != "" {
|
||||
req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token)
|
||||
}
|
||||
if tr.contentType != "" {
|
||||
req.Header.Set("Content-Type", tr.contentType)
|
||||
}
|
||||
|
||||
return tr.client.Do(req)
|
||||
}
|
||||
|
||||
func newCertServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
|
||||
svc := new(mocks.Service)
|
||||
logger := smqlog.NewMock()
|
||||
idp := uuid.NewMock()
|
||||
authn := new(authnmocks.Authentication)
|
||||
mux := api.MakeHandler(svc, authn, logger, "", idp)
|
||||
|
||||
return httptest.NewServer(mux), svc, authn
|
||||
}
|
||||
|
||||
func TestIssueCert(t *testing.T) {
|
||||
cs, svc, auth := newCertServer()
|
||||
defer cs.Close()
|
||||
|
||||
validReqString := `{"client_id": "%s","ttl": "%s"}`
|
||||
invalidReqString := `{"client_id": "%s","ttl": %s}`
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
domainID string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
contentType string
|
||||
clientID string
|
||||
ttl string
|
||||
request string
|
||||
status int
|
||||
authenticateErr error
|
||||
svcRes certs.Cert
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "issue cert successfully",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
contentType: contentType,
|
||||
clientID: clientID,
|
||||
ttl: ttl,
|
||||
request: fmt.Sprintf(validReqString, clientID, ttl),
|
||||
status: http.StatusCreated,
|
||||
svcRes: certs.Cert{SerialNumber: serial},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "issue cert with failed service",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
contentType: contentType,
|
||||
clientID: clientID,
|
||||
ttl: ttl,
|
||||
request: fmt.Sprintf(validReqString, clientID, ttl),
|
||||
status: http.StatusUnprocessableEntity,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: svcerr.ErrCreateEntity,
|
||||
err: svcerr.ErrCreateEntity,
|
||||
},
|
||||
{
|
||||
desc: "issue with invalid token",
|
||||
token: invalid,
|
||||
contentType: contentType,
|
||||
clientID: clientID,
|
||||
ttl: ttl,
|
||||
request: fmt.Sprintf(validReqString, clientID, ttl),
|
||||
status: http.StatusUnauthorized,
|
||||
svcRes: certs.Cert{},
|
||||
authenticateErr: svcerr.ErrAuthentication,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "issue with empty token",
|
||||
domainID: valid,
|
||||
contentType: contentType,
|
||||
request: fmt.Sprintf(validReqString, clientID, ttl),
|
||||
status: http.StatusUnauthorized,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "issue with empty domain id",
|
||||
token: valid,
|
||||
domainID: "",
|
||||
contentType: contentType,
|
||||
request: fmt.Sprintf(validReqString, clientID, ttl),
|
||||
status: http.StatusBadRequest,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "issue with empty client id",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
contentType: contentType,
|
||||
request: fmt.Sprintf(validReqString, "", ttl),
|
||||
status: http.StatusBadRequest,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrMissingID,
|
||||
},
|
||||
{
|
||||
desc: "issue with empty ttl",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
contentType: contentType,
|
||||
request: fmt.Sprintf(validReqString, clientID, ""),
|
||||
status: http.StatusBadRequest,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrMissingCertData,
|
||||
},
|
||||
{
|
||||
desc: "issue with invalid ttl",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
contentType: contentType,
|
||||
request: fmt.Sprintf(validReqString, clientID, invalid),
|
||||
status: http.StatusBadRequest,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrInvalidCertData,
|
||||
},
|
||||
{
|
||||
desc: "issue with invalid content type",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
contentType: "application/xml",
|
||||
request: fmt.Sprintf(validReqString, clientID, ttl),
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrUnsupportedContentType,
|
||||
},
|
||||
{
|
||||
desc: "issue with invalid request body",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
contentType: contentType,
|
||||
request: fmt.Sprintf(invalidReqString, clientID, ttl),
|
||||
status: http.StatusInternalServerError,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrValidation,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: cs.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/certs", cs.URL, tc.domainID),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(tc.request),
|
||||
}
|
||||
if tc.token == valid {
|
||||
tc.session = smqauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("IssueCert", mock.Anything, tc.domainID, tc.token, tc.clientID, tc.ttl).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewCert(t *testing.T) {
|
||||
cs, svc, auth := newCertServer()
|
||||
defer cs.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
domainID string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
serialID string
|
||||
status int
|
||||
authenticateRes smqauthn.Session
|
||||
authenticateErr error
|
||||
svcRes certs.Cert
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "view cert successfully",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
serialID: serial,
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.Cert{SerialNumber: serial},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "view with invalid token",
|
||||
token: invalid,
|
||||
serialID: serial,
|
||||
status: http.StatusUnauthorized,
|
||||
svcRes: certs.Cert{},
|
||||
authenticateErr: svcerr.ErrAuthentication,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "view with empty token",
|
||||
token: "",
|
||||
domainID: valid,
|
||||
serialID: serial,
|
||||
status: http.StatusUnauthorized,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "view non-existing cert",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
serialID: invalid,
|
||||
status: http.StatusNotFound,
|
||||
svcRes: certs.Cert{},
|
||||
svcErr: svcerr.ErrNotFound,
|
||||
err: svcerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: cs.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%s/certs/%s", cs.URL, tc.domainID, tc.serialID),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == valid {
|
||||
tc.session = smqauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("ViewCert", mock.Anything, tc.serialID).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevokeAllCerts(t *testing.T) {
|
||||
cs, svc, auth := newCertServer()
|
||||
defer cs.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
domainID string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
serialID string
|
||||
status int
|
||||
authenticateErr error
|
||||
svcRes certs.Revoke
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "revoke cert successfully",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
serialID: serial,
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.Revoke{RevocationTime: time.Now()},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "revoke with invalid token",
|
||||
token: invalid,
|
||||
serialID: serial,
|
||||
status: http.StatusUnauthorized,
|
||||
svcRes: certs.Revoke{},
|
||||
authenticateErr: svcerr.ErrAuthentication,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "revoke with empty domain id",
|
||||
token: valid,
|
||||
domainID: "",
|
||||
serialID: serial,
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "revoke with empty token",
|
||||
token: "",
|
||||
domainID: valid,
|
||||
serialID: serial,
|
||||
status: http.StatusUnauthorized,
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "revoke non-existing cert",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
serialID: invalid,
|
||||
status: http.StatusNotFound,
|
||||
svcRes: certs.Revoke{},
|
||||
svcErr: svcerr.ErrNotFound,
|
||||
err: svcerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: cs.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/certs/%s/revoke-all", cs.URL, tc.domainID, tc.serialID),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == valid {
|
||||
tc.session = smqauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("RevokeCert", mock.Anything, tc.domainID, tc.token, tc.serialID).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n ", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevokeBySerial(t *testing.T) {
|
||||
cs, svc, auth := newCertServer()
|
||||
defer cs.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
domainID string
|
||||
session smqauthn.Session
|
||||
serialID string
|
||||
status int
|
||||
authenticateErr error
|
||||
svcRes certs.Revoke
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "revoke cert by serial successfully",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
serialID: serial,
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.Revoke{RevocationTime: time.Now()},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "revoke by serial with invalid token",
|
||||
token: invalid,
|
||||
domainID: valid,
|
||||
serialID: serial,
|
||||
status: http.StatusUnauthorized,
|
||||
svcRes: certs.Revoke{},
|
||||
authenticateErr: svcerr.ErrAuthentication,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "revoke by serial with empty domain id",
|
||||
token: valid,
|
||||
domainID: "",
|
||||
serialID: serial,
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "revoke by serial with empty token",
|
||||
token: "",
|
||||
serialID: serial,
|
||||
domainID: valid,
|
||||
status: http.StatusUnauthorized,
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "revoke by serial with empty serial ID",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
serialID: "",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrMissingID,
|
||||
},
|
||||
{
|
||||
desc: "revoke non-existing cert by serial",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
serialID: invalid,
|
||||
status: http.StatusNotFound,
|
||||
svcRes: certs.Revoke{},
|
||||
svcErr: svcerr.ErrNotFound,
|
||||
err: svcerr.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "revoke by serial with service error",
|
||||
token: valid,
|
||||
domainID: valid,
|
||||
serialID: serial,
|
||||
status: http.StatusUnprocessableEntity,
|
||||
svcRes: certs.Revoke{},
|
||||
svcErr: svcerr.ErrRemoveEntity,
|
||||
err: svcerr.ErrRemoveEntity,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: cs.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/certs/%s/revoke", cs.URL, tc.domainID, tc.serialID),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == valid {
|
||||
tc.session = smqauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("RevokeBySerial", mock.Anything, tc.serialID).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSerials(t *testing.T) {
|
||||
cs, svc, auth := newCertServer()
|
||||
defer cs.Close()
|
||||
revoked := "false"
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
domainID string
|
||||
session smqauthn.Session
|
||||
clientID string
|
||||
revoked string
|
||||
offset uint64
|
||||
limit uint64
|
||||
query string
|
||||
status int
|
||||
authenticateErr error
|
||||
svcRes certs.CertPage
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list certs successfully with default limit",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
query: "",
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.CertPage{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Certificates: []certs.Cert{cert},
|
||||
},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list certs successfully with default revoke",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
query: "",
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.CertPage{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Certificates: []certs.Cert{cert},
|
||||
},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list certs successfully with all certs",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: "all",
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
query: "?revoked=all",
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.CertPage{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Certificates: []certs.Cert{cert},
|
||||
},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list certs successfully with limit",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
offset: 0,
|
||||
limit: 5,
|
||||
query: "?limit=5",
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.CertPage{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 5,
|
||||
Certificates: []certs.Cert{cert},
|
||||
},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list certs successfully with offset",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
offset: 1,
|
||||
limit: 10,
|
||||
query: "?offset=1",
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.CertPage{
|
||||
Total: 1,
|
||||
Offset: 1,
|
||||
Limit: 10,
|
||||
Certificates: []certs.Cert{},
|
||||
},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list certs successfully with offset and limit",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
offset: 1,
|
||||
limit: 5,
|
||||
query: "?offset=1&limit=5",
|
||||
status: http.StatusOK,
|
||||
svcRes: certs.CertPage{
|
||||
Total: 1,
|
||||
Offset: 1,
|
||||
Limit: 5,
|
||||
Certificates: []certs.Cert{},
|
||||
},
|
||||
svcErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list with invalid token",
|
||||
domainID: valid,
|
||||
token: invalid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
query: "",
|
||||
status: http.StatusUnauthorized,
|
||||
svcRes: certs.CertPage{},
|
||||
authenticateErr: svcerr.ErrAuthentication,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "list with empty token",
|
||||
domainID: valid,
|
||||
token: "",
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
query: "",
|
||||
status: http.StatusUnauthorized,
|
||||
svcRes: certs.CertPage{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "list with limit exceeding max limit",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
query: "?limit=1000",
|
||||
status: http.StatusBadRequest,
|
||||
svcRes: certs.CertPage{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrLimitSize,
|
||||
},
|
||||
{
|
||||
desc: "list with invalid offset",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
query: "?offset=invalid",
|
||||
status: http.StatusBadRequest,
|
||||
svcRes: certs.CertPage{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrValidation,
|
||||
},
|
||||
{
|
||||
desc: "list with invalid limit",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: clientID,
|
||||
revoked: revoked,
|
||||
query: "?limit=invalid",
|
||||
status: http.StatusBadRequest,
|
||||
svcRes: certs.CertPage{},
|
||||
svcErr: nil,
|
||||
err: apiutil.ErrValidation,
|
||||
},
|
||||
{
|
||||
desc: "list with invalid client id",
|
||||
domainID: valid,
|
||||
token: valid,
|
||||
clientID: invalid,
|
||||
revoked: revoked,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
query: "",
|
||||
status: http.StatusNotFound,
|
||||
svcRes: certs.CertPage{},
|
||||
svcErr: svcerr.ErrNotFound,
|
||||
err: svcerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: cs.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%s/serials/%s", cs.URL, tc.domainID, tc.clientID) + tc.query,
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == valid {
|
||||
tc.session = smqauthn.Session{DomainUserID: validID, UserID: validID, DomainID: validID}
|
||||
}
|
||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||
svcCall := svc.On("ListSerials", mock.Anything, tc.clientID, certs.PageMetadata{Offset: tc.offset, Limit: tc.limit, Revoked: "all"}).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n ", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type respBody struct {
|
||||
Err string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,157 +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/supermq/certs"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
var _ certs.Service = (*loggingMiddleware)(nil)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger *slog.Logger
|
||||
svc certs.Service
|
||||
}
|
||||
|
||||
// LoggingMiddleware adds logging facilities to the certs service.
|
||||
func LoggingMiddleware(svc certs.Service, logger *slog.Logger) certs.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
}
|
||||
|
||||
// IssueCert logs the issue_cert request. It logs the ttl, client ID and the time it took to complete the request.
|
||||
// If the request fails, it logs the error.
|
||||
func (lm *loggingMiddleware) IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (c certs.Cert, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.String("client_id", clientID),
|
||||
slog.String("ttl", ttl),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Issue certificate failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Issue certificate completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.IssueCert(ctx, domainID, token, clientID, ttl)
|
||||
}
|
||||
|
||||
// ListCerts logs the list_certs request. It logs the client ID and the time it took to complete the request.
|
||||
func (lm *loggingMiddleware) ListCerts(ctx context.Context, clientID string, pm certs.PageMetadata) (cp certs.CertPage, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.String("client_id", clientID),
|
||||
slog.Group("page",
|
||||
slog.Uint64("offset", cp.Offset),
|
||||
slog.Uint64("limit", cp.Limit),
|
||||
slog.Uint64("total", cp.Total),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("List certificates failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("List certificates completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.ListCerts(ctx, clientID, pm)
|
||||
}
|
||||
|
||||
// ListSerials logs the list_serials request. It logs the client ID and the time it took to complete the request.
|
||||
// If the request fails, it logs the error.
|
||||
func (lm *loggingMiddleware) ListSerials(ctx context.Context, clientID string, pm certs.PageMetadata) (cp certs.CertPage, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.String("client_id", clientID),
|
||||
slog.Group("page",
|
||||
slog.Uint64("offset", cp.Offset),
|
||||
slog.Uint64("limit", cp.Limit),
|
||||
slog.Uint64("total", cp.Total),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("List certifcates serials failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("List certificates serials completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.ListSerials(ctx, clientID, pm)
|
||||
}
|
||||
|
||||
// ViewCert logs the view_cert request. It logs the serial ID and the time it took to complete the request.
|
||||
// If the request fails, it logs the error.
|
||||
func (lm *loggingMiddleware) ViewCert(ctx context.Context, serialID string) (c certs.Cert, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.String("serial_id", serialID),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("View certificate failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("View certificate completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.ViewCert(ctx, serialID)
|
||||
}
|
||||
|
||||
// RevokeCert logs the revoke_cert request. It logs the client ID and the time it took to complete the request.
|
||||
// If the request fails, it logs the error.
|
||||
func (lm *loggingMiddleware) RevokeCert(ctx context.Context, domainID, token, clientID string) (c certs.Revoke, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.String("client_id", clientID),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Revoke certificate failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Revoke certificate completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.RevokeCert(ctx, domainID, token, clientID)
|
||||
}
|
||||
|
||||
// RevokeBySerial logs the revoke_by_serial request. It logs the serial ID and the time it took to complete the request.
|
||||
// If the request fails, it logs the error.
|
||||
func (lm *loggingMiddleware) RevokeBySerial(ctx context.Context, serialID string) (c certs.Revoke, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("request_id", middleware.GetReqID(ctx)),
|
||||
slog.String("serial_id", serialID),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Revoke certificate by serial failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Revoke certificate by serial completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.RevokeBySerial(ctx, serialID)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !test
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
var _ certs.Service = (*metricsMiddleware)(nil)
|
||||
|
||||
type metricsMiddleware struct {
|
||||
counter metrics.Counter
|
||||
latency metrics.Histogram
|
||||
svc certs.Service
|
||||
}
|
||||
|
||||
// MetricsMiddleware instruments core service by tracking request count and latency.
|
||||
func MetricsMiddleware(svc certs.Service, counter metrics.Counter, latency metrics.Histogram) certs.Service {
|
||||
return &metricsMiddleware{
|
||||
counter: counter,
|
||||
latency: latency,
|
||||
svc: svc,
|
||||
}
|
||||
}
|
||||
|
||||
// IssueCert instruments IssueCert method with metrics.
|
||||
func (ms *metricsMiddleware) IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (certs.Cert, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "issue_cert").Add(1)
|
||||
ms.latency.With("method", "issue_cert").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.IssueCert(ctx, domainID, token, clientID, ttl)
|
||||
}
|
||||
|
||||
// ListCerts instruments ListCerts method with metrics.
|
||||
func (ms *metricsMiddleware) ListCerts(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "list_certs").Add(1)
|
||||
ms.latency.With("method", "list_certs").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.ListCerts(ctx, clientID, pm)
|
||||
}
|
||||
|
||||
// ListSerials instruments ListSerials method with metrics.
|
||||
func (ms *metricsMiddleware) ListSerials(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "list_serials").Add(1)
|
||||
ms.latency.With("method", "list_serials").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.ListSerials(ctx, clientID, pm)
|
||||
}
|
||||
|
||||
// ViewCert instruments ViewCert method with metrics.
|
||||
func (ms *metricsMiddleware) ViewCert(ctx context.Context, serialID string) (certs.Cert, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "view_cert").Add(1)
|
||||
ms.latency.With("method", "view_cert").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.ViewCert(ctx, serialID)
|
||||
}
|
||||
|
||||
// RevokeCert instruments RevokeCert method with metrics.
|
||||
func (ms *metricsMiddleware) RevokeCert(ctx context.Context, domainID, token, clientID string) (certs.Revoke, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "revoke_cert").Add(1)
|
||||
ms.latency.With("method", "revoke_cert").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.RevokeCert(ctx, domainID, token, clientID)
|
||||
}
|
||||
|
||||
// RevokeBySerial instruments RevokeBySerial method with metrics.
|
||||
func (ms *metricsMiddleware) RevokeBySerial(ctx context.Context, serialID string) (certs.Revoke, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "revoke_by_serial").Add(1)
|
||||
ms.latency.With("method", "revoke_by_serial").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.RevokeBySerial(ctx, serialID)
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/certs"
|
||||
)
|
||||
|
||||
const maxLimitSize = 100
|
||||
|
||||
type addCertsReq struct {
|
||||
token string
|
||||
domainID string
|
||||
ClientID string `json:"client_id"`
|
||||
TTL string `json:"ttl"`
|
||||
}
|
||||
|
||||
func (req addCertsReq) validate() error {
|
||||
if req.token == "" {
|
||||
return apiutil.ErrBearerToken
|
||||
}
|
||||
|
||||
if req.domainID == "" {
|
||||
return apiutil.ErrMissingDomainID
|
||||
}
|
||||
|
||||
if req.ClientID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
|
||||
if req.TTL == "" {
|
||||
return apiutil.ErrMissingCertData
|
||||
}
|
||||
|
||||
if _, err := time.ParseDuration(req.TTL); err != nil {
|
||||
return apiutil.ErrInvalidCertData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type listReq struct {
|
||||
clientID string
|
||||
pm certs.PageMetadata
|
||||
}
|
||||
|
||||
func (req *listReq) validate() error {
|
||||
if req.pm.Limit > maxLimitSize {
|
||||
return apiutil.ErrLimitSize
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type viewReq struct {
|
||||
serialID string
|
||||
}
|
||||
|
||||
func (req *viewReq) validate() error {
|
||||
if req.serialID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type revokeAllReq struct {
|
||||
token string
|
||||
clientID string
|
||||
domainID string
|
||||
}
|
||||
|
||||
func (req *revokeAllReq) validate() error {
|
||||
if req.token == "" {
|
||||
return apiutil.ErrBearerToken
|
||||
}
|
||||
|
||||
if req.domainID == "" {
|
||||
return apiutil.ErrMissingDomainID
|
||||
}
|
||||
|
||||
if req.clientID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type revokeBySerialReq struct {
|
||||
serialID string
|
||||
}
|
||||
|
||||
func (req *revokeBySerialReq) validate() error {
|
||||
if req.serialID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type pageRes struct {
|
||||
Total uint64 `json:"total"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
}
|
||||
|
||||
type certsPageRes struct {
|
||||
pageRes
|
||||
Certs []certsRes `json:"certs"`
|
||||
}
|
||||
|
||||
type certsRes struct {
|
||||
ClientID string `json:"client_id"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
ExpiryTime time.Time `json:"expiry_time"`
|
||||
CAChain []string `json:"ca_chain,omitempty"`
|
||||
IssuingCA string `json:"issuing_ca,omitempty"`
|
||||
Revoked bool `json:"revoked"`
|
||||
issued bool
|
||||
}
|
||||
|
||||
type revokeCertsRes struct {
|
||||
RevocationTime time.Time `json:"revocation_time"`
|
||||
}
|
||||
|
||||
func (res certsPageRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res certsPageRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res certsPageRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (res certsRes) Code() int {
|
||||
if res.issued {
|
||||
return http.StatusCreated
|
||||
}
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res certsRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res certsRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (res revokeCertsRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res revokeCertsRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res revokeCertsRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/absmach/supermq"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/certs"
|
||||
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"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
contentType = "application/json"
|
||||
offsetKey = "offset"
|
||||
limitKey = "limit"
|
||||
revokedKey = "revoked"
|
||||
defOffset = 0
|
||||
defLimit = 10
|
||||
)
|
||||
|
||||
// MakeHandler returns a HTTP handler for API endpoints.
|
||||
func MakeHandler(svc certs.Service, authn smqauthn.Authentication, logger *slog.Logger, instanceID string, idp supermq.IDProvider) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
|
||||
}
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(api.AuthenticateMiddleware(authn, true))
|
||||
r.Use(api.RequestIDMiddleware(idp))
|
||||
|
||||
r.Route("/{domainID}", func(r chi.Router) {
|
||||
r.Route("/certs", func(r chi.Router) {
|
||||
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
issueCert(svc),
|
||||
decodeCerts,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "issue").ServeHTTP)
|
||||
r.Get("/{certID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewCert(svc),
|
||||
decodeViewCert,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "view").ServeHTTP)
|
||||
r.Post("/{clientID}/revoke-all", otelhttp.NewHandler(kithttp.NewServer(
|
||||
revokeAllCerts(svc),
|
||||
decodeRevokeAllCerts,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "revoke").ServeHTTP)
|
||||
r.Post("/{certID}/revoke", otelhttp.NewHandler(kithttp.NewServer(
|
||||
revokeBySerial(svc),
|
||||
decodeRevokeBySerial,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "revoke_by_serial").ServeHTTP)
|
||||
})
|
||||
r.Get("/serials/{clientID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
listSerials(svc),
|
||||
decodeListCerts,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "list_serials").ServeHTTP)
|
||||
})
|
||||
})
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
r.Get("/health", supermq.Health("certs", instanceID))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func decodeListCerts(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
l, err := apiutil.ReadNumQuery[uint64](r, limitKey, defLimit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
o, err := apiutil.ReadNumQuery[uint64](r, offsetKey, defOffset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
revoked, err := apiutil.ReadStringQuery(r, revokedKey, "all")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
req := listReq{
|
||||
clientID: chi.URLParam(r, "clientID"),
|
||||
pm: certs.PageMetadata{
|
||||
Offset: o,
|
||||
Limit: l,
|
||||
Revoked: revoked,
|
||||
},
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeViewCert(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
req := viewReq{
|
||||
serialID: chi.URLParam(r, "certID"),
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeCerts(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if r.Header.Get("Content-Type") != contentType {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
|
||||
req := addCertsReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
domainID: chi.URLParam(r, "domainID"),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeRevokeAllCerts(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
req := revokeAllReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
clientID: chi.URLParam(r, "clientID"),
|
||||
domainID: chi.URLParam(r, "domainID"),
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeRevokeBySerial(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
req := revokeBySerialReq{
|
||||
serialID: chi.URLParam(r, "certID"),
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package certs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
)
|
||||
|
||||
type Cert struct {
|
||||
SerialNumber string `json:"serial_number"`
|
||||
CAChain []string `json:"ca_chain,omitempty"`
|
||||
IssuingCA string `json:"issuing_ca,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
ExpiryTime time.Time `json:"expiry_time"`
|
||||
ClientID string `json:"entity_id"`
|
||||
Revoked bool `json:"revoked"`
|
||||
}
|
||||
|
||||
type CertPage struct {
|
||||
Total uint64 `json:"total"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
Certificates []Cert `json:"certificates,omitempty"`
|
||||
}
|
||||
|
||||
// Repository specifies a Config persistence API.
|
||||
type Repository interface {
|
||||
// Save saves cert for client into database
|
||||
Save(ctx context.Context, cert Cert) (string, error)
|
||||
|
||||
// Update updates an existing certificate in the database
|
||||
Update(ctx context.Context, cert Cert) error
|
||||
|
||||
// RetrieveAll retrieve issued certificates
|
||||
RetrieveAll(ctx context.Context, offset, limit uint64) (CertPage, error)
|
||||
|
||||
// Remove removes certificate from DB for a given client ID
|
||||
Remove(ctx context.Context, clientID string) error
|
||||
|
||||
// RemoveBySerial removes certificate from DB for a given serial number
|
||||
RemoveBySerial(ctx context.Context, serialID string) error
|
||||
|
||||
// RetrieveByClient retrieves issued certificates for a given client ID
|
||||
RetrieveByClient(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error)
|
||||
|
||||
// RetrieveBySerial retrieves a certificate for a given serial ID
|
||||
RetrieveBySerial(ctx context.Context, serialID string) (Cert, error)
|
||||
}
|
||||
|
||||
type PageMetadata struct {
|
||||
Total uint64 `json:"total,omitempty"`
|
||||
Offset uint64 `json:"offset,omitempty"`
|
||||
Limit uint64 `json:"limit,omitempty"`
|
||||
CommonName string `json:"common_name,omitempty"`
|
||||
Revoked string `json:"revoked,omitempty"`
|
||||
}
|
||||
|
||||
var ErrMissingCerts = errors.New("CA path or CA key path not set")
|
||||
|
||||
func LoadCertificates(caPath, caKeyPath string) (tls.Certificate, *x509.Certificate, error) {
|
||||
if caPath == "" || caKeyPath == "" {
|
||||
return tls.Certificate{}, &x509.Certificate{}, ErrMissingCerts
|
||||
}
|
||||
|
||||
_, err := os.Stat(caPath)
|
||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||
return tls.Certificate{}, &x509.Certificate{}, err
|
||||
}
|
||||
|
||||
_, err = os.Stat(caKeyPath)
|
||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||
return tls.Certificate{}, &x509.Certificate{}, err
|
||||
}
|
||||
|
||||
tlsCert, err := tls.LoadX509KeyPair(caPath, caKeyPath)
|
||||
if err != nil {
|
||||
return tlsCert, &x509.Certificate{}, err
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(caPath)
|
||||
if err != nil {
|
||||
return tlsCert, &x509.Certificate{}, err
|
||||
}
|
||||
|
||||
caCert, err := ReadCert(b)
|
||||
if err != nil {
|
||||
return tlsCert, &x509.Certificate{}, err
|
||||
}
|
||||
|
||||
return tlsCert, caCert, nil
|
||||
}
|
||||
|
||||
func ReadCert(b []byte) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode(b)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM data")
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package certs_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadCertificates(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
caPath string
|
||||
caKeyPath string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "load valid tls certificate and valid key",
|
||||
caPath: "../docker/ssl/certs/ca.crt",
|
||||
caKeyPath: "../docker/ssl/certs/ca.key",
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "load valid tls certificate and missing key",
|
||||
caPath: "../docker/ssl/certs/ca.crt",
|
||||
caKeyPath: "",
|
||||
err: certs.ErrMissingCerts,
|
||||
},
|
||||
{
|
||||
desc: "load missing tls certificate and valid key",
|
||||
caPath: "",
|
||||
caKeyPath: "../docker/ssl/certs/ca.key",
|
||||
err: certs.ErrMissingCerts,
|
||||
},
|
||||
{
|
||||
desc: "load empty tls certificate and empty key",
|
||||
caPath: "",
|
||||
caKeyPath: "",
|
||||
err: certs.ErrMissingCerts,
|
||||
},
|
||||
{
|
||||
desc: "load valid tls certificate and invalid key",
|
||||
caPath: "../docker/ssl/certs/ca.crt",
|
||||
caKeyPath: "certs.go",
|
||||
err: errors.New("tls: failed to find any PEM data in key input"),
|
||||
},
|
||||
{
|
||||
desc: "load invalid tls certificate and valid key",
|
||||
caPath: "certs.go",
|
||||
caKeyPath: "../docker/ssl/certs/ca.key",
|
||||
err: errors.New("tls: failed to find any PEM data in certificate input"),
|
||||
},
|
||||
{
|
||||
desc: "load invalid tls certificate and invalid key",
|
||||
caPath: "certs.go",
|
||||
caKeyPath: "certs.go",
|
||||
err: errors.New("tls: failed to find any PEM data in certificate input"),
|
||||
},
|
||||
|
||||
{
|
||||
desc: "load valid tls certificate and non-existing key",
|
||||
caPath: "../docker/ssl/certs/ca.crt",
|
||||
caKeyPath: "ca.key",
|
||||
err: errors.New("stat ca.key: no such file or directory"),
|
||||
},
|
||||
{
|
||||
desc: "load non-existing tls certificate and valid key",
|
||||
caPath: "ca.crt",
|
||||
caKeyPath: "../docker/ssl/certs/ca.key",
|
||||
err: errors.New("stat ca.crt: no such file or directory"),
|
||||
},
|
||||
{
|
||||
desc: "load non-existing tls certificate and non-existing key",
|
||||
caPath: "ca.crt",
|
||||
caKeyPath: "ca.key",
|
||||
err: errors.New("stat ca.crt: no such file or directory"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tlsCert, caCert, err := certs.LoadCertificates(tc.caPath, tc.caKeyPath)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.NotNil(t, tlsCert)
|
||||
assert.NotNil(t, caCert)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package certs contains the domain concept definitions needed to support
|
||||
// SuperMQ certs service functionality.
|
||||
package certs
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package mocks contains mocks for testing purposes.
|
||||
package mocks
|
||||
@@ -1,283 +0,0 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/absmach/supermq/certs"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewAgent creates a new instance of Agent. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAgent(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Agent {
|
||||
mock := &Agent{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Agent is an autogenerated mock type for the Agent type
|
||||
type Agent struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Agent_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Agent) EXPECT() *Agent_Expecter {
|
||||
return &Agent_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Issue provides a mock function for the type Agent
|
||||
func (_mock *Agent) Issue(entityId string, ttl string, ipAddrs []string) (certs.Cert, error) {
|
||||
ret := _mock.Called(entityId, ttl, ipAddrs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Issue")
|
||||
}
|
||||
|
||||
var r0 certs.Cert
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(string, string, []string) (certs.Cert, error)); ok {
|
||||
return returnFunc(entityId, ttl, ipAddrs)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(string, string, []string) certs.Cert); ok {
|
||||
r0 = returnFunc(entityId, ttl, ipAddrs)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.Cert)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(string, string, []string) error); ok {
|
||||
r1 = returnFunc(entityId, ttl, ipAddrs)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Agent_Issue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Issue'
|
||||
type Agent_Issue_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Issue is a helper method to define mock.On call
|
||||
// - entityId string
|
||||
// - ttl string
|
||||
// - ipAddrs []string
|
||||
func (_e *Agent_Expecter) Issue(entityId interface{}, ttl interface{}, ipAddrs interface{}) *Agent_Issue_Call {
|
||||
return &Agent_Issue_Call{Call: _e.mock.On("Issue", entityId, ttl, ipAddrs)}
|
||||
}
|
||||
|
||||
func (_c *Agent_Issue_Call) Run(run func(entityId string, ttl string, ipAddrs []string)) *Agent_Issue_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 []string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].([]string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Agent_Issue_Call) Return(cert certs.Cert, err error) *Agent_Issue_Call {
|
||||
_c.Call.Return(cert, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Agent_Issue_Call) RunAndReturn(run func(entityId string, ttl string, ipAddrs []string) (certs.Cert, error)) *Agent_Issue_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListCerts provides a mock function for the type Agent
|
||||
func (_mock *Agent) ListCerts(pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
ret := _mock.Called(pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListCerts")
|
||||
}
|
||||
|
||||
var r0 certs.CertPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(certs.PageMetadata) (certs.CertPage, error)); ok {
|
||||
return returnFunc(pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(certs.PageMetadata) certs.CertPage); ok {
|
||||
r0 = returnFunc(pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.CertPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(certs.PageMetadata) error); ok {
|
||||
r1 = returnFunc(pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Agent_ListCerts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListCerts'
|
||||
type Agent_ListCerts_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListCerts is a helper method to define mock.On call
|
||||
// - pm certs.PageMetadata
|
||||
func (_e *Agent_Expecter) ListCerts(pm interface{}) *Agent_ListCerts_Call {
|
||||
return &Agent_ListCerts_Call{Call: _e.mock.On("ListCerts", pm)}
|
||||
}
|
||||
|
||||
func (_c *Agent_ListCerts_Call) Run(run func(pm certs.PageMetadata)) *Agent_ListCerts_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 certs.PageMetadata
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(certs.PageMetadata)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Agent_ListCerts_Call) Return(certPage certs.CertPage, err error) *Agent_ListCerts_Call {
|
||||
_c.Call.Return(certPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Agent_ListCerts_Call) RunAndReturn(run func(pm certs.PageMetadata) (certs.CertPage, error)) *Agent_ListCerts_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Revoke provides a mock function for the type Agent
|
||||
func (_mock *Agent) Revoke(serialNumber string) error {
|
||||
ret := _mock.Called(serialNumber)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Revoke")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = returnFunc(serialNumber)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Agent_Revoke_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Revoke'
|
||||
type Agent_Revoke_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Revoke is a helper method to define mock.On call
|
||||
// - serialNumber string
|
||||
func (_e *Agent_Expecter) Revoke(serialNumber interface{}) *Agent_Revoke_Call {
|
||||
return &Agent_Revoke_Call{Call: _e.mock.On("Revoke", serialNumber)}
|
||||
}
|
||||
|
||||
func (_c *Agent_Revoke_Call) Run(run func(serialNumber string)) *Agent_Revoke_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Agent_Revoke_Call) Return(err error) *Agent_Revoke_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Agent_Revoke_Call) RunAndReturn(run func(serialNumber string) error) *Agent_Revoke_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// View provides a mock function for the type Agent
|
||||
func (_mock *Agent) View(serialNumber string) (certs.Cert, error) {
|
||||
ret := _mock.Called(serialNumber)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for View")
|
||||
}
|
||||
|
||||
var r0 certs.Cert
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(string) (certs.Cert, error)); ok {
|
||||
return returnFunc(serialNumber)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(string) certs.Cert); ok {
|
||||
r0 = returnFunc(serialNumber)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.Cert)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = returnFunc(serialNumber)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Agent_View_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'View'
|
||||
type Agent_View_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// View is a helper method to define mock.On call
|
||||
// - serialNumber string
|
||||
func (_e *Agent_Expecter) View(serialNumber interface{}) *Agent_View_Call {
|
||||
return &Agent_View_Call{Call: _e.mock.On("View", serialNumber)}
|
||||
}
|
||||
|
||||
func (_c *Agent_View_Call) Run(run func(serialNumber string)) *Agent_View_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Agent_View_Call) Return(cert certs.Cert, err error) *Agent_View_Call {
|
||||
_c.Call.Return(cert, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Agent_View_Call) RunAndReturn(run func(serialNumber string) (certs.Cert, error)) *Agent_View_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -1,489 +0,0 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Repository {
|
||||
mock := &Repository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Repository is an autogenerated mock type for the Repository type
|
||||
type Repository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Repository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Repository) EXPECT() *Repository_Expecter {
|
||||
return &Repository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Remove provides a mock function for the type Repository
|
||||
func (_mock *Repository) Remove(ctx context.Context, clientID string) error {
|
||||
ret := _mock.Called(ctx, clientID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Remove")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = returnFunc(ctx, clientID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Repository_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
||||
type Repository_Remove_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Remove is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - clientID string
|
||||
func (_e *Repository_Expecter) Remove(ctx interface{}, clientID interface{}) *Repository_Remove_Call {
|
||||
return &Repository_Remove_Call{Call: _e.mock.On("Remove", ctx, clientID)}
|
||||
}
|
||||
|
||||
func (_c *Repository_Remove_Call) Run(run func(ctx context.Context, clientID string)) *Repository_Remove_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_Remove_Call) Return(err error) *Repository_Remove_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_Remove_Call) RunAndReturn(run func(ctx context.Context, clientID string) error) *Repository_Remove_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RemoveBySerial provides a mock function for the type Repository
|
||||
func (_mock *Repository) RemoveBySerial(ctx context.Context, serialID string) error {
|
||||
ret := _mock.Called(ctx, serialID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RemoveBySerial")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = returnFunc(ctx, serialID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Repository_RemoveBySerial_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveBySerial'
|
||||
type Repository_RemoveBySerial_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RemoveBySerial is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - serialID string
|
||||
func (_e *Repository_Expecter) RemoveBySerial(ctx interface{}, serialID interface{}) *Repository_RemoveBySerial_Call {
|
||||
return &Repository_RemoveBySerial_Call{Call: _e.mock.On("RemoveBySerial", ctx, serialID)}
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveBySerial_Call) Run(run func(ctx context.Context, serialID string)) *Repository_RemoveBySerial_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveBySerial_Call) Return(err error) *Repository_RemoveBySerial_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveBySerial_Call) RunAndReturn(run func(ctx context.Context, serialID string) error) *Repository_RemoveBySerial_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RetrieveAll provides a mock function for the type Repository
|
||||
func (_mock *Repository) RetrieveAll(ctx context.Context, offset uint64, limit uint64) (certs.CertPage, error) {
|
||||
ret := _mock.Called(ctx, offset, limit)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RetrieveAll")
|
||||
}
|
||||
|
||||
var r0 certs.CertPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64) (certs.CertPage, error)); ok {
|
||||
return returnFunc(ctx, offset, limit)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, uint64) certs.CertPage); ok {
|
||||
r0 = returnFunc(ctx, offset, limit)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.CertPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok {
|
||||
r1 = returnFunc(ctx, offset, limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_RetrieveAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveAll'
|
||||
type Repository_RetrieveAll_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RetrieveAll is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - offset uint64
|
||||
// - limit uint64
|
||||
func (_e *Repository_Expecter) RetrieveAll(ctx interface{}, offset interface{}, limit interface{}) *Repository_RetrieveAll_Call {
|
||||
return &Repository_RetrieveAll_Call{Call: _e.mock.On("RetrieveAll", ctx, offset, limit)}
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveAll_Call) Run(run func(ctx context.Context, offset uint64, limit uint64)) *Repository_RetrieveAll_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 uint64
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(uint64)
|
||||
}
|
||||
var arg2 uint64
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(uint64)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveAll_Call) Return(certPage certs.CertPage, err error) *Repository_RetrieveAll_Call {
|
||||
_c.Call.Return(certPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveAll_Call) RunAndReturn(run func(ctx context.Context, offset uint64, limit uint64) (certs.CertPage, error)) *Repository_RetrieveAll_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RetrieveByClient provides a mock function for the type Repository
|
||||
func (_mock *Repository) RetrieveByClient(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
ret := _mock.Called(ctx, clientID, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RetrieveByClient")
|
||||
}
|
||||
|
||||
var r0 certs.CertPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) (certs.CertPage, error)); ok {
|
||||
return returnFunc(ctx, clientID, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) certs.CertPage); ok {
|
||||
r0 = returnFunc(ctx, clientID, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.CertPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, certs.PageMetadata) error); ok {
|
||||
r1 = returnFunc(ctx, clientID, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_RetrieveByClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveByClient'
|
||||
type Repository_RetrieveByClient_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RetrieveByClient is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - clientID string
|
||||
// - pm certs.PageMetadata
|
||||
func (_e *Repository_Expecter) RetrieveByClient(ctx interface{}, clientID interface{}, pm interface{}) *Repository_RetrieveByClient_Call {
|
||||
return &Repository_RetrieveByClient_Call{Call: _e.mock.On("RetrieveByClient", ctx, clientID, pm)}
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveByClient_Call) Run(run func(ctx context.Context, clientID string, pm certs.PageMetadata)) *Repository_RetrieveByClient_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 certs.PageMetadata
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(certs.PageMetadata)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveByClient_Call) Return(certPage certs.CertPage, err error) *Repository_RetrieveByClient_Call {
|
||||
_c.Call.Return(certPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveByClient_Call) RunAndReturn(run func(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error)) *Repository_RetrieveByClient_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RetrieveBySerial provides a mock function for the type Repository
|
||||
func (_mock *Repository) RetrieveBySerial(ctx context.Context, serialID string) (certs.Cert, error) {
|
||||
ret := _mock.Called(ctx, serialID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RetrieveBySerial")
|
||||
}
|
||||
|
||||
var r0 certs.Cert
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (certs.Cert, error)); ok {
|
||||
return returnFunc(ctx, serialID)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) certs.Cert); ok {
|
||||
r0 = returnFunc(ctx, serialID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.Cert)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = returnFunc(ctx, serialID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_RetrieveBySerial_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveBySerial'
|
||||
type Repository_RetrieveBySerial_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RetrieveBySerial is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - serialID string
|
||||
func (_e *Repository_Expecter) RetrieveBySerial(ctx interface{}, serialID interface{}) *Repository_RetrieveBySerial_Call {
|
||||
return &Repository_RetrieveBySerial_Call{Call: _e.mock.On("RetrieveBySerial", ctx, serialID)}
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveBySerial_Call) Run(run func(ctx context.Context, serialID string)) *Repository_RetrieveBySerial_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveBySerial_Call) Return(cert certs.Cert, err error) *Repository_RetrieveBySerial_Call {
|
||||
_c.Call.Return(cert, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RetrieveBySerial_Call) RunAndReturn(run func(ctx context.Context, serialID string) (certs.Cert, error)) *Repository_RetrieveBySerial_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Save provides a mock function for the type Repository
|
||||
func (_mock *Repository) Save(ctx context.Context, cert certs.Cert) (string, error) {
|
||||
ret := _mock.Called(ctx, cert)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Save")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, certs.Cert) (string, error)); ok {
|
||||
return returnFunc(ctx, cert)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, certs.Cert) string); ok {
|
||||
r0 = returnFunc(ctx, cert)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, certs.Cert) error); ok {
|
||||
r1 = returnFunc(ctx, cert)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
|
||||
type Repository_Save_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Save is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - cert certs.Cert
|
||||
func (_e *Repository_Expecter) Save(ctx interface{}, cert interface{}) *Repository_Save_Call {
|
||||
return &Repository_Save_Call{Call: _e.mock.On("Save", ctx, cert)}
|
||||
}
|
||||
|
||||
func (_c *Repository_Save_Call) Run(run func(ctx context.Context, cert certs.Cert)) *Repository_Save_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 certs.Cert
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(certs.Cert)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_Save_Call) Return(s string, err error) *Repository_Save_Call {
|
||||
_c.Call.Return(s, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_Save_Call) RunAndReturn(run func(ctx context.Context, cert certs.Cert) (string, error)) *Repository_Save_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Update provides a mock function for the type Repository
|
||||
func (_mock *Repository) Update(ctx context.Context, cert certs.Cert) error {
|
||||
ret := _mock.Called(ctx, cert)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, certs.Cert) error); ok {
|
||||
r0 = returnFunc(ctx, cert)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Repository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||
type Repository_Update_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Update is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - cert certs.Cert
|
||||
func (_e *Repository_Expecter) Update(ctx interface{}, cert interface{}) *Repository_Update_Call {
|
||||
return &Repository_Update_Call{Call: _e.mock.On("Update", ctx, cert)}
|
||||
}
|
||||
|
||||
func (_c *Repository_Update_Call) Run(run func(ctx context.Context, cert certs.Cert)) *Repository_Update_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 certs.Cert
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(certs.Cert)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_Update_Call) Return(err error) *Repository_Update_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_Update_Call) RunAndReturn(run func(ctx context.Context, cert certs.Cert) error) *Repository_Update_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -1,480 +0,0 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Service is an autogenerated mock type for the Service type
|
||||
type Service struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Service_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Service) EXPECT() *Service_Expecter {
|
||||
return &Service_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// IssueCert provides a mock function for the type Service
|
||||
func (_mock *Service) IssueCert(ctx context.Context, domainID string, token string, clientID string, ttl string) (certs.Cert, error) {
|
||||
ret := _mock.Called(ctx, domainID, token, clientID, ttl)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IssueCert")
|
||||
}
|
||||
|
||||
var r0 certs.Cert
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string) (certs.Cert, error)); ok {
|
||||
return returnFunc(ctx, domainID, token, clientID, ttl)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string) certs.Cert); ok {
|
||||
r0 = returnFunc(ctx, domainID, token, clientID, ttl)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.Cert)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok {
|
||||
r1 = returnFunc(ctx, domainID, token, clientID, ttl)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_IssueCert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IssueCert'
|
||||
type Service_IssueCert_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IssueCert is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - domainID string
|
||||
// - token string
|
||||
// - clientID string
|
||||
// - ttl string
|
||||
func (_e *Service_Expecter) IssueCert(ctx interface{}, domainID interface{}, token interface{}, clientID interface{}, ttl interface{}) *Service_IssueCert_Call {
|
||||
return &Service_IssueCert_Call{Call: _e.mock.On("IssueCert", ctx, domainID, token, clientID, ttl)}
|
||||
}
|
||||
|
||||
func (_c *Service_IssueCert_Call) Run(run func(ctx context.Context, domainID string, token string, clientID string, ttl string)) *Service_IssueCert_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
var arg3 string
|
||||
if args[3] != nil {
|
||||
arg3 = args[3].(string)
|
||||
}
|
||||
var arg4 string
|
||||
if args[4] != nil {
|
||||
arg4 = args[4].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
arg3,
|
||||
arg4,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_IssueCert_Call) Return(cert certs.Cert, err error) *Service_IssueCert_Call {
|
||||
_c.Call.Return(cert, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_IssueCert_Call) RunAndReturn(run func(ctx context.Context, domainID string, token string, clientID string, ttl string) (certs.Cert, error)) *Service_IssueCert_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListCerts provides a mock function for the type Service
|
||||
func (_mock *Service) ListCerts(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
ret := _mock.Called(ctx, clientID, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListCerts")
|
||||
}
|
||||
|
||||
var r0 certs.CertPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) (certs.CertPage, error)); ok {
|
||||
return returnFunc(ctx, clientID, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) certs.CertPage); ok {
|
||||
r0 = returnFunc(ctx, clientID, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.CertPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, certs.PageMetadata) error); ok {
|
||||
r1 = returnFunc(ctx, clientID, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ListCerts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListCerts'
|
||||
type Service_ListCerts_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListCerts is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - clientID string
|
||||
// - pm certs.PageMetadata
|
||||
func (_e *Service_Expecter) ListCerts(ctx interface{}, clientID interface{}, pm interface{}) *Service_ListCerts_Call {
|
||||
return &Service_ListCerts_Call{Call: _e.mock.On("ListCerts", ctx, clientID, pm)}
|
||||
}
|
||||
|
||||
func (_c *Service_ListCerts_Call) Run(run func(ctx context.Context, clientID string, pm certs.PageMetadata)) *Service_ListCerts_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 certs.PageMetadata
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(certs.PageMetadata)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListCerts_Call) Return(certPage certs.CertPage, err error) *Service_ListCerts_Call {
|
||||
_c.Call.Return(certPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListCerts_Call) RunAndReturn(run func(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error)) *Service_ListCerts_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListSerials provides a mock function for the type Service
|
||||
func (_mock *Service) ListSerials(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
ret := _mock.Called(ctx, clientID, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListSerials")
|
||||
}
|
||||
|
||||
var r0 certs.CertPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) (certs.CertPage, error)); ok {
|
||||
return returnFunc(ctx, clientID, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, certs.PageMetadata) certs.CertPage); ok {
|
||||
r0 = returnFunc(ctx, clientID, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.CertPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, certs.PageMetadata) error); ok {
|
||||
r1 = returnFunc(ctx, clientID, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ListSerials_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListSerials'
|
||||
type Service_ListSerials_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListSerials is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - clientID string
|
||||
// - pm certs.PageMetadata
|
||||
func (_e *Service_Expecter) ListSerials(ctx interface{}, clientID interface{}, pm interface{}) *Service_ListSerials_Call {
|
||||
return &Service_ListSerials_Call{Call: _e.mock.On("ListSerials", ctx, clientID, pm)}
|
||||
}
|
||||
|
||||
func (_c *Service_ListSerials_Call) Run(run func(ctx context.Context, clientID string, pm certs.PageMetadata)) *Service_ListSerials_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 certs.PageMetadata
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(certs.PageMetadata)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListSerials_Call) Return(certPage certs.CertPage, err error) *Service_ListSerials_Call {
|
||||
_c.Call.Return(certPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListSerials_Call) RunAndReturn(run func(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error)) *Service_ListSerials_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RevokeBySerial provides a mock function for the type Service
|
||||
func (_mock *Service) RevokeBySerial(ctx context.Context, serialID string) (certs.Revoke, error) {
|
||||
ret := _mock.Called(ctx, serialID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RevokeBySerial")
|
||||
}
|
||||
|
||||
var r0 certs.Revoke
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (certs.Revoke, error)); ok {
|
||||
return returnFunc(ctx, serialID)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) certs.Revoke); ok {
|
||||
r0 = returnFunc(ctx, serialID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.Revoke)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = returnFunc(ctx, serialID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_RevokeBySerial_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RevokeBySerial'
|
||||
type Service_RevokeBySerial_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RevokeBySerial is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - serialID string
|
||||
func (_e *Service_Expecter) RevokeBySerial(ctx interface{}, serialID interface{}) *Service_RevokeBySerial_Call {
|
||||
return &Service_RevokeBySerial_Call{Call: _e.mock.On("RevokeBySerial", ctx, serialID)}
|
||||
}
|
||||
|
||||
func (_c *Service_RevokeBySerial_Call) Run(run func(ctx context.Context, serialID string)) *Service_RevokeBySerial_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_RevokeBySerial_Call) Return(revoke certs.Revoke, err error) *Service_RevokeBySerial_Call {
|
||||
_c.Call.Return(revoke, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_RevokeBySerial_Call) RunAndReturn(run func(ctx context.Context, serialID string) (certs.Revoke, error)) *Service_RevokeBySerial_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RevokeCert provides a mock function for the type Service
|
||||
func (_mock *Service) RevokeCert(ctx context.Context, domainID string, token string, clientID string) (certs.Revoke, error) {
|
||||
ret := _mock.Called(ctx, domainID, token, clientID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RevokeCert")
|
||||
}
|
||||
|
||||
var r0 certs.Revoke
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) (certs.Revoke, error)); ok {
|
||||
return returnFunc(ctx, domainID, token, clientID)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) certs.Revoke); ok {
|
||||
r0 = returnFunc(ctx, domainID, token, clientID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.Revoke)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
|
||||
r1 = returnFunc(ctx, domainID, token, clientID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_RevokeCert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RevokeCert'
|
||||
type Service_RevokeCert_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RevokeCert is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - domainID string
|
||||
// - token string
|
||||
// - clientID string
|
||||
func (_e *Service_Expecter) RevokeCert(ctx interface{}, domainID interface{}, token interface{}, clientID interface{}) *Service_RevokeCert_Call {
|
||||
return &Service_RevokeCert_Call{Call: _e.mock.On("RevokeCert", ctx, domainID, token, clientID)}
|
||||
}
|
||||
|
||||
func (_c *Service_RevokeCert_Call) Run(run func(ctx context.Context, domainID string, token string, clientID string)) *Service_RevokeCert_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
var arg3 string
|
||||
if args[3] != nil {
|
||||
arg3 = args[3].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
arg3,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_RevokeCert_Call) Return(revoke certs.Revoke, err error) *Service_RevokeCert_Call {
|
||||
_c.Call.Return(revoke, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_RevokeCert_Call) RunAndReturn(run func(ctx context.Context, domainID string, token string, clientID string) (certs.Revoke, error)) *Service_RevokeCert_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewCert provides a mock function for the type Service
|
||||
func (_mock *Service) ViewCert(ctx context.Context, serialID string) (certs.Cert, error) {
|
||||
ret := _mock.Called(ctx, serialID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewCert")
|
||||
}
|
||||
|
||||
var r0 certs.Cert
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (certs.Cert, error)); ok {
|
||||
return returnFunc(ctx, serialID)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) certs.Cert); ok {
|
||||
r0 = returnFunc(ctx, serialID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(certs.Cert)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = returnFunc(ctx, serialID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ViewCert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewCert'
|
||||
type Service_ViewCert_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ViewCert is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - serialID string
|
||||
func (_e *Service_Expecter) ViewCert(ctx interface{}, serialID interface{}) *Service_ViewCert_Call {
|
||||
return &Service_ViewCert_Call{Call: _e.mock.On("ViewCert", ctx, serialID)}
|
||||
}
|
||||
|
||||
func (_c *Service_ViewCert_Call) Run(run func(ctx context.Context, serialID string)) *Service_ViewCert_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewCert_Call) Return(cert certs.Cert, err error) *Service_ViewCert_Call {
|
||||
_c.Call.Return(cert, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewCert_Call) RunAndReturn(run func(ctx context.Context, serialID string) (certs.Cert, error)) *Service_ViewCert_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package certs
|
||||
|
||||
// Agent represents the PKI interface that all PKI implementations must satisfy.
|
||||
type Agent interface {
|
||||
Issue(entityId, ttl string, ipAddrs []string) (Cert, error)
|
||||
View(serialNumber string) (Cert, error)
|
||||
Revoke(serialNumber string) error
|
||||
ListCerts(pm PageMetadata) (CertPage, error)
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package amcerts
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/certs/sdk"
|
||||
)
|
||||
|
||||
type Cert struct {
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Revoked bool `json:"revoked"`
|
||||
ExpiryTime time.Time `json:"expiry_time"`
|
||||
ClientID string `json:"entity_id"`
|
||||
DownloadUrl string `json:"-"`
|
||||
}
|
||||
|
||||
type CertPage struct {
|
||||
Total uint64 `json:"total"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
Certificates []Cert `json:"certificates,omitempty"`
|
||||
}
|
||||
|
||||
type Agent interface {
|
||||
Issue(entityId, ttl string, ipAddrs []string) (Cert, error)
|
||||
|
||||
View(serialNumber string) (Cert, error)
|
||||
|
||||
Revoke(serialNumber string) error
|
||||
|
||||
ListCerts(pm sdk.PageMetadata) (CertPage, error)
|
||||
}
|
||||
|
||||
type sdkAgent struct {
|
||||
sdk sdk.SDK
|
||||
}
|
||||
|
||||
func NewAgent(host, certsURL string, TLSVerification bool) (Agent, error) {
|
||||
msgContentType := string(sdk.CTJSONSenML)
|
||||
certConfig := sdk.Config{
|
||||
CertsURL: certsURL,
|
||||
HostURL: host,
|
||||
MsgContentType: sdk.ContentType(msgContentType),
|
||||
TLSVerification: TLSVerification,
|
||||
}
|
||||
|
||||
return sdkAgent{
|
||||
sdk: sdk.NewSDK(certConfig),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c sdkAgent) Issue(entityId, ttl string, ipAddrs []string) (Cert, error) {
|
||||
cert, err := c.sdk.IssueCert(entityId, ttl, ipAddrs, sdk.Options{CommonName: "SuperMQ"})
|
||||
if err != nil {
|
||||
return Cert{}, err
|
||||
}
|
||||
|
||||
return Cert{
|
||||
SerialNumber: cert.SerialNumber,
|
||||
Certificate: cert.Certificate,
|
||||
Revoked: cert.Revoked,
|
||||
ExpiryTime: cert.ExpiryTime,
|
||||
ClientID: cert.EntityID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c sdkAgent) View(serial string) (Cert, error) {
|
||||
cert, err := c.sdk.ViewCert(serial)
|
||||
if err != nil {
|
||||
return Cert{}, err
|
||||
}
|
||||
return Cert{
|
||||
SerialNumber: cert.SerialNumber,
|
||||
Certificate: cert.Certificate,
|
||||
Key: cert.Key,
|
||||
Revoked: cert.Revoked,
|
||||
ExpiryTime: cert.ExpiryTime,
|
||||
ClientID: cert.EntityID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c sdkAgent) Revoke(serial string) error {
|
||||
if err := c.sdk.RevokeCert(serial); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c sdkAgent) ListCerts(pm sdk.PageMetadata) (CertPage, error) {
|
||||
certPage, err := c.sdk.ListCerts(pm)
|
||||
if err != nil {
|
||||
return CertPage{}, err
|
||||
}
|
||||
|
||||
var crts []Cert
|
||||
for _, c := range certPage.Certificates {
|
||||
crts = append(crts, Cert{
|
||||
SerialNumber: c.SerialNumber,
|
||||
Certificate: c.Certificate,
|
||||
Key: c.Key,
|
||||
Revoked: c.Revoked,
|
||||
ExpiryTime: c.ExpiryTime,
|
||||
ClientID: c.EntityID,
|
||||
})
|
||||
}
|
||||
|
||||
return CertPage{
|
||||
Total: certPage.Total,
|
||||
Limit: certPage.Limit,
|
||||
Offset: certPage.Offset,
|
||||
Certificates: crts,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package amcerts
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openbao
|
||||
@@ -1,356 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package openbao wraps OpenBao client for PKI operations
|
||||
package openbao
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/openbao/openbao/api/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
issue = "issue"
|
||||
cert = "cert"
|
||||
revoke = "revoke"
|
||||
)
|
||||
|
||||
var (
|
||||
errFailedToLogin = errors.New("failed to login to OpenBao")
|
||||
errNoAuthInfo = errors.New("no auth information from OpenBao")
|
||||
errRenewWatcher = errors.New("unable to initialize new lifetime watcher for renewing auth token")
|
||||
)
|
||||
|
||||
// Agent represents the OpenBao PKI interface.
|
||||
type Agent interface {
|
||||
Issue(entityId, ttl string, ipAddrs []string) (certs.Cert, error)
|
||||
View(serialNumber string) (certs.Cert, error)
|
||||
Revoke(serialNumber string) error
|
||||
ListCerts(pm certs.PageMetadata) (certs.CertPage, error)
|
||||
}
|
||||
|
||||
type openbaoPKIAgent struct {
|
||||
appRole string
|
||||
appSecret string
|
||||
namespace string
|
||||
path string
|
||||
role string
|
||||
host string
|
||||
issueURL string
|
||||
readURL string
|
||||
revokeURL string
|
||||
client *api.Client
|
||||
secret *api.Secret
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewAgent instantiates an OpenBao PKI client.
|
||||
func NewAgent(appRole, appSecret, host, namespace, path, role string, logger *slog.Logger) (Agent, error) {
|
||||
conf := api.DefaultConfig()
|
||||
conf.Address = host
|
||||
|
||||
client, err := api.NewClient(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if namespace != "" {
|
||||
client.SetNamespace(namespace)
|
||||
}
|
||||
|
||||
p := openbaoPKIAgent{
|
||||
appRole: appRole,
|
||||
appSecret: appSecret,
|
||||
host: host,
|
||||
namespace: namespace,
|
||||
role: role,
|
||||
path: path,
|
||||
client: client,
|
||||
logger: logger,
|
||||
issueURL: "/" + path + "/" + issue + "/" + role,
|
||||
readURL: "/" + path + "/" + cert + "/",
|
||||
revokeURL: "/" + path + "/" + revoke,
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (va *openbaoPKIAgent) Issue(entityId, ttl string, ipAddrs []string) (certs.Cert, error) {
|
||||
err := va.LoginAndRenew()
|
||||
if err != nil {
|
||||
return certs.Cert{}, err
|
||||
}
|
||||
|
||||
secretValues := map[string]interface{}{
|
||||
"common_name": entityId,
|
||||
"ttl": ttl,
|
||||
"exclude_cn_from_sans": true,
|
||||
}
|
||||
|
||||
if len(ipAddrs) > 0 {
|
||||
secretValues["ip_sans"] = ipAddrs
|
||||
}
|
||||
|
||||
secret, err := va.client.Logical().Write(va.issueURL, secretValues)
|
||||
if err != nil {
|
||||
return certs.Cert{}, err
|
||||
}
|
||||
|
||||
if secret == nil || secret.Data == nil {
|
||||
return certs.Cert{}, fmt.Errorf("no certificate data returned from OpenBao")
|
||||
}
|
||||
|
||||
cert := certs.Cert{
|
||||
ClientID: entityId,
|
||||
}
|
||||
|
||||
if certData, ok := secret.Data["certificate"].(string); ok {
|
||||
cert.Certificate = certData
|
||||
}
|
||||
|
||||
if keyData, ok := secret.Data["private_key"].(string); ok {
|
||||
cert.Key = keyData
|
||||
}
|
||||
|
||||
if serialNumber, ok := secret.Data["serial_number"].(string); ok {
|
||||
cert.SerialNumber = serialNumber
|
||||
}
|
||||
if caChain, ok := secret.Data["ca_chain"].([]interface{}); ok {
|
||||
for _, ca := range caChain {
|
||||
if caStr, ok := ca.(string); ok {
|
||||
cert.CAChain = append(cert.CAChain, caStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if issuingCA, ok := secret.Data["issuing_ca"].(string); ok {
|
||||
cert.IssuingCA = issuingCA
|
||||
}
|
||||
|
||||
if expirationInterface, ok := secret.Data["expiration"]; ok {
|
||||
switch exp := expirationInterface.(type) {
|
||||
case int64:
|
||||
cert.ExpiryTime = time.Unix(exp, 0)
|
||||
case float64:
|
||||
cert.ExpiryTime = time.Unix(int64(exp), 0)
|
||||
case json.Number:
|
||||
if expInt, err := exp.Int64(); err == nil {
|
||||
cert.ExpiryTime = time.Unix(expInt, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (va *openbaoPKIAgent) View(serialNumber string) (certs.Cert, error) {
|
||||
err := va.LoginAndRenew()
|
||||
if err != nil {
|
||||
return certs.Cert{}, err
|
||||
}
|
||||
|
||||
secret, err := va.client.Logical().Read(va.readURL + serialNumber)
|
||||
if err != nil {
|
||||
return certs.Cert{}, err
|
||||
}
|
||||
|
||||
if secret == nil || secret.Data == nil {
|
||||
return certs.Cert{}, fmt.Errorf("certificate not found")
|
||||
}
|
||||
|
||||
cert := certs.Cert{
|
||||
SerialNumber: serialNumber,
|
||||
}
|
||||
|
||||
if certData, ok := secret.Data["certificate"].(string); ok {
|
||||
cert.Certificate = certData
|
||||
}
|
||||
|
||||
if cert.Certificate != "" {
|
||||
if expiry, err := va.parseCertificateExpiry(cert.Certificate); err == nil {
|
||||
cert.ExpiryTime = expiry
|
||||
}
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (va *openbaoPKIAgent) parseCertificateExpiry(certPEM string) (time.Time, error) {
|
||||
block, _ := pem.Decode([]byte(certPEM))
|
||||
if block == nil {
|
||||
return time.Time{}, fmt.Errorf("failed to decode PEM certificate")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("failed to parse X509 certificate: %w", err)
|
||||
}
|
||||
|
||||
return cert.NotAfter, nil
|
||||
}
|
||||
|
||||
func (va *openbaoPKIAgent) Revoke(serialNumber string) error {
|
||||
err := va.LoginAndRenew()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secretValues := map[string]interface{}{
|
||||
"serial_number": serialNumber,
|
||||
}
|
||||
|
||||
_, err = va.client.Logical().Write(va.revokeURL, secretValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (va *openbaoPKIAgent) ListCerts(pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
err := va.LoginAndRenew()
|
||||
if err != nil {
|
||||
return certs.CertPage{}, err
|
||||
}
|
||||
|
||||
secret, err := va.client.Logical().List(va.path + "/certs")
|
||||
if err != nil {
|
||||
return certs.CertPage{}, err
|
||||
}
|
||||
|
||||
certPage := certs.CertPage{
|
||||
Certificates: []certs.Cert{},
|
||||
Limit: pm.Limit,
|
||||
Offset: pm.Offset,
|
||||
}
|
||||
|
||||
if secret == nil || secret.Data == nil {
|
||||
return certPage, nil
|
||||
}
|
||||
|
||||
keysInterface, ok := secret.Data["keys"]
|
||||
if !ok {
|
||||
return certPage, nil
|
||||
}
|
||||
|
||||
var serialNumbers []string
|
||||
if err := mapstructure.Decode(keysInterface, &serialNumbers); err != nil {
|
||||
return certPage, fmt.Errorf("failed to decode certificate serial numbers: %w", err)
|
||||
}
|
||||
|
||||
var filteredCerts []certs.Cert
|
||||
for _, serialNumber := range serialNumbers {
|
||||
cert, err := va.View(serialNumber)
|
||||
if err != nil {
|
||||
va.logger.Warn("failed to retrieve certificate details", "serial", serialNumber, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if pm.CommonName != "" {
|
||||
if !va.matchesCommonName(cert.Certificate, pm.CommonName) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
filteredCerts = append(filteredCerts, cert)
|
||||
}
|
||||
|
||||
certPage.Total = uint64(len(filteredCerts))
|
||||
|
||||
start := pm.Offset
|
||||
end := pm.Offset + pm.Limit
|
||||
if pm.Limit == 0 {
|
||||
end = uint64(len(filteredCerts))
|
||||
}
|
||||
if start >= uint64(len(filteredCerts)) {
|
||||
return certPage, nil
|
||||
}
|
||||
if end > uint64(len(filteredCerts)) {
|
||||
end = uint64(len(filteredCerts))
|
||||
}
|
||||
|
||||
for i := start; i < end; i++ {
|
||||
certPage.Certificates = append(certPage.Certificates, filteredCerts[i])
|
||||
}
|
||||
|
||||
return certPage, nil
|
||||
}
|
||||
|
||||
func (va *openbaoPKIAgent) LoginAndRenew() error {
|
||||
if va.secret != nil && va.secret.Auth != nil && va.secret.Auth.ClientToken != "" {
|
||||
_, err := va.client.Auth().Token().LookupSelf()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
authData := map[string]interface{}{
|
||||
"role_id": va.appRole,
|
||||
"secret_id": va.appSecret,
|
||||
}
|
||||
|
||||
authResp, err := va.client.Logical().Write("auth/approle/login", authData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", errFailedToLogin, err)
|
||||
}
|
||||
|
||||
if authResp == nil || authResp.Auth == nil {
|
||||
return errNoAuthInfo
|
||||
}
|
||||
|
||||
va.secret = authResp
|
||||
va.client.SetToken(authResp.Auth.ClientToken)
|
||||
|
||||
if authResp.Auth.Renewable {
|
||||
watcher, err := va.client.NewLifetimeWatcher(&api.LifetimeWatcherInput{
|
||||
Secret: authResp,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", errRenewWatcher, err)
|
||||
}
|
||||
|
||||
go va.renewToken(watcher)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (va *openbaoPKIAgent) renewToken(watcher *api.LifetimeWatcher) {
|
||||
defer watcher.Stop()
|
||||
|
||||
watcher.Start()
|
||||
for {
|
||||
select {
|
||||
case err := <-watcher.DoneCh():
|
||||
if err != nil {
|
||||
va.logger.Error("token renewal failed", "error", err)
|
||||
}
|
||||
return
|
||||
case renewal := <-watcher.RenewCh():
|
||||
va.logger.Info("token renewed successfully", "lease_duration", renewal.Secret.LeaseDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (va *openbaoPKIAgent) matchesCommonName(certPEM, expectedCommonName string) bool {
|
||||
if certPEM == "" || expectedCommonName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(certPEM))
|
||||
if block == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return cert.Subject.CommonName == expectedCommonName
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
"github.com/absmach/supermq/pkg/postgres"
|
||||
)
|
||||
|
||||
var _ certs.Repository = (*certsRepository)(nil)
|
||||
|
||||
type PageMetadata struct {
|
||||
Offset uint64 `db:"offset,omitempty"`
|
||||
Limit uint64 `db:"limit,omitempty"`
|
||||
ClientID string `db:"client_id,omitempty"`
|
||||
}
|
||||
|
||||
type certsRepository struct {
|
||||
db postgres.Database
|
||||
}
|
||||
|
||||
// NewRepository instantiates a PostgreSQL implementation of certs
|
||||
// repository.
|
||||
func NewRepository(db postgres.Database) certs.Repository {
|
||||
return &certsRepository{db: db}
|
||||
}
|
||||
|
||||
func (cr certsRepository) RetrieveAll(ctx context.Context, offset, limit uint64) (certs.CertPage, error) {
|
||||
pm := certs.PageMetadata{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
return cr.retrieveCertificates(ctx, "", pm)
|
||||
}
|
||||
|
||||
func (cr certsRepository) RetrieveByClient(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
return cr.retrieveCertificates(ctx, clientID, pm)
|
||||
}
|
||||
|
||||
func (cr certsRepository) Save(ctx context.Context, cert certs.Cert) (string, error) {
|
||||
dbcrt := toDBCert(cert)
|
||||
|
||||
q := `INSERT INTO certs (client_id, serial_number, expiry_time, revoked)
|
||||
VALUES (:client_id, :serial_number, :expiry_time, :revoked)
|
||||
RETURNING serial_number`
|
||||
|
||||
row, err := cr.db.NamedQueryContext(ctx, q, dbcrt)
|
||||
if err != nil {
|
||||
return "", postgres.HandleError(repoerr.ErrCreateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var serialNumber string
|
||||
if row.Next() {
|
||||
if err := row.Scan(&serialNumber); err != nil {
|
||||
return "", errors.Wrap(repoerr.ErrFailedOpDB, err)
|
||||
}
|
||||
}
|
||||
|
||||
return serialNumber, nil
|
||||
}
|
||||
|
||||
func (cr certsRepository) Update(ctx context.Context, cert certs.Cert) error {
|
||||
dbcrt := toDBCert(cert)
|
||||
|
||||
q := `UPDATE certs SET
|
||||
client_id = :client_id,
|
||||
expiry_time = :expiry_time,
|
||||
revoked = :revoked
|
||||
WHERE serial_number = :serial_number`
|
||||
|
||||
result, err := cr.db.NamedExecContext(ctx, q, dbcrt)
|
||||
if err != nil {
|
||||
return postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(repoerr.ErrFailedOpDB, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return errors.Wrap(repoerr.ErrNotFound, errors.New("certificate not found"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr certsRepository) Remove(ctx context.Context, clientID string) error {
|
||||
q := `DELETE FROM certs WHERE client_id = :client_id`
|
||||
var c certs.Cert
|
||||
c.ClientID = clientID
|
||||
dbcrt := toDBCert(c)
|
||||
if _, err := cr.db.NamedExecContext(ctx, q, dbcrt); err != nil {
|
||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr certsRepository) RemoveBySerial(ctx context.Context, serialID string) error {
|
||||
q := `DELETE FROM certs WHERE serial_number = :serial_number`
|
||||
var c certs.Cert
|
||||
c.SerialNumber = serialID
|
||||
dbcrt := toDBCert(c)
|
||||
if _, err := cr.db.NamedExecContext(ctx, q, dbcrt); err != nil {
|
||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PageQuery(pm certs.PageMetadata) (string, error) {
|
||||
var query []string
|
||||
|
||||
if pm.Revoked != "all" {
|
||||
switch pm.Revoked {
|
||||
case "true":
|
||||
query = append(query, "revoked = true")
|
||||
case "false":
|
||||
query = append(query, "revoked = false")
|
||||
}
|
||||
}
|
||||
|
||||
if pm.CommonName != "" {
|
||||
query = append(query, "client_id ILIKE '%' || :client_id || '%'")
|
||||
}
|
||||
|
||||
var emq string
|
||||
if len(query) > 0 {
|
||||
emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
return emq, nil
|
||||
}
|
||||
|
||||
func (cr certsRepository) retrieveCertificates(ctx context.Context, clientID string, pm certs.PageMetadata) (certs.CertPage, error) {
|
||||
pageQuery, err := PageQuery(pm)
|
||||
if err != nil {
|
||||
return certs.CertPage{}, err
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`SELECT client_id, serial_number, expiry_time, revoked FROM certs %s`,
|
||||
pageQuery)
|
||||
|
||||
q = applyLimitOffset(q)
|
||||
|
||||
param := PageMetadata{
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
ClientID: clientID,
|
||||
}
|
||||
|
||||
rows, err := cr.db.NamedQueryContext(ctx, q, param)
|
||||
if err != nil {
|
||||
return certs.CertPage{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
certificates := []certs.Cert{}
|
||||
for rows.Next() {
|
||||
c := certs.Cert{}
|
||||
if err := rows.Scan(&c.ClientID, &c.SerialNumber, &c.ExpiryTime, &c.Revoked); err != nil {
|
||||
return certs.CertPage{}, err
|
||||
}
|
||||
certificates = append(certificates, c)
|
||||
}
|
||||
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) AS total_count
|
||||
FROM certs %s`, pageQuery)
|
||||
|
||||
total, err := postgres.Total(ctx, cr.db, cq, param)
|
||||
if err != nil {
|
||||
return certs.CertPage{}, errors.Wrap(repoerr.ErrFailedOpDB, err)
|
||||
}
|
||||
|
||||
return certs.CertPage{
|
||||
Total: total,
|
||||
Limit: pm.Limit,
|
||||
Offset: pm.Offset,
|
||||
Certificates: certificates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cr certsRepository) RetrieveBySerial(ctx context.Context, serial string) (certs.Cert, error) {
|
||||
q := `SELECT client_id, serial_number, expiry_time, revoked FROM certs WHERE serial_number = $1`
|
||||
var dbcrt dbCert
|
||||
var c certs.Cert
|
||||
|
||||
if err := cr.db.QueryRowxContext(ctx, q, serial).StructScan(&dbcrt); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return c, errors.Wrap(repoerr.ErrNotFound, err)
|
||||
}
|
||||
|
||||
return c, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
c = toCert(dbcrt)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type dbCert struct {
|
||||
ClientID string `db:"client_id"`
|
||||
SerialNumber string `db:"serial_number"`
|
||||
ExpiryTime time.Time `db:"expiry_time"`
|
||||
Revoked bool `db:"revoked"`
|
||||
}
|
||||
|
||||
func toDBCert(c certs.Cert) dbCert {
|
||||
return dbCert{
|
||||
ClientID: c.ClientID,
|
||||
SerialNumber: c.SerialNumber,
|
||||
ExpiryTime: c.ExpiryTime,
|
||||
Revoked: c.Revoked,
|
||||
}
|
||||
}
|
||||
|
||||
func toCert(cdb dbCert) certs.Cert {
|
||||
var c certs.Cert
|
||||
c.ClientID = cdb.ClientID
|
||||
c.SerialNumber = cdb.SerialNumber
|
||||
c.ExpiryTime = cdb.ExpiryTime
|
||||
c.Revoked = cdb.Revoked
|
||||
return c
|
||||
}
|
||||
|
||||
func applyLimitOffset(query string) string {
|
||||
return fmt.Sprintf(`%s
|
||||
LIMIT :limit OFFSET :offset`, query)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package postgres contains repository implementations using PostgreSQL as
|
||||
// the underlying database.
|
||||
package postgres
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import migrate "github.com/rubenv/sql-migrate"
|
||||
|
||||
// Migration of Certs service.
|
||||
func Migration() *migrate.MemoryMigrationSource {
|
||||
return &migrate.MemoryMigrationSource{
|
||||
Migrations: []*migrate.Migration{
|
||||
{
|
||||
Id: "certs_1",
|
||||
Up: []string{
|
||||
`CREATE TABLE IF NOT EXISTS certs (
|
||||
client_id TEXT NOT NULL,
|
||||
expiry_time TIMESTAMPTZ NOT NULL,
|
||||
serial_number TEXT NOT NULL,
|
||||
revoked BOOLEAN DEFAULT FALSE,
|
||||
PRIMARY KEY (client_id, serial_number)
|
||||
);`,
|
||||
},
|
||||
Down: []string{
|
||||
"DROP TABLE IF EXISTS certs;",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/supermq/certs/postgres"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
pgclient "github.com/absmach/supermq/pkg/postgres"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
)
|
||||
|
||||
var (
|
||||
testLog, _ = smqlog.New(os.Stdout, "info")
|
||||
db *sqlx.DB
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
container, err := pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: "postgres",
|
||||
Tag: "16.1-alpine",
|
||||
Env: []string{
|
||||
"POSTGRES_USER=test",
|
||||
"POSTGRES_PASSWORD=test",
|
||||
"POSTGRES_DB=test",
|
||||
"listen_addresses = '*'",
|
||||
},
|
||||
}, func(config *docker.HostConfig) {
|
||||
config.AutoRemove = true
|
||||
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start container: %s", err)
|
||||
}
|
||||
|
||||
port := container.GetPort("5432/tcp")
|
||||
|
||||
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 {
|
||||
testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err))
|
||||
}
|
||||
|
||||
dbConfig := pgclient.Config{
|
||||
Host: "localhost",
|
||||
Port: port,
|
||||
User: "test",
|
||||
Pass: "test",
|
||||
Name: "test",
|
||||
SSLMode: "disable",
|
||||
SSLCert: "",
|
||||
SSLKey: "",
|
||||
SSLRootCert: "",
|
||||
}
|
||||
|
||||
if db, err = pgclient.Setup(dbConfig, *postgres.Migration()); err != nil {
|
||||
testLog.Error(fmt.Sprintf("Could not setup test DB connection: %s", err))
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
|
||||
// Defers will not be run when using os.Exit
|
||||
db.Close()
|
||||
if err := pool.Purge(container); err != nil {
|
||||
testLog.Error(fmt.Sprintf("Could not purge container: %s", err))
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package certs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
mgsdk "github.com/absmach/supermq/pkg/sdk"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrFailedCertCreation failed to create certificate.
|
||||
ErrFailedCertCreation = errors.New("failed to create client certificate")
|
||||
|
||||
// ErrFailedCertRevocation failed to revoke certificate.
|
||||
ErrFailedCertRevocation = errors.New("failed to revoke certificate")
|
||||
|
||||
ErrFailedToRemoveCertFromDB = errors.New("failed to remove cert serial from db")
|
||||
|
||||
ErrFailedReadFromPKI = errors.New("failed to read certificate from PKI")
|
||||
|
||||
ErrFailedReadFromDB = errors.New("failed to read certificate from database")
|
||||
)
|
||||
|
||||
var _ Service = (*certsService)(nil)
|
||||
|
||||
// Service specifies an API that must be fulfilled by the domain service
|
||||
// implementation, and all of its decorators (e.g. logging & metrics).
|
||||
type Service interface {
|
||||
// IssueCert issues certificate for given client id if access is granted with token
|
||||
IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (Cert, error)
|
||||
|
||||
// ListCerts lists certificates issued for a given client ID
|
||||
ListCerts(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error)
|
||||
|
||||
// ListSerials lists certificate serial IDs issued for a given client ID
|
||||
ListSerials(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error)
|
||||
|
||||
// ViewCert retrieves the certificate issued for a given serial ID
|
||||
ViewCert(ctx context.Context, serialID string) (Cert, error)
|
||||
|
||||
// RevokeCert revokes a certificate for a given client ID
|
||||
RevokeCert(ctx context.Context, domainID, token, clientID string) (Revoke, error)
|
||||
|
||||
// RevokeBySerial revokes a certificate by its serial number from both PKI and database
|
||||
RevokeBySerial(ctx context.Context, serialID string) (Revoke, error)
|
||||
}
|
||||
|
||||
// Revoke defines the conditions to revoke a certificate.
|
||||
type Revoke struct {
|
||||
RevocationTime time.Time `json:"revocation_time"`
|
||||
}
|
||||
type certsService struct {
|
||||
sdk mgsdk.SDK
|
||||
certsRepo Repository
|
||||
pki Agent
|
||||
}
|
||||
|
||||
// New returns new Certs service.
|
||||
func New(sdk mgsdk.SDK, certsRepo Repository, pkiAgent Agent) Service {
|
||||
return &certsService{
|
||||
sdk: sdk,
|
||||
pki: pkiAgent,
|
||||
certsRepo: certsRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *certsService) IssueCert(ctx context.Context, domainID, token, clientID, ttl string) (Cert, error) {
|
||||
var err error
|
||||
|
||||
client, err := cs.sdk.Client(ctx, clientID, domainID, token)
|
||||
if err != nil {
|
||||
return Cert{}, errors.Wrap(ErrFailedCertCreation, err)
|
||||
}
|
||||
|
||||
cert, err := cs.pki.Issue(client.ID, ttl, []string{})
|
||||
if err != nil {
|
||||
return Cert{}, errors.Wrap(ErrFailedCertCreation, err)
|
||||
}
|
||||
|
||||
_, err = cs.certsRepo.Save(ctx, cert)
|
||||
if err != nil {
|
||||
return Cert{}, errors.Wrap(ErrFailedCertCreation, err)
|
||||
}
|
||||
|
||||
return Cert{
|
||||
SerialNumber: cert.SerialNumber,
|
||||
Certificate: cert.Certificate,
|
||||
Key: cert.Key,
|
||||
ExpiryTime: cert.ExpiryTime,
|
||||
IssuingCA: cert.IssuingCA,
|
||||
CAChain: cert.CAChain,
|
||||
ClientID: cert.ClientID,
|
||||
Revoked: cert.Revoked,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (cs *certsService) RevokeCert(ctx context.Context, domainID, token, clientID string) (Revoke, error) {
|
||||
var revoke Revoke
|
||||
var err error
|
||||
|
||||
cp, err := cs.certsRepo.RetrieveByClient(ctx, clientID, PageMetadata{Offset: 0, Limit: 10000})
|
||||
if err != nil {
|
||||
return revoke, errors.Wrap(ErrFailedCertRevocation, err)
|
||||
}
|
||||
|
||||
for _, c := range cp.Certificates {
|
||||
err := cs.pki.Revoke(c.SerialNumber)
|
||||
if err != nil {
|
||||
return revoke, errors.Wrap(ErrFailedCertRevocation, err)
|
||||
}
|
||||
|
||||
c.Revoked = true
|
||||
err = cs.certsRepo.Update(ctx, c)
|
||||
if err != nil {
|
||||
return revoke, errors.Wrap(ErrFailedReadFromDB, err)
|
||||
}
|
||||
|
||||
revoke.RevocationTime = time.Now().UTC()
|
||||
}
|
||||
|
||||
return revoke, nil
|
||||
}
|
||||
|
||||
func (cs *certsService) RevokeBySerial(ctx context.Context, serialID string) (Revoke, error) {
|
||||
var revoke Revoke
|
||||
|
||||
cert, err := cs.certsRepo.RetrieveBySerial(ctx, serialID)
|
||||
if err != nil {
|
||||
return revoke, errors.Wrap(ErrFailedReadFromDB, err)
|
||||
}
|
||||
|
||||
err = cs.pki.Revoke(serialID)
|
||||
if err != nil {
|
||||
return revoke, errors.Wrap(ErrFailedCertRevocation, err)
|
||||
}
|
||||
|
||||
cert.Revoked = true
|
||||
err = cs.certsRepo.Update(ctx, cert)
|
||||
if err != nil {
|
||||
return revoke, errors.Wrap(ErrFailedReadFromDB, err)
|
||||
}
|
||||
|
||||
revoke.RevocationTime = time.Now().UTC()
|
||||
return revoke, nil
|
||||
}
|
||||
|
||||
func (cs *certsService) ListCerts(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error) {
|
||||
cp, err := cs.certsRepo.RetrieveByClient(ctx, clientID, pm)
|
||||
if err != nil {
|
||||
return CertPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
for i, cert := range cp.Certificates {
|
||||
vcert, err := cs.pki.View(cert.SerialNumber)
|
||||
if err != nil {
|
||||
return CertPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
cp.Certificates[i].Certificate = vcert.Certificate
|
||||
cp.Certificates[i].Key = vcert.Key
|
||||
}
|
||||
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
func (cs *certsService) ListSerials(ctx context.Context, clientID string, pm PageMetadata) (CertPage, error) {
|
||||
cp, err := cs.certsRepo.RetrieveByClient(ctx, clientID, pm)
|
||||
if err != nil {
|
||||
return CertPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
func (cs *certsService) ViewCert(ctx context.Context, serialID string) (Cert, error) {
|
||||
cert, err := cs.certsRepo.RetrieveBySerial(ctx, serialID)
|
||||
if err != nil {
|
||||
return Cert{}, errors.Wrap(ErrFailedReadFromDB, err)
|
||||
}
|
||||
|
||||
vcert, err := cs.pki.View(serialID)
|
||||
if err != nil {
|
||||
return Cert{}, errors.Wrap(ErrFailedReadFromPKI, err)
|
||||
}
|
||||
|
||||
return Cert{
|
||||
SerialNumber: cert.SerialNumber,
|
||||
Certificate: vcert.Certificate,
|
||||
Key: vcert.Key,
|
||||
ExpiryTime: vcert.ExpiryTime,
|
||||
ClientID: cert.ClientID,
|
||||
Revoked: cert.Revoked,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package certs_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/absmach/supermq/certs/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
mgsdk "github.com/absmach/supermq/pkg/sdk"
|
||||
sdkmocks "github.com/absmach/supermq/pkg/sdk/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
invalid = "invalid"
|
||||
email = "user@example.com"
|
||||
domain = "domain"
|
||||
token = "token"
|
||||
clientsNum = 1
|
||||
clientKey = "clientKey"
|
||||
clientID = "1"
|
||||
ttl = "1h"
|
||||
certNum = 10
|
||||
validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22"
|
||||
)
|
||||
|
||||
func newService(_ *testing.T) (certs.Service, *mocks.Agent, *sdkmocks.SDK, *mocks.Repository) {
|
||||
agent := new(mocks.Agent)
|
||||
repo := new(mocks.Repository)
|
||||
sdk := new(sdkmocks.SDK)
|
||||
|
||||
return certs.New(sdk, repo, agent), agent, sdk, repo
|
||||
}
|
||||
|
||||
var cert = certs.Cert{
|
||||
ClientID: clientID,
|
||||
SerialNumber: "Serial",
|
||||
ExpiryTime: time.Now().Add(time.Duration(1000)),
|
||||
}
|
||||
|
||||
func TestIssueCert(t *testing.T) {
|
||||
svc, agent, sdk, repo := newService(t)
|
||||
cases := []struct {
|
||||
domainID string
|
||||
token string
|
||||
desc string
|
||||
clientID string
|
||||
ttl string
|
||||
ipAddr []string
|
||||
key string
|
||||
cert certs.Cert
|
||||
clientErr errors.SDKError
|
||||
issueCertErr error
|
||||
saveErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "issue new cert",
|
||||
domainID: domain,
|
||||
token: token,
|
||||
clientID: clientID,
|
||||
ttl: ttl,
|
||||
ipAddr: []string{},
|
||||
cert: cert,
|
||||
},
|
||||
{
|
||||
desc: "issue new for failed pki",
|
||||
domainID: domain,
|
||||
token: token,
|
||||
clientID: clientID,
|
||||
ttl: ttl,
|
||||
ipAddr: []string{},
|
||||
clientErr: nil,
|
||||
issueCertErr: certs.ErrFailedCertCreation,
|
||||
err: certs.ErrFailedCertCreation,
|
||||
},
|
||||
{
|
||||
desc: "issue new cert for non existing client id",
|
||||
domainID: domain,
|
||||
token: token,
|
||||
clientID: "2",
|
||||
ttl: ttl,
|
||||
ipAddr: []string{},
|
||||
clientErr: errors.NewSDKError(errors.ErrMalformedEntity),
|
||||
err: certs.ErrFailedCertCreation,
|
||||
},
|
||||
{
|
||||
desc: "issue new cert for invalid token",
|
||||
domainID: domain,
|
||||
token: invalid,
|
||||
clientID: clientID,
|
||||
ttl: ttl,
|
||||
ipAddr: []string{},
|
||||
clientErr: errors.NewSDKError(svcerr.ErrAuthentication),
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdk.On("Client", mock.Anything, tc.clientID, tc.domainID, tc.token).Return(mgsdk.Client{ID: tc.clientID, Credentials: mgsdk.ClientCredentials{Secret: clientKey}}, tc.clientErr)
|
||||
agentCall := agent.On("Issue", clientID, tc.ttl, tc.ipAddr).Return(tc.cert, tc.issueCertErr)
|
||||
repoCall := repo.On("Save", mock.Anything, tc.cert).Return("", tc.saveErr)
|
||||
resp, err := svc.IssueCert(context.Background(), tc.domainID, tc.token, tc.clientID, tc.ttl)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.cert.SerialNumber, resp.SerialNumber, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.cert.SerialNumber, resp.SerialNumber))
|
||||
sdkCall.Unset()
|
||||
agentCall.Unset()
|
||||
repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevokeCert(t *testing.T) {
|
||||
svc, agent, _, repo := newService(t)
|
||||
cases := []struct {
|
||||
domainID string
|
||||
token string
|
||||
desc string
|
||||
clientID string
|
||||
page certs.CertPage
|
||||
authErr error
|
||||
clientErr errors.SDKError
|
||||
revokeErr error
|
||||
listErr error
|
||||
retrieveErr error
|
||||
updateErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "revoke cert",
|
||||
domainID: domain,
|
||||
token: token,
|
||||
clientID: clientID,
|
||||
page: certs.CertPage{Limit: 10000, Offset: 0, Total: 1, Certificates: []certs.Cert{cert}},
|
||||
},
|
||||
{
|
||||
desc: "revoke cert for failed pki revoke",
|
||||
domainID: domain,
|
||||
token: token,
|
||||
clientID: clientID,
|
||||
page: certs.CertPage{Limit: 10000, Offset: 0, Total: 1, Certificates: []certs.Cert{cert}},
|
||||
revokeErr: certs.ErrFailedCertRevocation,
|
||||
err: certs.ErrFailedCertRevocation,
|
||||
},
|
||||
{
|
||||
desc: "revoke cert with failed to list certs",
|
||||
domainID: domain,
|
||||
token: token,
|
||||
clientID: clientID,
|
||||
page: certs.CertPage{},
|
||||
retrieveErr: certs.ErrFailedCertRevocation,
|
||||
listErr: certs.ErrFailedCertRevocation,
|
||||
err: certs.ErrFailedCertRevocation,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("RetrieveByClient", mock.Anything, tc.clientID, mock.Anything).Return(tc.page, tc.retrieveErr)
|
||||
repoCall1 := repo.On("Update", mock.Anything, mock.Anything).Return(tc.updateErr)
|
||||
agentCall := agent.On("Revoke", mock.Anything).Return(tc.revokeErr)
|
||||
agentCall1 := agent.On("ListCerts", mock.Anything).Return(tc.page, tc.listErr)
|
||||
_, err := svc.RevokeCert(context.Background(), tc.domainID, tc.token, tc.clientID)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
agentCall.Unset()
|
||||
agentCall1.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevokeBySerial(t *testing.T) {
|
||||
svc, agent, _, repo := newService(t)
|
||||
cases := []struct {
|
||||
desc string
|
||||
serialID string
|
||||
revokeErr error
|
||||
updateErr error
|
||||
retrieveErr error
|
||||
Cert certs.Cert
|
||||
expectedTime time.Time
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "revoke cert by serial successfully",
|
||||
serialID: cert.SerialNumber,
|
||||
expectedTime: time.Now(),
|
||||
Cert: certs.Cert{SerialNumber: cert.SerialNumber, ClientID: cert.ClientID, ExpiryTime: cert.ExpiryTime, Revoked: false},
|
||||
},
|
||||
{
|
||||
desc: "revoke cert by serial with PKI revoke failure",
|
||||
serialID: cert.SerialNumber,
|
||||
revokeErr: certs.ErrFailedCertRevocation,
|
||||
err: certs.ErrFailedCertRevocation,
|
||||
},
|
||||
{
|
||||
desc: "revoke cert by serial with repository remove failure",
|
||||
serialID: cert.SerialNumber,
|
||||
updateErr: certs.ErrFailedReadFromDB,
|
||||
err: certs.ErrFailedReadFromDB,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
agentCall := agent.On("Revoke", tc.serialID).Return(tc.revokeErr)
|
||||
repoCall := repo.On("Update", mock.Anything, mock.Anything).Return(tc.updateErr)
|
||||
repoCall1 := repo.On("RetrieveBySerial", mock.Anything, mock.Anything).Return(tc.Cert, tc.retrieveErr)
|
||||
|
||||
result, err := svc.RevokeBySerial(context.Background(), tc.serialID)
|
||||
|
||||
if tc.err != nil {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
} else {
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
assert.False(t, result.RevocationTime.IsZero(), fmt.Sprintf("%s: revocation time should be set", tc.desc))
|
||||
assert.True(t, time.Since(result.RevocationTime) < time.Minute, fmt.Sprintf("%s: revocation time should be recent", tc.desc))
|
||||
}
|
||||
|
||||
agentCall.Unset()
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListCerts(t *testing.T) {
|
||||
svc, agent, _, repo := newService(t)
|
||||
var mycerts []certs.Cert
|
||||
for i := 0; i < certNum; i++ {
|
||||
c := certs.Cert{
|
||||
ClientID: clientID,
|
||||
SerialNumber: fmt.Sprintf("%d", i),
|
||||
ExpiryTime: time.Now().Add(time.Hour),
|
||||
}
|
||||
mycerts = append(mycerts, c)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
clientID string
|
||||
page certs.CertPage
|
||||
listErr error
|
||||
retrieveErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list all certs successfully",
|
||||
clientID: clientID,
|
||||
page: certs.CertPage{Limit: certNum, Offset: 0, Total: certNum, Certificates: mycerts},
|
||||
},
|
||||
{
|
||||
desc: "list all certs with failed pki",
|
||||
clientID: clientID,
|
||||
page: certs.CertPage{},
|
||||
retrieveErr: svcerr.ErrViewEntity,
|
||||
err: svcerr.ErrViewEntity,
|
||||
},
|
||||
{
|
||||
desc: "list half certs successfully",
|
||||
clientID: clientID,
|
||||
page: certs.CertPage{Limit: certNum, Offset: certNum / 2, Total: certNum / 2, Certificates: mycerts[certNum/2:]},
|
||||
},
|
||||
{
|
||||
desc: "list last cert successfully",
|
||||
clientID: clientID,
|
||||
page: certs.CertPage{Limit: certNum, Offset: certNum - 1, Total: 1, Certificates: []certs.Cert{mycerts[certNum-1]}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("RetrieveByClient", mock.Anything, tc.clientID, mock.Anything, mock.Anything).Return(tc.page, tc.retrieveErr)
|
||||
agentCall := agent.On("View", mock.Anything).Return(certs.Cert{}, tc.listErr)
|
||||
page, err := svc.ListCerts(context.Background(), tc.clientID, certs.PageMetadata{Offset: tc.page.Offset, Limit: tc.page.Limit})
|
||||
size := uint64(len(page.Certificates))
|
||||
assert.Equal(t, tc.page.Total, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.page.Total, size))
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
repoCall.Unset()
|
||||
agentCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSerials(t *testing.T) {
|
||||
svc, _, _, repo := newService(t)
|
||||
|
||||
var issuedCerts []certs.Cert
|
||||
for i := 0; i < certNum; i++ {
|
||||
crt := certs.Cert{
|
||||
ClientID: cert.ClientID,
|
||||
SerialNumber: cert.SerialNumber,
|
||||
ExpiryTime: cert.ExpiryTime,
|
||||
}
|
||||
issuedCerts = append(issuedCerts, crt)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
clientID string
|
||||
offset uint64
|
||||
limit uint64
|
||||
certs []certs.Cert
|
||||
retrieveErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list all certs successfully",
|
||||
clientID: clientID,
|
||||
offset: 0,
|
||||
limit: certNum,
|
||||
certs: issuedCerts,
|
||||
},
|
||||
{
|
||||
desc: "list all certs with failed pki",
|
||||
clientID: clientID,
|
||||
offset: 0,
|
||||
limit: certNum,
|
||||
certs: nil,
|
||||
retrieveErr: svcerr.ErrViewEntity,
|
||||
err: svcerr.ErrViewEntity,
|
||||
},
|
||||
{
|
||||
desc: "list half certs successfully",
|
||||
clientID: clientID,
|
||||
offset: certNum / 2,
|
||||
limit: certNum,
|
||||
certs: issuedCerts[certNum/2:],
|
||||
},
|
||||
{
|
||||
desc: "list last cert successfully",
|
||||
clientID: clientID,
|
||||
offset: certNum - 1,
|
||||
limit: certNum,
|
||||
certs: []certs.Cert{issuedCerts[certNum-1]},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("RetrieveByClient", mock.Anything, tc.clientID, certs.PageMetadata{Offset: tc.offset, Limit: tc.limit}).Return(certs.CertPage{Certificates: tc.certs}, tc.retrieveErr)
|
||||
page, err := svc.ListSerials(context.Background(), tc.clientID, certs.PageMetadata{Offset: tc.offset, Limit: tc.limit})
|
||||
assert.Equal(t, len(tc.certs), len(page.Certificates), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.certs, page.Certificates))
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewCert(t *testing.T) {
|
||||
svc, agent, _, repo := newService(t)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
serialID string
|
||||
cert certs.Cert
|
||||
repoErr error
|
||||
agentErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "view cert with valid serial",
|
||||
serialID: cert.SerialNumber,
|
||||
cert: cert,
|
||||
},
|
||||
{
|
||||
desc: "list cert with invalid serial",
|
||||
serialID: invalid,
|
||||
cert: certs.Cert{},
|
||||
agentErr: svcerr.ErrNotFound,
|
||||
err: svcerr.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("RetrieveBySerial", mock.Anything, tc.serialID).Return(tc.cert, tc.repoErr)
|
||||
agentCall := agent.On("View", tc.serialID).Return(tc.cert, tc.agentErr)
|
||||
res, err := svc.ViewCert(context.Background(), tc.serialID)
|
||||
assert.Equal(t, tc.cert.SerialNumber, res.SerialNumber, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.cert.SerialNumber, res.SerialNumber))
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
repoCall.Unset()
|
||||
agentCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package tracing provides tracing instrumentation for SuperMQ Users Groups service.
|
||||
//
|
||||
// This package provides tracing middleware for SuperMQ Users Groups service.
|
||||
// It can be used to trace incoming requests and add tracing capabilities to
|
||||
// SuperMQ Users Groups service.
|
||||
//
|
||||
// For more details about tracing instrumentation for SuperMQ messaging refer
|
||||
// to the documentation at https://docs.supermq.abstractmachines.fr/tracing/.
|
||||
package tracing
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user