From b2c5dcefbf5ceac147f7bf0adaf43ee02c334374 Mon Sep 17 00:00:00 2001 From: Andrii Holovko Date: Tue, 13 Sep 2022 17:18:34 +0300 Subject: [PATCH] feat: OIDC client credential authorization Closes #751 Signed-off-by: Andrii Holovko --- cmd/vc-rest/startcmd/start.go | 9 +- pkg/restapi/v1/issuer/controller_test.go | 2 +- pkg/restapi/v1/mw/api_key_auth.go | 35 +++++++ pkg/restapi/v1/mw/api_key_auth_test.go | 63 ++++++++++++ pkg/restapi/v1/util/auth.go | 12 +-- pkg/restapi/v1/verifier/controller.go | 24 +++-- pkg/restapi/v1/verifier/controller_test.go | 66 ++++++++++++- .../testdata/create_profile_data.json | 2 +- scripts/generate_test_keys.sh | 1 + ....feature => issuer_profile_v1_api.feature} | 8 +- ...eature => verifier_profile_v1_api.feature} | 3 + test/bdd/fixtures/.env | 5 + test/bdd/fixtures/docker-compose.yml | 65 ++++++++++++ .../fixtures/hydra-config/hydra_configure.sh | 28 ++++++ .../fixtures/mysql-config/mysql_config.sql | 14 +++ .../fixtures/oathkeeper-config/config.yaml | 38 +++++++ .../rules/resource-server.json | 99 +++++++++++++++++++ test/bdd/go.mod | 3 + test/bdd/go.sum | 6 ++ test/bdd/pkg/bddutil/util.go | 53 ++++++++++ test/bdd/pkg/v1/issuer/issuer_steps.go | 72 ++++++++------ test/bdd/pkg/v1/verifier/verifer_steps.go | 91 +++++++++-------- 22 files changed, 604 insertions(+), 95 deletions(-) create mode 100644 pkg/restapi/v1/mw/api_key_auth.go create mode 100644 pkg/restapi/v1/mw/api_key_auth_test.go rename test/bdd/features/{issuer_v1_api.feature => issuer_profile_v1_api.feature} (82%) rename test/bdd/features/{verifier_profile_api.feature => verifier_profile_v1_api.feature} (93%) create mode 100755 test/bdd/fixtures/hydra-config/hydra_configure.sh create mode 100644 test/bdd/fixtures/mysql-config/mysql_config.sql create mode 100644 test/bdd/fixtures/oathkeeper-config/config.yaml create mode 100644 test/bdd/fixtures/oathkeeper-config/rules/resource-server.json diff --git a/cmd/vc-rest/startcmd/start.go b/cmd/vc-rest/startcmd/start.go index 040b29050..b8076a7a3 100644 --- a/cmd/vc-rest/startcmd/start.go +++ b/cmd/vc-rest/startcmd/start.go @@ -39,6 +39,7 @@ import ( verifierops "github.com/trustbloc/vcs/pkg/restapi/v0.1/verifier/operation" "github.com/trustbloc/vcs/pkg/restapi/v1/healthcheck" issuerv1 "github.com/trustbloc/vcs/pkg/restapi/v1/issuer" + "github.com/trustbloc/vcs/pkg/restapi/v1/mw" verifierv1 "github.com/trustbloc/vcs/pkg/restapi/v1/verifier" "github.com/trustbloc/vcs/pkg/storage/mongodb" "github.com/trustbloc/vcs/pkg/storage/mongodb/issuerstore" @@ -130,6 +131,10 @@ func buildEchoHandler(conf *Configuration) (*echo.Echo, error) { e.Use(echomw.Logger()) e.Use(echomw.Recover()) + if conf.StartupParameters.token != "" { + e.Use(mw.APIKeyAuth(conf.StartupParameters.token)) + } + swagger, err := spec.GetSwagger() if err != nil { return nil, fmt.Errorf("failed to get openapi spec: %w", err) @@ -161,11 +166,11 @@ func buildEchoHandler(conf *Configuration) (*echo.Echo, error) { issuerProfileStore := issuerstore.NewProfileStore(mongodbClient) issuerProfileSvc := issuersvc.NewProfileService(&issuersvc.ServiceConfig{ ProfileStore: issuerProfileStore, - DIDCreator: did.NewCreator(&did.CreatorConfig{ + DIDCreator: did.NewCreator(&did.CreatorConfig{ VDR: conf.VDR, DIDAnchorOrigin: conf.StartupParameters.didAnchorOrigin, }), - KMSRegistry: kmsRegistry, + KMSRegistry: kmsRegistry, }) issuerv1.RegisterHandlers(e, issuerv1.NewController(issuerProfileSvc, kmsRegistry)) diff --git a/pkg/restapi/v1/issuer/controller_test.go b/pkg/restapi/v1/issuer/controller_test.go index a5fc86470..14f977801 100644 --- a/pkg/restapi/v1/issuer/controller_test.go +++ b/pkg/restapi/v1/issuer/controller_test.go @@ -76,7 +76,7 @@ func createContext(orgID string) echo.Context { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) if orgID != "" { - req.Header.Set("Authorization", "Bearer "+orgID) + req.Header.Set("X-User", orgID) } rec := httptest.NewRecorder() diff --git a/pkg/restapi/v1/mw/api_key_auth.go b/pkg/restapi/v1/mw/api_key_auth.go new file mode 100644 index 000000000..fce5b403d --- /dev/null +++ b/pkg/restapi/v1/mw/api_key_auth.go @@ -0,0 +1,35 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package mw + +import ( + "crypto/subtle" + "net/http" + + "github.com/labstack/echo/v4" +) + +const ( + header = "X-API-Key" +) + +// APIKeyAuth returns a middleware that authenticates requests using the API key from X-API-Key header. +func APIKeyAuth(apiKey string) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + apiKeyHeader := c.Request().Header.Get(header) + if subtle.ConstantTimeCompare([]byte(apiKeyHeader), []byte(apiKey)) != 1 { + return &echo.HTTPError{ + Code: http.StatusUnauthorized, + Message: "Unauthorized", + } + } + + return next(c) + } + } +} diff --git a/pkg/restapi/v1/mw/api_key_auth_test.go b/pkg/restapi/v1/mw/api_key_auth_test.go new file mode 100644 index 000000000..7bb85d30c --- /dev/null +++ b/pkg/restapi/v1/mw/api_key_auth_test.go @@ -0,0 +1,63 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package mw_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/require" + + "github.com/trustbloc/vcs/pkg/restapi/v1/mw" +) + +func TestApiKeyAuth(t *testing.T) { + t.Run("Success", func(t *testing.T) { + handlerCalled := false + handler := func(c echo.Context) error { + handlerCalled = true + return c.String(http.StatusOK, "test") + } + + middlewareChain := mw.APIKeyAuth("test-api-key")(handler) + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("X-API-Key", "test-api-key") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := middlewareChain(c) + + require.NoError(t, err) + require.True(t, handlerCalled) + }) + + t.Run("401 Unauthorized", func(t *testing.T) { + handlerCalled := false + handler := func(c echo.Context) error { + handlerCalled = true + return c.String(http.StatusOK, "test") + } + + middlewareChain := mw.APIKeyAuth("test-api-key")(handler) + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("X-API-Key", "invalid-api-key") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := middlewareChain(c) + + require.Error(t, err) + require.Contains(t, err.Error(), "Unauthorized") + require.False(t, handlerCalled) + }) +} diff --git a/pkg/restapi/v1/util/auth.go b/pkg/restapi/v1/util/auth.go index f743483b2..4c3c378fd 100644 --- a/pkg/restapi/v1/util/auth.go +++ b/pkg/restapi/v1/util/auth.go @@ -8,21 +8,21 @@ package util import ( "fmt" - "strings" "github.com/labstack/echo/v4" "github.com/trustbloc/vcs/pkg/restapi/resterr" ) +const ( + userHeader = "X-User" +) + func GetOrgIDFromOIDC(ctx echo.Context) (string, error) { - // TODO: resolve orgID from auth token - authHeader := ctx.Request().Header.Get("Authorization") - if authHeader == "" || !strings.Contains(authHeader, "Bearer") { + orgID := ctx.Request().Header.Get(userHeader) + if orgID == "" { return "", resterr.NewUnauthorizedError(fmt.Errorf("missing authorization")) } - orgID := authHeader[len("Bearer "):] // for now assume that token is just plain orgID - return orgID, nil } diff --git a/pkg/restapi/v1/verifier/controller.go b/pkg/restapi/v1/verifier/controller.go index 29179eaae..394173b07 100644 --- a/pkg/restapi/v1/verifier/controller.go +++ b/pkg/restapi/v1/verifier/controller.go @@ -14,10 +14,11 @@ import ( "errors" "fmt" "net/http" - "strings" "github.com/labstack/echo/v4" + "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/restapi/v1/util" "github.com/trustbloc/vcs/pkg/verifier" ) @@ -48,14 +49,11 @@ func NewController(profileSvc profileService) *Controller { // GetVerifierProfiles gets all verifier profiles for organization. // GET /verifier/profiles. func (c *Controller) GetVerifierProfiles(ctx echo.Context) error { - // TODO: resolve orgID from auth token - authHeader := ctx.Request().Header.Get("Authorization") - if authHeader == "" || !strings.Contains(authHeader, "Bearer") { - return echo.NewHTTPError(http.StatusUnauthorized, "missing authorization") + orgID, err := util.GetOrgIDFromOIDC(ctx) + if err != nil { + return err } - orgID := authHeader[len("Bearer "):] // for now assume that token is just plain orgID - profiles, err := c.profileSvc.GetAllProfiles(orgID) if err != nil { return fmt.Errorf("failed to get verifier profiles: %w", err) @@ -73,12 +71,22 @@ func (c *Controller) GetVerifierProfiles(ctx echo.Context) error { // PostVerifierProfiles creates a new verifier profile. // POST /verifier/profiles. func (c *Controller) PostVerifierProfiles(ctx echo.Context) error { + orgID, err := util.GetOrgIDFromOIDC(ctx) + if err != nil { + return err + } + var body CreateVerifierProfileData - if err := ctx.Bind(&body); err != nil { + if err = ctx.Bind(&body); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err) } + if body.OrganizationID != orgID { + return resterr.NewValidationError(resterr.InvalidValue, "organizationID", + fmt.Errorf("org id mismatch (want %q, got %q)", orgID, body.OrganizationID)) + } + createdProfile, err := c.profileSvc.Create(mapCreateVerifierProfileData(&body)) if err != nil { return fmt.Errorf("failed to create verifier profile: %w", err) diff --git a/pkg/restapi/v1/verifier/controller_test.go b/pkg/restapi/v1/verifier/controller_test.go index 29b9207c1..e14eb02e6 100644 --- a/pkg/restapi/v1/verifier/controller_test.go +++ b/pkg/restapi/v1/verifier/controller_test.go @@ -84,7 +84,7 @@ func TestController_GetVerifierProfiles(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - req.Header.Set("Authorization", "Bearer org1") + req.Header.Set("X-User", "org1") rec := httptest.NewRecorder() c := e.NewContext(req, rec) @@ -96,6 +96,25 @@ func TestController_GetVerifierProfiles(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) }) + t.Run("missing authorization", func(t *testing.T) { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + mockProfileSvc.EXPECT().GetAllProfiles("org1").Times(0) + + e := echo.New() + + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + controller := verifier.NewController(mockProfileSvc) + + err := controller.GetVerifierProfiles(c) + require.Error(t, err) + require.Contains(t, err.Error(), "missing authorization") + }) + t.Run("error from profile service", func(t *testing.T) { mockProfileSvc := NewMockProfileService(gomock.NewController(t)) mockProfileSvc.EXPECT().GetAllProfiles("org1").Times(1).Return(nil, errors.New("get all profiles error")) @@ -104,7 +123,7 @@ func TestController_GetVerifierProfiles(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - req.Header.Set("Authorization", "Bearer org1") + req.Header.Set("X-User", "org1") rec := httptest.NewRecorder() c := e.NewContext(req, rec) @@ -126,6 +145,7 @@ func TestController_PostVerifierProfiles(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(createProfileData)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + req.Header.Set("X-User", "org1") rec := httptest.NewRecorder() c := e.NewContext(req, rec) @@ -137,6 +157,45 @@ func TestController_PostVerifierProfiles(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) }) + t.Run("missing authorization", func(t *testing.T) { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + mockProfileSvc.EXPECT().Create(gomock.Any()).Times(0) + + e := echo.New() + + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(createProfileData)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + controller := verifier.NewController(mockProfileSvc) + + err := controller.PostVerifierProfiles(c) + require.Error(t, err) + require.Contains(t, err.Error(), "missing authorization") + }) + + t.Run("invalid org id", func(t *testing.T) { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + mockProfileSvc.EXPECT().Create(gomock.Any()).Times(0) + + e := echo.New() + + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(createProfileData)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + req.Header.Set("X-User", "invalid") + + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + controller := verifier.NewController(mockProfileSvc) + + err := controller.PostVerifierProfiles(c) + require.Error(t, err) + require.Contains(t, err.Error(), "org id mismatch") + }) + t.Run("error from profile service", func(t *testing.T) { mockProfileSvc := NewMockProfileService(gomock.NewController(t)) mockProfileSvc.EXPECT().Create(gomock.Any()).Times(1).Return(nil, errors.New("create profile error")) @@ -145,6 +204,7 @@ func TestController_PostVerifierProfiles(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(createProfileData)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + req.Header.Set("X-User", "org1") rec := httptest.NewRecorder() c := e.NewContext(req, rec) @@ -206,6 +266,7 @@ func TestController_GetVerifierProfilesProfileID(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + req.Header.Set("X-User", "org1") rec := httptest.NewRecorder() c := e.NewContext(req, rec) @@ -225,6 +286,7 @@ func TestController_GetVerifierProfilesProfileID(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + req.Header.Set("X-User", "org1") rec := httptest.NewRecorder() c := e.NewContext(req, rec) diff --git a/pkg/restapi/v1/verifier/testdata/create_profile_data.json b/pkg/restapi/v1/verifier/testdata/create_profile_data.json index c4caaad1d..adbcf7064 100644 --- a/pkg/restapi/v1/verifier/testdata/create_profile_data.json +++ b/pkg/restapi/v1/verifier/testdata/create_profile_data.json @@ -1,6 +1,6 @@ { "name": "test profile", - "organizationID": "orgID", + "organizationID": "org1", "url": "https://test-verifier.com", "checks": { "credential": { diff --git a/scripts/generate_test_keys.sh b/scripts/generate_test_keys.sh index c2a9ebfd5..8c91feaaa 100755 --- a/scripts/generate_test_keys.sh +++ b/scripts/generate_test_keys.sh @@ -22,6 +22,7 @@ subjectAltName = @alt_names DNS.1 = localhost DNS.2 = testnet.orb.local DNS.4 = file-server.trustbloc.local +DNS.5 = oidc-provider.example.com " >> "$tmp" #create CA diff --git a/test/bdd/features/issuer_v1_api.feature b/test/bdd/features/issuer_profile_v1_api.feature similarity index 82% rename from test/bdd/features/issuer_v1_api.feature rename to test/bdd/features/issuer_profile_v1_api.feature index 438f85fc7..55d3628d9 100644 --- a/test/bdd/features/issuer_v1_api.feature +++ b/test/bdd/features/issuer_profile_v1_api.feature @@ -5,15 +5,17 @@ # @all -@issuer_rest +@issuer_profile_rest Feature: Issuer VC REST API + Background: + Given "Charlie" has been authorized with client id "National Bank" and secret "bank-secret" to use vcs + @issuerProfileRecreationV1 Scenario: Delete and recreate issuer profile Given "Charlie" sends request to create an issuer profile with the organization "National Bank" And "Charlie" deletes the issuer profile Then "Charlie" can recreate the issuer profile with the organization "National Bank" - @issuerProfileUpdateV1 Scenario: Create and update issuer profile Given "Charlie" sends request to create an issuer profile with the organization "National Bank" @@ -23,4 +25,4 @@ Feature: Issuer VC REST API Scenario: Create and update issuer profile Given "Charlie" sends request to create an issuer profile with the organization "National Bank" And "Charlie" deactivates the issuer profile - And "Charlie" activates the issuer profile \ No newline at end of file + And "Charlie" activates the issuer profile diff --git a/test/bdd/features/verifier_profile_api.feature b/test/bdd/features/verifier_profile_v1_api.feature similarity index 93% rename from test/bdd/features/verifier_profile_api.feature rename to test/bdd/features/verifier_profile_v1_api.feature index b3608b132..92e271c79 100644 --- a/test/bdd/features/verifier_profile_api.feature +++ b/test/bdd/features/verifier_profile_v1_api.feature @@ -7,6 +7,9 @@ @all @verifier_profile_rest Feature: Verifier Profile Management REST API + Background: + Given organization "org1" has been authorized using client id "org1" and secret "org1-secret" + Scenario: Create a new verifier profile When organization "org1" creates a verifier profile with data from "verifier_profile_create.json" Then verifier profile is created diff --git a/test/bdd/fixtures/.env b/test/bdd/fixtures/.env index e30370b36..892544af8 100644 --- a/test/bdd/fixtures/.env +++ b/test/bdd/fixtures/.env @@ -44,3 +44,8 @@ MONGODB_PORT=27017 # sidetree SIDETREE_MOCK_IMAGE=ghcr.io/trustbloc-cicd/sidetree-mock SIDETREE_MOCK_IMAGE_TAG=0.7.0-snapshot-1a17931 + +# OAuth authorization +HYDRA_IMAGE_TAG=v1.10.7-alpine +OATHKEEPER_IMAGE_TAG=v0.38.19-alpine +MYSQL_IMAGE_TAG=8.0.30 diff --git a/test/bdd/fixtures/docker-compose.yml b/test/bdd/fixtures/docker-compose.yml index e1de8f191..c2f25aeb4 100644 --- a/test/bdd/fixtures/docker-compose.yml +++ b/test/bdd/fixtures/docker-compose.yml @@ -249,6 +249,71 @@ services: networks: - bdd_net + oathkeeper.trustbloc.local: + container_name: oathkeeper.trustbloc.local + image: oryd/oathkeeper:${OATHKEEPER_IMAGE_TAG} + ports: + - "4455:4455" + command: /bin/sh -c "cp /etc/tls/ec-cacert.pem /usr/local/share/ca-certificates/;update-ca-certificates;oathkeeper serve proxy --config /oathkeeper/config.yaml" + user: root + entrypoint: "" + environment: + - LOG_LEVEL=debug + - PORT=4455 + - ISSUER_URL=https://oathkeeper-proxy.trustbloc.local + - SERVE_PROXY_TLS_KEY_PATH=/etc/tls/ec-key.pem + - SERVE_PROXY_TLS_CERT_PATH=/etc/tls/ec-pubCert.pem + - LOG_LEAK_SENSITIVE_VALUES=true + volumes: + - ./oathkeeper-config:/oathkeeper + - ./keys/tls:/etc/tls + networks: + - bdd_net + + oidc-provider.example.com: + container_name: oidc-provider.example.com + image: oryd/hydra:${HYDRA_IMAGE_TAG} + ports: + - "4444:4444" + - "4445:4445" + command: /bin/sh -c "sleep 20 && hydra migrate sql --read-from-env --yes; (sleep 10; tmp/hydra_configure.sh)& hydra serve all" + entrypoint: "" + environment: + - DSN=mysql://thirdpartyoidc:thirdpartyoidc-secret-pw@tcp(mysql:3306)/thirdpartyoidc?max_conns=20&max_idle_conns=4 + - URLS_SELF_ISSUER=https://oidc-provider.example.com:4444/ + - SECRETS_SYSTEM=testSecretsSystem + - OIDC_SUBJECT_TYPES_SUPPORTED=public + - OIDC_SUBJECT_TYPE_PAIRWISE_SALT=testSecretsSystem + - SERVE_TLS_KEY_PATH=/etc/tls/ec-key.pem + - SERVE_TLS_CERT_PATH=/etc/tls/ec-pubCert.pem + - SERVE_PUBLIC_PORT=4444 + - SERVE_ADMIN_PORT=4445 + - LOG_LEAK_SENSITIVE_VALUES=true + restart: unless-stopped + volumes: + - ./keys/tls:/etc/tls + - ./hydra-config/hydra_configure.sh:/tmp/hydra_configure.sh + depends_on: + - mysql + networks: + - bdd_net + + mysql: + container_name: mysql + image: mysql:${MYSQL_IMAGE_TAG} + ports: + - "3306:3306" + restart: always + command: --default-authentication-plugin=mysql_native_password + environment: + MYSQL_ROOT_PASSWORD: secret + logging: + driver: "none" + volumes: + - ./mysql-config:/docker-entrypoint-initdb.d + networks: + - bdd_net + networks: bdd_net: driver: bridge diff --git a/test/bdd/fixtures/hydra-config/hydra_configure.sh b/test/bdd/fixtures/hydra-config/hydra_configure.sh new file mode 100755 index 000000000..ed71bd0f5 --- /dev/null +++ b/test/bdd/fixtures/hydra-config/hydra_configure.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# +# Copyright SecureKey Technologies Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +echo "Creating OAuth clients..." + +hydra clients create \ + --endpoint https://oidc-provider.example.com:4445 \ + --id org1 \ + --secret org1-secret \ + --grant-types client_credentials \ + --response-types token,code \ + --scope org_admin \ + --skip-tls-verify + +hydra clients create \ + --endpoint https://oidc-provider.example.com:4445 \ + --id "National Bank" \ + --secret bank-secret \ + --grant-types client_credentials \ + --response-types token,code \ + --scope org_admin \ + --skip-tls-verify + +echo "Finished creating OAuth clients" diff --git a/test/bdd/fixtures/mysql-config/mysql_config.sql b/test/bdd/fixtures/mysql-config/mysql_config.sql new file mode 100644 index 000000000..ac2668ed1 --- /dev/null +++ b/test/bdd/fixtures/mysql-config/mysql_config.sql @@ -0,0 +1,14 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +\! echo "Configuring MySQL users..."; + +/* +oidc provider (hydra) +*/ +CREATE USER 'thirdpartyoidc'@'%' IDENTIFIED BY 'thirdpartyoidc-secret-pw'; +CREATE DATABASE thirdpartyoidc; +GRANT ALL PRIVILEGES ON thirdpartyoidc.* TO 'thirdpartyoidc'@'%'; diff --git a/test/bdd/fixtures/oathkeeper-config/config.yaml b/test/bdd/fixtures/oathkeeper-config/config.yaml new file mode 100644 index 000000000..49dec92a1 --- /dev/null +++ b/test/bdd/fixtures/oathkeeper-config/config.yaml @@ -0,0 +1,38 @@ +# +# Copyright SecureKey Technologies Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +serve: + proxy: + port: 4455 + api: + port: 4458 + +access_rules: + repositories: + - file:///oathkeeper/rules/resource-server.json + matching_strategy: glob + +authenticators: + oauth2_introspection: + enabled: true + config: + introspection_url: https://oidc-provider.example.com:4445/oauth2/introspect + noop: + enabled: true + +authorizers: + allow: + enabled: true + +mutators: + header: + enabled: true + config: + headers: + X-User: "{{ print .Subject }}" + X-API-Key: "rw_token" + noop: + enabled: true diff --git a/test/bdd/fixtures/oathkeeper-config/rules/resource-server.json b/test/bdd/fixtures/oathkeeper-config/rules/resource-server.json new file mode 100644 index 000000000..27289fc04 --- /dev/null +++ b/test/bdd/fixtures/oathkeeper-config/rules/resource-server.json @@ -0,0 +1,99 @@ +[ + { + "id": "profile-management", + "upstream": { + "url": "http://vc-rest-echo.trustbloc.local:8075" + }, + "match": { + "url": "https://localhost:4455/<{issuer,verifier}>/profiles", + "methods": [ + "GET", + "POST" + ] + }, + "authenticators": [ + { + "handler": "oauth2_introspection" + } + ], + "mutators": [ + { + "handler": "header", + "config": { + "headers": { + "X-User": "{{ print .Subject }}", + "X-API-Key": "rw_token" + } + } + } + ], + "authorizer": { + "handler": "allow" + } + }, + { + "id": "profile-management-profileID", + "upstream": { + "url": "http://vc-rest-echo.trustbloc.local:8075" + }, + "match": { + "url": "https://localhost:4455/<{issuer,verifier}>/profiles/<*>", + "methods": [ + "GET", + "POST", + "PUT", + "DELETE" + ] + }, + "authenticators": [ + { + "handler": "oauth2_introspection" + } + ], + "mutators": [ + { + "handler": "header", + "config": { + "headers": { + "X-User": "{{ print .Subject }}", + "X-API-Key": "rw_token" + } + } + } + ], + "authorizer": { + "handler": "allow" + } + }, + { + "id": "profile-management-activate-deactivate", + "upstream": { + "url": "http://vc-rest-echo.trustbloc.local:8075" + }, + "match": { + "url": "https://localhost:4455/<{issuer,verifier}>/profiles/<*>/<{activate,deactivate}>", + "methods": [ + "POST" + ] + }, + "authenticators": [ + { + "handler": "oauth2_introspection" + } + ], + "mutators": [ + { + "handler": "header", + "config": { + "headers": { + "X-User": "{{ print .Subject }}", + "X-API-Key": "rw_token" + } + } + } + ], + "authorizer": { + "handler": "allow" + } + } +] diff --git a/test/bdd/go.mod b/test/bdd/go.mod index f8fe5dd27..9f740add5 100644 --- a/test/bdd/go.mod +++ b/test/bdd/go.mod @@ -16,9 +16,11 @@ require ( github.com/hyperledger/aries-framework-go v0.1.9-0.20220819134023-730ac301c3c0 github.com/hyperledger/aries-framework-go-ext/component/vdr/orb v1.0.0-rc2.0.20220811162145-47649b185a56 github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20220526205258-18d510d84955 + github.com/ory/hydra-client-go v1.11.8 github.com/tidwall/gjson v1.9.3 github.com/trustbloc/edge-core v0.1.9-0.20220718150010-aa7941986372 github.com/trustbloc/vcs v0.0.0-00010101000000-000000000000 + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 ) require ( @@ -98,6 +100,7 @@ require ( golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.7 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect google.golang.org/grpc v1.44.0 // indirect google.golang.org/protobuf v1.28.0 // indirect diff --git a/test/bdd/go.sum b/test/bdd/go.sum index a250afcec..47508299c 100644 --- a/test/bdd/go.sum +++ b/test/bdd/go.sum @@ -668,6 +668,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/ory/dockertest/v3 v3.8.1 h1:vU/8d1We4qIad2YM0kOwRVtnyue7ExvacPiw1yDm17g= +github.com/ory/hydra-client-go v1.11.8 h1:GwJjvH/DBcfYzoST4vUpi4pIRzDGH5oODKpIVuhwVyc= +github.com/ory/hydra-client-go v1.11.8/go.mod h1:4YuBuwUEC4yiyDrnKjGYc1tB3gUXan4ZiUYMjXJbfxA= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= @@ -1038,8 +1040,11 @@ golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1248,6 +1253,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= diff --git a/test/bdd/pkg/bddutil/util.go b/test/bdd/pkg/bddutil/util.go index 813cc7578..d99275f3c 100644 --- a/test/bdd/pkg/bddutil/util.go +++ b/test/bdd/pkg/bddutil/util.go @@ -7,9 +7,11 @@ SPDX-License-Identifier: Apache-2.0 package bddutil import ( + "context" "crypto/ed25519" "crypto/tls" _ "embed" //nolint:gci // required for go:embed + "encoding/base64" "encoding/json" "errors" "fmt" @@ -27,6 +29,7 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" ldstore "github.com/hyperledger/aries-framework-go/pkg/store/ld" + hydra "github.com/ory/hydra-client-go" "github.com/trustbloc/edge-core/pkg/log" ) @@ -344,3 +347,53 @@ func DocumentLoader() (*ld.DocumentLoader, error) { return loader, nil } + +const oidcProviderURL = "https://localhost:4444" + +func IssueAccessToken(ctx context.Context, clientID, secret string) (string, error) { + config := hydra.NewConfiguration() + + config.Servers = []hydra.ServerConfiguration{ + { + URL: oidcProviderURL, + }, + } + + httpClient := http.Client{ + Transport: &BasicAuthTransport{ + ClientID: clientID, + Secret: secret, + }, + } + + config.HTTPClient = &httpClient + + client := hydra.NewAPIClient(config) + + req := client.PublicApi.Oauth2Token(ctx).GrantType("client_credentials").ClientId("org1") + + resp, _, err := req.Execute() + if err != nil { + return "", fmt.Errorf("execute oauth2 token request: %w", err) + } + + return *resp.AccessToken, nil +} + +type BasicAuthTransport struct { + ClientID string + Secret string +} + +func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", fmt.Sprintf("Basic %s", + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", t.ClientID, t.Secret))))) + + transport := http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + + return transport.RoundTrip(req) +} diff --git a/test/bdd/pkg/v1/issuer/issuer_steps.go b/test/bdd/pkg/v1/issuer/issuer_steps.go index 71d10b54e..d70db04f0 100644 --- a/test/bdd/pkg/v1/issuer/issuer_steps.go +++ b/test/bdd/pkg/v1/issuer/issuer_steps.go @@ -8,20 +8,20 @@ package issuer import ( "bytes" + "context" + "crypto/tls" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "github.com/cucumber/godog" - "github.com/trustbloc/edge-core/pkg/log" - "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" - "github.com/trustbloc/vcs/test/bdd/pkg/context" + bddcontext "github.com/trustbloc/vcs/test/bdd/pkg/context" ) const ( - issuerURL = "http://localhost:8075" + issuerURL = "https://localhost:4455" issuerProfileURL = issuerURL + "/issuer/profiles" issuerProfileURLFormat = issuerProfileURL + "/%s" ) @@ -30,34 +30,48 @@ func getProfileIDKey(user string) string { return user + "-profileID" } -func getProfileAuthToken(user string) string { - // temporary we use org id as token - return user + "-userOrg" +func getProfileAuthTokenKey(user string) string { + return user + "-accessToken" } -var logger = log.New("bdd-test") - // Steps is steps for VC BDD tests type Steps struct { - bddContext *context.BDDContext + bddContext *bddcontext.BDDContext + tlsConfig *tls.Config } // NewSteps returns new agent from client SDK -func NewSteps(ctx *context.BDDContext) *Steps { - return &Steps{bddContext: ctx} +func NewSteps(ctx *bddcontext.BDDContext) *Steps { + return &Steps{ + bddContext: ctx, + tlsConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } } // RegisterSteps registers agent steps func (e *Steps) RegisterSteps(s *godog.ScenarioContext) { + s.Step(`^"([^"]*)" has been authorized with client id "([^"]*)" and secret "([^"]*)" to use vcs$`, e.authorizeUser) s.Step(`^"([^"]*)" sends request to create an issuer profile with the organization "([^"]*)"$`, e.createIssuerProfile) s.Step(`^"([^"]*)" deactivates the issuer profile$`, e.deactivateIssuerProfile) s.Step(`^"([^"]*)" activates the issuer profile$`, e.activateIssuerProfile) s.Step(`^"([^"]*)" deletes the issuer profile$`, e.deleteIssuerProfile) s.Step(`^"([^"]*)" updates the issuer profile name to "([^"]*)"$`, e.updateIssuerProfileName) - s.Step(`^"([^"]*)" can recreate the issuer profile with the organization "([^"]*)"$`, e.createIssuerProfile) } +func (e *Steps) authorizeUser(user, clientID, secret string) error { + accessToken, err := bddutil.IssueAccessToken(context.Background(), clientID, secret) + if err != nil { + return err + } + + e.bddContext.Args[getProfileAuthTokenKey(user)] = accessToken + + return nil +} + func (e *Steps) createIssuerProfile(user, organizationName string) error { //nolint: funlen profileRequest := createIssuerProfileData{ Name: "Test", @@ -74,23 +88,21 @@ func (e *Steps) createIssuerProfile(user, organizationName string) error { //nol }, } - e.bddContext.Args[getProfileAuthToken(user)] = organizationName - requestBytes, err := json.Marshal(profileRequest) if err != nil { return err } - resp, err := bddutil.HTTPDo(http.MethodPost, issuerProfileURL, "application/json", - e.bddContext.Args[getProfileAuthToken(user)], //nolint: bodyclose - bytes.NewBuffer(requestBytes)) + resp, err := bddutil.HTTPSDo(http.MethodPost, issuerProfileURL, "application/json", + e.bddContext.Args[getProfileAuthTokenKey(user)], //nolint: bodyclose + bytes.NewBuffer(requestBytes), e.tlsConfig) if err != nil { return err } defer bddutil.CloseResponseBody(resp.Body) - respBytes, err := ioutil.ReadAll(resp.Body) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return err } @@ -113,7 +125,7 @@ func (e *Steps) createIssuerProfile(user, organizationName string) error { //nol func (e *Steps) updateIssuerProfileName(user, profileName string) error { id := e.bddContext.Args[getProfileIDKey(user)] - token := e.bddContext.Args[getProfileAuthToken(user)] + token := e.bddContext.Args[getProfileAuthTokenKey(user)] profileRequest := updateIssuerProfileData{ Name: profileName, @@ -124,15 +136,15 @@ func (e *Steps) updateIssuerProfileName(user, profileName string) error { return err } - resp, err := bddutil.HTTPDo(http.MethodDelete, fmt.Sprintf(issuerProfileURLFormat, //nolint: bodyclose - id), "", token, bytes.NewBuffer(requestBytes)) + resp, err := bddutil.HTTPSDo(http.MethodDelete, fmt.Sprintf(issuerProfileURLFormat, //nolint: bodyclose + id), "", token, bytes.NewBuffer(requestBytes), e.tlsConfig) if err != nil { return err } defer bddutil.CloseResponseBody(resp.Body) - respBytes, err := ioutil.ReadAll(resp.Body) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return err } @@ -149,26 +161,26 @@ func (e *Steps) deleteIssuerProfile(user string) error { } func (e *Steps) activateIssuerProfile(user string) error { - return e.doSimpleProfileIDRequest(user, http.MethodPost, issuerProfileURLFormat + "/activate") + return e.doSimpleProfileIDRequest(user, http.MethodPost, issuerProfileURLFormat+"/activate") } func (e *Steps) deactivateIssuerProfile(user string) error { - return e.doSimpleProfileIDRequest(user, http.MethodPost, issuerProfileURLFormat + "/deactivate") + return e.doSimpleProfileIDRequest(user, http.MethodPost, issuerProfileURLFormat+"/deactivate") } func (e *Steps) doSimpleProfileIDRequest(user, httpMethod, urlFormat string) error { id := e.bddContext.Args[getProfileIDKey(user)] - token := e.bddContext.Args[getProfileAuthToken(user)] + token := e.bddContext.Args[getProfileAuthTokenKey(user)] - resp, err := bddutil.HTTPDo(httpMethod, fmt.Sprintf(urlFormat, //nolint: bodyclose - id), "", token, nil) + resp, err := bddutil.HTTPSDo(httpMethod, fmt.Sprintf(urlFormat, //nolint: bodyclose + id), "", token, nil, e.tlsConfig) if err != nil { return err } defer bddutil.CloseResponseBody(resp.Body) - respBytes, err := ioutil.ReadAll(resp.Body) + respBytes, err := io.ReadAll(resp.Body) if err != nil { return err } diff --git a/test/bdd/pkg/v1/verifier/verifer_steps.go b/test/bdd/pkg/v1/verifier/verifer_steps.go index 34e57373e..5f519cbc9 100644 --- a/test/bdd/pkg/v1/verifier/verifer_steps.go +++ b/test/bdd/pkg/v1/verifier/verifer_steps.go @@ -9,27 +9,27 @@ package verifier import ( "bytes" "context" + "crypto/tls" _ "embed" "encoding/json" "errors" "fmt" "io" "net/http" - "net/http/httputil" - "os" "text/template" "github.com/cucumber/godog" "github.com/google/go-cmp/cmp" "github.com/hyperledger/aries-framework-go/pkg/common/log" + "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" bddcontext "github.com/trustbloc/vcs/test/bdd/pkg/context" ) const ( contentType = "Content-Type" applicationJSON = "application/json" - host = "http://localhost:8075" + host = "https://localhost:4455" ) var ( @@ -53,19 +53,23 @@ type Steps struct { responseBody []byte profileID string testdata map[string][]byte + accessTokens map[string]string } // NewSteps returns new Steps context. func NewSteps(ctx *bddcontext.BDDContext) *Steps { - httpClient := http.DefaultClient - - if os.Getenv("HTTP_CLIENT_TRACE_ON") == "true" { - httpClient.Transport = &DumpTransport{r: http.DefaultTransport} + httpClient := http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, } return &Steps{ - bddContext: ctx, - httpClient: httpClient, + bddContext: ctx, + httpClient: &httpClient, + accessTokens: make(map[string]string), testdata: map[string][]byte{ "verifier_profile_create.json": verifierProfileCreateJSON, "verifier_profile_created.json": verifierProfileCreatedJSON, @@ -77,6 +81,7 @@ func NewSteps(ctx *bddcontext.BDDContext) *Steps { // RegisterSteps registers scenario steps. func (s *Steps) RegisterSteps(sc *godog.ScenarioContext) { + sc.Step(`^organization "([^"]*)" has been authorized using client id "([^"]*)" and secret "([^"]*)"$`, s.authorize) sc.Step(`^organization "([^"]*)" creates a verifier profile with data from "([^"]*)"$`, s.createProfile) sc.Step(`^organization "([^"]*)" has a verifier profile with data from "([^"]*)"$`, s.createProfile) sc.Step(`^organization "([^"]*)" gets a verifier profile by ID$`, s.getProfileByID) @@ -93,11 +98,23 @@ func (s *Steps) RegisterSteps(sc *godog.ScenarioContext) { sc.Step(`^verifier profile matches "([^"]*)"$`, s.checkProfileMatches) } +func (s *Steps) authorize(ctx context.Context, org, clientID, secret string) error { + accessToken, err := bddutil.IssueAccessToken(ctx, clientID, secret) + if err != nil { + return fmt.Errorf("failed to get access token: %w", err) + } + + s.accessTokens[org] = accessToken + + return nil +} + func (s *Steps) createProfile(ctx context.Context, orgID, content string) error { var profile verifierProfile if err := s.httpDo(ctx, http.MethodPost, host+"/verifier/profiles", withBody(bytes.NewReader(s.testdata[content])), + withBearerToken(s.accessTokens[orgID]), withParsedResponse(&profile), ); err != nil { return fmt.Errorf("create verifier profile: %w", err) @@ -112,6 +129,7 @@ func (s *Steps) getProfileByID(ctx context.Context, orgID string) error { var profile verifierProfile if err := s.httpDo(ctx, http.MethodGet, host+"/verifier/profiles/"+s.profileID, + withBearerToken(s.accessTokens[orgID]), withParsedResponse(&profile), ); err != nil { return fmt.Errorf("get verifier profile: %w", err) @@ -127,6 +145,7 @@ func (s *Steps) updateProfile(ctx context.Context, orgID, content string) error if err := s.httpDo(ctx, http.MethodPut, host+"/verifier/profiles/"+s.profileID, withBody(bytes.NewReader(s.testdata[content])), + withBearerToken(s.accessTokens[orgID]), withParsedResponse(&profile), ); err != nil { return fmt.Errorf("update verifier profile: %w", err) @@ -138,7 +157,8 @@ func (s *Steps) updateProfile(ctx context.Context, orgID, content string) error } func (s *Steps) deleteProfile(ctx context.Context, orgID string) error { - if err := s.httpDo(ctx, http.MethodDelete, host+"/verifier/profiles/"+s.profileID); err != nil { + if err := s.httpDo(ctx, http.MethodDelete, host+"/verifier/profiles/"+s.profileID, + withBearerToken(s.accessTokens[orgID])); err != nil { return fmt.Errorf("delete verifier profile: %w", err) } @@ -146,7 +166,8 @@ func (s *Steps) deleteProfile(ctx context.Context, orgID string) error { } func (s *Steps) activateProfile(ctx context.Context, orgID string) error { - if err := s.httpDo(ctx, http.MethodPost, host+"/verifier/profiles/"+s.profileID+"/activate"); err != nil { + if err := s.httpDo(ctx, http.MethodPost, host+"/verifier/profiles/"+s.profileID+"/activate", + withBearerToken(s.accessTokens[orgID])); err != nil { return fmt.Errorf("activate verifier profile: %w", err) } @@ -154,7 +175,8 @@ func (s *Steps) activateProfile(ctx context.Context, orgID string) error { } func (s *Steps) deactivateProfile(ctx context.Context, orgID string) error { - if err := s.httpDo(ctx, http.MethodPost, host+"/verifier/profiles/"+s.profileID+"/deactivate"); err != nil { + if err := s.httpDo(ctx, http.MethodPost, host+"/verifier/profiles/"+s.profileID+"/deactivate", + withBearerToken(s.accessTokens[orgID])); err != nil { return fmt.Errorf("deactivate verifier profile: %w", err) } @@ -178,7 +200,8 @@ func (s *Steps) checkProfileDeleted(ctx context.Context) error { return err } - if err := s.httpDo(ctx, http.MethodGet, host+"/verifier/profiles/"+s.profileID); err != nil { + if err := s.httpDo(ctx, http.MethodGet, host+"/verifier/profiles/"+s.profileID, + withBearerToken(s.accessTokens["org1"])); err != nil { if err.Error() != "404 Not Found" { return err } @@ -195,6 +218,7 @@ func (s *Steps) checkProfileActivated(ctx context.Context) error { var profile verifierProfile if err := s.httpDo(ctx, http.MethodGet, host+"/verifier/profiles/"+s.profileID, + withBearerToken(s.accessTokens["org1"]), withParsedResponse(&profile), ); err != nil { return err @@ -215,6 +239,7 @@ func (s *Steps) checkProfileDeactivated(ctx context.Context) error { var profile verifierProfile if err := s.httpDo(ctx, http.MethodGet, host+"/verifier/profiles/"+s.profileID, + withBearerToken(s.accessTokens["org1"]), withParsedResponse(&profile), ); err != nil { return err @@ -267,6 +292,7 @@ func (s *Steps) checkProfileMatches(content string) error { type options struct { body io.Reader + bearerToken string parsedResponse interface{} } @@ -278,6 +304,12 @@ func withBody(body io.Reader) opt { } } +func withBearerToken(token string) opt { + return func(o *options) { + o.bearerToken = token + } +} + func withParsedResponse(v interface{}) opt { return func(o *options) { o.parsedResponse = v @@ -300,6 +332,10 @@ func (s *Steps) httpDo(ctx context.Context, method, url string, opts ...opt) err req.Header.Add(contentType, applicationJSON) + if op.bearerToken != "" { + req.Header.Add("Authorization", "Bearer "+op.bearerToken) + } + resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("http do: %w", err) @@ -353,32 +389,3 @@ func parseError(status string, body []byte) error { func (s *Steps) ProfileID() string { return s.profileID } - -// DumpTransport is http.RoundTripper that dumps requests and responses. -type DumpTransport struct { - r http.RoundTripper -} - -// RoundTrip implements the RoundTripper interface. -func (d *DumpTransport) RoundTrip(req *http.Request) (*http.Response, error) { - reqDump, err := httputil.DumpRequest(req, true) - if err != nil { - return nil, fmt.Errorf("failed to dump request: %w", err) - } - - fmt.Printf("\n****REQUEST****\n%s\n\n", string(reqDump)) - - resp, err := d.r.RoundTrip(req) - if err != nil { - return nil, err - } - - respDump, err := httputil.DumpResponse(resp, true) - if err != nil { - return nil, fmt.Errorf("failed to dump response: %w", err) - } - - fmt.Printf("****RESPONSE****\n%s****************\n", string(respDump)) - - return resp, nil -}