diff --git a/api/openapi/auth.yml b/api/openapi/auth.yml index 15dffb967a5..a0898e7b5bf 100644 --- a/api/openapi/auth.yml +++ b/api/openapi/auth.yml @@ -175,74 +175,23 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - /domains/{domainID}/enable: - post: - summary: Enables a domain - description: | - Enables a specific domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - security: - - bearerAuth: [] - responses: - "200": - description: Successfully enabled domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - /domains/{domainID}/disable: - post: - summary: Disable a domain + /domains/{domainID}/status: + put: + summary: changes the domain status description: | - Disable a specific domain that is identified by the domain ID. - tags: - - Domains - parameters: - - $ref: "#/components/parameters/DomainID" - security: - - bearerAuth: [] - responses: - "200": - description: Successfully disabled domain. - "400": - description: Failed due to malformed domain's ID. - "401": - description: Missing or invalid access token provided. - "403": - description: Unauthorized access the domain ID. - "404": - description: A non-existent entity request. - "422": - description: Database can't process request. - "500": - $ref: "#/components/responses/ServiceError" - - /domains/{domainID}/freeze: - post: - summary: Freeze a domain - description: | - Freeze a specific domain that is identified by the domain ID. + Changes a specific domain status that is identified by the domain ID. tags: - Domains parameters: - $ref: "#/components/parameters/DomainID" + requestBody: + $ref: "#/components/requestBodies/DomainStatusUpdateReq" security: - bearerAuth: [] responses: "200": - description: Successfully freezed domain. + description: Successfully enabled domain. "400": description: Failed due to malformed domain's ID. "401": @@ -255,7 +204,7 @@ paths: description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" - + /domains/{domainID}/users/assign: post: summary: Assign users to domain @@ -594,6 +543,13 @@ components: type: string example: domain alias description: Domain alias. + DomainStatusUpdate: + type: object + properties: + status: + type: string + example: enabled + description: Domain status. Permissions: type: object properties: @@ -783,6 +739,13 @@ components: application/json: schema: $ref: "#/components/schemas/DomainUpdate" + DomainStatusUpdateReq: + description: JSON-formated document describing the domain status to be updated + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DomainStatusUpdate" AssignUserReq: description: JSON-formated document describing the policy related to assigning users to a domain required: true diff --git a/auth/api/http/domains/decode.go b/auth/api/http/domains/decode.go index f49659fa9dc..f28b6c88634 100644 --- a/auth/api/http/domains/decode.go +++ b/auth/api/http/domains/decode.go @@ -76,27 +76,23 @@ func decodeListDomainRequest(ctx context.Context, r *http.Request) (interface{}, return req, nil } -func decodeEnableDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := enableDomainReq{ +func decodeStatusDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := statusDomainReq{ token: apiutil.ExtractBearerToken(r), domainID: chi.URLParam(r, "domainID"), } - return req, nil -} - -func decodeDisableDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := disableDomainReq{ - token: apiutil.ExtractBearerToken(r), - domainID: chi.URLParam(r, "domainID"), + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) } return req, nil } -func decodeFreezeDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { - req := freezeDomainReq{ +func decodeDeleteDomainRequest(_ context.Context, r *http.Request) (interface{}, error) { + req := deleteDomainReq{ token: apiutil.ExtractBearerToken(r), domainID: chi.URLParam(r, "domainID"), } + return req, nil } diff --git a/auth/api/http/domains/endpoint.go b/auth/api/http/domains/endpoint.go index bfc4d800103..ff38bfb8b8d 100644 --- a/auth/api/http/domains/endpoint.go +++ b/auth/api/http/domains/endpoint.go @@ -115,58 +115,34 @@ func listDomainsEndpoint(svc auth.Service) endpoint.Endpoint { } } -func enableDomainEndpoint(svc auth.Service) endpoint.Endpoint { +func statusDomainEndpoint(svc auth.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(enableDomainReq) + req := request.(statusDomainReq) if err := req.validate(); err != nil { return nil, err } - - enable := auth.EnabledStatus d := auth.DomainReq{ - Status: &enable, + Status: &req.Status, } if _, err := svc.ChangeDomainStatus(ctx, req.token, req.domainID, d); err != nil { return nil, err } - return enableDomainRes{}, nil + return statusDomainRes{}, nil } } -func disableDomainEndpoint(svc auth.Service) endpoint.Endpoint { +func deleteDomainEndpoint(svc auth.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(disableDomainReq) + req := request.(deleteDomainReq) if err := req.validate(); err != nil { return nil, err } - - disable := auth.DisabledStatus - d := auth.DomainReq{ - Status: &disable, - } - if _, err := svc.ChangeDomainStatus(ctx, req.token, req.domainID, d); err != nil { - return nil, err + d := auth.Domain{ + ID: req.domainID, } - return disableDomainRes{}, nil + return nil, svc.DeleteDomain(ctx, req.token, d) } -} -func freezeDomainEndpoint(svc auth.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(freezeDomainReq) - if err := req.validate(); err != nil { - return nil, err - } - - freeze := auth.FreezeStatus - d := auth.DomainReq{ - Status: &freeze, - } - if _, err := svc.ChangeDomainStatus(ctx, req.token, req.domainID, d); err != nil { - return nil, err - } - return freezeDomainRes{}, nil - } } func assignDomainUsersEndpoint(svc auth.Service) endpoint.Endpoint { diff --git a/auth/api/http/domains/endpoint_test.go b/auth/api/http/domains/endpoint_test.go index 4a53418b479..e1a7ca904bb 100644 --- a/auth/api/http/domains/endpoint_test.go +++ b/auth/api/http/domains/endpoint_test.go @@ -60,6 +60,12 @@ type testRequest struct { body io.Reader } +type testStatusDomainReq struct { + token string + domainID string + Status string +} + func (tr testRequest) make() (*http.Response, error) { req, err := http.NewRequest(tr.method, tr.url, tr.body) if err != nil { @@ -747,158 +753,102 @@ func TestUpdateDomain(t *testing.T) { } } -func TestEnableDomain(t *testing.T) { +func TestChangeStatusDomain(t *testing.T) { ds, svc := newDomainsServer() defer ds.Close() - disabledDomain := domain - disabledDomain.Status = auth.DisabledStatus - cases := []struct { desc string - domain auth.Domain response auth.Domain - token string status int svcErr error err error + request testStatusDomainReq }{ { - desc: "enable domain with valid token", - domain: disabledDomain, + desc: "change domain status with valid token", response: auth.Domain{ ID: domain.ID, - Status: auth.EnabledStatus, + Status: auth.DisabledStatus, }, - token: validToken, - status: http.StatusOK, - err: nil, + status: http.StatusOK, + err: nil, + request: testStatusDomainReq{token: validToken, domainID: testsutil.GenerateUUID(&testing.T{}), Status: "disabled"}, }, { - desc: "enable domain with invalid token", - domain: disabledDomain, - token: inValidToken, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, + desc: "change domain status with invalid token", + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + request: testStatusDomainReq{token: inValidToken, domainID: testsutil.GenerateUUID(&testing.T{}), Status: "enabled"}, }, { - desc: "enable domain with empty token", - domain: disabledDomain, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, + desc: "change domain status with empty token", + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, + request: testStatusDomainReq{token: "", domainID: testsutil.GenerateUUID(&testing.T{}), Status: "enabled"}, }, { - desc: "enable domain with empty id", - domain: auth.Domain{ - ID: "", - }, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, + desc: "change domain status with empty id", + status: http.StatusBadRequest, + err: apiutil.ErrMissingID, + request: testStatusDomainReq{token: validToken, domainID: "", Status: "enabled"}, }, { - desc: "enable domain with invalid id", - domain: auth.Domain{ - ID: "invalid", - }, - token: validToken, - status: http.StatusForbidden, - svcErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, + desc: "change domain status with invalid id", + status: http.StatusForbidden, + svcErr: svcerr.ErrAuthorization, + err: svcerr.ErrAuthorization, + request: testStatusDomainReq{token: validToken, domainID: "invalid", Status: "enabled"}, }, - } - - for _, tc := range cases { - data := toJSON(tc.domain) - req := testRequest{ - client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains/%s/enable", ds.URL, tc.domain.ID), - contentType: contentType, - token: tc.token, - body: strings.NewReader(data), - } - svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) - res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) - assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - svcCall.Unset() - } -} - -func TestDisableDomain(t *testing.T) { - ds, svc := newDomainsServer() - defer ds.Close() - - cases := []struct { - desc string - domain auth.Domain - response auth.Domain - token string - status int - svcErr error - err error - }{ { - desc: "disable domain with valid token", - domain: domain, + desc: "change domain status to enabled", response: auth.Domain{ - ID: domain.ID, - Status: auth.DisabledStatus, + ID: domain.ID, }, - token: validToken, - status: http.StatusOK, - err: nil, + status: http.StatusOK, + err: nil, + request: testStatusDomainReq{token: validToken, domainID: testsutil.GenerateUUID(&testing.T{}), Status: "enabled"}, }, { - desc: "disable domain with invalid token", - domain: domain, - token: inValidToken, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "disable domain with empty token", - domain: domain, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, + desc: "change domain status to disabled", + response: auth.Domain{ + ID: domain.ID, + }, + status: http.StatusOK, + err: nil, + request: testStatusDomainReq{token: validToken, domainID: testsutil.GenerateUUID(&testing.T{}), Status: "disabled"}, }, { - desc: "disable domain with empty id", - domain: auth.Domain{ - ID: "", + desc: "change domain status to freezed", + response: auth.Domain{ + ID: domain.ID, }, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, + status: http.StatusOK, + err: nil, + request: testStatusDomainReq{token: validToken, domainID: testsutil.GenerateUUID(&testing.T{}), Status: "freezed"}, }, { - desc: "disable domain with invalid id", - domain: auth.Domain{ - ID: "invalid", + desc: "change domain status with invalid input status", + response: auth.Domain{ + ID: domain.ID, }, - token: validToken, - status: http.StatusForbidden, - svcErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, + status: http.StatusBadRequest, + err: apiutil.ErrInvalidDomainStatus, + request: testStatusDomainReq{token: validToken, domainID: testsutil.GenerateUUID(&testing.T{}), Status: "enable"}, }, } - for _, tc := range cases { - data := toJSON(tc.domain) + data := toJSON(tc.request) req := testRequest{ client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains/%s/disable", ds.URL, tc.domain.ID), + method: http.MethodPut, + url: fmt.Sprintf("%s/domains/%s/status", ds.URL, tc.request.domainID), contentType: contentType, - token: tc.token, + token: tc.request.token, body: strings.NewReader(data), } - svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) + svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.request.token, tc.request.domainID, mock.Anything).Return(tc.response, tc.svcErr) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) @@ -906,63 +856,49 @@ func TestDisableDomain(t *testing.T) { } } -func TestFreezeDomain(t *testing.T) { +func TestDeleteDomain(t *testing.T) { ds, svc := newDomainsServer() defer ds.Close() cases := []struct { - desc string - domain auth.Domain - response auth.Domain - token string - status int - svcErr error - err error + desc string + token string + domain auth.Domain + contentType string + status int + svcErr error + err error }{ { - desc: "freeze domain with valid token", - domain: domain, - response: auth.Domain{ - ID: domain.ID, - Status: auth.FreezeStatus, + desc: "delete domain successfully", + token: validToken, + domain: auth.Domain{ + ID: ID, }, - token: validToken, - status: http.StatusOK, - err: nil, - }, - { - desc: "freeze domain with invalid token", - domain: domain, - token: inValidToken, - status: http.StatusUnauthorized, - svcErr: svcerr.ErrAuthentication, - err: svcerr.ErrAuthentication, - }, - { - desc: "freeze domain with empty token", - domain: domain, - token: "", - status: http.StatusUnauthorized, - err: apiutil.ErrBearerToken, + contentType: contentType, + status: http.StatusOK, + err: nil, }, { - desc: "freeze domain with empty id", + desc: "delete domain with empty token", + token: "", domain: auth.Domain{ - ID: "", + ID: ID, }, - token: validToken, - status: http.StatusBadRequest, - err: apiutil.ErrMissingID, + contentType: contentType, + status: http.StatusUnauthorized, + err: apiutil.ErrBearerToken, }, { - desc: "freeze domain with invalid id", + desc: "delete domain with invalid token", + token: inValidToken, domain: auth.Domain{ - ID: "invalid", + ID: ID, }, - token: validToken, - status: http.StatusForbidden, - svcErr: svcerr.ErrAuthorization, - err: svcerr.ErrAuthorization, + contentType: contentType, + status: http.StatusUnauthorized, + svcErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, }, } @@ -970,13 +906,13 @@ func TestFreezeDomain(t *testing.T) { data := toJSON(tc.domain) req := testRequest{ client: ds.Client(), - method: http.MethodPost, - url: fmt.Sprintf("%s/domains/%s/freeze", ds.URL, tc.domain.ID), + method: http.MethodDelete, + url: fmt.Sprintf("%s/domains/%s", ds.URL, tc.domain.ID), contentType: contentType, token: tc.token, body: strings.NewReader(data), } - svcCall := svc.On("ChangeDomainStatus", mock.Anything, tc.token, tc.domain.ID, mock.Anything).Return(tc.response, tc.svcErr) + svcCall := svc.On("DeleteDomain", mock.Anything, tc.token, mock.Anything).Return(tc.svcErr) res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) diff --git a/auth/api/http/domains/requests.go b/auth/api/http/domains/requests.go index c1da123ca0b..64a03daa398 100644 --- a/auth/api/http/domains/requests.go +++ b/auth/api/http/domains/requests.go @@ -107,12 +107,17 @@ func (req listDomainsReq) validate() error { return nil } -type enableDomainReq struct { +type statusDomainReq struct { token string domainID string + Status auth.Status `json:"status,omitempty"` } -func (req enableDomainReq) validate() error { +func (req statusDomainReq) validate() error { + validStatuses := make(map[auth.Status]struct{}) + validStatuses[auth.EnabledStatus] = struct{}{} + validStatuses[auth.DisabledStatus] = struct{}{} + validStatuses[auth.FreezeStatus] = struct{}{} if req.token == "" { return apiutil.ErrBearerToken } @@ -121,32 +126,18 @@ func (req enableDomainReq) validate() error { return apiutil.ErrMissingID } - return nil -} - -type disableDomainReq struct { - token string - domainID string -} - -func (req disableDomainReq) validate() error { - if req.token == "" { - return apiutil.ErrBearerToken - } - - if req.domainID == "" { + if _, ok := validStatuses[req.Status]; !ok { return apiutil.ErrMissingID } - return nil } -type freezeDomainReq struct { +type deleteDomainReq struct { token string domainID string } -func (req freezeDomainReq) validate() error { +func (req deleteDomainReq) validate() error { if req.token == "" { return apiutil.ErrBearerToken } diff --git a/auth/api/http/domains/responses.go b/auth/api/http/domains/responses.go index 4a43a80b554..fffcaafcb3b 100644 --- a/auth/api/http/domains/responses.go +++ b/auth/api/http/domains/responses.go @@ -114,45 +114,17 @@ func (res listDomainsRes) MarshalJSON() ([]byte, error) { return json.Marshal(res.Data) } -type enableDomainRes struct{} +type statusDomainRes struct{} -func (res enableDomainRes) Code() int { +func (res statusDomainRes) Code() int { return http.StatusOK } -func (res enableDomainRes) Headers() map[string]string { +func (res statusDomainRes) Headers() map[string]string { return map[string]string{} } -func (res enableDomainRes) Empty() bool { - return true -} - -type disableDomainRes struct{} - -func (res disableDomainRes) Code() int { - return http.StatusOK -} - -func (res disableDomainRes) Headers() map[string]string { - return map[string]string{} -} - -func (res disableDomainRes) Empty() bool { - return true -} - -type freezeDomainRes struct{} - -func (res freezeDomainRes) Code() int { - return http.StatusOK -} - -func (res freezeDomainRes) Headers() map[string]string { - return map[string]string{} -} - -func (res freezeDomainRes) Empty() bool { +func (res statusDomainRes) Empty() bool { return true } diff --git a/auth/api/http/domains/transport.go b/auth/api/http/domains/transport.go index caacb74dba6..a96d9497863 100644 --- a/auth/api/http/domains/transport.go +++ b/auth/api/http/domains/transport.go @@ -42,6 +42,13 @@ func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { opts..., ), "view_domain").ServeHTTP) + r.Delete("/", otelhttp.NewHandler(kithttp.NewServer( + deleteDomainEndpoint(svc), + decodeDeleteDomainRequest, + api.EncodeResponse, + opts..., + ), "delete_domain").ServeHTTP) + r.Get("/permissions", otelhttp.NewHandler(kithttp.NewServer( retrieveDomainPermissionsEndpoint(svc), decodeRetrieveDomainPermissionsRequest, @@ -56,26 +63,12 @@ func MakeHandler(svc auth.Service, mux *chi.Mux, logger *slog.Logger) *chi.Mux { opts..., ), "update_domain").ServeHTTP) - r.Post("/enable", otelhttp.NewHandler(kithttp.NewServer( - enableDomainEndpoint(svc), - decodeEnableDomainRequest, - api.EncodeResponse, - opts..., - ), "enable_domain").ServeHTTP) - - r.Post("/disable", otelhttp.NewHandler(kithttp.NewServer( - disableDomainEndpoint(svc), - decodeDisableDomainRequest, - api.EncodeResponse, - opts..., - ), "disable_domain").ServeHTTP) - - r.Post("/freeze", otelhttp.NewHandler(kithttp.NewServer( - freezeDomainEndpoint(svc), - decodeFreezeDomainRequest, + r.Put("/status", otelhttp.NewHandler(kithttp.NewServer( + statusDomainEndpoint(svc), + decodeStatusDomainRequest, api.EncodeResponse, opts..., - ), "freeze_domain").ServeHTTP) + ), "status_domain").ServeHTTP) r.Route("/users", func(r chi.Router) { r.Post("/assign", otelhttp.NewHandler(kithttp.NewServer( diff --git a/auth/api/logging.go b/auth/api/logging.go index a99fd2a92d8..65daad6392d 100644 --- a/auth/api/logging.go +++ b/auth/api/logging.go @@ -349,6 +349,25 @@ func (lm *loggingMiddleware) CreateDomain(ctx context.Context, token string, d a return lm.svc.CreateDomain(ctx, token, d) } +func (lm *loggingMiddleware) DeleteDomain(ctx context.Context, token string, d auth.Domain) (err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.Group("domain", + slog.String("id", d.ID), + slog.String("name", d.Name), + ), + } + if err != nil { + args := append(args, slog.String("error", err.Error())) + lm.logger.Warn("Delete domain failed to complete successfully", args...) + return + } + lm.logger.Info("Delete domain completed successfully", args...) + }(time.Now()) + return lm.svc.DeleteDomain(ctx, token, d) +} + func (lm *loggingMiddleware) RetrieveDomain(ctx context.Context, token, id string) (do auth.Domain, err error) { defer func(begin time.Time) { args := []any{ @@ -365,6 +384,23 @@ func (lm *loggingMiddleware) RetrieveDomain(ctx context.Context, token, id strin return lm.svc.RetrieveDomain(ctx, token, id) } +// RetrieveStatusByID retrieves Domain Status by its unique ID. +func (lm *loggingMiddleware) RetrieveStatus(ctx context.Context, token string, id string) (do auth.Domain, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("domain_id", id), + } + if err != nil { + args = append(args, slog.Any("error", err)) + lm.logger.Warn("Retrieve domain status failed to complete successfully", args...) + return + } + lm.logger.Info("Retrieve domain status completed successfully", args...) + }(time.Now()) + return lm.svc.RetrieveDomain(ctx, token, id) +} + func (lm *loggingMiddleware) RetrieveDomainPermissions(ctx context.Context, token, id string) (permissions auth.Permissions, err error) { defer func(begin time.Time) { args := []any{ diff --git a/auth/api/metrics.go b/auth/api/metrics.go index 8296d1a238c..d36f3e180e2 100644 --- a/auth/api/metrics.go +++ b/auth/api/metrics.go @@ -176,6 +176,14 @@ func (ms *metricsMiddleware) CreateDomain(ctx context.Context, token string, d a return ms.svc.CreateDomain(ctx, token, d) } +func (ms *metricsMiddleware) DeleteDomain(ctx context.Context, token string, d auth.Domain) error { + defer func(begin time.Time) { + ms.counter.With("method", "delete_domain").Add(1) + ms.latency.With("method", "delete_domain").Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.DeleteDomain(ctx, token, d) +} + func (ms *metricsMiddleware) RetrieveDomain(ctx context.Context, token, id string) (auth.Domain, error) { defer func(begin time.Time) { ms.counter.With("method", "retrieve_domain").Add(1) diff --git a/auth/domains.go b/auth/domains.go index c4ac9a33292..1b7fabc9052 100644 --- a/auth/domains.go +++ b/auth/domains.go @@ -143,6 +143,7 @@ type Policy struct { type Domains interface { CreateDomain(ctx context.Context, token string, d Domain) (Domain, error) + DeleteDomain(ctx context.Context, token string, d Domain) error RetrieveDomain(ctx context.Context, token string, id string) (Domain, error) RetrieveDomainPermissions(ctx context.Context, token string, id string) (Permissions, error) UpdateDomain(ctx context.Context, token string, id string, d DomainReq) (Domain, error) @@ -172,7 +173,7 @@ type DomainsRepository interface { // Update updates the client name and metadata. Update(ctx context.Context, id string, userID string, d DomainReq) (Domain, error) - // Delete + // Delete delete domain Delete(ctx context.Context, id string) error // SavePolicies save policies in domains database diff --git a/auth/mocks/domains.go b/auth/mocks/domains.go index 42a8c73f6c5..55d45e5d772 100644 --- a/auth/mocks/domains.go +++ b/auth/mocks/domains.go @@ -162,6 +162,10 @@ func (_m *DomainsRepository) RetrieveByID(ctx context.Context, id string) (auth. return r0, r1 } +func (_m *DomainsRepository) RetrieveStatusByID(ctx context.Context, id string) (auth.Domain, error) { + return auth.Domain{}, nil +} + // RetrievePermissions provides a mock function with given fields: ctx, subject, id func (_m *DomainsRepository) RetrievePermissions(ctx context.Context, subject string, id string) ([]string, error) { ret := _m.Called(ctx, subject, id) diff --git a/auth/mocks/service.go b/auth/mocks/service.go index daa26c9b283..71b4869ae6a 100644 --- a/auth/mocks/service.go +++ b/auth/mocks/service.go @@ -201,6 +201,23 @@ func (_m *Service) CreateDomain(ctx context.Context, token string, d auth.Domain return r0, r1 } +// DeleteDomain provides a mock function with given fields: ctx, token, d +func (_m *Service) DeleteDomain(ctx context.Context, token string, d auth.Domain) error { + ret := _m.Called(ctx, token, d) + + if len(ret) == 0 { + panic("no return value specified for DeleteDomain") + } + + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, auth.Domain) error); ok { + r1 = rf(ctx, token, d) + } else { + r1 = ret.Error(0) + } + return r1 +} + // DeletePolicies provides a mock function with given fields: ctx, prs func (_m *Service) DeletePolicies(ctx context.Context, prs []auth.PolicyReq) error { ret := _m.Called(ctx, prs) @@ -519,6 +536,11 @@ func (_m *Service) RetrieveDomain(ctx context.Context, token string, id string) return r0, r1 } +// RetrieveStatusByID retrieves Domain Status by its unique ID. +func (_m *Service) RetrieveStatus(ctx context.Context, token string, id string) (auth.Domain, error) { + return auth.Domain{}, nil +} + // RetrieveDomainPermissions provides a mock function with given fields: ctx, token, id func (_m *Service) RetrieveDomainPermissions(ctx context.Context, token string, id string) (auth.Permissions, error) { ret := _m.Called(ctx, token, id) diff --git a/auth/postgres/domains.go b/auth/postgres/domains.go index 6144c3712b7..a564e4ccd89 100644 --- a/auth/postgres/domains.go +++ b/auth/postgres/domains.go @@ -96,6 +96,29 @@ func (repo domainRepo) RetrieveByID(ctx context.Context, id string) (auth.Domain return auth.Domain{}, repoerr.ErrNotFound } +func (repo domainRepo) RetrieveStatusByID(ctx context.Context, id string) (auth.Domain, error) { + q := `SELECT d.status FROM domains d where d.id = :id;` + dbdp := dbDomainsPage{ + ID: id, + } + + rows, err := repo.db.NamedQueryContext(ctx, q, dbdp) + if err != nil { + return auth.Domain{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + defer rows.Close() + + var status auth.Status = auth.FreezeStatus + if rows.Next() { + if err = rows.Scan(&status); err != nil { + return auth.Domain{}, postgres.HandleError(repoerr.ErrViewEntity, err) + } + var domain auth.Domain = auth.Domain{Status: status} + return domain, nil + } + return auth.Domain{}, repoerr.ErrNotFound +} + func (repo domainRepo) RetrievePermissions(ctx context.Context, subject, id string) ([]string, error) { q := `SELECT pc.relation as relation FROM domains as d diff --git a/auth/service.go b/auth/service.go index 83b34c6f1eb..16e0b8a503f 100644 --- a/auth/service.go +++ b/auth/service.go @@ -559,6 +559,27 @@ func (svc service) CreateDomain(ctx context.Context, token string, d Domain) (do return dom, nil } +func (svc service) DeleteDomain(ctx context.Context, token string, d Domain) error { + key, err := svc.Identify(ctx, token) + if err != nil { + return errors.Wrap(svcerr.ErrAuthentication, err) + } + if err := svc.Authorize(ctx, PolicyReq{ + Subject: key.Subject, + SubjectType: UserType, + SubjectKind: UsersKind, + ObjectType: DomainType, + Permission: AdminPermission, + Object: d.ID, + }); err != nil { + return errors.Wrap(svcerr.ErrAuthorization, err) + } + if err := svc.domains.Delete(ctx, d.ID); err != nil { + return errors.Wrap(svcerr.ErrFailedDelete, err) + } + return nil +} + func (svc service) RetrieveDomain(ctx context.Context, token, id string) (Domain, error) { if err := svc.Authorize(ctx, PolicyReq{ Subject: token, diff --git a/auth/service_test.go b/auth/service_test.go index ba46445a833..184d2f41749 100644 --- a/auth/service_test.go +++ b/auth/service_test.go @@ -1737,6 +1737,56 @@ func TestCreateDomain(t *testing.T) { } } +func TestDeleteDomain(t *testing.T) { + svc, accessToken := newService() + + cases := []struct { + desc string + d auth.Domain + token string + err error + retrieveByIDResponse auth.Domain + retreiveByIDErr error + checkPolicyErr error + }{ + { + desc: "delete domain successfully", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: accessToken, + err: nil, + }, + { + desc: "delete domain with invalid token", + d: auth.Domain{ + Status: auth.EnabledStatus, + }, + token: inValidToken, + err: svcerr.ErrAuthentication, + }, + { + desc: "delete domain with invalid status", + d: auth.Domain{ + Status: auth.AllStatus, + }, + token: accessToken, + err: svcerr.ErrInvalidStatus, + }, + } + + for _, tc := range cases { + repoCall := drepo.On("Delete", mock.Anything, tc.d.ID).Return(tc.err) + repoCall2 := drepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(tc.retrieveByIDResponse, tc.retreiveByIDErr) + repoCall3 := prepo.On("CheckPolicy", mock.Anything, mock.Anything).Return(tc.checkPolicyErr) + err := svc.DeleteDomain(context.Background(), tc.token, tc.d) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall2.Unset() + repoCall3.Unset() + } +} + func TestRetrieveDomain(t *testing.T) { svc, accessToken := newService() diff --git a/auth/tracing/tracing.go b/auth/tracing/tracing.go index 73702f56ed8..46a49c5a129 100644 --- a/auth/tracing/tracing.go +++ b/auth/tracing/tracing.go @@ -239,6 +239,14 @@ func (tm *tracingMiddleware) CreateDomain(ctx context.Context, token string, d a return tm.svc.CreateDomain(ctx, token, d) } +func (tm *tracingMiddleware) DeleteDomain(ctx context.Context, token string, d auth.Domain) error { + ctx, span := tm.tracer.Start(ctx, "delete_domain", trace.WithAttributes( + attribute.String("name", d.Name), + )) + defer span.End() + return tm.svc.DeleteDomain(ctx, token, d) +} + func (tm *tracingMiddleware) RetrieveDomain(ctx context.Context, token, id string) (auth.Domain, error) { ctx, span := tm.tracer.Start(ctx, "view_domain", trace.WithAttributes( attribute.String("id", id), diff --git a/internal/apiutil/errors.go b/internal/apiutil/errors.go index dabe5881d2f..1aaa4a47e3a 100644 --- a/internal/apiutil/errors.go +++ b/internal/apiutil/errors.go @@ -36,6 +36,9 @@ var ( // ErrInvalidStatus indicates an invalid user account status. ErrInvalidStatus = errors.New("invalid user account status") + // ErrInvalidDomainStatus indicates an invalid domain status. + ErrInvalidDomainStatus = errors.New("invalid domain status") + // ErrInvalidRole indicates that an invalid role. ErrInvalidRole = errors.New("invalid client role") diff --git a/pkg/errors/service/types.go b/pkg/errors/service/types.go index e7d04936dd8..05ca112e95c 100644 --- a/pkg/errors/service/types.go +++ b/pkg/errors/service/types.go @@ -69,4 +69,7 @@ var ( // ErrFailedUpdateRole indicates a failure to update user role. ErrFailedUpdateRole = errors.New("failed to update user role") + + // ErrFailedDelete indicates a failure to delete domain. + ErrFailedDelete = errors.New("failed to delete domain") )