Compare commits

..

116 Commits

Author SHA1 Message Date
Dušan Borovčanin 65c6a4d8f4 NOISSUE - Update mGate version (#3217)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-31 16:21:25 +01:00
Sammy Kerata Oina ec223e7467 NOISSUE - Update dependencies in go.mod and go.sum (#3215)
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-10-31 12:47:47 +01:00
Dušan Borovčanin aa3cc3d7ee NOISSUE - Fix API docs (#3212)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-30 18:16:29 +01:00
Steve Munene 9650dde716 NOISSUE - Update API docs (#3211)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-30 17:37:01 +01:00
Arvindh d02747a56f NOISSUE - Fix mqtt adapter target ws server protcol and update mgate version (#3209)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-10-29 14:30:23 +01:00
Felix Gateru dc0df1c955 SMQ-3137 - Proxy CoAP using mgate (#2222)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-10-24 17:55:02 +02:00
Steve Munene 64b4497ad4 NOISSUE - Increase NGINX client body size (#3204)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-22 15:55:37 +02:00
Dušan Borovčanin 1b26264495 NOISSUE - Fix SSO User metadata update (#3203)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-22 15:32:55 +02:00
Felix Gateru 69366d77cc SMQ-3171 - Add domain ID to channel authorization request (#3179)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-10-22 12:53:37 +02:00
Washington Kigani Kamadi fa8862cbc8 NOISSUE - Add auth_provider field to sdk (#3202)
Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
2025-10-22 12:51:38 +02:00
Dušan Borovčanin 15b8901853 NOISSUE - Fix password reset (#3200)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-21 19:44:41 +02:00
Dušan Borovčanin 9258237cb6 NOISSUE - Add domain field in domains callouts (#3199)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-21 14:31:11 +02:00
Dušan Borovčanin 2adeecd9ec NOISSUE - Update call-home (#3198)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-20 20:44:46 +02:00
Dušan Borovčanin c5eee6d8fd NOISSUE - Update dependencies (#3197)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-20 16:54:01 +02:00
dependabot[bot] 648edbd386 NOISSUE - Bump github.com/nats-io/nats.go from 1.46.1 to 1.47.0 (#3195)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 12:45:33 +02:00
dependabot[bot] 35fb296318 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.14.0 to 9.14.1 (#3196)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 12:44:21 +02:00
dependabot[bot] a98777b191 NOISSUE - Bump golang from 1.25.1-alpine to 1.25.3-alpine in /docker in the docker-dependency group (#3194)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 12:07:32 +02:00
Dušan Borovčanin 3c693a7b41 NOISSUE - Fix key logging (#3193)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-17 15:24:24 +02:00
Dušan Borovčanin c230a24b7d NOISSUE - Use structured requests in callouts (#3191)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-17 13:11:20 +02:00
Steve Munene 42b2a8c166 NOISSUE - Close PATs sql rows (#3190)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-16 16:32:54 +02:00
Dušan Borovčanin 6468ebe749 NOISSUE - Fix Domain creation callout (#3189)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-10-16 15:38:51 +02:00
b1ackd0t 1adbe1ff24 NOISSUE - Enhance docker-compose and Makefile configurations (#3153)
Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
2025-10-15 14:41:16 +02:00
Arvindh 27995cb093 NOISSUE - Add Auth provider in profile view response (#3187)
Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Co-authored-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-15 14:17:37 +02:00
dependabot[bot] 5833bafbee NOISSUE - Bump golang.org/x/oauth2 from 0.31.0 to 0.32.0 (#3183)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 14:09:44 +02:00
dependabot[bot] 6394908b2c NOISSUE - Bump golang.org/x/crypto from 0.42.0 to 0.43.0 (#3182)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 10:27:49 +02:00
dependabot[bot] 0e0598c6d8 NOISSUE - Bump github.com/authzed/spicedb from 1.45.4 to 1.46.0 (#3184)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 10:26:21 +02:00
dependabot[bot] 3e400da5ad NOISSUE - Bump google.golang.org/grpc from 1.75.1 to 1.76.0 (#3185)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 10:25:56 +02:00
Arvindh 2112281171 NOISSUE - Auto generation username for oauth user during singup (#3178)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-10-09 15:19:46 +02:00
Arvindh 6f40c7fa1e NOISSUE - Handle Domain & Channel Route not available error during creation (#3180)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-10-09 15:06:06 +02:00
Arvindh b0e9e43b9e NOISSUE - Handle user registration errors (#3177)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-10-08 19:56:54 +02:00
Felix Gateru bc7727c275 SMQ-3175 - Fix clearing domain metadata on domain name and tags update (#3176)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-10-08 17:36:03 +02:00
Arvindh 8d1c3c41b5 NOISSUE - Fix OAuth auto verification on register (#3174)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-10-07 18:24:23 +02:00
dependabot[bot] d84df8604f NOISSUE - Bump github.com/authzed/authzed-go from 1.5.1-0.20250909211335-16b58d03994c to 1.6.0 (#3168)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 11:04:55 +02:00
dependabot[bot] d4d8bc5351 NOISSUE - Bump google.golang.org/protobuf from 1.36.9 to 1.36.10 (#3167)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 10:55:26 +02:00
dependabot[bot] ac9bc9aad6 NOISSUE - Bump github.com/nats-io/nats.go from 1.46.0 to 1.46.1 (#3166)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 10:48:52 +02:00
dependabot[bot] 4b83301f69 NOISSUE - Bump peter-evans/repository-dispatch from 3 to 4 in /.github/workflows in the gh-dependency group (#3161)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-02 13:18:19 +02:00
Arvindh b746a8679f NOISSUE - Fixing auto verification unverified users flow (#3163)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-10-02 13:17:32 +02:00
Arvindh e6022542b9 NOISSUE - Auto verify OAuth user during OAuth Callback (#3162)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-10-02 11:14:06 +02:00
Arvindh 6c77e1b738 SMQ-3159 - Return specific error on non-existent user during login (#3160)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-10-01 18:51:50 +02:00
Steve Munene 04774ae972 NOISSUE - Remove empty file (#3157)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-01 11:44:26 +02:00
Steve Munene ae7c93020a SMQ-3028 - Unify SMQ certs with Absmach Certs (#3146)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-10-01 11:08:33 +02:00
Felix Gateru b031dc08b8 NOISSUE - Group service middleware into single folder (#2472)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-26 13:55:00 +02:00
dependabot[bot] 0465ceb7a3 NOISSUE - Bump github.com/nats-io/nats.go from 1.45.0 to 1.46.0 (#3152)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-25 16:47:52 +02:00
dependabot[bot] af424ee2cb NOISSUE - Bump github.com/authzed/spicedb from 1.45.3 to 1.45.4 (#3150)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-21 19:43:31 +03:00
dependabot[bot] 96a15b32dd NOISSUE - Bump github.com/eclipse/paho.mqtt.golang from 1.5.0 to 1.5.1 (#3151)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-21 19:20:37 +03:00
Felix Gateru 61f1e9aca2 MG-314 - Update API docs related for listing entities (#3149)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-18 12:18:00 +02:00
Nataly Musilah 29f7063c15 NOISSUE - Fix Groups and Channels Users Listing (#3138)
Signed-off-by: musilah <nataleigh.nk@gmail.com>
2025-09-18 11:28:40 +02:00
Felix Gateru 1f48a73677 SMQ-2945 - Add channels events consumer (#2951)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-16 17:23:01 +02:00
Felix Gateru cd9cb9e1d5 SMQ-2944 - Add client events consumer (#2947)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-16 17:21:39 +02:00
dependabot[bot] 8d9e92f228 NOISSUE - Bump google.golang.org/protobuf from 1.36.8 to 1.36.9 (#3142)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 12:41:24 +02:00
dependabot[bot] ad685c27ad NOISSUE - Bump github.com/redis/go-redis/v9 from 9.13.0 to 9.14.0 (#3141)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 12:24:53 +02:00
dependabot[bot] 44485cacec NOISSUE - Bump google.golang.org/grpc from 1.75.0 to 1.75.1 (#3143)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 12:24:13 +02:00
Felix Gateru 7fac6ff4c4 SMQ-2801 - Add health check endpoint with client authentication to CoAP adapter (#3139)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-15 12:23:36 +02:00
Felix Gateru 1930a0c8f2 SMQ-2801 - Add health check endpoint to WS adapter (#3144)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-15 10:33:39 +02:00
Felix Gateru 9eac9366ba SMQ-2801 - Add health check endpoint to HTTP adapter (#3140)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-12 12:14:14 +02:00
Felix Gateru ad191c4609 SMQ-2801 - Add health check endpoint for MQTT adapter (#3024)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-12 11:37:29 +02:00
dependabot[bot] 1d935f0519 NOISSUE - Bump golang.org/x/crypto from 0.41.0 to 0.42.0 (#3133)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 08:54:25 +02:00
dependabot[bot] bc1ccace4a NOISSUE - Bump github.com/absmach/callhome from 0.14.0 to 0.18.1 (#3134)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 08:53:53 +02:00
dependabot[bot] 89fdb4e6c7 NOISSUE - Bump golang.org/x/oauth2 from 0.30.0 to 0.31.0 (#3135)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 08:52:42 +02:00
dependabot[bot] 44e44639aa NOISSUE - Bump github.com/jackc/pgx/v5 from 5.7.5 to 5.7.6 (#3136)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 08:52:08 +02:00
dependabot[bot] d31c66174e NOISSUE - Bump actions/setup-go from 5 to 6 in /.github/workflows in the gh-dependency group (#3131)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-06 18:05:13 +02:00
dependabot[bot] e6d7a07277 NOISSUE - Bump golang from 1.25.0-alpine to 1.25.1-alpine in /docker in the docker-dependency group (#3127)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-06 18:03:18 +02:00
dependabot[bot] a2ea92a5ba NOISSUE - Bump github.com/prometheus/client_golang from 1.23.0 to 1.23.2 (#3130)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-06 18:01:27 +02:00
dependabot[bot] 3320a716f3 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.12.1 to 9.13.0 (#3128)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-06 18:00:49 +02:00
Felix Gateru a70b838f1c SMQ-2924 - Allow bulk deleting of invitations from repository (#2942)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-09-05 19:26:45 +02:00
Nataly Musilah c50221a042 NOISSUE - Update entity sorting (#3076)
Signed-off-by: Musilah <nataleigh.nk@gmail.com>
Signed-off-by: musilah <nataleigh.nk@gmail.com>
2025-09-05 15:58:03 +02:00
Arvindh 1efa8b5bce NOISSUE - Allow unverified users by default in docker compose (#3124)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-09-05 15:40:47 +02:00
Arvindh e57ad79cd4 SMQ-3093 - User email verification (#3101)
Signed-off-by: Arvindh <arvindh91@gmail.com>
2025-09-05 15:23:58 +02:00
Sammy Kerata Oina 22616911d2 NOISSUE - Change email body format from plain text to HTML in Send method (#3118)
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-09-04 19:54:01 +02:00
Steve Munene 766bb5519a SMQ-2275 - Update SDK godocs (#3119)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-09-04 11:35:12 +02:00
Steve Munene fbcdef9693 SMQ-3061 - Separate CLI command for Domain and User Invitations (#3117)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-09-03 13:00:23 +02:00
dependabot[bot] 9aacde7c5b NOISSUE - Bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.62.0 to 0.63.0 (#3115)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:43:14 +02:00
dependabot[bot] cdf26051f6 NOISSUE - Bump github.com/openbao/openbao/api/v2 from 2.3.1 to 2.4.0 (#3112)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:30:12 +02:00
dependabot[bot] a9a412e909 NOISSUE - Bump github.com/spf13/cobra from 1.9.1 to 1.10.1 (#3111)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:28:43 +02:00
dependabot[bot] 0754fa1126 NOISSUE - Bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc from 0.62.0 to 0.63.0 (#3116)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:28:20 +02:00
Steve Munene ec75f4eadb SMQ-3062 - Update Domain Invitation Deletion Endpoint (#3110)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-09-02 10:16:49 +02:00
Washington Kigani Kamadi af35396601 NOISSUE - Obtain email host from env (#3098)
Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
2025-09-01 15:21:01 +02:00
dependabot[bot] 6a27594751 NOISSUE - Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp from 1.37.0 to 1.38.0 (#3105)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 14:52:42 +02:00
dependabot[bot] 555d89d358 NOISSUE - Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace from 1.37.0 to 1.38.0 (#3103)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 10:41:50 +02:00
dependabot[bot] c3c9dcf318 NOISSUE - Bump github.com/go-chi/chi/v5 from 5.2.2 to 5.2.3 (#3104)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 10:35:19 +02:00
dependabot[bot] 5a75772f62 NOISSUE - Bump go.opentelemetry.io/otel from 1.37.0 to 1.38.0 (#3107)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 10:35:01 +02:00
Steve Munene b9ac94f058 SMQ-2495 - Refactor CLI to use entity IDs (#3100)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-09-01 10:22:14 +02:00
Felix Gateru 8ab1f4c408 SMQ-2836 - Skip checking super admin if the role in JWT is not super admin (#3099)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-29 12:09:31 +02:00
Dušan Borovčanin e842bfaab6 NOISSUE - Update Protobufs (#3091)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-26 13:31:08 +02:00
dependabot[bot] 886cd63415 NOISSUE - Bump github.com/stretchr/testify from 1.10.0 to 1.11.0 (#3089)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 12:29:54 +02:00
dependabot[bot] 9cd22910d8 NOISSUE - Bump github.com/dgraph-io/ristretto/v2 from 2.2.0 to 2.3.0 (#3090)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 12:27:28 +02:00
Felix Gateru 906e365adf NOISSUE - Fix clients listing repository tests (#3088)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-26 12:25:57 +02:00
dependabot[bot] 8e7e897906 NOISSUE - Bump github.com/authzed/authzed-go from 1.4.1 to 1.5.0 (#3084)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 00:13:39 +02:00
dependabot[bot] 4b50ef93a2 NOISSUE - Bump actions/checkout from 4 to 5 in /.github/workflows in the gh-dependency group (#3080)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 23:54:33 +02:00
dependabot[bot] 73c2d76b58 NOISSUE - Bump google.golang.org/grpc from 1.74.2 to 1.75.0 (#3083)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 23:53:02 +02:00
dependabot[bot] 473338527a NOISSUE - Bump google.golang.org/protobuf from 1.36.7 to 1.36.8 (#3082)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 23:29:09 +02:00
dependabot[bot] 12d1a0de32 NOISSUE - Bump github.com/authzed/spicedb from 1.45.1 to 1.45.3 (#3085)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 23:27:48 +02:00
dependabot[bot] b4e0e89c38 NOISSUE - Bump github.com/nats-io/nats.go from 1.44.0 to 1.45.0 (#3086)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 23:20:39 +02:00
dependabot[bot] 6f5e535b30 NOISSUE - Bump golang from 1.24.5-alpine to 1.25.0-alpine in /docker in the docker-dependency group (#3081)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 23:19:09 +02:00
Dušan Borovčanin fb1062a98a NOISSUE - Fix linters and Mockery (#3087)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-25 23:15:38 +02:00
dusan c56a5ae577 NOISSUE - Update Go version
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-25 18:37:56 +02:00
Dušan Borovčanin a19cd9c97b NOISSUE - Replace interface{} with any (#3079)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2025-08-25 18:06:41 +02:00
Felix Gateru 0e1a090f16 SMQ-3070 - Fix property based tests (#3078)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-25 16:17:19 +02:00
dependabot[bot] ef712443e1 NOISSUE - Bump github.com/go-viper/mapstructure/v2 from 2.3.0 to 2.4.0 (#3077)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 13:24:09 +02:00
Smith Jilks 248b708909 NOISSUE - Update callouts client tls config (#3068)
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
2025-08-21 16:44:11 +02:00
Felix Gateru c63c936b36 SMQ-3073 - Enable user messaging through WS and HTTP protocols (#3075)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-21 16:10:34 +02:00
Felix Gateru 4927333691 NOISSUE - Update SMQ version in docs (#3072)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-20 15:56:54 +02:00
dependabot[bot] 5acc8f96a6 NOISSUE - Bump golang.org/x/crypto from 0.40.0 to 0.41.0 (#3059)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 18:28:33 +02:00
dependabot[bot] 9d91740063 NOISSUE - Bump google.golang.org/protobuf from 1.36.6 to 1.36.7 (#3060)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 14:06:20 +02:00
dependabot[bot] 1951f206cb NOISSUE - Bump github.com/docker/docker from 27.1.1+incompatible to 28.0.0+incompatible (#3067)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 14:00:08 +02:00
dependabot[bot] 441feb8578 NOISSUE - Bump github.com/redis/go-redis/v9 from 9.11.0 to 9.12.1 (#3058)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 13:57:48 +02:00
dependabot[bot] 7d11f16988 NOISSUE - Bump github.com/absmach/certs from 0.0.0-20250602111612-89538302ad6a to 0.17.0 (#3057)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 13:56:36 +02:00
Nataly Musilah 2e3f52fdbf NOISSUE - Allow sorting (#3052)
Signed-off-by: Musilah <nataleigh.nk@gmail.com>
2025-08-18 14:54:26 +02:00
Felix Gateru b942a42271 SMQ-3053 - Fix listing subgroups for users with subgroup related actions (#3066)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-18 09:24:23 +02:00
Steve Munene 0178594c25 SMQ-3056 - Fix exposed secret in MQTT (#3065)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-08-18 09:23:01 +02:00
Steve Munene 3dbd617ed0 SMQ-3050 - Fix PAT redis connection (#3051)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
2025-08-13 14:50:20 +02:00
Felix Gateru 8188da16b3 NOISSUE - Update removal of view invitation endpoint in sdk and cli (#3055)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-13 14:48:12 +02:00
Smith Jilks bc7b988098 NOISSUE - Add ability to handle both tls cert file and raw cert data (#2871)
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
2025-08-08 15:48:36 +02:00
Felix Gateru a0b3970ad6 SMQ-3032 - Remove view invitations endpoint (#3042)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-07 20:31:15 +02:00
Felix Gateru 0f20663cfb SMQ-2704 - Update listing of group hierarchy (#2710)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-07 11:53:23 +02:00
Felix Gateru 4dc98763a6 SMQ-3044 - Fix listing channel logs with operation query parameter (#3043)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
2025-08-05 20:55:09 +02:00
452 changed files with 15249 additions and 15101 deletions
+4 -20
View File
@@ -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"
+11 -5
View File
@@ -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
+11 -8
View File
@@ -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
+1 -1
View File
@@ -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: |
+2 -2
View File
@@ -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
+23 -25
View File
@@ -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: |
+25 -10
View File
@@ -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
+13 -4
View File
@@ -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" +
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+23 -5
View File
@@ -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" +
+1 -1
View File
@@ -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
-54
View File
@@ -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
View File
@@ -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),
+1 -1
View File
@@ -115,7 +115,7 @@ func TestEncodeResponse(t *testing.T) {
cases := []struct {
desc string
resp interface{}
resp any
header http.Header
code int
body []byte
+6
View File
@@ -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")
)
+2 -2
View File
@@ -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)
+4 -4
View File
@@ -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)
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
-346
View File
@@ -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: []
+95 -5
View File
@@ -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
+117 -8
View File
@@ -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:
+69 -54
View File
@@ -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:
+80 -6
View File
@@ -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:
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
+30 -5
View File
@@ -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:
+1 -1
View File
@@ -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
+92 -2
View File
@@ -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
+10 -10
View File
@@ -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,
+6 -6
View File
@@ -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 {
+1
View File
@@ -9,6 +9,7 @@ type authenticateRes struct {
id string
userID string
userRole smqauth.Role
verified bool
}
type authorizeRes struct {
+10 -10
View File
@@ -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(),
+8 -6
View File
@@ -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
}
+7 -6
View File
@@ -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
+2
View File
@@ -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 {
+5 -4
View File
@@ -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{
+3 -3
View File
@@ -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 {
+1 -1
View File
@@ -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 ""
+2 -2
View File
@@ -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"),
+13 -13
View File
@@ -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
+13 -13
View File
@@ -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
+15
View File
@@ -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
}
+1
View File
@@ -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 {
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
-12
View File
@@ -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
View File
@@ -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/).
-5
View File
@@ -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
-127
View File
@@ -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
}
}
-787
View File
@@ -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"`
}
-157
View File
@@ -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)
}
-91
View File
@@ -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)
}
-103
View File
@@ -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
}
-75
View File
@@ -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
}
-151
View File
@@ -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
View File
@@ -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)
}
-93
View File
@@ -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)
}
}
}
-6
View File
@@ -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
-5
View File
@@ -1,5 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
// Package mocks contains mocks for testing purposes.
package mocks
-283
View File
@@ -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
}
-489
View File
@@ -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
}
-480
View File
@@ -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
}
-12
View File
@@ -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)
}
-118
View File
@@ -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
}
-4
View File
@@ -1,4 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package amcerts
-4
View File
@@ -1,4 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package openbao
-356
View File
@@ -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
}
-237
View File
@@ -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)
}
-6
View File
@@ -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
-29
View File
@@ -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;",
},
},
},
}
}
-88
View File
@@ -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)
}
-198
View File
@@ -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
}
-396
View File
@@ -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()
})
}
}
-12
View File
@@ -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