mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-08 19:40:30 +01:00
Compare commits
18 Commits
issue_4522
...
platform-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f95a8ec4f6 | ||
|
|
ddf81dff1f | ||
|
|
b0f1b2b192 | ||
|
|
9f743cfba8 | ||
|
|
504f6a4d04 | ||
|
|
9e118ded1f | ||
|
|
89d394145d | ||
|
|
e99ac3dd76 | ||
|
|
7a94ee4986 | ||
|
|
5f10007c3e | ||
|
|
7e4b4a73e0 | ||
|
|
9cd7ccbde0 | ||
|
|
ad1521bfe8 | ||
|
|
423c924314 | ||
|
|
bb0aa813c0 | ||
|
|
cf6d34e2df | ||
|
|
b27de15827 | ||
|
|
953cb71a43 |
@@ -66,9 +66,10 @@ func runGenerateAuthz(_ context.Context) error {
|
||||
registry := coretypes.NewRegistry()
|
||||
|
||||
allowedResources := map[string]bool{
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourcesRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourcesServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourcesRole).String(): true,
|
||||
}
|
||||
|
||||
allowedTypes := map[string]bool{}
|
||||
|
||||
@@ -448,6 +448,7 @@ components:
|
||||
- delete
|
||||
- list
|
||||
- assignee
|
||||
- attach
|
||||
type: string
|
||||
AuthtypesRole:
|
||||
properties:
|
||||
@@ -4463,19 +4464,20 @@ components:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesCumulativeWindow'
|
||||
type: object
|
||||
RuletypesEvaluationEnvelope:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationRolling'
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationCumulative'
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
spec: {}
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesEvaluationEnvelope:
|
||||
discriminator:
|
||||
mapping:
|
||||
cumulative: '#/components/schemas/RuletypesEvaluationCumulative'
|
||||
rolling: '#/components/schemas/RuletypesEvaluationRolling'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationRolling'
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationCumulative'
|
||||
type: object
|
||||
RuletypesEvaluationKind:
|
||||
enum:
|
||||
- rolling
|
||||
@@ -4487,6 +4489,9 @@ components:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesRollingWindow'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesGettableTestRule:
|
||||
properties:
|
||||
@@ -4794,15 +4799,12 @@ components:
|
||||
- compositeQuery
|
||||
type: object
|
||||
RuletypesRuleThresholdData:
|
||||
discriminator:
|
||||
mapping:
|
||||
basic: '#/components/schemas/RuletypesThresholdBasic'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/RuletypesThresholdBasic'
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesThresholdKind'
|
||||
spec: {}
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesRuleType:
|
||||
enum:
|
||||
@@ -4844,6 +4846,9 @@ components:
|
||||
$ref: '#/components/schemas/RuletypesThresholdKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesBasicRuleThresholds'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesThresholdKind:
|
||||
enum:
|
||||
@@ -9490,9 +9495,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:list
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:list
|
||||
summary: List service accounts
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -9552,9 +9557,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:create
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:create
|
||||
summary: Create service account
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -9602,9 +9607,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:delete
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:delete
|
||||
summary: Deletes a service account
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -9659,9 +9664,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:read
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:read
|
||||
summary: Gets a service account
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -9719,9 +9724,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:update
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:update
|
||||
summary: Updates a service account
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -9773,9 +9778,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:read
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:read
|
||||
summary: List service account keys
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -9841,9 +9846,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:update
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:update
|
||||
summary: Create a service account key
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -9896,9 +9901,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:update
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:update
|
||||
summary: Revoke a service account key
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -9961,9 +9966,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:update
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:update
|
||||
summary: Updates a service account key
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -10022,9 +10027,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:read
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:read
|
||||
summary: Gets service account roles
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -10084,9 +10089,11 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:attach
|
||||
- role:attach
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:attach
|
||||
- role:attach
|
||||
summary: Create service account role
|
||||
tags:
|
||||
- serviceaccount
|
||||
@@ -10133,9 +10140,11 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- serviceaccount:attach
|
||||
- role:attach
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- serviceaccount:attach
|
||||
- role:attach
|
||||
summary: Delete service account role
|
||||
tags:
|
||||
- serviceaccount
|
||||
|
||||
@@ -327,6 +327,11 @@ function App(): JSX.Element {
|
||||
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
|
||||
beforeSend(event) {
|
||||
// Drop the event if its level is 'warning' or 'info'
|
||||
if (event.level === 'warning' || event.level === 'info') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessionReplayUrl = posthog.get_session_replay_url?.({
|
||||
withTimestamp: true,
|
||||
});
|
||||
|
||||
@@ -1839,6 +1839,7 @@ export enum AuthtypesRelationDTO {
|
||||
delete = 'delete',
|
||||
list = 'list',
|
||||
assignee = 'assignee',
|
||||
attach = 'attach',
|
||||
}
|
||||
export interface AuthtypesRoleDTO {
|
||||
/**
|
||||
@@ -6676,28 +6677,36 @@ export interface RuletypesCumulativeWindowDTO {
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
export enum RuletypesEvaluationCumulativeDTOKind {
|
||||
cumulative = 'cumulative',
|
||||
}
|
||||
export interface RuletypesEvaluationCumulativeDTO {
|
||||
kind?: RuletypesEvaluationKindDTO;
|
||||
spec?: RuletypesCumulativeWindowDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum cumulative
|
||||
*/
|
||||
kind: RuletypesEvaluationCumulativeDTOKind;
|
||||
spec: RuletypesCumulativeWindowDTO;
|
||||
}
|
||||
|
||||
export type RuletypesEvaluationEnvelopeDTO =
|
||||
| (RuletypesEvaluationRollingDTO & {
|
||||
kind: RuletypesEvaluationKindDTO;
|
||||
spec: unknown;
|
||||
})
|
||||
| (RuletypesEvaluationCumulativeDTO & {
|
||||
kind: RuletypesEvaluationKindDTO;
|
||||
spec: unknown;
|
||||
});
|
||||
| RuletypesEvaluationRollingDTO
|
||||
| RuletypesEvaluationCumulativeDTO;
|
||||
|
||||
export enum RuletypesEvaluationKindDTO {
|
||||
rolling = 'rolling',
|
||||
cumulative = 'cumulative',
|
||||
}
|
||||
export enum RuletypesEvaluationRollingDTOKind {
|
||||
rolling = 'rolling',
|
||||
}
|
||||
export interface RuletypesEvaluationRollingDTO {
|
||||
kind?: RuletypesEvaluationKindDTO;
|
||||
spec?: RuletypesRollingWindowDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum rolling
|
||||
*/
|
||||
kind: RuletypesEvaluationRollingDTOKind;
|
||||
spec: RuletypesRollingWindowDTO;
|
||||
}
|
||||
|
||||
export interface RuletypesGettableTestRuleDTO {
|
||||
@@ -7052,10 +7061,7 @@ export interface RuletypesRuleConditionDTO {
|
||||
thresholds?: RuletypesRuleThresholdDataDTO;
|
||||
}
|
||||
|
||||
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO & {
|
||||
kind: RuletypesThresholdKindDTO;
|
||||
spec: unknown;
|
||||
};
|
||||
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO;
|
||||
|
||||
export enum RuletypesRuleTypeDTO {
|
||||
threshold_rule = 'threshold_rule',
|
||||
@@ -7091,9 +7097,16 @@ export enum RuletypesSeasonalityDTO {
|
||||
daily = 'daily',
|
||||
weekly = 'weekly',
|
||||
}
|
||||
export enum RuletypesThresholdBasicDTOKind {
|
||||
basic = 'basic',
|
||||
}
|
||||
export interface RuletypesThresholdBasicDTO {
|
||||
kind?: RuletypesThresholdKindDTO;
|
||||
spec?: RuletypesBasicRuleThresholdsDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum basic
|
||||
*/
|
||||
kind: RuletypesThresholdBasicDTOKind;
|
||||
spec: RuletypesBasicRuleThresholdsDTO;
|
||||
}
|
||||
|
||||
export enum RuletypesThresholdKindDTO {
|
||||
|
||||
@@ -7,6 +7,10 @@ export default {
|
||||
kind: 'role',
|
||||
type: 'metaresources',
|
||||
},
|
||||
{
|
||||
kind: 'serviceaccount',
|
||||
type: 'metaresources',
|
||||
},
|
||||
{
|
||||
kind: 'role',
|
||||
type: 'role',
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/channels", handler.New(provider.authZ.ViewAccess(provider.alertmanagerHandler.ListChannels), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/channels", handler.New(provider.authzMiddleware.ViewAccess(provider.alertmanagerHandler.ListChannels), handler.OpenAPIDef{
|
||||
ID: "ListChannels",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "List notification channels",
|
||||
@@ -27,7 +27,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(provider.authZ.ViewAccess(provider.alertmanagerHandler.GetChannelByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.alertmanagerHandler.GetChannelByID), handler.OpenAPIDef{
|
||||
ID: "GetChannelByID",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Get notification channel by ID",
|
||||
@@ -44,7 +44,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/channels", handler.New(provider.authZ.AdminAccess(provider.alertmanagerHandler.CreateChannel), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/channels", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.CreateChannel), handler.OpenAPIDef{
|
||||
ID: "CreateChannel",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Create notification channel",
|
||||
@@ -61,7 +61,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(provider.authZ.AdminAccess(provider.alertmanagerHandler.UpdateChannelByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.UpdateChannelByID), handler.OpenAPIDef{
|
||||
ID: "UpdateChannelByID",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Update notification channel",
|
||||
@@ -78,7 +78,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(provider.authZ.AdminAccess(provider.alertmanagerHandler.DeleteChannelByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.DeleteChannelByID), handler.OpenAPIDef{
|
||||
ID: "DeleteChannelByID",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Delete notification channel",
|
||||
@@ -95,7 +95,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/channels/test", handler.New(provider.authZ.EditAccess(provider.alertmanagerHandler.TestReceiver), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/channels/test", handler.New(provider.authzMiddleware.EditAccess(provider.alertmanagerHandler.TestReceiver), handler.OpenAPIDef{
|
||||
ID: "TestChannel",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Test notification channel",
|
||||
@@ -112,7 +112,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/testChannel", handler.New(provider.authZ.EditAccess(provider.alertmanagerHandler.TestReceiver), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/testChannel", handler.New(provider.authzMiddleware.EditAccess(provider.alertmanagerHandler.TestReceiver), handler.OpenAPIDef{
|
||||
ID: "TestChannelDeprecated",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Test notification channel (deprecated)",
|
||||
@@ -129,7 +129,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(provider.authZ.ViewAccess(provider.alertmanagerHandler.GetAllRoutePolicies), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(provider.authzMiddleware.ViewAccess(provider.alertmanagerHandler.GetAllRoutePolicies), handler.OpenAPIDef{
|
||||
ID: "GetAllRoutePolicies",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "List route policies",
|
||||
@@ -146,7 +146,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authZ.ViewAccess(provider.alertmanagerHandler.GetRoutePolicyByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.alertmanagerHandler.GetRoutePolicyByID), handler.OpenAPIDef{
|
||||
ID: "GetRoutePolicyByID",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Get route policy by ID",
|
||||
@@ -163,7 +163,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(provider.authZ.AdminAccess(provider.alertmanagerHandler.CreateRoutePolicy), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.CreateRoutePolicy), handler.OpenAPIDef{
|
||||
ID: "CreateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Create route policy",
|
||||
@@ -180,7 +180,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authZ.AdminAccess(provider.alertmanagerHandler.UpdateRoutePolicy), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.UpdateRoutePolicy), handler.OpenAPIDef{
|
||||
ID: "UpdateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Update route policy",
|
||||
@@ -197,7 +197,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authZ.AdminAccess(provider.alertmanagerHandler.DeleteRoutePolicyByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.DeleteRoutePolicyByID), handler.OpenAPIDef{
|
||||
ID: "DeleteRoutePolicyByID",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Delete route policy",
|
||||
@@ -214,7 +214,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/alerts", handler.New(provider.authZ.ViewAccess(provider.alertmanagerHandler.GetAlerts), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/alerts", handler.New(provider.authzMiddleware.ViewAccess(provider.alertmanagerHandler.GetAlerts), handler.OpenAPIDef{
|
||||
ID: "GetAlerts",
|
||||
Tags: []string{"alerts"},
|
||||
Summary: "Get alerts",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/domains", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.List), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/domains", handler.New(provider.authzMiddleware.AdminAccess(provider.authDomainHandler.List), handler.OpenAPIDef{
|
||||
ID: "ListAuthDomains",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "List all auth domains",
|
||||
@@ -27,7 +27,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/domains", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.Create), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/domains", handler.New(provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Create), handler.OpenAPIDef{
|
||||
ID: "CreateAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Create auth domain",
|
||||
@@ -44,7 +44,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.Get), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Get), handler.OpenAPIDef{
|
||||
ID: "GetAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Get auth domain by ID",
|
||||
@@ -61,7 +61,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.Update), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Update), handler.OpenAPIDef{
|
||||
ID: "UpdateAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Update auth domain",
|
||||
@@ -78,7 +78,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.Delete), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Delete), handler.OpenAPIDef{
|
||||
ID: "DeleteAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Delete auth domain",
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/credentials", handler.New(
|
||||
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.GetConnectionCredentials),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.GetConnectionCredentials),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetConnectionCredentials",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -31,7 +31,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts", handler.New(
|
||||
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.CreateAccount),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.CreateAccount),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateAccount",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -51,7 +51,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts", handler.New(
|
||||
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.ListAccounts),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.ListAccounts),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListAccounts",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -71,7 +71,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.GetAccount),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.GetAccount),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetAccount",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -91,7 +91,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.UpdateAccount),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.UpdateAccount),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateAccount",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -111,7 +111,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.DisconnectAccount),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.DisconnectAccount),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DisconnectAccount",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -131,7 +131,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/services", handler.New(
|
||||
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.ListServicesMetadata),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.ListServicesMetadata),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListServicesMetadata",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -152,7 +152,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.authZ.AdminAccess(provider.cloudIntegrationHandler.GetService),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.GetService),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetService",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -173,7 +173,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}/services/{service_id}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.UpdateService),
|
||||
provider.authzMiddleware.AdminAccess(provider.cloudIntegrationHandler.UpdateService),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateService",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -195,7 +195,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
// Agent check-in endpoint is kept same as older one to maintain backward compatibility with already deployed agents.
|
||||
// In the future, this endpoint will be deprecated and a new endpoint will be introduced for consistency with above endpoints.
|
||||
if err := router.Handle("/api/v1/cloud-integrations/{cloud_provider}/agent-check-in", handler.New(
|
||||
provider.authZ.ViewAccess(provider.cloudIntegrationHandler.AgentCheckIn),
|
||||
provider.authzMiddleware.ViewAccess(provider.cloudIntegrationHandler.AgentCheckIn),
|
||||
handler.OpenAPIDef{
|
||||
ID: "AgentCheckInDeprecated",
|
||||
Tags: []string{"cloudintegration"},
|
||||
@@ -215,7 +215,7 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/check_in", handler.New(
|
||||
provider.authZ.ViewAccess(provider.cloudIntegrationHandler.AgentCheckIn),
|
||||
provider.authzMiddleware.ViewAccess(provider.cloudIntegrationHandler.AgentCheckIn),
|
||||
handler.OpenAPIDef{
|
||||
ID: "AgentCheckIn",
|
||||
Tags: []string{"cloudintegration"},
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authZ.AdminAccess(provider.dashboardHandler.CreatePublic), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authzMiddleware.AdminAccess(provider.dashboardHandler.CreatePublic), handler.OpenAPIDef{
|
||||
ID: "CreatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Create public dashboard",
|
||||
@@ -31,7 +31,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authZ.AdminAccess(provider.dashboardHandler.GetPublic), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authzMiddleware.AdminAccess(provider.dashboardHandler.GetPublic), handler.OpenAPIDef{
|
||||
ID: "GetPublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Get public dashboard",
|
||||
@@ -48,7 +48,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authZ.AdminAccess(provider.dashboardHandler.UpdatePublic), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authzMiddleware.AdminAccess(provider.dashboardHandler.UpdatePublic), handler.OpenAPIDef{
|
||||
ID: "UpdatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Update public dashboard",
|
||||
@@ -65,7 +65,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authZ.AdminAccess(provider.dashboardHandler.DeletePublic), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authzMiddleware.AdminAccess(provider.dashboardHandler.DeletePublic), handler.OpenAPIDef{
|
||||
ID: "DeletePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Delete public dashboard",
|
||||
@@ -82,7 +82,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}", handler.New(provider.authZ.CheckWithoutClaims(
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}", handler.New(provider.authzMiddleware.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicData,
|
||||
authtypes.Relation{Verb: coretypes.VerbRead},
|
||||
coretypes.ResourceMetaResourcePublicDashboard,
|
||||
@@ -110,7 +110,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}/widgets/{idx}/query_range", handler.New(provider.authZ.CheckWithoutClaims(
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}/widgets/{idx}/query_range", handler.New(provider.authzMiddleware.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicWidgetQueryRange,
|
||||
authtypes.Relation{Verb: coretypes.VerbRead},
|
||||
coretypes.ResourceMetaResourcePublicDashboard,
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addFieldsRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/fields/keys", handler.New(provider.authZ.ViewAccess(provider.fieldsHandler.GetFieldsKeys), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/fields/keys", handler.New(provider.authzMiddleware.ViewAccess(provider.fieldsHandler.GetFieldsKeys), handler.OpenAPIDef{
|
||||
ID: "GetFieldsKeys",
|
||||
Tags: []string{"fields"},
|
||||
Summary: "Get field keys",
|
||||
@@ -28,7 +28,7 @@ func (provider *provider) addFieldsRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/fields/values", handler.New(provider.authZ.ViewAccess(provider.fieldsHandler.GetFieldsValues), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/fields/values", handler.New(provider.authzMiddleware.ViewAccess(provider.fieldsHandler.GetFieldsValues), handler.OpenAPIDef{
|
||||
ID: "GetFieldsValues",
|
||||
Tags: []string{"fields"},
|
||||
Summary: "Get field values",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addFlaggerRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/features", handler.New(provider.authZ.ViewAccess(provider.flaggerHandler.GetFeatures), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/features", handler.New(provider.authzMiddleware.ViewAccess(provider.flaggerHandler.GetFeatures), handler.OpenAPIDef{
|
||||
ID: "GetFeatures",
|
||||
Tags: []string{"features"},
|
||||
Summary: "Get features",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys", handler.New(provider.authZ.EditAccess(provider.gatewayHandler.GetIngestionKeys), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.GetIngestionKeys), handler.OpenAPIDef{
|
||||
ID: "GetIngestionKeys",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Get ingestion keys for workspace",
|
||||
@@ -28,7 +28,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/search", handler.New(provider.authZ.EditAccess(provider.gatewayHandler.SearchIngestionKeys), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/search", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.SearchIngestionKeys), handler.OpenAPIDef{
|
||||
ID: "SearchIngestionKeys",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Search ingestion keys for workspace",
|
||||
@@ -46,7 +46,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys", handler.New(provider.authZ.EditAccess(provider.gatewayHandler.CreateIngestionKey), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.CreateIngestionKey), handler.OpenAPIDef{
|
||||
ID: "CreateIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Create ingestion key for workspace",
|
||||
@@ -63,7 +63,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(provider.authZ.EditAccess(provider.gatewayHandler.UpdateIngestionKey), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.UpdateIngestionKey), handler.OpenAPIDef{
|
||||
ID: "UpdateIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Update ingestion key for workspace",
|
||||
@@ -80,7 +80,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(provider.authZ.EditAccess(provider.gatewayHandler.DeleteIngestionKey), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.DeleteIngestionKey), handler.OpenAPIDef{
|
||||
ID: "DeleteIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Delete ingestion key for workspace",
|
||||
@@ -97,7 +97,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}/limits", handler.New(provider.authZ.EditAccess(provider.gatewayHandler.CreateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}/limits", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.CreateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "CreateIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Create limit for the ingestion key",
|
||||
@@ -114,7 +114,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(provider.authZ.EditAccess(provider.gatewayHandler.UpdateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.UpdateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "UpdateIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Update limit for the ingestion key",
|
||||
@@ -131,7 +131,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(provider.authZ.EditAccess(provider.gatewayHandler.DeleteIngestionKeyLimit), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.DeleteIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "DeleteIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Delete limit for the ingestion key",
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addGlobalRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/global/config", handler.New(provider.authZ.OpenAccess(provider.globalHandler.GetConfig), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/global/config", handler.New(provider.authzMiddleware.OpenAccess(provider.globalHandler.GetConfig), handler.OpenAPIDef{
|
||||
ID: "GetGlobalConfig",
|
||||
Tags: []string{"global"},
|
||||
Summary: "Get global config",
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func (provider *provider) addInfraMonitoringRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/infra_monitoring/hosts", handler.New(
|
||||
provider.authZ.ViewAccess(provider.infraMonitoringHandler.ListHosts),
|
||||
provider.authzMiddleware.ViewAccess(provider.infraMonitoringHandler.ListHosts),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListHosts",
|
||||
Tags: []string{"inframonitoring"},
|
||||
@@ -30,7 +30,7 @@ func (provider *provider) addInfraMonitoringRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/infra_monitoring/pods", handler.New(
|
||||
provider.authZ.ViewAccess(provider.infraMonitoringHandler.ListPods),
|
||||
provider.authzMiddleware.ViewAccess(provider.infraMonitoringHandler.ListPods),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListPods",
|
||||
Tags: []string{"inframonitoring"},
|
||||
@@ -49,7 +49,7 @@ func (provider *provider) addInfraMonitoringRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/infra_monitoring/nodes", handler.New(
|
||||
provider.authZ.ViewAccess(provider.infraMonitoringHandler.ListNodes),
|
||||
provider.authzMiddleware.ViewAccess(provider.infraMonitoringHandler.ListNodes),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListNodes",
|
||||
Tags: []string{"inframonitoring"},
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules", handler.New(
|
||||
provider.authZ.ViewAccess(provider.llmPricingRuleHandler.List),
|
||||
provider.authzMiddleware.ViewAccess(provider.llmPricingRuleHandler.List),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListLLMPricingRules",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
@@ -32,7 +32,7 @@ func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules", handler.New(
|
||||
provider.authZ.AdminAccess(provider.llmPricingRuleHandler.CreateOrUpdate),
|
||||
provider.authzMiddleware.AdminAccess(provider.llmPricingRuleHandler.CreateOrUpdate),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateOrUpdateLLMPricingRules",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
@@ -50,7 +50,7 @@ func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules/{id}", handler.New(
|
||||
provider.authZ.ViewAccess(provider.llmPricingRuleHandler.Get),
|
||||
provider.authzMiddleware.ViewAccess(provider.llmPricingRuleHandler.Get),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetLLMPricingRule",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
@@ -70,7 +70,7 @@ func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules/{id}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.llmPricingRuleHandler.Delete),
|
||||
provider.authzMiddleware.AdminAccess(provider.llmPricingRuleHandler.Delete),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteLLMPricingRule",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/metrics", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.ListMetrics),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.ListMetrics),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListMetrics",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -31,7 +31,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/stats", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.GetStats),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetStats),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricsStats",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -50,7 +50,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/treemap", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.GetTreemap),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetTreemap),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricsTreemap",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -69,7 +69,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/{metric_name}/attributes", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.GetMetricAttributes),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricAttributes),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricAttributes",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -89,7 +89,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/{metric_name}/metadata", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.GetMetricMetadata),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricMetadata),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricMetadata",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -108,7 +108,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/{metric_name}/metadata", handler.New(
|
||||
provider.authZ.EditAccess(provider.metricsExplorerHandler.UpdateMetricMetadata),
|
||||
provider.authzMiddleware.EditAccess(provider.metricsExplorerHandler.UpdateMetricMetadata),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateMetricMetadata",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -127,7 +127,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/{metric_name}/highlights", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.GetMetricHighlights),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricHighlights),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricHighlights",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -146,7 +146,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/{metric_name}/alerts", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.GetMetricAlerts),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricAlerts),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricAlerts",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -165,7 +165,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/{metric_name}/dashboards", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.GetMetricDashboards),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricDashboards),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricDashboards",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -184,7 +184,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/inspect", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.InspectMetrics),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.InspectMetrics),
|
||||
handler.OpenAPIDef{
|
||||
ID: "InspectMetrics",
|
||||
Tags: []string{"metrics"},
|
||||
@@ -203,7 +203,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/onboarding", handler.New(
|
||||
provider.authZ.ViewAccess(provider.metricsExplorerHandler.GetOnboardingStatus),
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetOnboardingStatus),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricsOnboardingStatus",
|
||||
Tags: []string{"metrics"},
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addOrgRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/orgs/me", handler.New(provider.authZ.AdminAccess(provider.orgHandler.Get), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/orgs/me", handler.New(provider.authzMiddleware.AdminAccess(provider.orgHandler.Get), handler.OpenAPIDef{
|
||||
ID: "GetMyOrganization",
|
||||
Tags: []string{"orgs"},
|
||||
Summary: "Get my organization",
|
||||
@@ -26,7 +26,7 @@ func (provider *provider) addOrgRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/orgs/me", handler.New(provider.authZ.AdminAccess(provider.orgHandler.Update), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/orgs/me", handler.New(provider.authzMiddleware.AdminAccess(provider.orgHandler.Update), handler.OpenAPIDef{
|
||||
ID: "UpdateMyOrganization",
|
||||
Tags: []string{"orgs"},
|
||||
Summary: "Update my organization",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/user/preferences", handler.New(provider.authZ.ViewAccess(provider.preferenceHandler.ListByUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user/preferences", handler.New(provider.authzMiddleware.ViewAccess(provider.preferenceHandler.ListByUser), handler.OpenAPIDef{
|
||||
ID: "ListUserPreferences",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "List user preferences",
|
||||
@@ -27,7 +27,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user/preferences/{name}", handler.New(provider.authZ.ViewAccess(provider.preferenceHandler.GetByUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user/preferences/{name}", handler.New(provider.authzMiddleware.ViewAccess(provider.preferenceHandler.GetByUser), handler.OpenAPIDef{
|
||||
ID: "GetUserPreference",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "Get user preference",
|
||||
@@ -44,7 +44,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user/preferences/{name}", handler.New(provider.authZ.ViewAccess(provider.preferenceHandler.UpdateByUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user/preferences/{name}", handler.New(provider.authzMiddleware.ViewAccess(provider.preferenceHandler.UpdateByUser), handler.OpenAPIDef{
|
||||
ID: "UpdateUserPreference",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "Update user preference",
|
||||
@@ -61,7 +61,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/org/preferences", handler.New(provider.authZ.AdminAccess(provider.preferenceHandler.ListByOrg), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/org/preferences", handler.New(provider.authzMiddleware.AdminAccess(provider.preferenceHandler.ListByOrg), handler.OpenAPIDef{
|
||||
ID: "ListOrgPreferences",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "List org preferences",
|
||||
@@ -78,7 +78,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/org/preferences/{name}", handler.New(provider.authZ.AdminAccess(provider.preferenceHandler.GetByOrg), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/org/preferences/{name}", handler.New(provider.authzMiddleware.AdminAccess(provider.preferenceHandler.GetByOrg), handler.OpenAPIDef{
|
||||
ID: "GetOrgPreference",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "Get org preference",
|
||||
@@ -95,7 +95,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/org/preferences/{name}", handler.New(provider.authZ.AdminAccess(provider.preferenceHandler.UpdateByOrg), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/org/preferences/{name}", handler.New(provider.authzMiddleware.AdminAccess(provider.preferenceHandler.UpdateByOrg), handler.OpenAPIDef{
|
||||
ID: "UpdateOrgPreference",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "Update org preference",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addPromoteRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/logs/promote_paths", handler.New(provider.authZ.EditAccess(provider.promoteHandler.HandlePromoteAndIndexPaths), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/logs/promote_paths", handler.New(provider.authzMiddleware.EditAccess(provider.promoteHandler.HandlePromoteAndIndexPaths), handler.OpenAPIDef{
|
||||
ID: "HandlePromoteAndIndexPaths",
|
||||
Tags: []string{"logs"},
|
||||
Summary: "Promote and index paths",
|
||||
@@ -26,7 +26,7 @@ func (provider *provider) addPromoteRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/logs/promote_paths", handler.New(provider.authZ.ViewAccess(provider.promoteHandler.ListPromotedAndIndexedPaths), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/logs/promote_paths", handler.New(provider.authzMiddleware.ViewAccess(provider.promoteHandler.ListPromotedAndIndexedPaths), handler.OpenAPIDef{
|
||||
ID: "ListPromotedAndIndexedPaths",
|
||||
Tags: []string{"logs"},
|
||||
Summary: "Promote and index paths",
|
||||
|
||||
@@ -41,7 +41,8 @@ type provider struct {
|
||||
config apiserver.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
router *mux.Router
|
||||
authZ *middleware.AuthZ
|
||||
authzMiddleware *middleware.AuthZ
|
||||
authzService authz.AuthZ
|
||||
orgHandler organization.Handler
|
||||
userHandler user.Handler
|
||||
sessionHandler session.Handler
|
||||
@@ -73,7 +74,7 @@ type provider struct {
|
||||
|
||||
func NewFactory(
|
||||
orgGetter organization.Getter,
|
||||
authz authz.AuthZ,
|
||||
authzService authz.AuthZ,
|
||||
orgHandler organization.Handler,
|
||||
userHandler user.Handler,
|
||||
sessionHandler session.Handler,
|
||||
@@ -108,7 +109,7 @@ func NewFactory(
|
||||
providerSettings,
|
||||
config,
|
||||
orgGetter,
|
||||
authz,
|
||||
authzService,
|
||||
orgHandler,
|
||||
userHandler,
|
||||
sessionHandler,
|
||||
@@ -145,7 +146,7 @@ func newProvider(
|
||||
providerSettings factory.ProviderSettings,
|
||||
config apiserver.Config,
|
||||
orgGetter organization.Getter,
|
||||
authz authz.AuthZ,
|
||||
authzService authz.AuthZ,
|
||||
orgHandler organization.Handler,
|
||||
userHandler user.Handler,
|
||||
sessionHandler session.Handler,
|
||||
@@ -183,6 +184,7 @@ func newProvider(
|
||||
router: router,
|
||||
orgHandler: orgHandler,
|
||||
userHandler: userHandler,
|
||||
authzService: authzService,
|
||||
sessionHandler: sessionHandler,
|
||||
authDomainHandler: authDomainHandler,
|
||||
preferenceHandler: preferenceHandler,
|
||||
@@ -210,7 +212,7 @@ func newProvider(
|
||||
llmPricingRuleHandler: llmPricingRuleHandler,
|
||||
}
|
||||
|
||||
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz)
|
||||
provider.authzMiddleware = middleware.NewAuthZ(settings.Logger(), orgGetter, authzService)
|
||||
|
||||
if err := provider.AddToRouter(router); err != nil {
|
||||
return nil, err
|
||||
@@ -336,10 +338,7 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
|
||||
}
|
||||
|
||||
func newSecuritySchemes(role types.Role) []handler.OpenAPISecurityScheme {
|
||||
return []handler.OpenAPISecurityScheme{
|
||||
{Name: authtypes.IdentNProviderAPIKey.StringValue(), Scopes: []string{role.String()}},
|
||||
{Name: authtypes.IdentNProviderTokenizer.StringValue(), Scopes: []string{role.String()}},
|
||||
}
|
||||
return newScopedSecuritySchemes([]string{role.String()})
|
||||
}
|
||||
|
||||
func newAnonymousSecuritySchemes(scopes []string) []handler.OpenAPISecurityScheme {
|
||||
@@ -347,3 +346,10 @@ func newAnonymousSecuritySchemes(scopes []string) []handler.OpenAPISecuritySchem
|
||||
{Name: authtypes.IdentNProviderAnonymous.StringValue(), Scopes: scopes},
|
||||
}
|
||||
}
|
||||
|
||||
func newScopedSecuritySchemes(scopes []string) []handler.OpenAPISecurityScheme {
|
||||
return []handler.OpenAPISecurityScheme{
|
||||
{Name: authtypes.IdentNProviderAPIKey.StringValue(), Scopes: scopes},
|
||||
{Name: authtypes.IdentNProviderTokenizer.StringValue(), Scopes: scopes},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addQuerierRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v5/query_range", handler.New(provider.authZ.ViewAccess(provider.querierHandler.QueryRange), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v5/query_range", handler.New(provider.authzMiddleware.ViewAccess(provider.querierHandler.QueryRange), handler.OpenAPIDef{
|
||||
ID: "QueryRangeV5",
|
||||
Tags: []string{"querier"},
|
||||
Summary: "Query range",
|
||||
@@ -451,7 +451,7 @@ func (provider *provider) addQuerierRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v5/substitute_vars", handler.New(provider.authZ.ViewAccess(provider.querierHandler.ReplaceVariables), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v5/substitute_vars", handler.New(provider.authzMiddleware.ViewAccess(provider.querierHandler.ReplaceVariables), handler.OpenAPIDef{
|
||||
ID: "ReplaceVariables",
|
||||
Tags: []string{"querier"},
|
||||
Summary: "Replace variables",
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func (provider *provider) addRawDataExportRoutes(router *mux.Router) error {
|
||||
|
||||
if err := router.Handle("/api/v1/export_raw_data", handler.New(provider.authZ.ViewAccess(provider.rawDataExportHandler.ExportRawData), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/export_raw_data", handler.New(provider.authzMiddleware.ViewAccess(provider.rawDataExportHandler.ExportRawData), handler.OpenAPIDef{
|
||||
ID: "HandleExportRawDataPOST",
|
||||
Tags: []string{"logs", "traces"},
|
||||
Summary: "Export raw data",
|
||||
|
||||
@@ -57,7 +57,7 @@ func (handler *healthOpenAPIHandler) AuditDef() *pkghandler.AuditDef {
|
||||
|
||||
func (provider *provider) addRegistryRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/healthz", newHealthOpenAPIHandler(
|
||||
provider.authZ.OpenAccess(provider.factoryHandler.Healthz),
|
||||
provider.authzMiddleware.OpenAccess(provider.factoryHandler.Healthz),
|
||||
"Healthz",
|
||||
"Health check",
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
@@ -65,14 +65,14 @@ func (provider *provider) addRegistryRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/readyz", newHealthOpenAPIHandler(
|
||||
provider.authZ.OpenAccess(provider.factoryHandler.Readyz),
|
||||
provider.authzMiddleware.OpenAccess(provider.factoryHandler.Readyz),
|
||||
"Readyz",
|
||||
"Readiness check",
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/livez", pkghandler.New(provider.authZ.OpenAccess(provider.factoryHandler.Livez),
|
||||
if err := router.Handle("/api/v2/livez", pkghandler.New(provider.authzMiddleware.OpenAccess(provider.factoryHandler.Livez),
|
||||
pkghandler.OpenAPIDef{
|
||||
ID: "Livez",
|
||||
Tags: []string{"health"},
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/roles", handler.New(provider.authZ.AdminAccess(provider.authzHandler.Create), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.Create), handler.OpenAPIDef{
|
||||
ID: "CreateRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Create role",
|
||||
@@ -28,7 +28,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles", handler.New(provider.authZ.AdminAccess(provider.authzHandler.List), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.List), handler.OpenAPIDef{
|
||||
ID: "ListRoles",
|
||||
Tags: []string{"role"},
|
||||
Summary: "List roles",
|
||||
@@ -45,7 +45,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.authzHandler.Get), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.Get), handler.OpenAPIDef{
|
||||
ID: "GetRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Get role",
|
||||
@@ -62,7 +62,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}/relations/{relation}/objects", handler.New(provider.authZ.AdminAccess(provider.authzHandler.GetObjects), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles/{id}/relations/{relation}/objects", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.GetObjects), handler.OpenAPIDef{
|
||||
ID: "GetObjects",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Get objects for a role by relation",
|
||||
@@ -79,7 +79,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.authzHandler.Patch), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.Patch), handler.OpenAPIDef{
|
||||
ID: "PatchRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch role",
|
||||
@@ -96,7 +96,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}/relations/{relation}/objects", handler.New(provider.authZ.AdminAccess(provider.authzHandler.PatchObjects), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles/{id}/relations/{relation}/objects", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.PatchObjects), handler.OpenAPIDef{
|
||||
ID: "PatchObjects",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch objects for a role by relation",
|
||||
@@ -113,7 +113,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.authzHandler.Delete), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.Delete), handler.OpenAPIDef{
|
||||
ID: "DeleteRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Delete role",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/rules", handler.New(provider.authZ.ViewAccess(provider.rulerHandler.ListRules), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/rules", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.ListRules), handler.OpenAPIDef{
|
||||
ID: "ListRules",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List alert rules",
|
||||
@@ -23,7 +23,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authZ.ViewAccess(provider.rulerHandler.GetRuleByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.GetRuleByID), handler.OpenAPIDef{
|
||||
ID: "GetRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Get alert rule by ID",
|
||||
@@ -37,7 +37,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules", handler.New(provider.authZ.EditAccess(provider.rulerHandler.CreateRule), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/rules", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.CreateRule), handler.OpenAPIDef{
|
||||
ID: "CreateRule",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create alert rule",
|
||||
@@ -54,7 +54,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.UpdateRuleByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.UpdateRuleByID), handler.OpenAPIDef{
|
||||
ID: "UpdateRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update alert rule",
|
||||
@@ -69,7 +69,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.DeleteRuleByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.DeleteRuleByID), handler.OpenAPIDef{
|
||||
ID: "DeleteRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete alert rule",
|
||||
@@ -81,7 +81,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.PatchRuleByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.PatchRuleByID), handler.OpenAPIDef{
|
||||
ID: "PatchRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Patch alert rule",
|
||||
@@ -98,7 +98,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/test", handler.New(provider.authZ.EditAccess(provider.rulerHandler.TestRule), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/rules/test", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.TestRule), handler.OpenAPIDef{
|
||||
ID: "TestRule",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Test alert rule",
|
||||
@@ -115,7 +115,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authZ.ViewAccess(provider.rulerHandler.ListDowntimeSchedules), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.ListDowntimeSchedules), handler.OpenAPIDef{
|
||||
ID: "ListDowntimeSchedules",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "List downtime schedules",
|
||||
@@ -129,7 +129,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authZ.ViewAccess(provider.rulerHandler.GetDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.GetDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
ID: "GetDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Get downtime schedule by ID",
|
||||
@@ -143,7 +143,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authZ.EditAccess(provider.rulerHandler.CreateDowntimeSchedule), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.CreateDowntimeSchedule), handler.OpenAPIDef{
|
||||
ID: "CreateDowntimeSchedule",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Create downtime schedule",
|
||||
@@ -159,7 +159,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.UpdateDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.UpdateDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
ID: "UpdateDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Update downtime schedule",
|
||||
@@ -173,7 +173,7 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.DeleteDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.DeleteDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
ID: "DeleteDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Delete downtime schedule",
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/stats", handler.New(
|
||||
provider.authZ.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryStats),
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryStats),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryStats",
|
||||
Tags: []string{"rules"},
|
||||
@@ -30,7 +30,7 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/timeline", handler.New(
|
||||
provider.authZ.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryTimeline),
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryTimeline),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryTimeline",
|
||||
Tags: []string{"rules"},
|
||||
@@ -47,7 +47,7 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/top_contributors", handler.New(
|
||||
provider.authZ.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryContributors),
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryContributors),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryTopContributors",
|
||||
Tags: []string{"rules"},
|
||||
@@ -64,7 +64,7 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/filter_keys", handler.New(
|
||||
provider.authZ.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryFilterKeys),
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryFilterKeys),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryFilterKeys",
|
||||
Tags: []string{"rules"},
|
||||
@@ -81,7 +81,7 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/filter_values", handler.New(
|
||||
provider.authZ.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryFilterValues),
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryFilterValues),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryFilterValues",
|
||||
Tags: []string{"rules"},
|
||||
@@ -98,7 +98,7 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/overall_status", handler.New(
|
||||
provider.authZ.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryOverallStatus),
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryOverallStatus),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryOverallStatus",
|
||||
Tags: []string{"rules"},
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
package signozapiserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/service_accounts", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.Create), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.Create, authtypes.Relation{Verb: coretypes.VerbCreate}, coretypes.ResourceMetaResourcesServiceAccount, serviceAccountCollectionSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create service account",
|
||||
@@ -23,12 +31,14 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourcesServiceAccount.Scope(coretypes.VerbCreate)}),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.List), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.List, authtypes.Relation{Verb: coretypes.VerbList}, coretypes.ResourceMetaResourcesServiceAccount, serviceAccountCollectionSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "ListServiceAccounts",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "List service accounts",
|
||||
@@ -40,12 +50,12 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourcesServiceAccount.Scope(coretypes.VerbList)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.authZ.OpenAccess(provider.serviceAccountHandler.GetMe), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.authzMiddleware.OpenAccess(provider.serviceAccountHandler.GetMe), handler.OpenAPIDef{
|
||||
ID: "GetMyServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Gets my service account",
|
||||
@@ -62,7 +72,9 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.Get), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.Get, authtypes.Relation{Verb: coretypes.VerbRead}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "GetServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Gets a service account",
|
||||
@@ -74,12 +86,14 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.GetRoles), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.GetRoles, authtypes.Relation{Verb: coretypes.VerbRead}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "GetServiceAccountRoles",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Gets service account roles",
|
||||
@@ -91,12 +105,19 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.SetRole), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles", handler.New(provider.authzMiddleware.CheckAll(provider.serviceAccountHandler.SetRole, []middleware.AuthZCheckGroup{
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceServiceAccount, SelectorCallback: serviceAccountInstanceSelectorCallback, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceRole, SelectorCallback: provider.roleAttachSelectorFromBody, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccountRole",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create service account role",
|
||||
@@ -108,12 +129,19 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbAttach), coretypes.ResourceRole.Scope(coretypes.VerbAttach)}),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles/{rid}", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.DeleteRole), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles/{rid}", handler.New(provider.authzMiddleware.CheckAll(provider.serviceAccountHandler.DeleteRole, []middleware.AuthZCheckGroup{
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceServiceAccount, SelectorCallback: serviceAccountInstanceSelectorCallback, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceRole, SelectorCallback: provider.roleAttachSelectorFromPath, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "DeleteServiceAccountRole",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Delete service account role",
|
||||
@@ -125,12 +153,12 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbAttach), coretypes.ResourceRole.Scope(coretypes.VerbAttach)}),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.authZ.OpenAccess(provider.serviceAccountHandler.UpdateMe), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.authzMiddleware.OpenAccess(provider.serviceAccountHandler.UpdateMe), handler.OpenAPIDef{
|
||||
ID: "UpdateMyServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates my service account",
|
||||
@@ -147,7 +175,9 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.Update), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.Update, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "UpdateServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates a service account",
|
||||
@@ -159,12 +189,14 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.Delete), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.Delete, authtypes.Relation{Verb: coretypes.VerbDelete}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "DeleteServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Deletes a service account",
|
||||
@@ -176,12 +208,14 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbDelete)}),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.CreateFactorAPIKey), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.CreateFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create a service account key",
|
||||
@@ -193,12 +227,14 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.ListFactorAPIKey), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.ListFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbRead}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "ListServiceAccountKeys",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "List service account keys",
|
||||
@@ -210,12 +246,14 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.UpdateFactorAPIKey), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.UpdateFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "UpdateServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates a service account key",
|
||||
@@ -227,12 +265,14 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.RevokeFactorAPIKey), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.RevokeFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "RevokeServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Revoke a service account key",
|
||||
@@ -244,10 +284,70 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) roleAttachSelectorFromPath(req *http.Request, claims authtypes.Claims) ([]coretypes.Selector, error) {
|
||||
roleID, err := valuer.NewUUID(mux.Vars(req)["rid"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role, err := provider.authzService.Get(req.Context(), valuer.MustNewUUID(claims.OrgID), roleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []coretypes.Selector{
|
||||
coretypes.TypeRole.MustSelector(role.Name),
|
||||
coretypes.TypeRole.MustSelector(coretypes.WildCardSelectorString),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (provider *provider) roleAttachSelectorFromBody(req *http.Request, claims authtypes.Claims) ([]coretypes.Selector, error) {
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
|
||||
postableRole := new(serviceaccounttypes.PostableServiceAccountRole)
|
||||
if err := json.Unmarshal(body, postableRole); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role, err := provider.authzService.Get(req.Context(), valuer.MustNewUUID(claims.OrgID), postableRole.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []coretypes.Selector{
|
||||
coretypes.TypeRole.MustSelector(role.Name),
|
||||
coretypes.TypeRole.MustSelector(coretypes.WildCardSelectorString),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func serviceAccountCollectionSelectorCallback(_ *http.Request, _ authtypes.Claims) ([]coretypes.Selector, error) {
|
||||
return []coretypes.Selector{
|
||||
coretypes.TypeMetaResources.MustSelector(coretypes.WildCardSelectorString),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func serviceAccountInstanceSelectorCallback(req *http.Request, _ authtypes.Claims) ([]coretypes.Selector, error) {
|
||||
id := mux.Vars(req)["id"]
|
||||
idSelector, err := coretypes.TypeServiceAccount.Selector(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []coretypes.Selector{
|
||||
idSelector,
|
||||
coretypes.TypeServiceAccount.MustSelector(coretypes.WildCardSelectorString),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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.authZ.OpenAccess(provider.sessionHandler.CreateSessionByEmailPassword), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/sessions/email_password", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionByEmailPassword), handler.OpenAPIDef{
|
||||
ID: "CreateSessionByEmailPassword",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by email and password",
|
||||
@@ -26,7 +26,7 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions/context", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.GetSessionContext), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/sessions/context", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.GetSessionContext), handler.OpenAPIDef{
|
||||
ID: "GetSessionContext",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Get session context",
|
||||
@@ -43,7 +43,7 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions/rotate", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.RotateSession), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/sessions/rotate", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.RotateSession), handler.OpenAPIDef{
|
||||
ID: "RotateSession",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Rotate session",
|
||||
@@ -60,7 +60,7 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.DeleteSession), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/sessions", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.DeleteSession), handler.OpenAPIDef{
|
||||
ID: "DeleteSession",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Delete session",
|
||||
@@ -77,7 +77,7 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/complete/google", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.CreateSessionByGoogleCallback), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/complete/google", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionByGoogleCallback), handler.OpenAPIDef{
|
||||
ID: "CreateSessionByGoogleCallback",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by google callback",
|
||||
@@ -94,7 +94,7 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/complete/saml", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.CreateSessionBySAMLCallback), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/complete/saml", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionBySAMLCallback), handler.OpenAPIDef{
|
||||
ID: "CreateSessionBySAMLCallback",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by saml callback",
|
||||
@@ -114,7 +114,7 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/complete/oidc", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.CreateSessionByOIDCCallback), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/complete/oidc", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionByOIDCCallback), handler.OpenAPIDef{
|
||||
ID: "CreateSessionByOIDCCallback",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by oidc callback",
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/span_mapper_groups", handler.New(
|
||||
provider.authZ.ViewAccess(provider.spanMapperHandler.ListGroups),
|
||||
provider.authzMiddleware.ViewAccess(provider.spanMapperHandler.ListGroups),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListSpanMapperGroups",
|
||||
Tags: []string{"spanmapper"},
|
||||
@@ -32,7 +32,7 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/span_mapper_groups", handler.New(
|
||||
provider.authZ.AdminAccess(provider.spanMapperHandler.CreateGroup),
|
||||
provider.authzMiddleware.AdminAccess(provider.spanMapperHandler.CreateGroup),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateSpanMapperGroup",
|
||||
Tags: []string{"spanmapper"},
|
||||
@@ -52,7 +52,7 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.spanMapperHandler.UpdateGroup),
|
||||
provider.authzMiddleware.AdminAccess(provider.spanMapperHandler.UpdateGroup),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateSpanMapperGroup",
|
||||
Tags: []string{"spanmapper"},
|
||||
@@ -70,7 +70,7 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.spanMapperHandler.DeleteGroup),
|
||||
provider.authzMiddleware.AdminAccess(provider.spanMapperHandler.DeleteGroup),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteSpanMapperGroup",
|
||||
Tags: []string{"spanmapper"},
|
||||
@@ -90,7 +90,7 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers", handler.New(
|
||||
provider.authZ.ViewAccess(provider.spanMapperHandler.ListMappers),
|
||||
provider.authzMiddleware.ViewAccess(provider.spanMapperHandler.ListMappers),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListSpanMappers",
|
||||
Tags: []string{"spanmapper"},
|
||||
@@ -110,7 +110,7 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers", handler.New(
|
||||
provider.authZ.AdminAccess(provider.spanMapperHandler.CreateMapper),
|
||||
provider.authzMiddleware.AdminAccess(provider.spanMapperHandler.CreateMapper),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateSpanMapper",
|
||||
Tags: []string{"spanmapper"},
|
||||
@@ -130,7 +130,7 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.spanMapperHandler.UpdateMapper),
|
||||
provider.authzMiddleware.AdminAccess(provider.spanMapperHandler.UpdateMapper),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateSpanMapper",
|
||||
Tags: []string{"spanmapper"},
|
||||
@@ -148,7 +148,7 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.spanMapperHandler.DeleteMapper),
|
||||
provider.authzMiddleware.AdminAccess(provider.spanMapperHandler.DeleteMapper),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteSpanMapper",
|
||||
Tags: []string{"spanmapper"},
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func (provider *provider) addTraceDetailRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v3/traces/{traceID}/waterfall", handler.New(
|
||||
provider.authZ.ViewAccess(provider.traceDetailHandler.GetWaterfall),
|
||||
provider.authzMiddleware.ViewAccess(provider.traceDetailHandler.GetWaterfall),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetWaterfall",
|
||||
Tags: []string{"tracedetail"},
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/invite", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateInvite), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/invite", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.CreateInvite), handler.OpenAPIDef{
|
||||
ID: "CreateInvite",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create invite",
|
||||
@@ -27,7 +27,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/invite/bulk", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateBulkInvite), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/invite/bulk", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.CreateBulkInvite), handler.OpenAPIDef{
|
||||
ID: "CreateBulkInvite",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create bulk invite",
|
||||
@@ -43,7 +43,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListUsersDeprecated), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.ListUsersDeprecated), handler.OpenAPIDef{
|
||||
ID: "ListUsersDeprecated",
|
||||
Tags: []string{"users"},
|
||||
Summary: "List users",
|
||||
@@ -60,7 +60,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListUsers), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.ListUsers), handler.OpenAPIDef{
|
||||
ID: "ListUsers",
|
||||
Tags: []string{"users"},
|
||||
Summary: "List users v2",
|
||||
@@ -77,7 +77,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user/me", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetMyUserDeprecated), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user/me", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.GetMyUserDeprecated), handler.OpenAPIDef{
|
||||
ID: "GetMyUserDeprecated",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get my user",
|
||||
@@ -94,7 +94,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetMyUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.GetMyUser), handler.OpenAPIDef{
|
||||
ID: "GetMyUser",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get my user v2",
|
||||
@@ -111,7 +111,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authZ.OpenAccess(provider.userHandler.UpdateMyUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.UpdateMyUser), handler.OpenAPIDef{
|
||||
ID: "UpdateMyUserV2",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update my user v2",
|
||||
@@ -128,7 +128,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.GetUserDeprecated), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authzMiddleware.SelfAccess(provider.userHandler.GetUserDeprecated), handler.OpenAPIDef{
|
||||
ID: "GetUserDeprecated",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get user",
|
||||
@@ -145,7 +145,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.GetUser), handler.OpenAPIDef{
|
||||
ID: "GetUser",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get user by user id",
|
||||
@@ -162,7 +162,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.UpdateUserDeprecated), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authzMiddleware.SelfAccess(provider.userHandler.UpdateUserDeprecated), handler.OpenAPIDef{
|
||||
ID: "UpdateUserDeprecated",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update user",
|
||||
@@ -179,7 +179,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.UpdateUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.UpdateUser), handler.OpenAPIDef{
|
||||
ID: "UpdateUser",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update user v2",
|
||||
@@ -196,7 +196,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.DeleteUser), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.DeleteUser), handler.OpenAPIDef{
|
||||
ID: "DeleteUser",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Delete user",
|
||||
@@ -213,7 +213,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/getResetPasswordToken/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetResetPasswordTokenDeprecated), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/getResetPasswordToken/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.GetResetPasswordTokenDeprecated), handler.OpenAPIDef{
|
||||
ID: "GetResetPasswordTokenDeprecated",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get reset password token",
|
||||
@@ -230,7 +230,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/reset_password_tokens", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetResetPasswordToken), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/{id}/reset_password_tokens", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.GetResetPasswordToken), handler.OpenAPIDef{
|
||||
ID: "GetResetPasswordToken",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get reset password token for a user",
|
||||
@@ -247,7 +247,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/reset_password_tokens", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateResetPasswordToken), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/{id}/reset_password_tokens", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.CreateResetPasswordToken), handler.OpenAPIDef{
|
||||
ID: "CreateResetPasswordToken",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create or regenerate reset password token for a user",
|
||||
@@ -264,7 +264,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/resetPassword", handler.New(provider.authZ.OpenAccess(provider.userHandler.ResetPassword), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/resetPassword", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ResetPassword), handler.OpenAPIDef{
|
||||
ID: "ResetPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Reset password",
|
||||
@@ -281,7 +281,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me/factor_password", handler.New(provider.authZ.OpenAccess(provider.userHandler.ChangePassword), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/me/factor_password", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ChangePassword), handler.OpenAPIDef{
|
||||
ID: "UpdateMyPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Updates my password",
|
||||
@@ -298,7 +298,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/factor_password/forgot", handler.New(provider.authZ.OpenAccess(provider.userHandler.ForgotPassword), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/factor_password/forgot", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ForgotPassword), handler.OpenAPIDef{
|
||||
ID: "ForgotPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Forgot password",
|
||||
@@ -315,7 +315,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/roles", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetRolesByUserID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/{id}/roles", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.GetRolesByUserID), handler.OpenAPIDef{
|
||||
ID: "GetRolesByUserID",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get user roles",
|
||||
@@ -332,7 +332,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/roles", handler.New(provider.authZ.AdminAccess(provider.userHandler.SetRoleByUserID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/{id}/roles", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.SetRoleByUserID), handler.OpenAPIDef{
|
||||
ID: "SetRoleByUserID",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Set user roles",
|
||||
@@ -349,7 +349,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/roles/{roleId}", handler.New(provider.authZ.AdminAccess(provider.userHandler.RemoveUserRoleByRoleID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/users/{id}/roles/{roleId}", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.RemoveUserRoleByRoleID), handler.OpenAPIDef{
|
||||
ID: "RemoveUserRoleByUserIDAndRoleID",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Remove a role from user",
|
||||
@@ -366,7 +366,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/roles/{id}/users", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetUsersByRoleID), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/roles/{id}/users", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.GetUsersByRoleID), handler.OpenAPIDef{
|
||||
ID: "GetUsersByRoleID",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get users by role id",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addZeusRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/zeus/profiles", handler.New(provider.authZ.AdminAccess(provider.zeusHandler.PutProfile), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/zeus/profiles", handler.New(provider.authzMiddleware.AdminAccess(provider.zeusHandler.PutProfile), handler.OpenAPIDef{
|
||||
ID: "PutProfile",
|
||||
Tags: []string{"zeus"},
|
||||
Summary: "Put profile in Zeus for a deployment.",
|
||||
@@ -27,7 +27,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authZ.ViewAccess(provider.zeusHandler.GetHosts), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authzMiddleware.ViewAccess(provider.zeusHandler.GetHosts), handler.OpenAPIDef{
|
||||
ID: "GetHosts",
|
||||
Tags: []string{"zeus"},
|
||||
Summary: "Get host info from Zeus.",
|
||||
@@ -44,7 +44,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authZ.AdminAccess(provider.zeusHandler.PutHost), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authzMiddleware.AdminAccess(provider.zeusHandler.PutHost), handler.OpenAPIDef{
|
||||
ID: "PutHost",
|
||||
Tags: []string{"zeus"},
|
||||
Summary: "Put host in Zeus for a deployment.",
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -18,6 +19,20 @@ const (
|
||||
authzDeniedMessage string = "::AUTHZ-DENIED::"
|
||||
)
|
||||
|
||||
type AuthZCheckDef struct {
|
||||
Relation authtypes.Relation
|
||||
Resource coretypes.Resource
|
||||
SelectorCallback selectorCallbackWithClaimsFn
|
||||
Roles []string
|
||||
}
|
||||
|
||||
// AuthZCheckGroup is a set of checks OR'd together.
|
||||
// At least one check in the group must pass for the group to pass.
|
||||
type AuthZCheckGroup []AuthZCheckDef
|
||||
|
||||
type selectorCallbackWithClaimsFn func(*http.Request, authtypes.Claims) ([]coretypes.Selector, error)
|
||||
type selectorCallbackWithoutClaimsFn func(*http.Request, []*types.Organization) ([]coretypes.Selector, valuer.UUID, error)
|
||||
|
||||
type AuthZ struct {
|
||||
logger *slog.Logger
|
||||
orgGetter organization.Getter
|
||||
@@ -186,7 +201,7 @@ func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, typeable coretypes.Resource, cb authtypes.SelectorCallbackWithClaimsFn, roles []string) http.HandlerFunc {
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, typeable coretypes.Resource, cb selectorCallbackWithClaimsFn, roles []string) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
@@ -216,7 +231,61 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, typeable coretypes.Resource, cb authtypes.SelectorCallbackWithoutClaimsFn, roles []string) http.HandlerFunc {
|
||||
// CheckAll verifies groups of permission checks.
|
||||
// Within each group, checks are OR'd (any check passing = group passes).
|
||||
// Across groups, results are AND'd (all groups must pass).
|
||||
//
|
||||
// This model expresses any combination:
|
||||
// - Single check: []AuthZCheckGroup{{checkA}}
|
||||
// - Pure AND: []AuthZCheckGroup{{checkA}, {checkB}}
|
||||
// - Cross-resource OR: []AuthZCheckGroup{{checkA, checkB}}
|
||||
// - Mixed (A OR B) AND C: []AuthZCheckGroup{{checkA, checkB}, {checkC}}
|
||||
func (middleware *AuthZ) CheckAll(next http.HandlerFunc, groups []AuthZCheckGroup) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
for _, group := range groups {
|
||||
groupPassed := false
|
||||
var lastErr error
|
||||
|
||||
for _, check := range group {
|
||||
selectors, err := check.SelectorCallback(req, claims)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
roleSelectors := make([]coretypes.Selector, len(check.Roles))
|
||||
for idx, role := range check.Roles {
|
||||
roleSelectors[idx] = coretypes.TypeRole.MustSelector(role)
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(ctx, claims, orgID, check.Relation, check.Resource, selectors, roleSelectors)
|
||||
if err == nil {
|
||||
groupPassed = true
|
||||
break
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
if !groupPassed {
|
||||
render.Error(rw, lastErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, typeable coretypes.Resource, cb selectorCallbackWithoutClaimsFn, roles []string) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
orgs, err := middleware.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
|
||||
@@ -44,6 +44,8 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const signozDiscriminatorKey string = "x-signoz-discriminator"
|
||||
|
||||
type OpenAPI struct {
|
||||
apiserver apiserver.APIServer
|
||||
reflector *openapi3.Reflector
|
||||
@@ -142,6 +144,8 @@ func (openapi *OpenAPI) CreateAndWrite(path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
attachDiscriminators(openapi.reflector.Spec)
|
||||
|
||||
// The library's MarshalYAML does a JSON round-trip that converts all numbers
|
||||
// to float64, causing large integers (e.g. epoch millisecond timestamps) to
|
||||
// render in scientific notation (1.6409952e+12).
|
||||
@@ -199,3 +203,59 @@ func convertJSONNumbers(v interface{}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// attachDiscriminators promotes x-signoz-discriminator extensions
|
||||
// into openapi3 Discriminator fields. Malformed markers are dropped.
|
||||
func attachDiscriminators(spec *openapi3.Spec) {
|
||||
if spec.Components == nil || spec.Components.Schemas == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for name, entry := range spec.Components.Schemas.MapOfSchemaOrRefValues {
|
||||
if entry.Schema == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
raw, ok := entry.Schema.MapOfAnything[signozDiscriminatorKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
marker, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
propertyName, ok := marker["propertyName"].(string)
|
||||
if !ok || propertyName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
disc := openapi3.Discriminator{PropertyName: propertyName}
|
||||
if rawMapping, ok := marker["mapping"]; ok {
|
||||
if mapping, ok := rawMapping.(map[string]string); ok {
|
||||
disc.Mapping = mapping
|
||||
} else if mapping, ok := rawMapping.(map[string]any); ok {
|
||||
converted := make(map[string]string, len(mapping))
|
||||
for k, v := range mapping {
|
||||
if s, ok := v.(string); ok {
|
||||
converted[k] = s
|
||||
}
|
||||
}
|
||||
disc.Mapping = converted
|
||||
}
|
||||
}
|
||||
|
||||
entry.Schema.Discriminator = &disc
|
||||
delete(entry.Schema.MapOfAnything, signozDiscriminatorKey)
|
||||
|
||||
// The parent's reflected `properties` / `required` duplicate
|
||||
// what the oneOf variants already declare, and orval intersects
|
||||
// the two — turning a clean discriminated union DTO into a
|
||||
// noisy union of intersections. Drop them here.
|
||||
entry.Schema.Properties = nil
|
||||
entry.Schema.Required = nil
|
||||
|
||||
spec.Components.Schemas.MapOfSchemaOrRefValues[name] = entry
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewServiceAccountAuthzactory(sqlstore),
|
||||
sqlmigration.NewDropUserDeletedAtFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewMigrateAWSAllRegionsFactory(sqlstore),
|
||||
sqlmigration.NewAddServiceAccountManagedRoleTransactionsFactory(sqlstore),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
150
pkg/sqlmigration/078_add_sa_managed_role_txn.go
Normal file
150
pkg/sqlmigration/078_add_sa_managed_role_txn.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type addServiceAccountManagedRoleTransactions struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewAddServiceAccountManagedRoleTransactionsFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_sa_managed_role_txn"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &addServiceAccountManagedRoleTransactions{sqlstore: sqlstore}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *addServiceAccountManagedRoleTransactions) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
// managedRoleTuple describes a single FGA tuple to insert for a managed role.
|
||||
type managedRoleTuple struct {
|
||||
roleName string
|
||||
objectType string // "metaresources" or "metaresource"
|
||||
objectName string // "service-accounts" or "service-account"
|
||||
relation string // "create", "list", "read", "update", "delete"
|
||||
}
|
||||
|
||||
func (migration *addServiceAccountManagedRoleTransactions) Up(ctx context.Context, db *bun.DB) error {
|
||||
// All tuples that need to be created for service account FGA managed role permissions.
|
||||
tuples := []managedRoleTuple{
|
||||
{authtypes.SigNozAdminRoleName, "role", "role", "attach"},
|
||||
{authtypes.SigNozAdminRoleName, "serviceaccount", "serviceaccount", "attach"},
|
||||
{authtypes.SigNozAdminRoleName, "metaresources", "serviceaccount", "create"},
|
||||
{authtypes.SigNozAdminRoleName, "metaresources", "serviceaccount", "list"},
|
||||
{authtypes.SigNozAdminRoleName, "serviceaccount", "serviceaccount", "read"},
|
||||
{authtypes.SigNozAdminRoleName, "serviceaccount", "serviceaccount", "update"},
|
||||
{authtypes.SigNozAdminRoleName, "serviceaccount", "serviceaccount", "delete"},
|
||||
}
|
||||
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
var storeID string
|
||||
err = tx.QueryRowContext(ctx, `SELECT id FROM store WHERE name = ? LIMIT 1`, "signoz").Scan(&storeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch all orgs.
|
||||
var orgIDs []string
|
||||
rows, err := tx.QueryContext(ctx, `SELECT id FROM organizations`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var orgID string
|
||||
if err := rows.Scan(&orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
orgIDs = append(orgIDs, orgID)
|
||||
}
|
||||
|
||||
isPG := migration.sqlstore.BunDB().Dialect().Name() == dialect.PG
|
||||
|
||||
for _, orgID := range orgIDs {
|
||||
for _, tuple := range tuples {
|
||||
entropy := ulid.DefaultEntropy()
|
||||
now := time.Now().UTC()
|
||||
tupleID := ulid.MustNew(ulid.Timestamp(now), entropy).String()
|
||||
|
||||
objectID := "organization/" + orgID + "/" + tuple.objectName + "/*"
|
||||
roleSubject := "organization/" + orgID + "/role/" + tuple.roleName
|
||||
|
||||
if isPG {
|
||||
user := "role:" + roleSubject + "#assignee"
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO tuple (store, object_type, object_id, relation, _user, user_type, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, object_type, object_id, relation, _user) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, user, "userset", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
continue
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO changelog (store, object_type, object_id, relation, _user, operation, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, user, "TUPLE_OPERATION_WRITE", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, user_type, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, "role", roleSubject, "assignee", "userset", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
continue
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO changelog (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, operation, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, "role", roleSubject, "assignee", 0, tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *addServiceAccountManagedRoleTransactions) Down(context.Context, *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -176,7 +176,7 @@ func GetAdditionTuples(name string, orgID valuer.UUID, relation Relation, additi
|
||||
transactionTuples := NewTuples(
|
||||
resource,
|
||||
MustNewSubject(
|
||||
resource,
|
||||
coretypes.NewResourceRole(),
|
||||
name,
|
||||
orgID,
|
||||
&coretypes.VerbAssignee,
|
||||
@@ -200,7 +200,7 @@ func GetDeletionTuples(name string, orgID valuer.UUID, relation Relation, deleti
|
||||
transactionTuples := NewTuples(
|
||||
resource,
|
||||
MustNewSubject(
|
||||
resource,
|
||||
coretypes.NewResourceRole(),
|
||||
name,
|
||||
orgID,
|
||||
&coretypes.VerbAssignee,
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
type SelectorCallbackWithClaimsFn func(*http.Request, Claims) ([]coretypes.Selector, error)
|
||||
type SelectorCallbackWithoutClaimsFn func(*http.Request, []*types.Organization) ([]coretypes.Selector, valuer.UUID, error)
|
||||
|
||||
var (
|
||||
ErrCodeAuthZUnavailable = errors.MustNewCode("authz_unavailable")
|
||||
ErrCodeAuthZForbidden = errors.MustNewCode("authz_forbidden")
|
||||
|
||||
@@ -9,7 +9,6 @@ var Resources = []Resource{
|
||||
ResourceMetaResourcesRole,
|
||||
ResourceMetaResourcesOrganization,
|
||||
ResourceMetaResourcesServiceAccount,
|
||||
ResourceMetaResourcesServiceAccount,
|
||||
ResourceMetaResourcesUser,
|
||||
ResourceMetaResourceNotificationChannel,
|
||||
ResourceMetaResourcesNotificationChannel,
|
||||
|
||||
@@ -28,6 +28,8 @@ func NewVerb(verb string) (Verb, error) {
|
||||
return VerbList, nil
|
||||
case "assignee":
|
||||
return VerbAssignee, nil
|
||||
case "attach":
|
||||
return VerbAttach, nil
|
||||
default:
|
||||
return Verb{}, errors.Newf(errors.TypeInvalidInput, ErrCodeInvalidVerb, "verb %s is invalid, valid verbs are: %s", verb, Verb{}.Enum())
|
||||
}
|
||||
@@ -41,6 +43,7 @@ func (Verb) Enum() []any {
|
||||
VerbDelete,
|
||||
VerbList,
|
||||
VerbAssignee,
|
||||
VerbAttach,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
qb "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/go-playground/validator/v10"
|
||||
v1 "github.com/perses/perses/pkg/model/api/v1"
|
||||
"github.com/perses/perses/pkg/model/api/v1/common"
|
||||
"github.com/perses/perses/pkg/model/api/v1/dashboard"
|
||||
)
|
||||
|
||||
// StorableDashboardDataV2 wraps v1.DashboardSpec (Perses) with additional SigNoz-specific fields.
|
||||
//
|
||||
// We embed DashboardSpec (not v1.Dashboard) to avoid carrying Perses's Metadata
|
||||
// (Name, Project, CreatedAt, UpdatedAt, Tags, Version) and Kind field. SigNoz
|
||||
// manages identity (ID), timestamps (TimeAuditable), and multi-tenancy (OrgID)
|
||||
// separately on StorableDashboardV2/DashboardV2.
|
||||
//
|
||||
// The following v1 request fields map to locations inside v1.DashboardSpec:
|
||||
// - title → Display.Name (common.Display)
|
||||
// - description → Display.Description (common.Display)
|
||||
//
|
||||
// Fields that have no Perses equivalent will be added in this wrapper (like image, uploadGrafana, etc.)
|
||||
type StorableDashboardDataV2 = v1.DashboardSpec
|
||||
|
||||
// UnmarshalAndValidateDashboardV2JSON unmarshals the JSON into a StorableDashboardDataV2
|
||||
// (= PostableDashboardV2 = UpdatableDashboardV2) and validates plugin kinds and specs.
|
||||
func UnmarshalAndValidateDashboardV2JSON(data []byte) (*StorableDashboardDataV2, error) {
|
||||
var d StorableDashboardDataV2
|
||||
// Note: DashboardSpec has a custom UnmarshalJSON which prevents
|
||||
// DisallowUnknownFields from working at the top level. Unknown
|
||||
// fields in plugin specs are still rejected by validateAndNormalizePluginSpec.
|
||||
if err := json.Unmarshal(data, &d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateDashboardV2(d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// Plugin kind → spec type factory. Each value is a pointer to the zero value of the
|
||||
// expected spec struct. validatePluginSpec marshals plugin.Spec back to JSON and
|
||||
// unmarshals into the typed struct to catch field-level errors.
|
||||
var (
|
||||
panelPluginSpecs = map[PanelPluginKind]func() any{
|
||||
PanelKindTimeSeries: func() any { return new(TimeSeriesPanelSpec) },
|
||||
PanelKindBarChart: func() any { return new(BarChartPanelSpec) },
|
||||
PanelKindNumber: func() any { return new(NumberPanelSpec) },
|
||||
PanelKindPieChart: func() any { return new(PieChartPanelSpec) },
|
||||
PanelKindTable: func() any { return new(TablePanelSpec) },
|
||||
PanelKindHistogram: func() any { return new(HistogramPanelSpec) },
|
||||
PanelKindList: func() any { return new(ListPanelSpec) },
|
||||
}
|
||||
queryPluginSpecs = map[QueryPluginKind]func() any{
|
||||
QueryKindBuilder: func() any { return new(BuilderQuerySpec) },
|
||||
QueryKindComposite: func() any { return new(CompositeQuerySpec) },
|
||||
QueryKindFormula: func() any { return new(FormulaSpec) },
|
||||
QueryKindPromQL: func() any { return new(PromQLQuerySpec) },
|
||||
QueryKindClickHouseSQL: func() any { return new(ClickHouseSQLQuerySpec) },
|
||||
QueryKindTraceOperator: func() any { return new(TraceOperatorSpec) },
|
||||
}
|
||||
variablePluginSpecs = map[VariablePluginKind]func() any{
|
||||
VariableKindDynamic: func() any { return new(DynamicVariableSpec) },
|
||||
VariableKindQuery: func() any { return new(QueryVariableSpec) },
|
||||
VariableKindCustom: func() any { return new(CustomVariableSpec) },
|
||||
VariableKindTextbox: func() any { return new(TextboxVariableSpec) },
|
||||
}
|
||||
datasourcePluginSpecs = map[DatasourcePluginKind]func() any{
|
||||
DatasourceKindSigNoz: func() any { return new(struct{}) },
|
||||
}
|
||||
|
||||
// allowedQueryKinds maps each panel plugin kind to the query plugin
|
||||
// kinds it supports. Composite sub-query types are mapped to these
|
||||
// same kind strings via compositeSubQueryTypeToPluginKind.
|
||||
allowedQueryKinds = map[PanelPluginKind][]QueryPluginKind{
|
||||
PanelKindTimeSeries: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
|
||||
PanelKindBarChart: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
|
||||
PanelKindNumber: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
|
||||
PanelKindHistogram: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
|
||||
PanelKindPieChart: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindClickHouseSQL},
|
||||
PanelKindTable: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindClickHouseSQL},
|
||||
PanelKindList: {QueryKindBuilder},
|
||||
}
|
||||
|
||||
// compositeSubQueryTypeToPluginKind maps CompositeQuery sub-query type
|
||||
// strings to the equivalent top-level query plugin kind for validation.
|
||||
compositeSubQueryTypeToPluginKind = map[qb.QueryType]QueryPluginKind{
|
||||
qb.QueryTypeBuilder: QueryKindBuilder,
|
||||
qb.QueryTypeFormula: QueryKindFormula,
|
||||
qb.QueryTypeTraceOperator: QueryKindTraceOperator,
|
||||
qb.QueryTypePromQL: QueryKindPromQL,
|
||||
qb.QueryTypeClickHouseSQL: QueryKindClickHouseSQL,
|
||||
}
|
||||
)
|
||||
|
||||
func validateDashboardV2(d StorableDashboardDataV2) error {
|
||||
for name, ds := range d.Datasources {
|
||||
if err := validateDatasourcePlugin(&ds.Plugin, fmt.Sprintf("spec.datasources.%s.plugin", name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, v := range d.Variables {
|
||||
if err := validateVariablePlugin(v, fmt.Sprintf("spec.variables[%d]", i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for key, panel := range d.Panels {
|
||||
if panel == nil {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "spec.panels.%s: panel must not be null", key)
|
||||
}
|
||||
path := fmt.Sprintf("spec.panels.%s", key)
|
||||
if err := validatePanelPlugin(&panel.Spec.Plugin, path+".spec.plugin"); err != nil {
|
||||
return err
|
||||
}
|
||||
panelKind := PanelPluginKind(panel.Spec.Plugin.Kind)
|
||||
allowed := allowedQueryKinds[panelKind]
|
||||
for qi := range panel.Spec.Queries {
|
||||
queryPath := fmt.Sprintf("%s.spec.queries[%d].spec.plugin", path, qi)
|
||||
if err := validateQueryPlugin(&panel.Spec.Queries[qi].Spec.Plugin, queryPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateQueryAllowedForPanel(panel.Spec.Queries[qi].Spec.Plugin, allowed, panelKind, queryPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDatasourcePlugin(plugin *common.Plugin, path string) error {
|
||||
kind := DatasourcePluginKind(plugin.Kind)
|
||||
factory, ok := datasourcePluginSpecs[kind]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
|
||||
"%s: unknown datasource plugin kind %q; allowed values: %s", path, kind, formatEnum(kind.Enum()))
|
||||
}
|
||||
return validateAndNormalizePluginSpec(plugin, factory, path)
|
||||
}
|
||||
|
||||
func validateVariablePlugin(v dashboard.Variable, path string) error {
|
||||
switch spec := v.Spec.(type) {
|
||||
case *dashboard.ListVariableSpec:
|
||||
pluginPath := path + ".spec.plugin"
|
||||
kind := VariablePluginKind(spec.Plugin.Kind)
|
||||
factory, ok := variablePluginSpecs[kind]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
|
||||
"%s: unknown variable plugin kind %q; allowed values: %s", pluginPath, kind, formatEnum(kind.Enum()))
|
||||
}
|
||||
return validateAndNormalizePluginSpec(&spec.Plugin, factory, pluginPath)
|
||||
case *dashboard.TextVariableSpec:
|
||||
// TextVariables have no plugin, nothing to validate.
|
||||
return nil
|
||||
default:
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "%s: unsupported variable kind %q", path, v.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func validatePanelPlugin(plugin *common.Plugin, path string) error {
|
||||
kind := PanelPluginKind(plugin.Kind)
|
||||
factory, ok := panelPluginSpecs[kind]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
|
||||
"%s: unknown panel plugin kind %q; allowed values: %s", path, kind, formatEnum(kind.Enum()))
|
||||
}
|
||||
return validateAndNormalizePluginSpec(plugin, factory, path)
|
||||
}
|
||||
|
||||
func validateQueryPlugin(plugin *common.Plugin, path string) error {
|
||||
kind := QueryPluginKind(plugin.Kind)
|
||||
factory, ok := queryPluginSpecs[kind]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
|
||||
"%s: unknown query plugin kind %q; allowed values: %s", path, kind, formatEnum(kind.Enum()))
|
||||
}
|
||||
return validateAndNormalizePluginSpec(plugin, factory, path)
|
||||
}
|
||||
|
||||
func formatEnum(values []any) string {
|
||||
parts := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
parts[i] = fmt.Sprintf("`%v`", v)
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
// validateAndNormalizePluginSpec validates the plugin spec and writes the typed
|
||||
// struct (with defaults) back into plugin.Spec so that DB storage and API
|
||||
// responses contain normalized values.
|
||||
func validateAndNormalizePluginSpec(plugin *common.Plugin, factory func() any, path string) error {
|
||||
if plugin.Kind == "" {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "%s: plugin kind is required", path)
|
||||
}
|
||||
if plugin.Spec == nil {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "%s: plugin spec is required", path)
|
||||
}
|
||||
// Re-marshal the spec and unmarshal into the typed struct.
|
||||
specJSON, err := json.Marshal(plugin.Spec)
|
||||
if err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "%s.spec", path)
|
||||
}
|
||||
target := factory()
|
||||
decoder := json.NewDecoder(bytes.NewReader(specJSON))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(target); err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "%s.spec", path)
|
||||
}
|
||||
if err := validator.New().Struct(target); err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "%s.spec", path)
|
||||
}
|
||||
// Write the typed struct back so defaults are included.
|
||||
plugin.Spec = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateQueryAllowedForPanel checks that the query plugin kind is permitted
|
||||
// for the given panel. For composite queries it recurses into sub-queries.
|
||||
func validateQueryAllowedForPanel(plugin common.Plugin, allowed []QueryPluginKind, panelKind PanelPluginKind, path string) error {
|
||||
queryKind := QueryPluginKind(plugin.Kind)
|
||||
if !slices.Contains(allowed, queryKind) {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
|
||||
"%s: query kind %q is not supported by panel kind %q", path, queryKind, panelKind)
|
||||
}
|
||||
|
||||
// For composite queries, validate each sub-query type.
|
||||
if queryKind == QueryKindComposite && plugin.Spec != nil {
|
||||
specJSON, err := json.Marshal(plugin.Spec)
|
||||
if err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "%s.spec", path)
|
||||
}
|
||||
var composite struct {
|
||||
Queries []struct {
|
||||
Type qb.QueryType `json:"type"`
|
||||
} `json:"queries"`
|
||||
}
|
||||
if err := json.Unmarshal(specJSON, &composite); err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "%s.spec", path)
|
||||
}
|
||||
for si, sub := range composite.Queries {
|
||||
pluginKind, ok := compositeSubQueryTypeToPluginKind[sub.Type]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(allowed, pluginKind) {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
|
||||
"%s.spec.queries[%d]: sub-query type %q is not supported by panel kind %q",
|
||||
path, si, sub.Type, panelKind)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
107
pkg/types/dashboardtypes/perses_dashboard_data.go
Normal file
107
pkg/types/dashboardtypes/perses_dashboard_data.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
qb "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
v1 "github.com/perses/perses/pkg/model/api/v1"
|
||||
"github.com/perses/perses/pkg/model/api/v1/common"
|
||||
)
|
||||
|
||||
// DashboardData is the SigNoz dashboard v2 spec shape. It mirrors
|
||||
// v1.DashboardSpec (Perses) field-for-field, except every common.Plugin
|
||||
// occurrence is replaced with a typed SigNoz plugin whose OpenAPI schema is a
|
||||
// per-site discriminated oneOf.
|
||||
type DashboardData struct {
|
||||
Display *common.Display `json:"display,omitempty"`
|
||||
Datasources map[string]*DatasourceSpec `json:"datasources,omitempty"`
|
||||
Variables []Variable `json:"variables,omitempty"`
|
||||
Panels map[string]*Panel `json:"panels"`
|
||||
Layouts []Layout `json:"layouts"`
|
||||
Duration common.DurationString `json:"duration"`
|
||||
RefreshInterval common.DurationString `json:"refreshInterval,omitempty"`
|
||||
Links []v1.Link `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Unmarshal + validate entry point
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func (d *DashboardData) UnmarshalJSON(data []byte) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
dec.DisallowUnknownFields()
|
||||
type alias DashboardData
|
||||
var tmp alias
|
||||
if err := dec.Decode(&tmp); err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid dashboard spec")
|
||||
}
|
||||
*d = DashboardData(tmp)
|
||||
return d.Validate()
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Cross-field validation
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func (d *DashboardData) Validate() error {
|
||||
for key, panel := range d.Panels {
|
||||
if panel == nil {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "spec.panels.%s: panel must not be null", key)
|
||||
}
|
||||
path := fmt.Sprintf("spec.panels.%s", key)
|
||||
panelKind := panel.Spec.Plugin.Kind
|
||||
if len(panel.Spec.Queries) != 1 {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "%s.spec.queries: panel must have one query", path)
|
||||
}
|
||||
allowed := allowedQueryKinds[panelKind]
|
||||
for qi, q := range panel.Spec.Queries {
|
||||
queryPath := fmt.Sprintf("%s.spec.queries[%d].spec.plugin", path, qi)
|
||||
if err := validateQueryAllowedForPanel(q.Spec.Plugin, allowed, panelKind, queryPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateQueryAllowedForPanel(plugin QueryPlugin, allowed []QueryPluginKind, panelKind PanelPluginKind, path string) error {
|
||||
if !slices.Contains(allowed, plugin.Kind) {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
|
||||
"%s: query kind %q is not supported by panel kind %q", path, plugin.Kind, panelKind)
|
||||
}
|
||||
|
||||
if plugin.Kind != QueryKindComposite {
|
||||
return nil
|
||||
}
|
||||
composite, ok := plugin.Spec.(*CompositeQuerySpec)
|
||||
if !ok || composite == nil {
|
||||
// Unreachable via UnmarshalJSON; reaching here means a Go caller broke the Kind/Spec pairing.
|
||||
return errors.NewInternalf(errors.CodeInternal, "%s: composite query plugin has unexpected spec type %T", path, plugin.Spec)
|
||||
}
|
||||
for si, sub := range composite.Queries {
|
||||
subKind, ok := compositeSubQueryTypeToPluginKind[sub.Type]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(allowed, subKind) {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
|
||||
"%s.spec.queries[%d]: sub-query type %q is not supported by panel kind %q",
|
||||
path, si, sub.Type, panelKind)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
compositeSubQueryTypeToPluginKind = map[qb.QueryType]QueryPluginKind{
|
||||
qb.QueryTypeBuilder: QueryKindBuilder,
|
||||
qb.QueryTypeFormula: QueryKindFormula,
|
||||
qb.QueryTypeTraceOperator: QueryKindTraceOperator,
|
||||
qb.QueryTypePromQL: QueryKindPromQL,
|
||||
qb.QueryTypeClickHouseSQL: QueryKindClickHouseSQL,
|
||||
}
|
||||
)
|
||||
@@ -7,33 +7,75 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func unmarshalDashboard(data []byte) (*DashboardData, error) {
|
||||
var d DashboardData
|
||||
if err := json.Unmarshal(data, &d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func TestValidateBigExample(t *testing.T) {
|
||||
data, err := os.ReadFile("testdata/perses.json")
|
||||
require.NoError(t, err, "reading example file")
|
||||
_, err = UnmarshalAndValidateDashboardV2JSON(data)
|
||||
_, err = unmarshalDashboard(data)
|
||||
require.NoError(t, err, "expected valid dashboard")
|
||||
}
|
||||
|
||||
func TestValidateDashboardWithSections(t *testing.T) {
|
||||
data, err := os.ReadFile("testdata/perses_with_sections.json")
|
||||
require.NoError(t, err, "reading example file")
|
||||
_, err = UnmarshalAndValidateDashboardV2JSON(data)
|
||||
_, err = unmarshalDashboard(data)
|
||||
require.NoError(t, err, "expected valid dashboard")
|
||||
}
|
||||
|
||||
func TestInvalidateNotAJSON(t *testing.T) {
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON([]byte("not json"))
|
||||
_, err := unmarshalDashboard([]byte("not json"))
|
||||
require.Error(t, err, "expected error for invalid JSON")
|
||||
}
|
||||
|
||||
// TestUnmarshalErrorPreservesNestedMessage guards the wrap on dec.Decode in
|
||||
// DashboardData.UnmarshalJSON. The wrap stamps a consistent type/code on
|
||||
// decode failures, but must not smother the rich messages produced by nested
|
||||
// UnmarshalJSON methods (panel/query/variable/datasource plugin envelopes).
|
||||
func TestUnmarshalErrorPreservesNestedMessage(t *testing.T) {
|
||||
data := []byte(`{
|
||||
"panels": {
|
||||
"p1": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"plugin": {"kind": "NonExistentPanel", "spec": {}}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`)
|
||||
|
||||
_, err := unmarshalDashboard(data)
|
||||
require.Error(t, err)
|
||||
|
||||
require.Contains(t, err.Error(), "unknown panel plugin kind",
|
||||
"outer wrap should not smother the inner UnmarshalJSON message")
|
||||
require.Contains(t, err.Error(), `"NonExistentPanel"`,
|
||||
"the offending value should still appear in the error")
|
||||
require.Contains(t, err.Error(), "allowed values:",
|
||||
"the allowed-values hint should still appear in the error")
|
||||
|
||||
assert.True(t, errors.Ast(err, errors.TypeInvalidInput),
|
||||
"outer wrap should classify the error as TypeInvalidInput")
|
||||
assert.True(t, errors.Asc(err, ErrCodeDashboardInvalidInput),
|
||||
"outer wrap should stamp ErrCodeDashboardInvalidInput")
|
||||
}
|
||||
|
||||
func TestValidateEmptySpec(t *testing.T) {
|
||||
// no variables no panels
|
||||
data := []byte(`{}`)
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON(data)
|
||||
_, err := unmarshalDashboard(data)
|
||||
require.NoError(t, err, "expected valid")
|
||||
}
|
||||
|
||||
@@ -59,17 +101,13 @@ func TestValidateOnlyVariables(t *testing.T) {
|
||||
"kind": "TextVariable",
|
||||
"spec": {
|
||||
"name": "mytext",
|
||||
"value": "default",
|
||||
"plugin": {
|
||||
"kind": "signoz/TextboxVariable",
|
||||
"spec": {}
|
||||
}
|
||||
"value": "default"
|
||||
}
|
||||
}
|
||||
],
|
||||
"layouts": []
|
||||
}`)
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON(data)
|
||||
_, err := unmarshalDashboard(data)
|
||||
require.NoError(t, err, "expected valid")
|
||||
}
|
||||
|
||||
@@ -148,7 +186,7 @@ func TestInvalidateUnknownPluginKind(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON([]byte(tt.data))
|
||||
_, err := unmarshalDashboard([]byte(tt.data))
|
||||
require.Error(t, err, "expected error containing %q, got nil", tt.wantContain)
|
||||
require.Contains(t, err.Error(), tt.wantContain, "error should mention %q", tt.wantContain)
|
||||
})
|
||||
@@ -169,7 +207,7 @@ func TestInvalidateOneInvalidPanel(t *testing.T) {
|
||||
},
|
||||
"layouts": []
|
||||
}`)
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON(data)
|
||||
_, err := unmarshalDashboard(data)
|
||||
require.Error(t, err, "expected error for invalid panel plugin kind")
|
||||
require.Contains(t, err.Error(), "FakePanel", "error should mention FakePanel")
|
||||
}
|
||||
@@ -245,7 +283,7 @@ func TestRejectUnknownFieldsInPluginSpec(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON([]byte(tt.data))
|
||||
_, err := unmarshalDashboard([]byte(tt.data))
|
||||
require.Error(t, err, "expected error for unknown field")
|
||||
require.Contains(t, err.Error(), tt.wantContain, "error should mention %q", tt.wantContain)
|
||||
})
|
||||
@@ -323,7 +361,7 @@ func TestInvalidateWrongFieldTypeInPluginSpec(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON([]byte(tt.data))
|
||||
_, err := unmarshalDashboard([]byte(tt.data))
|
||||
require.Error(t, err, "expected validation error")
|
||||
if tt.wantContain != "" {
|
||||
require.Contains(t, err.Error(), tt.wantContain, "error should mention %q", tt.wantContain)
|
||||
@@ -531,13 +569,69 @@ func TestInvalidateBadPanelSpecValues(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON([]byte(tt.data))
|
||||
_, err := unmarshalDashboard([]byte(tt.data))
|
||||
require.Error(t, err, "expected error containing %q, got nil", tt.wantContain)
|
||||
require.Contains(t, err.Error(), tt.wantContain, "error should mention %q", tt.wantContain)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidatePanelWithoutQueries(t *testing.T) {
|
||||
data := []byte(`{
|
||||
"panels": {
|
||||
"p1": {
|
||||
"kind": "Panel",
|
||||
"spec": {"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}}}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`)
|
||||
_, err := unmarshalDashboard(data)
|
||||
require.Error(t, err, "expected panel-without-queries to be rejected")
|
||||
require.Contains(t, err.Error(), "panel must have one query")
|
||||
}
|
||||
|
||||
func TestInvalidatePanelWithEmptyQueriesArray(t *testing.T) {
|
||||
data := []byte(`{
|
||||
"panels": {
|
||||
"p1": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}},
|
||||
"queries": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`)
|
||||
_, err := unmarshalDashboard(data)
|
||||
require.Error(t, err, "expected panel with explicit empty queries array to be rejected")
|
||||
require.Contains(t, err.Error(), "panel must have one query")
|
||||
}
|
||||
|
||||
// Rendering multiple data sources in a single panel is supported via
|
||||
// signoz/CompositeQuery, not by listing multiple top-level queries.
|
||||
func TestInvalidatePanelWithMultipleDirectQueries(t *testing.T) {
|
||||
data := []byte(`{
|
||||
"panels": {
|
||||
"p1": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}},
|
||||
"queries": [
|
||||
{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/BuilderQuery", "spec": {"name": "A", "signal": "metrics"}}}},
|
||||
{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/BuilderQuery", "spec": {"name": "B", "signal": "metrics"}}}}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`)
|
||||
_, err := unmarshalDashboard(data)
|
||||
require.Error(t, err, "expected panel with two top-level queries to be rejected")
|
||||
require.Contains(t, err.Error(), "panel must have one query")
|
||||
}
|
||||
|
||||
func TestValidateRequiredFields(t *testing.T) {
|
||||
wrapVariable := func(pluginKind, pluginSpec string) string {
|
||||
return `{
|
||||
@@ -626,7 +720,7 @@ func TestValidateRequiredFields(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON([]byte(tt.data))
|
||||
_, err := unmarshalDashboard([]byte(tt.data))
|
||||
require.Error(t, err, "expected error containing %q, got nil", tt.wantContain)
|
||||
require.Contains(t, err.Error(), tt.wantContain, "error should mention %q", tt.wantContain)
|
||||
})
|
||||
@@ -642,13 +736,14 @@ func TestTimeSeriesPanelDefaults(t *testing.T) {
|
||||
"plugin": {
|
||||
"kind": "signoz/TimeSeriesPanel",
|
||||
"spec": {}
|
||||
}
|
||||
},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`)
|
||||
d, err := UnmarshalAndValidateDashboardV2JSON(data)
|
||||
d, err := unmarshalDashboard(data)
|
||||
require.NoError(t, err, "unmarshal and validate failed")
|
||||
|
||||
// After validation+normalization, the plugin spec should be a typed struct.
|
||||
@@ -689,13 +784,14 @@ func TestNumberPanelDefaults(t *testing.T) {
|
||||
"plugin": {
|
||||
"kind": "signoz/NumberPanel",
|
||||
"spec": {"thresholds": [{"value": 100, "color": "Red"}]}
|
||||
}
|
||||
},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`)
|
||||
d, err := UnmarshalAndValidateDashboardV2JSON(data)
|
||||
d, err := unmarshalDashboard(data)
|
||||
require.NoError(t, err, "unmarshal and validate failed")
|
||||
|
||||
require.IsType(t, &NumberPanelSpec{}, d.Panels["p1"].Spec.Plugin.Spec)
|
||||
@@ -716,6 +812,30 @@ func TestNumberPanelDefaults(t *testing.T) {
|
||||
"expected stored/response JSON to contain operator:>, got: %s", outputStr)
|
||||
}
|
||||
|
||||
// TestPersesFixtureStorageRoundTrip exercises the typed → map[string]any →
|
||||
// typed cycle that the create/get path performs against the kitchen-sink
|
||||
// fixture. Catches plugin specs whose UnmarshalJSON expects a different shape
|
||||
// than the default MarshalJSON emits.
|
||||
func TestPersesFixtureStorageRoundTrip(t *testing.T) {
|
||||
raw, err := os.ReadFile("testdata/perses.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
var data DashboardData
|
||||
require.NoError(t, json.Unmarshal(raw, &data), "initial unmarshal")
|
||||
|
||||
marshaled, err := json.Marshal(data)
|
||||
require.NoError(t, err, "marshal typed → JSON")
|
||||
|
||||
var asMap map[string]any
|
||||
require.NoError(t, json.Unmarshal(marshaled, &asMap), "JSON → map (storage shape)")
|
||||
|
||||
remarshaled, err := json.Marshal(asMap)
|
||||
require.NoError(t, err, "map → JSON (read-back shape)")
|
||||
|
||||
var roundtripped DashboardData
|
||||
require.NoError(t, json.Unmarshal(remarshaled, &roundtripped), "JSON → typed (the failure mode)")
|
||||
}
|
||||
|
||||
// TestStorageRoundTrip simulates the future DB store/load cycle:
|
||||
// marshal the normalized dashboard to JSON (what would be written to DB),
|
||||
// then unmarshal it back (what would be read from DB), and verify defaults survive.
|
||||
@@ -728,7 +848,8 @@ func TestStorageRoundTrip(t *testing.T) {
|
||||
"plugin": {
|
||||
"kind": "signoz/TimeSeriesPanel",
|
||||
"spec": {}
|
||||
}
|
||||
},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
}
|
||||
},
|
||||
"p2": {
|
||||
@@ -737,7 +858,8 @@ func TestStorageRoundTrip(t *testing.T) {
|
||||
"plugin": {
|
||||
"kind": "signoz/NumberPanel",
|
||||
"spec": {"thresholds": [{"value": 100, "color": "Red"}]}
|
||||
}
|
||||
},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -745,7 +867,7 @@ func TestStorageRoundTrip(t *testing.T) {
|
||||
}`)
|
||||
|
||||
// Step 1: Unmarshal + validate + normalize (what the API handler does).
|
||||
d, err := UnmarshalAndValidateDashboardV2JSON(input)
|
||||
d, err := unmarshalDashboard(input)
|
||||
require.NoError(t, err, "unmarshal and validate failed")
|
||||
|
||||
// Step 1.5: Verify struct fields have correct defaults (extra validation before storing).
|
||||
@@ -765,7 +887,7 @@ func TestStorageRoundTrip(t *testing.T) {
|
||||
require.NoError(t, err, "marshal for storage failed")
|
||||
|
||||
// Step 3: Unmarshal from JSON (simulates reading from DB).
|
||||
loaded, err := UnmarshalAndValidateDashboardV2JSON(stored)
|
||||
loaded, err := unmarshalDashboard(stored)
|
||||
require.NoError(t, err, "unmarshal from storage failed")
|
||||
|
||||
// Step 3.5: Verify struct fields have correct defaults after loading (before returning in API).
|
||||
@@ -878,7 +1000,7 @@ func TestPanelTypeQueryTypeCompatibility(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := UnmarshalAndValidateDashboardV2JSON(tc.data)
|
||||
_, err := unmarshalDashboard(tc.data)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
170
pkg/types/dashboardtypes/perses_drift_test.go
Normal file
170
pkg/types/dashboardtypes/perses_drift_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package dashboardtypes
|
||||
|
||||
// TestDashboardDataMatchesPerses asserts that DashboardData
|
||||
// and every nested SigNoz-owned type cover the JSON field set of their Perses
|
||||
// counterpart.
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
v1 "github.com/perses/perses/pkg/model/api/v1"
|
||||
"github.com/perses/perses/pkg/model/api/v1/dashboard"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDashboardDataMatchesPerses(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
ours reflect.Type
|
||||
perses reflect.Type
|
||||
}{
|
||||
{"DashboardSpec", typeOf[DashboardData](), typeOf[v1.DashboardSpec]()},
|
||||
{"Panel", typeOf[Panel](), typeOf[v1.Panel]()},
|
||||
{"PanelSpec", typeOf[PanelSpec](), typeOf[v1.PanelSpec]()},
|
||||
{"Query", typeOf[Query](), typeOf[v1.Query]()},
|
||||
{"QuerySpec", typeOf[QuerySpec](), typeOf[v1.QuerySpec]()},
|
||||
{"DatasourceSpec", typeOf[DatasourceSpec](), typeOf[v1.DatasourceSpec]()},
|
||||
{"Variable", typeOf[Variable](), typeOf[dashboard.Variable]()},
|
||||
{"ListVariableSpec", typeOf[ListVariableSpec](), typeOf[dashboard.ListVariableSpec]()},
|
||||
{"Layout", typeOf[Layout](), typeOf[dashboard.Layout]()},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
missing, extra := drift(c.ours, c.perses)
|
||||
|
||||
assert.Empty(t, missing,
|
||||
"DashboardData (%s) is missing json fields present on Perses %s — upstream likely added or renamed a field",
|
||||
c.ours.Name(), c.perses.Name())
|
||||
assert.Empty(t, extra,
|
||||
"DashboardData (%s) has json fields absent on Perses %s — upstream likely removed a field or we added one without the counterpart",
|
||||
c.ours.Name(), c.perses.Name())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriftDetectionMechanics(t *testing.T) {
|
||||
t.Run("upstream added a field", func(t *testing.T) {
|
||||
type ours struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type perses struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
missing, extra := drift(typeOf[ours](), typeOf[perses]())
|
||||
assert.Equal(t, []string{"description"}, missing, "missing fires: upstream has a field we don't")
|
||||
assert.Empty(t, extra)
|
||||
})
|
||||
|
||||
t.Run("upstream removed a field", func(t *testing.T) {
|
||||
type ours struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
type perses struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
missing, extra := drift(typeOf[ours](), typeOf[perses]())
|
||||
assert.Empty(t, missing)
|
||||
assert.Equal(t, []string{"description"}, extra, "extra fires: we kept a field upstream removed")
|
||||
})
|
||||
|
||||
t.Run("upstream renamed a field", func(t *testing.T) {
|
||||
type ours struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type perses struct {
|
||||
Name string `json:"title"`
|
||||
}
|
||||
missing, extra := drift(typeOf[ours](), typeOf[perses]())
|
||||
assert.Equal(t, []string{"title"}, missing, "missing fires for the new name")
|
||||
assert.Equal(t, []string{"name"}, extra, "extra fires for the old name — both fire on a rename")
|
||||
})
|
||||
|
||||
t.Run("we added a field upstream does not have", func(t *testing.T) {
|
||||
type ours struct {
|
||||
Name string `json:"name"`
|
||||
Internal string `json:"internal"`
|
||||
}
|
||||
type perses struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
missing, extra := drift(typeOf[ours](), typeOf[perses]())
|
||||
assert.Empty(t, missing)
|
||||
assert.Equal(t, []string{"internal"}, extra, "extra fires: we added a field with no upstream counterpart")
|
||||
})
|
||||
|
||||
t.Run("embedded struct flattens — drift inside the embed is caught", func(t *testing.T) {
|
||||
type embedded struct {
|
||||
Display string `json:"display"`
|
||||
NewBit string `json:"newBit"` // upstream added this inside the embed
|
||||
}
|
||||
type ours struct {
|
||||
Display string `json:"display"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type perses struct {
|
||||
embedded `json:",inline"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
missing, extra := drift(typeOf[ours](), typeOf[perses]())
|
||||
assert.Equal(t, []string{"newBit"}, missing, "field added inside an inlined embed surfaces at the parent level")
|
||||
assert.Empty(t, extra)
|
||||
})
|
||||
}
|
||||
|
||||
func drift(ours, perses reflect.Type) (missing, extra []string) {
|
||||
o, p := jsonFields(ours), jsonFields(perses)
|
||||
return sortedDiff(p, o), sortedDiff(o, p)
|
||||
}
|
||||
|
||||
// jsonFields returns the set of json tag names for a struct, flattening
|
||||
// anonymous embedded fields (matching encoding/json behavior).
|
||||
func jsonFields(t reflect.Type) map[string]struct{} {
|
||||
out := map[string]struct{}{}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return out
|
||||
}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
// Skip unexported fields (e.g., dashboard.ListVariableSpec has an
|
||||
// unexported `variableSpec` interface tag).
|
||||
if !f.IsExported() && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
tag := f.Tag.Get("json")
|
||||
name := strings.Split(tag, ",")[0]
|
||||
// Anonymous embed with empty json name (no tag, or `json:",inline"` /
|
||||
// `json:",omitempty"`-style options-only tag) is flattened by encoding/json.
|
||||
if f.Anonymous && name == "" {
|
||||
for k := range jsonFields(f.Type) {
|
||||
out[k] = struct{}{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if tag == "-" || name == "" {
|
||||
continue
|
||||
}
|
||||
out[name] = struct{}{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// sortedDiff returns keys in a but not in b, sorted.
|
||||
func sortedDiff(a, b map[string]struct{}) []string {
|
||||
var diff []string
|
||||
for k := range a {
|
||||
if _, ok := b[k]; !ok {
|
||||
diff = append(diff, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(diff)
|
||||
return diff
|
||||
}
|
||||
|
||||
func typeOf[T any]() reflect.Type { return reflect.TypeOf((*T)(nil)).Elem() }
|
||||
312
pkg/types/dashboardtypes/perses_plugin_wrappers.go
Normal file
312
pkg/types/dashboardtypes/perses_plugin_wrappers.go
Normal file
@@ -0,0 +1,312 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
)
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Panel plugin
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type PanelPlugin struct {
|
||||
Kind PanelPluginKind `json:"kind"`
|
||||
Spec any `json:"spec"`
|
||||
}
|
||||
|
||||
// PrepareJSONSchema drops the reflected struct shape (type: object, properties)
|
||||
// from the envelope so that only the JSONSchemaOneOf result binds.
|
||||
func (PanelPlugin) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return clearOneOfParentShape(s)
|
||||
}
|
||||
|
||||
func (p *PanelPlugin) UnmarshalJSON(data []byte) error {
|
||||
kind, specJSON, err := extractKindAndSpec(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
factory, ok := panelPluginSpecs[PanelPluginKind(kind)]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "unknown panel plugin kind %q; allowed values: %s", kind, allowedValuesForKind(slices.Sorted(maps.Keys(panelPluginSpecs))))
|
||||
}
|
||||
spec, err := decodeSpec(specJSON, factory(), kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Kind = PanelPluginKind(kind)
|
||||
p.Spec = spec
|
||||
return nil
|
||||
}
|
||||
|
||||
func (PanelPlugin) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
PanelPluginVariant[TimeSeriesPanelSpec]{Kind: string(PanelKindTimeSeries)},
|
||||
PanelPluginVariant[BarChartPanelSpec]{Kind: string(PanelKindBarChart)},
|
||||
PanelPluginVariant[NumberPanelSpec]{Kind: string(PanelKindNumber)},
|
||||
PanelPluginVariant[PieChartPanelSpec]{Kind: string(PanelKindPieChart)},
|
||||
PanelPluginVariant[TablePanelSpec]{Kind: string(PanelKindTable)},
|
||||
PanelPluginVariant[HistogramPanelSpec]{Kind: string(PanelKindHistogram)},
|
||||
PanelPluginVariant[ListPanelSpec]{Kind: string(PanelKindList)},
|
||||
}
|
||||
}
|
||||
|
||||
type PanelPluginVariant[S any] struct {
|
||||
Kind string `json:"kind" required:"true"`
|
||||
Spec S `json:"spec" required:"true"`
|
||||
}
|
||||
|
||||
func (v PanelPluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return restrictKindToOneValue(s, v.Kind)
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Query plugin
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type QueryPlugin struct {
|
||||
Kind QueryPluginKind `json:"kind"`
|
||||
Spec any `json:"spec"`
|
||||
}
|
||||
|
||||
func (QueryPlugin) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return clearOneOfParentShape(s)
|
||||
}
|
||||
|
||||
func (p *QueryPlugin) UnmarshalJSON(data []byte) error {
|
||||
kind, specJSON, err := extractKindAndSpec(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
factory, ok := queryPluginSpecs[QueryPluginKind(kind)]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "unknown query plugin kind %q; allowed values: %s", kind, allowedValuesForKind(slices.Sorted(maps.Keys(queryPluginSpecs))))
|
||||
}
|
||||
spec, err := decodeSpec(specJSON, factory(), kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Kind = QueryPluginKind(kind)
|
||||
p.Spec = spec
|
||||
return nil
|
||||
}
|
||||
|
||||
func (QueryPlugin) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
QueryPluginVariant[BuilderQuerySpec]{Kind: string(QueryKindBuilder)},
|
||||
QueryPluginVariant[CompositeQuerySpec]{Kind: string(QueryKindComposite)},
|
||||
QueryPluginVariant[FormulaSpec]{Kind: string(QueryKindFormula)},
|
||||
QueryPluginVariant[PromQLQuerySpec]{Kind: string(QueryKindPromQL)},
|
||||
QueryPluginVariant[ClickHouseSQLQuerySpec]{Kind: string(QueryKindClickHouseSQL)},
|
||||
QueryPluginVariant[TraceOperatorSpec]{Kind: string(QueryKindTraceOperator)},
|
||||
}
|
||||
}
|
||||
|
||||
type QueryPluginVariant[S any] struct {
|
||||
Kind string `json:"kind" required:"true"`
|
||||
Spec S `json:"spec" required:"true"`
|
||||
}
|
||||
|
||||
func (v QueryPluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return restrictKindToOneValue(s, v.Kind)
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Variable plugin
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type VariablePlugin struct {
|
||||
Kind VariablePluginKind `json:"kind"`
|
||||
Spec any `json:"spec"`
|
||||
}
|
||||
|
||||
func (VariablePlugin) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return clearOneOfParentShape(s)
|
||||
}
|
||||
|
||||
func (p *VariablePlugin) UnmarshalJSON(data []byte) error {
|
||||
kind, specJSON, err := extractKindAndSpec(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
factory, ok := variablePluginSpecs[VariablePluginKind(kind)]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "unknown variable plugin kind %q; allowed values: %s", kind, allowedValuesForKind(slices.Sorted(maps.Keys(variablePluginSpecs))))
|
||||
}
|
||||
spec, err := decodeSpec(specJSON, factory(), kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Kind = VariablePluginKind(kind)
|
||||
p.Spec = spec
|
||||
return nil
|
||||
}
|
||||
|
||||
func (VariablePlugin) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
VariablePluginVariant[DynamicVariableSpec]{Kind: string(VariableKindDynamic)},
|
||||
VariablePluginVariant[QueryVariableSpec]{Kind: string(VariableKindQuery)},
|
||||
VariablePluginVariant[CustomVariableSpec]{Kind: string(VariableKindCustom)},
|
||||
}
|
||||
}
|
||||
|
||||
type VariablePluginVariant[S any] struct {
|
||||
Kind string `json:"kind" required:"true"`
|
||||
Spec S `json:"spec" required:"true"`
|
||||
}
|
||||
|
||||
func (v VariablePluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return restrictKindToOneValue(s, v.Kind)
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Datasource plugin
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type DatasourcePlugin struct {
|
||||
Kind DatasourcePluginKind `json:"kind"`
|
||||
Spec any `json:"spec"`
|
||||
}
|
||||
|
||||
func (DatasourcePlugin) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return clearOneOfParentShape(s)
|
||||
}
|
||||
|
||||
func (p *DatasourcePlugin) UnmarshalJSON(data []byte) error {
|
||||
kind, specJSON, err := extractKindAndSpec(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
factory, ok := datasourcePluginSpecs[DatasourcePluginKind(kind)]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "unknown datasource plugin kind %q; allowed values: %s", kind, allowedValuesForKind(slices.Sorted(maps.Keys(datasourcePluginSpecs))))
|
||||
}
|
||||
spec, err := decodeSpec(specJSON, factory(), kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Kind = DatasourcePluginKind(kind)
|
||||
p.Spec = spec
|
||||
return nil
|
||||
}
|
||||
|
||||
func (DatasourcePlugin) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
DatasourcePluginVariant[struct{}]{Kind: string(DatasourceKindSigNoz)},
|
||||
}
|
||||
}
|
||||
|
||||
type DatasourcePluginVariant[S any] struct {
|
||||
Kind string `json:"kind" required:"true"`
|
||||
Spec S `json:"spec" required:"true"`
|
||||
}
|
||||
|
||||
func (v DatasourcePluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return restrictKindToOneValue(s, v.Kind)
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Helpers
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
var (
|
||||
panelPluginSpecs = map[PanelPluginKind]func() any{
|
||||
PanelKindTimeSeries: func() any { return new(TimeSeriesPanelSpec) },
|
||||
PanelKindBarChart: func() any { return new(BarChartPanelSpec) },
|
||||
PanelKindNumber: func() any { return new(NumberPanelSpec) },
|
||||
PanelKindPieChart: func() any { return new(PieChartPanelSpec) },
|
||||
PanelKindTable: func() any { return new(TablePanelSpec) },
|
||||
PanelKindHistogram: func() any { return new(HistogramPanelSpec) },
|
||||
PanelKindList: func() any { return new(ListPanelSpec) },
|
||||
}
|
||||
queryPluginSpecs = map[QueryPluginKind]func() any{
|
||||
QueryKindBuilder: func() any { return new(BuilderQuerySpec) },
|
||||
QueryKindComposite: func() any { return new(CompositeQuerySpec) },
|
||||
QueryKindFormula: func() any { return new(FormulaSpec) },
|
||||
QueryKindPromQL: func() any { return new(PromQLQuerySpec) },
|
||||
QueryKindClickHouseSQL: func() any { return new(ClickHouseSQLQuerySpec) },
|
||||
QueryKindTraceOperator: func() any { return new(TraceOperatorSpec) },
|
||||
}
|
||||
variablePluginSpecs = map[VariablePluginKind]func() any{
|
||||
VariableKindDynamic: func() any { return new(DynamicVariableSpec) },
|
||||
VariableKindQuery: func() any { return new(QueryVariableSpec) },
|
||||
VariableKindCustom: func() any { return new(CustomVariableSpec) },
|
||||
}
|
||||
datasourcePluginSpecs = map[DatasourcePluginKind]func() any{
|
||||
DatasourceKindSigNoz: func() any { return new(struct{}) },
|
||||
}
|
||||
|
||||
allowedQueryKinds = map[PanelPluginKind][]QueryPluginKind{
|
||||
PanelKindTimeSeries: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
|
||||
PanelKindBarChart: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
|
||||
PanelKindNumber: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
|
||||
PanelKindHistogram: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
|
||||
PanelKindPieChart: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindClickHouseSQL},
|
||||
PanelKindTable: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindClickHouseSQL},
|
||||
PanelKindList: {QueryKindBuilder},
|
||||
}
|
||||
)
|
||||
|
||||
func allowedValuesForKind[K ~string](kinds []K) string {
|
||||
parts := make([]string, len(kinds))
|
||||
for i, k := range kinds {
|
||||
parts[i] = "`" + string(k) + "`"
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
// extractKindAndSpec parses a {"kind": "...", "spec": {...}} envelope and returns
|
||||
// kind and the raw spec bytes for typed decoding.
|
||||
func extractKindAndSpec(data []byte) (string, []byte, error) {
|
||||
var head struct {
|
||||
Kind string `json:"kind"`
|
||||
Spec json.RawMessage `json:"spec"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &head); err != nil {
|
||||
return "", nil, errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid plugin envelope")
|
||||
}
|
||||
return head.Kind, head.Spec, nil
|
||||
}
|
||||
|
||||
// decodeSpec strict-decodes a spec JSON into target and runs struct-tag validation (go-playground/validator).
|
||||
func decodeSpec(specJSON []byte, target any, kind string) (any, error) {
|
||||
if len(specJSON) == 0 {
|
||||
return nil, errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "kind %q: spec is required", kind)
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(specJSON))
|
||||
dec.DisallowUnknownFields()
|
||||
if err := dec.Decode(target); err != nil {
|
||||
return nil, errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "kind %q: invalid spec JSON", kind)
|
||||
}
|
||||
if err := validator.New().Struct(target); err != nil {
|
||||
return nil, errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "kind %q: spec failed validation", kind)
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// clearOneOfParentShape drops Type and Properties on a schema that also has a JSONSchemaOneOf.
|
||||
func clearOneOfParentShape(s *jsonschema.Schema) error {
|
||||
s.Type = nil
|
||||
s.Properties = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// restrictKindToOneValue ensures that the schema only allows one Kind value for a type.
|
||||
// For eg. PanelPluginVariant[TimeSeriesPanelSpec]{Kind: string(PanelKindTimeSeries)} should
|
||||
// only allow "signoz/TimeSeriesPanel" in its kind field.
|
||||
func restrictKindToOneValue(schema *jsonschema.Schema, kind string) error {
|
||||
kindProp, ok := schema.Properties["kind"]
|
||||
if !ok || kindProp.TypeObject == nil {
|
||||
return errors.NewInternalf(errors.CodeInternal, "variant schema missing `kind` property")
|
||||
}
|
||||
kindProp.TypeObject.WithEnum(kind)
|
||||
schema.Properties["kind"] = kindProp
|
||||
return nil
|
||||
}
|
||||
182
pkg/types/dashboardtypes/perses_replicas.go
Normal file
182
pkg/types/dashboardtypes/perses_replicas.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
v1 "github.com/perses/perses/pkg/model/api/v1"
|
||||
"github.com/perses/perses/pkg/model/api/v1/common"
|
||||
"github.com/perses/perses/pkg/model/api/v1/dashboard"
|
||||
"github.com/perses/perses/pkg/model/api/v1/variable"
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
)
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Datasource
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type DatasourceSpec struct {
|
||||
Display *common.Display `json:"display,omitempty"`
|
||||
Default bool `json:"default"`
|
||||
Plugin DatasourcePlugin `json:"plugin"`
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Panel
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type Panel struct {
|
||||
Kind string `json:"kind"`
|
||||
Spec PanelSpec `json:"spec"`
|
||||
}
|
||||
|
||||
type PanelSpec struct {
|
||||
Display *v1.PanelDisplay `json:"display,omitempty"`
|
||||
Plugin PanelPlugin `json:"plugin"`
|
||||
Queries []Query `json:"queries,omitempty"`
|
||||
Links []v1.Link `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Query
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type Query struct {
|
||||
Kind string `json:"kind"`
|
||||
Spec QuerySpec `json:"spec"`
|
||||
}
|
||||
|
||||
type QuerySpec struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Plugin QueryPlugin `json:"plugin"`
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Variable
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
// Variable is the list/text sum type. Spec is set to *ListVariableSpec or
|
||||
// *dashboard.TextVariableSpec by UnmarshalJSON based on Kind. The schema is a
|
||||
// discriminated oneOf (see JSONSchemaOneOf).
|
||||
type Variable struct {
|
||||
Kind variable.Kind `json:"kind"`
|
||||
Spec any `json:"spec"`
|
||||
}
|
||||
|
||||
func (Variable) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return clearOneOfParentShape(s)
|
||||
}
|
||||
|
||||
func (v *Variable) UnmarshalJSON(data []byte) error {
|
||||
kind, specJSON, err := extractKindAndSpec(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch kind {
|
||||
case string(variable.KindList):
|
||||
spec, err := decodeSpec(specJSON, new(ListVariableSpec), kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Kind = variable.KindList
|
||||
v.Spec = spec
|
||||
case string(variable.KindText):
|
||||
spec, err := decodeSpec(specJSON, new(dashboard.TextVariableSpec), kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Kind = variable.KindText
|
||||
v.Spec = spec
|
||||
default:
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "unknown variable kind %q; allowed values: %s", kind, allowedValuesForKind([]variable.Kind{variable.KindList, variable.KindText}))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Variable) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
VariableEnvelope[ListVariableSpec]{Kind: string(variable.KindList)},
|
||||
VariableEnvelope[dashboard.TextVariableSpec]{Kind: string(variable.KindText)},
|
||||
}
|
||||
}
|
||||
|
||||
type VariableEnvelope[S any] struct {
|
||||
Kind string `json:"kind" required:"true"`
|
||||
Spec S `json:"spec" required:"true"`
|
||||
}
|
||||
|
||||
func (v VariableEnvelope[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return restrictKindToOneValue(s, v.Kind)
|
||||
}
|
||||
|
||||
// ListVariableSpec mirrors dashboard.ListVariableSpec (variable.ListSpec
|
||||
// fields + Name) but with a typed VariablePlugin replacing common.Plugin.
|
||||
type ListVariableSpec struct {
|
||||
Display *variable.Display `json:"display,omitempty"`
|
||||
DefaultValue *variable.DefaultValue `json:"defaultValue,omitempty"`
|
||||
AllowAllValue bool `json:"allowAllValue"`
|
||||
AllowMultiple bool `json:"allowMultiple"`
|
||||
CustomAllValue string `json:"customAllValue,omitempty"`
|
||||
CapturingRegexp string `json:"capturingRegexp,omitempty"`
|
||||
Sort *variable.Sort `json:"sort,omitempty"`
|
||||
Plugin VariablePlugin `json:"plugin"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// Layout
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
// Layout is the dashboard layout sum type. Spec is populated by UnmarshalJSON
|
||||
// with the concrete layout spec struct (today only dashboard.GridLayoutSpec)
|
||||
// based on Kind. No plugin is involved, so we reuse the Perses spec types as
|
||||
// leaf imports.
|
||||
type Layout struct {
|
||||
Kind dashboard.LayoutKind `json:"kind"`
|
||||
Spec any `json:"spec"`
|
||||
}
|
||||
|
||||
// layoutSpecs is the layout sum type factory. Perses only defines
|
||||
// KindGridLayout today; adding a new kind upstream surfaces as an
|
||||
// "unknown layout kind" runtime error here until we add it.
|
||||
var layoutSpecs = map[dashboard.LayoutKind]func() any{
|
||||
dashboard.KindGridLayout: func() any { return new(dashboard.GridLayoutSpec) },
|
||||
}
|
||||
|
||||
func (Layout) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return clearOneOfParentShape(s)
|
||||
}
|
||||
|
||||
func (l *Layout) UnmarshalJSON(data []byte) error {
|
||||
kind, specJSON, err := extractKindAndSpec(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
factory, ok := layoutSpecs[dashboard.LayoutKind(kind)]
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "unknown layout kind %q; allowed values: %s", kind, allowedValuesForKind(slices.Sorted(maps.Keys(layoutSpecs))))
|
||||
}
|
||||
spec, err := decodeSpec(specJSON, factory(), kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Kind = dashboard.LayoutKind(kind)
|
||||
l.Spec = spec
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Layout) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
LayoutEnvelope[dashboard.GridLayoutSpec]{Kind: string(dashboard.KindGridLayout)},
|
||||
}
|
||||
}
|
||||
|
||||
type LayoutEnvelope[S any] struct {
|
||||
Kind string `json:"kind" required:"true"`
|
||||
Spec S `json:"spec" required:"true"`
|
||||
}
|
||||
|
||||
func (v LayoutEnvelope[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return restrictKindToOneValue(s, v.Kind)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
qb "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
)
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
@@ -20,11 +21,10 @@ const (
|
||||
VariableKindDynamic VariablePluginKind = "signoz/DynamicVariable"
|
||||
VariableKindQuery VariablePluginKind = "signoz/QueryVariable"
|
||||
VariableKindCustom VariablePluginKind = "signoz/CustomVariable"
|
||||
VariableKindTextbox VariablePluginKind = "signoz/TextboxVariable"
|
||||
)
|
||||
|
||||
func (VariablePluginKind) Enum() []any {
|
||||
return []any{VariableKindDynamic, VariableKindQuery, VariableKindCustom, VariableKindTextbox}
|
||||
return []any{VariableKindDynamic, VariableKindQuery, VariableKindCustom}
|
||||
}
|
||||
|
||||
type DynamicVariableSpec struct {
|
||||
@@ -42,8 +42,6 @@ type CustomVariableSpec struct {
|
||||
CustomValue string `json:"customValue" validate:"required" required:"true"`
|
||||
}
|
||||
|
||||
type TextboxVariableSpec struct{}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// SigNoz query plugin specs — aliased from querybuildertypesv5
|
||||
// ══════════════════════════════════════════════
|
||||
@@ -87,6 +85,30 @@ func (b *BuilderQuerySpec) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON delegates to the inner Spec so the on-wire shape matches what
|
||||
// UnmarshalJSON expects (a flat builder-query payload with `signal` at the top
|
||||
// level). Without this, Go's default would wrap it as {"Spec": {...}} and the
|
||||
// signal-dispatch on read would fail.
|
||||
func (b BuilderQuerySpec) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(b.Spec)
|
||||
}
|
||||
|
||||
// PrepareJSONSchema drops the reflected struct shape so only the
|
||||
// JSONSchemaOneOf result binds.
|
||||
func (BuilderQuerySpec) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
return clearOneOfParentShape(s)
|
||||
}
|
||||
|
||||
// JSONSchemaOneOf exposes the three signal-dispatched shapes a builder query
|
||||
// can take. Mirrors qb.UnmarshalBuilderQueryBySignal's runtime dispatch.
|
||||
func (BuilderQuerySpec) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
qb.QueryBuilderQuery[qb.LogAggregation]{},
|
||||
qb.QueryBuilderQuery[qb.MetricAggregation]{},
|
||||
qb.QueryBuilderQuery[qb.TraceAggregation]{},
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// SigNoz panel plugin specs
|
||||
// ══════════════════════════════════════════════
|
||||
@@ -76,11 +76,7 @@
|
||||
"display": {
|
||||
"name": "textboxvar"
|
||||
},
|
||||
"value": "defaultvaluegoeshere",
|
||||
"plugin": {
|
||||
"kind": "signoz/TextboxVariable",
|
||||
"spec": {}
|
||||
}
|
||||
"value": "defaultvaluegoeshere"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -47,12 +47,13 @@ const (
|
||||
type ValidationOption func(*validationConfig)
|
||||
|
||||
type validationConfig struct {
|
||||
skipLimitOffsetValidation bool
|
||||
skipAggregationValidation bool
|
||||
skipHavingValidation bool
|
||||
skipAggregationOrderBy bool
|
||||
skipSelectFieldValidation bool
|
||||
skipGroupByValidation bool
|
||||
skipLimitOffsetValidation bool
|
||||
skipAggregationValidation bool
|
||||
skipHavingValidation bool
|
||||
skipAggregationOrderBy bool
|
||||
skipSelectFieldValidation bool
|
||||
skipGroupByValidation bool
|
||||
withTimestampGroupByValidation bool
|
||||
}
|
||||
|
||||
func applyValidationOptions(opts []ValidationOption) validationConfig {
|
||||
@@ -111,6 +112,13 @@ func WithSkipGroupByValidation() ValidationOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimestampGroupByValidation enables validation to disallow grouping by timestamp field.
|
||||
func WithTimestampGroupByValidation() ValidationOption {
|
||||
return func(cfg *validationConfig) {
|
||||
cfg.withTimestampGroupByValidation = true
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs preliminary validation on QueryBuilderQuery.
|
||||
func (q *QueryBuilderQuery[T]) Validate(opts ...ValidationOption) error {
|
||||
cfg := applyValidationOptions(opts)
|
||||
@@ -177,6 +185,11 @@ func (q *QueryBuilderQuery[T]) validateGroupBy(cfg validationConfig) error {
|
||||
errors.CodeInvalidInput, "invalid empty key name for group by at index %d", idx,
|
||||
)
|
||||
}
|
||||
if cfg.withTimestampGroupByValidation && item.Name == "timestamp" {
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput, "group by on timestamp is not allowed",
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -665,7 +678,9 @@ func validateQueryEnvelope(envelope QueryEnvelope, opts ...ValidationOption) err
|
||||
|
||||
func GetValidationOptions(requestType RequestType) []ValidationOption {
|
||||
switch requestType {
|
||||
case RequestTypeTimeSeries, RequestTypeScalar:
|
||||
case RequestTypeTimeSeries:
|
||||
return []ValidationOption{WithSkipSelectFieldValidation(), WithTimestampGroupByValidation()}
|
||||
case RequestTypeScalar:
|
||||
return []ValidationOption{WithSkipSelectFieldValidation()}
|
||||
case RequestTypeRaw, RequestTypeRawStream, RequestTypeTrace:
|
||||
return []ValidationOption{WithSkipAggregationValidation(), WithSkipHavingValidation(), WithSkipAggregationOrderBy(), WithSkipGroupByValidation()}
|
||||
|
||||
@@ -695,6 +695,59 @@ func TestQueryRangeRequest_ValidateCompositeQuery(t *testing.T) {
|
||||
wantErr: true,
|
||||
errMsg: "raw request type is not supported for metric queries",
|
||||
},
|
||||
{
|
||||
name: "timeseries request with group by timestamp should return error",
|
||||
request: QueryRangeRequest{
|
||||
Start: 1640995200000,
|
||||
End: 1640998800000,
|
||||
RequestType: RequestTypeTimeSeries,
|
||||
CompositeQuery: CompositeQuery{
|
||||
Queries: []QueryEnvelope{
|
||||
{
|
||||
Type: QueryTypeBuilder,
|
||||
Spec: QueryBuilderQuery[LogAggregation]{
|
||||
Name: "A",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Aggregations: []LogAggregation{
|
||||
{Expression: "count()"},
|
||||
},
|
||||
GroupBy: []GroupByKey{
|
||||
{TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "timestamp"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "group by on timestamp is not allowed",
|
||||
},
|
||||
{
|
||||
name: "scalar request with group by timestamp should pass",
|
||||
request: QueryRangeRequest{
|
||||
Start: 1640995200000,
|
||||
End: 1640998800000,
|
||||
RequestType: RequestTypeScalar,
|
||||
CompositeQuery: CompositeQuery{
|
||||
Queries: []QueryEnvelope{
|
||||
{
|
||||
Type: QueryTypeBuilder,
|
||||
Spec: QueryBuilderQuery[LogAggregation]{
|
||||
Name: "A",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Aggregations: []LogAggregation{
|
||||
{Expression: "count()"},
|
||||
},
|
||||
GroupBy: []GroupByKey{
|
||||
{TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "timestamp"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "raw request with log query without aggregations should pass",
|
||||
request: QueryRangeRequest{
|
||||
|
||||
@@ -250,17 +250,20 @@ type EvaluationEnvelope struct {
|
||||
|
||||
// evaluationRolling is the OpenAPI schema for an EvaluationEnvelope with kind=rolling.
|
||||
type evaluationRolling struct {
|
||||
Kind EvaluationKind `json:"kind" description:"The kind of evaluation."`
|
||||
Spec RollingWindow `json:"spec" description:"The rolling window evaluation specification."`
|
||||
Kind EvaluationKind `json:"kind" description:"The kind of evaluation." required:"true"`
|
||||
Spec RollingWindow `json:"spec" description:"The rolling window evaluation specification." required:"true"`
|
||||
}
|
||||
|
||||
// evaluationCumulative is the OpenAPI schema for an EvaluationEnvelope with kind=cumulative.
|
||||
type evaluationCumulative struct {
|
||||
Kind EvaluationKind `json:"kind" description:"The kind of evaluation."`
|
||||
Spec CumulativeWindow `json:"spec" description:"The cumulative window evaluation specification."`
|
||||
Kind EvaluationKind `json:"kind" description:"The kind of evaluation." required:"true"`
|
||||
Spec CumulativeWindow `json:"spec" description:"The cumulative window evaluation specification." required:"true"`
|
||||
}
|
||||
|
||||
var _ jsonschema.OneOfExposer = EvaluationEnvelope{}
|
||||
var (
|
||||
_ jsonschema.OneOfExposer = EvaluationEnvelope{}
|
||||
_ jsonschema.Preparer = EvaluationEnvelope{}
|
||||
)
|
||||
|
||||
// JSONSchemaOneOf returns the oneOf variants for the EvaluationEnvelope discriminated union.
|
||||
// Each variant represents a different evaluation kind with its corresponding spec schema.
|
||||
@@ -271,6 +274,22 @@ func (EvaluationEnvelope) JSONSchemaOneOf() []any {
|
||||
}
|
||||
}
|
||||
|
||||
func (EvaluationEnvelope) PrepareJSONSchema(schema *jsonschema.Schema) error {
|
||||
if schema.ExtraProperties == nil {
|
||||
schema.ExtraProperties = map[string]any{}
|
||||
}
|
||||
|
||||
schema.ExtraProperties["x-signoz-discriminator"] = map[string]any{
|
||||
"propertyName": "kind",
|
||||
"mapping": map[string]string{
|
||||
"rolling": "#/components/schemas/RuletypesEvaluationRolling",
|
||||
"cumulative": "#/components/schemas/RuletypesEvaluationCumulative",
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EvaluationEnvelope) UnmarshalJSON(data []byte) error {
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
|
||||
@@ -36,11 +36,14 @@ type RuleThresholdData struct {
|
||||
|
||||
// thresholdBasic is the OpenAPI schema for a RuleThresholdData with kind=basic.
|
||||
type thresholdBasic struct {
|
||||
Kind ThresholdKind `json:"kind" description:"The kind of threshold."`
|
||||
Spec BasicRuleThresholds `json:"spec" description:"The basic threshold specification (array of thresholds)."`
|
||||
Kind ThresholdKind `json:"kind" description:"The kind of threshold." required:"true"`
|
||||
Spec BasicRuleThresholds `json:"spec" description:"The basic threshold specification (array of thresholds)." required:"true"`
|
||||
}
|
||||
|
||||
var _ jsonschema.OneOfExposer = RuleThresholdData{}
|
||||
var (
|
||||
_ jsonschema.OneOfExposer = RuleThresholdData{}
|
||||
_ jsonschema.Preparer = RuleThresholdData{}
|
||||
)
|
||||
|
||||
// JSONSchemaOneOf returns the oneOf variants for the RuleThresholdData discriminated union.
|
||||
// Each variant represents a different threshold kind with its corresponding spec schema.
|
||||
@@ -50,6 +53,24 @@ func (RuleThresholdData) JSONSchemaOneOf() []any {
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareJSONSchema marks the schema with x-signoz-discriminator;
|
||||
// signoz.attachDiscriminators promotes it to a real OpenAPI 3
|
||||
// discriminator after reflection.
|
||||
func (RuleThresholdData) PrepareJSONSchema(schema *jsonschema.Schema) error {
|
||||
if schema.ExtraProperties == nil {
|
||||
schema.ExtraProperties = map[string]any{}
|
||||
}
|
||||
|
||||
schema.ExtraProperties["x-signoz-discriminator"] = map[string]any{
|
||||
"propertyName": "kind",
|
||||
"mapping": map[string]string{
|
||||
"basic": "#/components/schemas/RuletypesThresholdBasic",
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RuleThresholdData) UnmarshalJSON(data []byte) error {
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
|
||||
@@ -25,6 +25,8 @@ pytest_plugins = [
|
||||
"fixtures.cloudintegrations",
|
||||
"fixtures.jsontypes",
|
||||
"fixtures.seeder",
|
||||
"fixtures.serviceaccount",
|
||||
"fixtures.role",
|
||||
]
|
||||
|
||||
|
||||
|
||||
76
tests/fixtures/role.py
vendored
Normal file
76
tests/fixtures/role.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
"""Fixtures and helpers for role tests."""
|
||||
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
ROLES_BASE = "/api/v1/roles"
|
||||
|
||||
|
||||
def find_role_by_name(signoz: types.SigNoz, token: str, name: str) -> str:
|
||||
"""Find a role by name from the roles endpoint and return its UUID."""
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(ROLES_BASE),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
roles = resp.json()["data"]
|
||||
role = next(r for r in roles if r["name"] == name)
|
||||
return role["id"]
|
||||
|
||||
|
||||
def create_custom_role(signoz: types.SigNoz, token: str, name: str) -> str:
|
||||
"""Create a custom role and return its ID."""
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(ROLES_BASE),
|
||||
json={"name": name},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.CREATED, resp.text
|
||||
return resp.json()["data"]["id"]
|
||||
|
||||
|
||||
def delete_custom_role(signoz: types.SigNoz, token: str, role_id: str) -> None:
|
||||
"""Delete a custom role."""
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{ROLES_BASE}/{role_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, resp.text
|
||||
|
||||
|
||||
def patch_role_objects(
|
||||
signoz: types.SigNoz,
|
||||
token: str,
|
||||
role_id: str,
|
||||
relation: str,
|
||||
additions=None,
|
||||
deletions=None,
|
||||
) -> None:
|
||||
"""PATCH /api/v1/roles/{id}/relations/{relation}/objects."""
|
||||
body = {}
|
||||
if additions is not None:
|
||||
body["additions"] = additions
|
||||
if deletions is not None:
|
||||
body["deletions"] = deletions
|
||||
|
||||
resp = requests.patch(
|
||||
signoz.self.host_configs["8080"].get(f"{ROLES_BASE}/{role_id}/relations/{relation}/objects"),
|
||||
json=body,
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, f"PatchObjects {relation} failed: {resp.text}"
|
||||
|
||||
|
||||
def object_group(type_name: str, kind_name: str, selectors: list[str]) -> dict:
|
||||
"""Build an ObjectGroup dict for PatchObjects."""
|
||||
return {"resource": {"type": type_name, "kind": kind_name}, "selectors": selectors}
|
||||
26
tests/fixtures/serviceaccount.py
vendored
26
tests/fixtures/serviceaccount.py
vendored
@@ -6,24 +6,11 @@ import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
from fixtures.role import ROLES_BASE, find_role_by_name # noqa: F401 — re-export for existing callers
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
SERVICE_ACCOUNT_BASE = "/api/v1/service_accounts"
|
||||
ROLES_BASE = "/api/v1/roles"
|
||||
|
||||
|
||||
def find_role_by_name(signoz: types.SigNoz, token: str, name: str) -> str:
|
||||
"""Find a role by name from the roles endpoint and return its UUID."""
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(ROLES_BASE),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
roles = resp.json()["data"]
|
||||
role = next(r for r in roles if r["name"] == name)
|
||||
return role["id"]
|
||||
|
||||
|
||||
def create_service_account(signoz: types.SigNoz, token: str, name: str, role: str = "signoz-viewer") -> str:
|
||||
@@ -75,6 +62,17 @@ def delete_service_account(signoz: types.SigNoz, token: str, service_account_id:
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, resp.text
|
||||
|
||||
|
||||
def get_first_key_id(signoz: types.SigNoz, token: str, service_account_id: str) -> str:
|
||||
"""Return the ID of the first API key for a service account."""
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{service_account_id}/keys"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
return resp.json()["data"][0]["id"]
|
||||
|
||||
|
||||
def find_service_account_by_name(signoz: types.SigNoz, token: str, name: str) -> dict:
|
||||
"""Find a service account by name from the list endpoint."""
|
||||
list_resp = requests.get(
|
||||
|
||||
578
tests/integration/tests/serviceaccount/06_fga.py
Normal file
578
tests/integration/tests/serviceaccount/06_fga.py
Normal file
@@ -0,0 +1,578 @@
|
||||
"""Tests for resource-level FGA on service account endpoints.
|
||||
|
||||
Validates that a custom role with specific SA permissions gets exactly
|
||||
the access it was granted, and that SA role assignment requires BOTH
|
||||
serviceaccount:attach AND role:attach.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
from wiremock.resources.mappings import Mapping
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import (
|
||||
USER_ADMIN_EMAIL,
|
||||
USER_ADMIN_PASSWORD,
|
||||
add_license,
|
||||
change_user_role,
|
||||
create_active_user,
|
||||
find_user_by_email,
|
||||
)
|
||||
from fixtures.role import (
|
||||
create_custom_role,
|
||||
delete_custom_role,
|
||||
find_role_by_name,
|
||||
object_group,
|
||||
patch_role_objects,
|
||||
)
|
||||
from fixtures.serviceaccount import (
|
||||
SERVICE_ACCOUNT_BASE,
|
||||
create_service_account,
|
||||
find_service_account_by_name,
|
||||
get_first_key_id,
|
||||
)
|
||||
|
||||
SA_FGA_CUSTOM_ROLE_NAME = "sa-fga-readonly"
|
||||
SA_FGA_CUSTOM_USER_EMAIL = "customrole+safga@integration.test"
|
||||
SA_FGA_CUSTOM_USER_PASSWORD = "password123Z$"
|
||||
SA_FGA_TARGET_SA_NAME = "sa-fga-target"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Apply license (required for custom role CRUD)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_apply_license(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
make_http_mocks: Callable[[types.TestContainerDocker, list[Mapping]], None],
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
add_license(signoz, make_http_mocks, get_token)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Create custom role + user
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_create_custom_role_readonly_sa(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create the custom role.
|
||||
role_id = create_custom_role(signoz, admin_token, SA_FGA_CUSTOM_ROLE_NAME)
|
||||
|
||||
# Grant read on serviceaccount instances.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"read",
|
||||
additions=[
|
||||
object_group("serviceaccount", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Grant list on serviceaccount collection.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"list",
|
||||
additions=[
|
||||
object_group("metaresources", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Create the custom-role user: invite as VIEWER, activate, change role.
|
||||
user_id = create_active_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=SA_FGA_CUSTOM_USER_EMAIL,
|
||||
role="VIEWER",
|
||||
password=SA_FGA_CUSTOM_USER_PASSWORD,
|
||||
name="sa-fga-test-user",
|
||||
)
|
||||
change_user_role(signoz, admin_token, user_id, "signoz-viewer", SA_FGA_CUSTOM_ROLE_NAME)
|
||||
|
||||
# Create a target SA (with role + key) for the custom user to operate on.
|
||||
sa_id = create_service_account(signoz, admin_token, SA_FGA_TARGET_SA_NAME, role="signoz-viewer")
|
||||
|
||||
# Create a key on the target SA.
|
||||
key_resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/keys"),
|
||||
json={"name": "fga-key", "expiresAt": 0},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert key_resp.status_code == HTTPStatus.CREATED, key_resp.text
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Read-only access: allowed operations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_readonly_role_allowed_operations(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(SA_FGA_CUSTOM_USER_EMAIL, SA_FGA_CUSTOM_USER_PASSWORD)
|
||||
sa_id = find_service_account_by_name(signoz, get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD), SA_FGA_TARGET_SA_NAME)["id"]
|
||||
|
||||
# List SAs.
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(SERVICE_ACCOUNT_BASE),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, f"list SAs: {resp.text}"
|
||||
|
||||
# Get SA.
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, f"get SA: {resp.text}"
|
||||
|
||||
# Get SA roles.
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, f"get SA roles: {resp.text}"
|
||||
|
||||
# List SA keys.
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/keys"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, f"list SA keys: {resp.text}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Read-only access: forbidden operations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_readonly_role_forbidden_operations(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
token = get_token(SA_FGA_CUSTOM_USER_EMAIL, SA_FGA_CUSTOM_USER_PASSWORD)
|
||||
sa_id = find_service_account_by_name(signoz, admin_token, SA_FGA_TARGET_SA_NAME)["id"]
|
||||
viewer_role_id = find_role_by_name(signoz, admin_token, "signoz-viewer")
|
||||
key_id = get_first_key_id(signoz, admin_token, sa_id)
|
||||
|
||||
# Create SA — forbidden.
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(SERVICE_ACCOUNT_BASE),
|
||||
json={"name": "sa-fga-should-fail"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"create SA: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Update SA — forbidden.
|
||||
resp = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}"),
|
||||
json={"name": "sa-fga-renamed"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"update SA: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Delete SA — forbidden.
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"delete SA: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Assign role to SA — forbidden.
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles"),
|
||||
json={"id": viewer_role_id},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"assign SA role: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Remove role from SA — forbidden.
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles/{viewer_role_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"remove SA role: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Create key — forbidden (needs update).
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/keys"),
|
||||
json={"name": "fga-key-fail", "expiresAt": 0},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"create key: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Revoke key — forbidden (needs update).
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/keys/{key_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"revoke key: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Grant write permissions, verify access opens up
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_patch_role_add_write_permissions(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_by_name(signoz, admin_token, SA_FGA_CUSTOM_ROLE_NAME)
|
||||
sa_id = find_service_account_by_name(signoz, admin_token, SA_FGA_TARGET_SA_NAME)["id"]
|
||||
viewer_role_id = find_role_by_name(signoz, admin_token, "signoz-viewer")
|
||||
|
||||
# Grant create on collection.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"create",
|
||||
additions=[
|
||||
object_group("metaresources", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Grant update on instances.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"update",
|
||||
additions=[
|
||||
object_group("serviceaccount", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Grant delete on instances.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"delete",
|
||||
additions=[
|
||||
object_group("serviceaccount", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
custom_token = get_token(SA_FGA_CUSTOM_USER_EMAIL, SA_FGA_CUSTOM_USER_PASSWORD)
|
||||
|
||||
# Create SA — now allowed.
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(SERVICE_ACCOUNT_BASE),
|
||||
json={"name": "sa-fga-write-test"},
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.CREATED, f"create SA: {resp.text}"
|
||||
new_sa_id = resp.json()["data"]["id"]
|
||||
|
||||
# Update SA — now allowed.
|
||||
resp = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{new_sa_id}"),
|
||||
json={"name": "sa-fga-write-renamed"},
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, f"update SA: {resp.text}"
|
||||
|
||||
# Create key — now allowed (update permission covers key create).
|
||||
key_resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{new_sa_id}/keys"),
|
||||
json={"name": "fga-write-key", "expiresAt": 0},
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert key_resp.status_code == HTTPStatus.CREATED, f"create key: {key_resp.text}"
|
||||
new_key_id = key_resp.json()["data"]["id"]
|
||||
|
||||
# Revoke key — now allowed (update permission covers key revoke).
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{new_sa_id}/keys/{new_key_id}"),
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, f"revoke key: {resp.text}"
|
||||
|
||||
# Delete SA — now allowed.
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{new_sa_id}"),
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, f"delete SA: {resp.text}"
|
||||
|
||||
# Role assignment still forbidden (no attach).
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles"),
|
||||
json={"id": viewer_role_id},
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"assign SA role: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles/{viewer_role_id}"),
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"remove SA role: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Dual-attach: SA attach only (no role attach) → forbidden
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_attach_with_only_sa_attach_forbidden(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_by_name(signoz, admin_token, SA_FGA_CUSTOM_ROLE_NAME)
|
||||
sa_id = find_service_account_by_name(signoz, admin_token, SA_FGA_TARGET_SA_NAME)["id"]
|
||||
viewer_role_id = find_role_by_name(signoz, admin_token, "signoz-viewer")
|
||||
|
||||
# Grant attach on serviceaccount only.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"attach",
|
||||
additions=[
|
||||
object_group("serviceaccount", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
custom_token = get_token(SA_FGA_CUSTOM_USER_EMAIL, SA_FGA_CUSTOM_USER_PASSWORD)
|
||||
|
||||
# Assign role — forbidden (has SA attach, missing role attach).
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles"),
|
||||
json={"id": viewer_role_id},
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"assign with only SA attach: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Remove role — forbidden (CheckAll: role attach group fails).
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles/{viewer_role_id}"),
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"remove with only SA attach: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. Dual-attach: role attach only (no SA attach) → forbidden
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_attach_with_only_role_attach_forbidden(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_by_name(signoz, admin_token, SA_FGA_CUSTOM_ROLE_NAME)
|
||||
sa_id = find_service_account_by_name(signoz, admin_token, SA_FGA_TARGET_SA_NAME)["id"]
|
||||
viewer_role_id = find_role_by_name(signoz, admin_token, "signoz-viewer")
|
||||
|
||||
# Remove SA attach, grant role attach.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"attach",
|
||||
additions=[object_group("role", "role", ["*"])],
|
||||
deletions=[object_group("serviceaccount", "serviceaccount", ["*"])],
|
||||
)
|
||||
|
||||
custom_token = get_token(SA_FGA_CUSTOM_USER_EMAIL, SA_FGA_CUSTOM_USER_PASSWORD)
|
||||
|
||||
# Assign role — forbidden (middleware SA attach check fails).
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles"),
|
||||
json={"id": viewer_role_id},
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"assign with only role attach: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Remove role — forbidden (CheckAll: SA attach group fails).
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles/{viewer_role_id}"),
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"remove with only role attach: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 8. Dual-attach: both SA + role attach → succeeds
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_attach_with_both_permissions_succeeds(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_by_name(signoz, admin_token, SA_FGA_CUSTOM_ROLE_NAME)
|
||||
sa_id = find_service_account_by_name(signoz, admin_token, SA_FGA_TARGET_SA_NAME)["id"]
|
||||
|
||||
# Add back SA attach (role attach already present from previous test).
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"attach",
|
||||
additions=[
|
||||
object_group("serviceaccount", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
custom_token = get_token(SA_FGA_CUSTOM_USER_EMAIL, SA_FGA_CUSTOM_USER_PASSWORD)
|
||||
|
||||
# The target SA currently has signoz-viewer assigned. Assign a different role.
|
||||
editor_role_id = find_role_by_name(signoz, admin_token, "signoz-editor")
|
||||
|
||||
# Assign editor role — should succeed (both SA attach + role attach).
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles"),
|
||||
json={"id": editor_role_id},
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, f"assign with both attach: {resp.text}"
|
||||
|
||||
# Remove the editor role — should succeed (CheckAll: both groups pass).
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}/roles/{editor_role_id}"),
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, f"remove with both attach: {resp.text}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 9. Revoke read/list → verify access lost
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_remove_read_permissions_revokes_access(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_by_name(signoz, admin_token, SA_FGA_CUSTOM_ROLE_NAME)
|
||||
sa_id = find_service_account_by_name(signoz, admin_token, SA_FGA_TARGET_SA_NAME)["id"]
|
||||
|
||||
# Revoke read.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"read",
|
||||
deletions=[
|
||||
object_group("serviceaccount", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Revoke list.
|
||||
patch_role_objects(
|
||||
signoz,
|
||||
admin_token,
|
||||
role_id,
|
||||
"list",
|
||||
deletions=[
|
||||
object_group("metaresources", "serviceaccount", ["*"]),
|
||||
],
|
||||
)
|
||||
|
||||
custom_token = get_token(SA_FGA_CUSTOM_USER_EMAIL, SA_FGA_CUSTOM_USER_PASSWORD)
|
||||
|
||||
# List SAs — forbidden.
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(SERVICE_ACCOUNT_BASE),
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"list SAs after revoke: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
# Get SA — forbidden.
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{sa_id}"),
|
||||
headers={"Authorization": f"Bearer {custom_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"get SA after revoke: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 10. Clean up: delete custom role
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_delete_custom_role_cleanup(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_by_name(signoz, admin_token, SA_FGA_CUSTOM_ROLE_NAME)
|
||||
user = find_user_by_email(signoz, admin_token, SA_FGA_CUSTOM_USER_EMAIL)
|
||||
|
||||
# Remove the custom role from the user first — role deletion requires no assignees.
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user['id']}/roles"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
roles = resp.json()["data"]
|
||||
custom_entry = next((r for r in roles if r["name"] == SA_FGA_CUSTOM_ROLE_NAME), None)
|
||||
if custom_entry is not None:
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user['id']}/roles/{custom_entry['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, f"remove role from user: {resp.text}"
|
||||
|
||||
delete_custom_role(signoz, admin_token, role_id)
|
||||
Reference in New Issue
Block a user