mirror of
https://github.com/SigNoz/signoz.git
synced 2026-11-08 09:21:01 +00:00
Compare commits
4 Commits
nv/exec-st
...
keystone-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c6fe9bed8 | ||
|
|
dd0a9ebfc3 | ||
|
|
9fefa7266e | ||
|
|
7a632b64f7 |
@@ -13789,6 +13789,12 @@ paths:
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
@@ -17951,6 +17957,18 @@ paths:
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
@@ -17963,6 +17981,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key: []
|
||||
summary: Gets my service account
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -17979,6 +17999,18 @@ paths:
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
@@ -17991,6 +18023,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key: []
|
||||
summary: Updates my service account
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -24694,12 +24728,6 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
@@ -24781,12 +24809,6 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
@@ -30291,6 +30313,12 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
|
||||
@@ -142,7 +142,7 @@ The pieces:
|
||||
|
||||
For routes that link two resources, use `AttachDetachSiblingResourceDef` (both sides are authz-checked, e.g. attaching a role to a service account requires `attach` on **both** the service account and the role). When a side's ids come from a list extractor (`BodyJSONArray` or a custom `ResourceIDsExtractor`) and that list resolves to nothing at request time, there is nothing to link: the def resolves to no resources, so no check runs and no audit event is emitted (e.g. inviting a user with an empty `userRoles`). A single-id side (`OneID`) always resolves to exactly one id and fails closed when it is empty. For parent-child routes (e.g. creating an API key under a service account), both sides are checked too, but with different verbs: declare a `BasicResourceDef` checking the child with `create`/`delete`, alongside an `AttachDetachParentChildResourceDef` checking the parent with `attach`/`detach` (within that def the child is only recorded for audit) — see the `/api/v1/service_accounts/{id}/keys` route in [pkg/apiserver/signozapiserver/serviceaccount.go](/pkg/apiserver/signozapiserver/serviceaccount.go).
|
||||
|
||||
Prefer `CheckResources` with a `ResourceDef` for anything resource-shaped. The older coarse gates `ViewAccess`/`EditAccess`/`AdminAccess` only check "does the caller hold one of these roles" and give up per-resource granularity; `OpenAccess` performs no authorization (authentication still applies); `CheckWithoutClaims` serves anonymous routes such as public dashboards.
|
||||
Prefer `CheckResources` with a `ResourceDef` for anything resource-shaped. The older coarse gates `ViewAccess`/`EditAccess`/`AdminAccess` only check "does the caller hold one of these roles" and give up per-resource granularity; `CheckWithoutClaims` serves anonymous routes such as public dashboards.
|
||||
|
||||
### 5. Backfill existing organizations (only if needed)
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ Each route wraps a module handler method with the following:
|
||||
- A generic HTTP `handler.Handler` (from `pkg/http/handler`)
|
||||
- An `OpenAPIDef` that describes the operation for OpenAPI generation
|
||||
|
||||
Authentication is enforced for every route registered through `handler.New` by the router-level `AuthN` middleware; a route only needs to say something about it when it must be reachable without credentials, by passing `handler.AllowAnonymous()` to `handler.New`.
|
||||
|
||||
For example, in `pkg/apiserver/signozapiserver`:
|
||||
|
||||
```go
|
||||
@@ -183,7 +185,7 @@ The `handler.New` function ties the HTTP handler to OpenAPI metadata via `OpenAP
|
||||
|
||||
The generic handler:
|
||||
|
||||
- Automatically appends `401`, `403`, and `500` to `ErrorStatusCodes` when appropriate.
|
||||
- Automatically appends `500` to `ErrorStatusCodes`, `401` unless the route is public, and `403` when `SecuritySchemes` is set.
|
||||
- Registers request and response schemas with the OpenAPI reflector so they appear in `docs/api/openapi.yml`.
|
||||
|
||||
See existing examples in:
|
||||
|
||||
@@ -65,10 +65,10 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
// note: add ee override methods first
|
||||
|
||||
// routes available only in ee version
|
||||
router.HandleFunc("/api/v1/features", am.OpenAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/features", ah.getFeatureFlags).Methods(http.MethodGet)
|
||||
|
||||
// base overrides
|
||||
router.HandleFunc("/api/v1/version", am.OpenAccess(ah.getVersion)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/version", ah.getVersion).Methods(http.MethodGet)
|
||||
|
||||
// v4
|
||||
router.HandleFunc("/api/v4/query_range", am.ViewAccess(ah.queryRangeV4)).Methods(http.MethodPost)
|
||||
|
||||
@@ -208,7 +208,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/services", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.cloudIntegrationHandler.ListServicesMetadata),
|
||||
provider.cloudIntegrationHandler.ListServicesMetadata,
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListServicesMetadata",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -255,7 +255,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/services/{service_id}", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.cloudIntegrationHandler.GetService),
|
||||
provider.cloudIntegrationHandler.GetService,
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetService",
|
||||
Tags: []string{"cloudintegration"},
|
||||
|
||||
@@ -657,7 +657,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newAnonymousSecuritySchemes([]string{coretypes.ResourceMetaResourcePublicDashboard.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -686,7 +686,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newAnonymousSecuritySchemes([]string{coretypes.ResourceMetaResourcePublicDashboard.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addFlaggerRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/features", handler.New(provider.authzMiddleware.OpenAccess(provider.flaggerHandler.GetFeatures), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/features", handler.New(provider.flaggerHandler.GetFeatures, handler.OpenAPIDef{
|
||||
ID: "GetFeatures",
|
||||
Tags: []string{"features"},
|
||||
Summary: "Get features",
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addGlobalRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/global/config", handler.New(provider.authzMiddleware.OpenAccess(provider.globalHandler.GetConfig), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/global/config", handler.New(provider.globalHandler.GetConfig, handler.OpenAPIDef{
|
||||
ID: "GetGlobalConfig",
|
||||
Tags: []string{"global"},
|
||||
Summary: "Get global config",
|
||||
@@ -22,7 +22,7 @@ func (provider *provider) addGlobalRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: nil,
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ func (provider *provider) addLicensingRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v4/licenses/active", handler.New(provider.authzMiddleware.OpenAccess(provider.licensingHandler.GetActive), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v4/licenses/active", handler.New(provider.licensingHandler.GetActive, handler.OpenAPIDef{
|
||||
ID: "GetActiveLicense",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "Get the active license.",
|
||||
|
||||
@@ -70,6 +70,10 @@ func (h *prometheusOpenAPIHandler) ServeOpenAPI(opCtx openapi.OperationContext)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *prometheusOpenAPIHandler) AllowsAnonymous() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *prometheusOpenAPIHandler) ResourceDefs() []handler.ResourceDef {
|
||||
return []handler.ResourceDef{handler.TelemetryResourceDef{
|
||||
Verb: coretypes.VerbRead,
|
||||
|
||||
@@ -324,6 +324,7 @@ func newProvider(
|
||||
router.Use(middleware.NewRecovery(settings.Logger()).Wrap)
|
||||
router.Use(middleware.NewOtel("apiserver", providerSettings.MeterProvider, providerSettings.TracerProvider).Wrap)
|
||||
router.Use(middleware.NewIdentN(identNResolver, sharder, settings.Logger()).Wrap)
|
||||
router.Use(middleware.NewAuthN(settings.Logger()).Wrap)
|
||||
router.Use(middleware.NewTimeout(settings.Logger(),
|
||||
config.Timeout.ExcludedRoutes,
|
||||
config.Timeout.Default,
|
||||
|
||||
@@ -55,9 +55,13 @@ func (handler *healthOpenAPIHandler) ResourceDefs() []pkghandler.ResourceDef {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *healthOpenAPIHandler) AllowsAnonymous() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *provider) addRegistryRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/healthz", newHealthOpenAPIHandler(
|
||||
provider.authzMiddleware.OpenAccess(provider.factoryHandler.Healthz),
|
||||
provider.factoryHandler.Healthz,
|
||||
"Healthz",
|
||||
"Health check",
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
@@ -65,14 +69,14 @@ func (provider *provider) addRegistryRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/readyz", newHealthOpenAPIHandler(
|
||||
provider.authzMiddleware.OpenAccess(provider.factoryHandler.Readyz),
|
||||
provider.factoryHandler.Readyz,
|
||||
"Readyz",
|
||||
"Readiness check",
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/livez", pkghandler.New(provider.authzMiddleware.OpenAccess(provider.factoryHandler.Livez),
|
||||
if err := router.Handle("/api/v2/livez", pkghandler.New(provider.factoryHandler.Livez,
|
||||
pkghandler.OpenAPIDef{
|
||||
ID: "Livez",
|
||||
Tags: []string{"health"},
|
||||
@@ -81,6 +85,7 @@ func (provider *provider) addRegistryRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
},
|
||||
pkghandler.AllowAnonymous(),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.authzMiddleware.OpenAccess(provider.serviceAccountHandler.GetMe), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.serviceAccountHandler.GetMe, handler.OpenAPIDef{
|
||||
ID: "GetMyServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Gets my service account",
|
||||
@@ -79,7 +79,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: nil,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderAPIKey.StringValue()}},
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.authzMiddleware.OpenAccess(provider.serviceAccountHandler.UpdateMe), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.serviceAccountHandler.UpdateMe, handler.OpenAPIDef{
|
||||
ID: "UpdateMyServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates my service account",
|
||||
@@ -150,7 +150,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: nil,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderAPIKey.StringValue()}},
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/sessions/email_password", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionByEmailPassword), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/sessions/email_password", handler.New(provider.sessionHandler.CreateSessionByEmailPassword, handler.OpenAPIDef{
|
||||
ID: "CreateSessionByEmailPassword",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by email and password",
|
||||
@@ -22,11 +22,11 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions/context", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.GetSessionContext), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/sessions/context", handler.New(provider.sessionHandler.GetSessionContext, handler.OpenAPIDef{
|
||||
ID: "GetSessionContext",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Get session context",
|
||||
@@ -39,11 +39,11 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions/rotate", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.RotateSession), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/sessions/rotate", handler.New(provider.sessionHandler.RotateSession, handler.OpenAPIDef{
|
||||
ID: "RotateSession",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Rotate session",
|
||||
@@ -53,14 +53,14 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
Response: new(authtypes.GettableToken),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.DeleteSession), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/sessions", handler.New(provider.sessionHandler.DeleteSession, handler.OpenAPIDef{
|
||||
ID: "DeleteSession",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Delete session",
|
||||
@@ -70,14 +70,14 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderTokenizer.StringValue()}},
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/complete/google", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionByGoogleCallback), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/complete/google", handler.New(provider.sessionHandler.CreateSessionByGoogleCallback, handler.OpenAPIDef{
|
||||
ID: "CreateSessionByGoogleCallback",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by google callback",
|
||||
@@ -90,11 +90,11 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/complete/saml", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionBySAMLCallback), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/complete/saml", handler.New(provider.sessionHandler.CreateSessionBySAMLCallback, handler.OpenAPIDef{
|
||||
ID: "CreateSessionBySAMLCallback",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by saml callback",
|
||||
@@ -110,11 +110,11 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/complete/oidc", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionByOIDCCallback), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/complete/oidc", handler.New(provider.sessionHandler.CreateSessionByOIDCCallback, handler.OpenAPIDef{
|
||||
ID: "CreateSessionByOIDCCallback",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by oidc callback",
|
||||
@@ -127,7 +127,7 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
func (provider *provider) addStatsReporterRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/stats", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.statsHandler.Get),
|
||||
provider.statsHandler.Get,
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetStats",
|
||||
Tags: []string{"stats"},
|
||||
|
||||
@@ -38,7 +38,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user/me", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.GetMyUserDeprecated), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user/me", handler.New(provider.userHandler.GetMyUserDeprecated, handler.OpenAPIDef{
|
||||
ID: "GetMyUserDeprecated",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get my user",
|
||||
@@ -55,7 +55,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.GetMyUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.userHandler.GetMyUser, handler.OpenAPIDef{
|
||||
ID: "GetMyUser",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get my user v2",
|
||||
@@ -115,7 +115,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.UpdateMyUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.userHandler.UpdateMyUser, handler.OpenAPIDef{
|
||||
ID: "UpdateMyUserV2",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update my user v2",
|
||||
@@ -281,7 +281,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/reset_password_tokens/verify", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.VerifyResetPasswordToken), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/reset_password_tokens/verify", handler.New(provider.userHandler.VerifyResetPasswordToken, handler.OpenAPIDef{
|
||||
ID: "VerifyResetPasswordToken",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Verify a reset password token",
|
||||
@@ -294,11 +294,11 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me/factor_password", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ChangePassword), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/me/factor_password", handler.New(provider.userHandler.ChangePassword, handler.OpenAPIDef{
|
||||
ID: "UpdateMyPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Updates my password",
|
||||
@@ -315,7 +315,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/factor_password/forgot", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ForgotPassword), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/factor_password/forgot", handler.New(provider.userHandler.ForgotPassword, handler.OpenAPIDef{
|
||||
ID: "ForgotPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Forgot password",
|
||||
@@ -328,11 +328,11 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnprocessableEntity},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/factor_password/reset", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ResetPassword), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/factor_password/reset", handler.New(provider.userHandler.ResetPassword, handler.OpenAPIDef{
|
||||
ID: "ResetPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Reset password",
|
||||
@@ -345,7 +345,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
}, handler.AllowAnonymous())).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -16,28 +16,17 @@ type Handler interface {
|
||||
http.Handler
|
||||
ServeOpenAPI(openapi.OperationContext)
|
||||
ResourceDefs() []ResourceDef
|
||||
AllowsAnonymous() bool
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
handlerFunc http.HandlerFunc
|
||||
openAPIDef OpenAPIDef
|
||||
resourceDefs []ResourceDef
|
||||
anonymous bool
|
||||
}
|
||||
|
||||
func New(handlerFunc http.HandlerFunc, openAPIDef OpenAPIDef, opts ...Option) Handler {
|
||||
// Remove duplicate error status codes
|
||||
openAPIDef.ErrorStatusCodes = slices.DeleteFunc(openAPIDef.ErrorStatusCodes, func(statusCode int) bool {
|
||||
return statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden || statusCode == http.StatusInternalServerError
|
||||
})
|
||||
|
||||
// Add internal server error
|
||||
openAPIDef.ErrorStatusCodes = append(openAPIDef.ErrorStatusCodes, http.StatusInternalServerError)
|
||||
|
||||
// Add unauthorized and forbidden status codes
|
||||
if len(openAPIDef.SecuritySchemes) > 0 {
|
||||
openAPIDef.ErrorStatusCodes = append(openAPIDef.ErrorStatusCodes, http.StatusUnauthorized, http.StatusForbidden)
|
||||
}
|
||||
|
||||
handler := &handler{
|
||||
handlerFunc: handlerFunc,
|
||||
openAPIDef: openAPIDef,
|
||||
@@ -47,6 +36,22 @@ func New(handlerFunc http.HandlerFunc, openAPIDef OpenAPIDef, opts ...Option) Ha
|
||||
opt(handler)
|
||||
}
|
||||
|
||||
// Add internal server error, unauthorized and forbidden status codes
|
||||
statusCodes := []int{http.StatusInternalServerError}
|
||||
if !handler.anonymous {
|
||||
statusCodes = append(statusCodes, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
if len(handler.openAPIDef.SecuritySchemes) > 0 {
|
||||
statusCodes = append(statusCodes, http.StatusForbidden)
|
||||
}
|
||||
|
||||
for _, statusCode := range statusCodes {
|
||||
if !slices.Contains(handler.openAPIDef.ErrorStatusCodes, statusCode) {
|
||||
handler.openAPIDef.ErrorStatusCodes = append(handler.openAPIDef.ErrorStatusCodes, statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
@@ -135,3 +140,7 @@ func (handler *handler) ServeOpenAPI(opCtx openapi.OperationContext) {
|
||||
func (handler *handler) ResourceDefs() []ResourceDef {
|
||||
return handler.resourceDefs
|
||||
}
|
||||
|
||||
func (handler *handler) AllowsAnonymous() bool {
|
||||
return handler.anonymous
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ func (bespokeOpenAPIHandler) ServeOpenAPI(opCtx openapi.OperationContext) {
|
||||
|
||||
func (bespokeOpenAPIHandler) ResourceDefs() []ResourceDef { return nil }
|
||||
|
||||
func (bespokeOpenAPIHandler) AllowsAnonymous() bool { return false }
|
||||
|
||||
func TestAttachStabilities(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/development", New(func(http.ResponseWriter, *http.Request) {}, OpenAPIDef{ID: "Development", SuccessStatusCode: http.StatusOK, Stability: StabilityDevelopment})).Methods(http.MethodGet)
|
||||
@@ -73,3 +75,26 @@ func TestAttachStabilities(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewErrorStatusCodes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
def OpenAPIDef
|
||||
opts []Option
|
||||
expected []int
|
||||
}{
|
||||
{name: "Protected_NoSecuritySchemes_AddsUnauthorized", def: OpenAPIDef{}, expected: []int{http.StatusInternalServerError, http.StatusUnauthorized}},
|
||||
{name: "Protected_SecuritySchemes_AddsUnauthorizedAndForbidden", def: OpenAPIDef{SecuritySchemes: []OpenAPISecurityScheme{{Name: "tokenizer"}}}, expected: []int{http.StatusInternalServerError, http.StatusUnauthorized, http.StatusForbidden}},
|
||||
{name: "Protected_DeclaredCodes_NoDuplicates", def: OpenAPIDef{ErrorStatusCodes: []int{http.StatusUnauthorized, http.StatusInternalServerError}}, expected: []int{http.StatusUnauthorized, http.StatusInternalServerError}},
|
||||
{name: "Anonymous_NoUnauthorized", def: OpenAPIDef{ErrorStatusCodes: []int{http.StatusBadRequest}}, opts: []Option{AllowAnonymous()}, expected: []int{http.StatusBadRequest, http.StatusInternalServerError}},
|
||||
{name: "Anonymous_DeclaredUnauthorized_Kept", def: OpenAPIDef{ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized}}, opts: []Option{AllowAnonymous()}, expected: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError}},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
built := New(func(http.ResponseWriter, *http.Request) {}, testCase.def, testCase.opts...).(*handler)
|
||||
assert.Equal(t, testCase.expected, built.openAPIDef.ErrorStatusCodes)
|
||||
assert.Equal(t, len(testCase.opts) > 0, built.AllowsAnonymous())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,3 +7,9 @@ func WithResourceDefs(defs ...ResourceDef) Option {
|
||||
h.resourceDefs = append(h.resourceDefs, defs...)
|
||||
}
|
||||
}
|
||||
|
||||
func AllowAnonymous() Option {
|
||||
return func(h *handler) {
|
||||
h.anonymous = true
|
||||
}
|
||||
}
|
||||
|
||||
59
pkg/http/middleware/authn.go
Normal file
59
pkg/http/middleware/authn.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
const (
|
||||
authnDeniedMessage string = "::AUTHN-DENIED::"
|
||||
)
|
||||
|
||||
type AuthN struct {
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewAuthN(logger *slog.Logger) *AuthN {
|
||||
if logger == nil {
|
||||
panic("cannot build authn middleware, logger is empty")
|
||||
}
|
||||
|
||||
return &AuthN{logger: logger.With(slog.String("pkg", pkgname))}
|
||||
}
|
||||
|
||||
func (middleware *AuthN) Wrap(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if allowsAnonymous(req) {
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := authtypes.ClaimsFromContext(req.Context()); err != nil {
|
||||
middleware.logger.WarnContext(req.Context(), authnDeniedMessage, slog.String("path", req.URL.Path), slog.String("method", req.Method))
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func allowsAnonymous(req *http.Request) bool {
|
||||
route := mux.CurrentRoute(req)
|
||||
if route == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
registered, ok := route.GetHandler().(handler.Handler)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return registered.AllowsAnonymous()
|
||||
}
|
||||
70
pkg/http/middleware/authn_test.go
Normal file
70
pkg/http/middleware/authn_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func TestAuthN(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok := func(rw http.ResponseWriter, _ *http.Request) { rw.WriteHeader(http.StatusNoContent) }
|
||||
|
||||
newRouter := func(withClaims bool) *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
if withClaims {
|
||||
router.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
claims := authtypes.Claims{OrgID: valuer.GenerateUUID().StringValue(), UserID: valuer.GenerateUUID().StringValue()}
|
||||
next.ServeHTTP(rw, req.WithContext(authtypes.NewContextWithClaims(req.Context(), claims)))
|
||||
})
|
||||
})
|
||||
}
|
||||
router.Use(NewAuthN(slog.New(slog.DiscardHandler)).Wrap)
|
||||
|
||||
router.Handle("/anonymous", handler.New(ok, handler.OpenAPIDef{ID: "Anonymous", SuccessStatusCode: http.StatusNoContent}, handler.AllowAnonymous())).Methods(http.MethodGet)
|
||||
router.Handle("/protected", handler.New(ok, handler.OpenAPIDef{ID: "Protected", SuccessStatusCode: http.StatusNoContent})).Methods(http.MethodGet)
|
||||
router.HandleFunc("/raw", ok).Methods(http.MethodGet)
|
||||
subrouter := router.PathPrefix("/api/v3").Subrouter()
|
||||
subrouter.Handle("/protected-sub", handler.New(ok, handler.OpenAPIDef{ID: "ProtectedSub", SuccessStatusCode: http.StatusNoContent})).Methods(http.MethodGet)
|
||||
subrouter.HandleFunc("/raw-sub", ok).Methods(http.MethodGet)
|
||||
router.PathPrefix("/").Handler(http.HandlerFunc(ok))
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
withClaims bool
|
||||
status int
|
||||
}{
|
||||
{name: "AnonymousHandlerWithoutClaims", path: "/anonymous", withClaims: false, status: http.StatusNoContent},
|
||||
{name: "RawCatchAllWithoutClaims", path: "/index.html", withClaims: false, status: http.StatusNoContent},
|
||||
{name: "RawWithoutClaims", path: "/raw", withClaims: false, status: http.StatusNoContent},
|
||||
{name: "RawSubrouterWithoutClaims", path: "/api/v3/raw-sub", withClaims: false, status: http.StatusNoContent},
|
||||
{name: "ProtectedHandlerWithoutClaims", path: "/protected", withClaims: false, status: http.StatusUnauthorized},
|
||||
{name: "ProtectedSubrouterWithoutClaims", path: "/api/v3/protected-sub", withClaims: false, status: http.StatusUnauthorized},
|
||||
{name: "ProtectedHandlerWithClaims", path: "/protected", withClaims: true, status: http.StatusNoContent},
|
||||
{name: "ProtectedSubrouterWithClaims", path: "/api/v3/protected-sub", withClaims: true, status: http.StatusNoContent},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
newRouter(testCase.withClaims).ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, testCase.path, nil))
|
||||
assert.Equal(t, testCase.status, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -150,12 +150,6 @@ func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// CheckResources authorizes every resolved resource for the route. roles are the
|
||||
// allowed role names (the OSS role-gate); the resource selectors drive the EE check.
|
||||
func (middleware *AuthZ) CheckResources(next http.HandlerFunc, roles ...string) http.HandlerFunc {
|
||||
|
||||
@@ -401,9 +401,9 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v2/traces/fields", am.ViewAccess(aH.traceFields)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v2/traces/fields", am.EditAccess(aH.updateTraceField)).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/api/v1/version", am.OpenAccess(aH.getVersion)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/features", am.OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/health", am.OpenAccess(aH.getHealth)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/version", aH.getVersion).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/features", aH.getFeatureFlags).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/health", aH.getHealth).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/listErrors", am.ViewAccess(aH.listErrors)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/countErrors", am.ViewAccess(aH.countErrors)).Methods(http.MethodPost)
|
||||
@@ -418,7 +418,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v1/orgs/me/filters/{signal}", am.ViewAccess(aH.Signoz.Handlers.QuickFilter.GetSourceFilters)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/orgs/me/filters", am.AdminAccess(aH.Signoz.Handlers.QuickFilter.UpdateQuickFilters)).Methods(http.MethodPut)
|
||||
|
||||
router.HandleFunc("/api/v1/register", am.OpenAccess(aH.registerUser)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/register", aH.registerUser).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/api/v1/span_percentile", am.ViewAccess(aH.Signoz.Handlers.SpanPercentile.GetSpanPercentileDetails)).Methods(http.MethodPost)
|
||||
|
||||
|
||||
@@ -59,14 +59,14 @@ var ManagedRoleToTransactions = map[string][]Transaction{
|
||||
{Verb: VerbDelete, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindFactorAPIKey}, WildCardSelectorString)},
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindFactorAPIKey}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindFactorAPIKey}, WildCardSelectorString)},
|
||||
// factor-password — admin can issue and inspect reset tokens; users change their own password via OpenAccess
|
||||
// factor-password — admin can issue and inspect reset tokens; users change their own password through /api/v2/users/me/factor_password, which has no role check
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindFactorPassword}, WildCardSelectorString)},
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindFactorPassword}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindFactorPassword}, WildCardSelectorString)},
|
||||
// license — admin only.
|
||||
// Uniform LCRUD shape served by /api/v4/licenses: create = Activate,
|
||||
// update = Refresh, read = Get (includes the key), list, delete (non-cloud
|
||||
// licenses only). GET /api/v4/licenses/active is OpenAccess, so the read
|
||||
// licenses only). GET /api/v4/licenses/active has no role check, so the read
|
||||
// grant is not enforced there.
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindLicense}, WildCardSelectorString)},
|
||||
{Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindLicense}, WildCardSelectorString)},
|
||||
|
||||
41
tests/integration/tests/passwordauthn/10_authn.py
Normal file
41
tests/integration/tests/passwordauthn/10_authn.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
"/api/v2/users/me",
|
||||
"/api/v1/stats",
|
||||
"/api/v1/features",
|
||||
"/api/v2/features",
|
||||
"/api/v4/licenses/active",
|
||||
"/api/v2/dashboards",
|
||||
],
|
||||
)
|
||||
def test_protected_routes_reject_missing_credentials(signoz: types.SigNoz, path: str) -> None:
|
||||
response = requests.get(signoz.self.host_configs["8080"].get(path), timeout=5)
|
||||
|
||||
assert response.status_code == HTTPStatus.UNAUTHORIZED, response.text
|
||||
assert response.json()["error"]["code"] == "unauthenticated"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
"/api/v1/version",
|
||||
"/api/v1/health",
|
||||
"/api/v2/healthz",
|
||||
"/api/v2/readyz",
|
||||
"/api/v2/livez",
|
||||
"/api/v1/global/config",
|
||||
],
|
||||
)
|
||||
def test_public_routes_serve_missing_credentials(signoz: types.SigNoz, path: str) -> None:
|
||||
response = requests.get(signoz.self.host_configs["8080"].get(path), timeout=5)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
Reference in New Issue
Block a user