mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-06 13:10:40 +01:00
refactor: split typeable infrastructure into pkg/types/coretypes (#11105)
* refactor: move authtypes to coretypes
* refactor: migrate downstream consumers to coretypes Kind/Type/Relation
Wire all consumers of the typeable infrastructure through coretypes:
- Replace authtypes.Name/Type/Relation references with coretypes equivalents
- Switch Typeable singletons to constructor calls (authtypes.NewTypeableUser
etc.), with the embedded coretypes.Typeable populated so Kind/Type/Prefix/
Scope dispatch correctly through the embed
- Update dashboardtypes meta-resource declarations to use authtypes
constructors so they expose Tuples (authz callers need it)
- Rename Resource.Name field accesses to Resource.Kind to match the field
rename in authtypes.Resource
- Fix typeable_metaresource.go calling the plural NewTypeableMetaResources
helper — should be the singular NewTypeableMetaResource
go build ./... and go vet ./... clean (parser-generated unreachable-code
warnings are pre-existing). Authz unit tests pass.
* refactor(audittypes): unify Action with coretypes.Relation
Drop the duplicate Action enum from audittypes — the verbs (create/update/
delete) match coretypes.Relation exactly. Move PastTense onto Relation so
audit EventName derivation continues to work without a parallel hierarchy.
Also retypes AuditDef.ResourceKind from string to coretypes.Kind so audit
declarations get the same regex validation that authz already enforces.
* refactor(retentiontypes): extract TTLSetting into its own package
TTLSetting is the bun model for ClickHouse TTL settings — has nothing to do
with the Organization domain it was previously co-located with in
pkg/types/organization.go. Moved to pkg/types/retentiontypes/ alongside the
ClickHouse reader that's its sole consumer.
No schema change; the bun table tag (table:ttl_setting) is unchanged.
* chore(openapi): regenerate spec for coretypes.Relation and Resource.Kind
* chore(frontend): regenerate API client and migrate Resource.name → Resource.kind
Regenerated TypeScript API types after the AuthtypesResource field rename
and the new CoretypesRelation enum. Updated:
- frontend/scripts/generate-permissions-type.cjs to read `r.kind` from the
/api/v1/authz/resources response and emit `kind:` in the static
permissions.type.ts file.
- frontend/src/hooks/useAuthZ/{permissions.type,types,utils,useAuthZ}.tsx:
Resource.name → Resource.kind throughout.
- frontend/src/container/RolesSettings/{utils.tsx,__tests__/utils.test.ts}:
same field migration.
- frontend/src/components/createGuardedRoute/createGuardedRoute.test.tsx:
same.
- useAuthZ/utils.ts: cast string relations to CoretypesRelationDTO at the
AuthtypesTransactionDTO boundary now that relation is an enum, not a raw
string.
yarn generate:api passes (orval generation + lint + typecheck).
* refactor: migrate downstream consumers to Resource/Verb rename
* chore(openapi): regenerate spec for Resource/Verb rename
* feat(coretypes): add ListResources accessor with stable sort
* feat(cmd): add 'generate authz' subcommand for permissions type
* refactor(authz): drop runtime authz/resources endpoint
* refactor(frontend): consume static permissions.type.ts directly
* chore(frontend): regenerate Orval client without authz/resources
* ci: move authz schema check from jsci to goci
* refactor(coretypes): move Selector/Object/Transaction from authtypes
* feat(coretypes): add managed role names and permission policy
* feat(coretypes): add Registry assembling resources, types, and managed-role transactions
* refactor(authz): wire *coretypes.Registry; drop RegisterTypeable
* refactor(cmd): wire coretypes.NewRegistry into server bootstraps
* chore: regenerate openapi spec for authtypes -> coretypes type moves
* chore(frontend): regenerate API client for Authtypes -> Coretypes type moves
* refactor(coretypes): rename GettableResource to ResourceRef
* refactor(authz): collapse Registry around static data; bridge once at construction
* refactor(coretypes): tighten Registry, restore anonymous public-dashboard grant
Drops passthrough fields from coretypes.Registry; adds an O(1) lookup map
for NewResourceFromTypeAndKind; replaces stringly-typed Type compares with
Type.Equals; removes the now-redundant getUniqueTypes helper. Restores the
signoz-anonymous read grant on metaresource/public-dashboard that was
silently dropped, and removes the invalid signoz-admin/VerbCreate/TypeUser
entry that panicked at startup.
* chore: regenerate openapi spec for coretypes -> authtypes type moves
* chore(frontend): regenerate API client for Coretypes -> Authtypes type moves
* fix(authz): disambiguate kind→type by relation, preserve multi-part selectors
permissions.type.ts now lists the same kind (dashboard, role,
public-dashboard) under both metaresource and metaresources, so the prior
kind→type map silently overwrote one with the other. Resolve the type
using the requesting relation's allowed types, and slice the selector at
the first colon so multi-part selectors (e.g. id:version) round-trip
correctly. Updates useAuthZ.test.tsx to use the regenerated kind field.
* refactor(authtypes): introduce Relation wrapper over coretypes.Verb
The authz layer modeled relations as raw coretypes.Verb everywhere, which
forced authz-level concepts (action, role-binding) to share a type with
schema-level enumerations. Introduce authtypes.Relation as a thin wrapper
over coretypes.Verb so the authz APIs (CheckWithTupleCreation, ListObjects,
GetObjects, PatchObjects, NewTuples, Transaction.Relation, etc.) can grow
authz-specific affordances without leaking back into coretypes.
Also reshuffles the static coretypes data into dedicated registry_*.go files
(types, kinds, verbs, resources, managed roles) to keep the schema declarations
isolated from the value types they configure.
* refactor(authtypes): expose Relation.Enum() and regenerate openapi spec
Without an Enum() method on Relation the openapi generator emitted an
empty AuthtypesRelation schema (no allowed values). Forward the enum
from the embedded coretypes.Verb so the wire contract is faithful.
* refactor(ee/authz): drop always-nil error returns from managed-role tuple helpers
getManagedRoleGrantTuples and getManagedRoleTransactionTuples never
returned a non-nil error, which the linter (unparam) had flagged. Drop
the unused error return; callers no longer need the err check either.
* chore(frontend): regenerate API client for authtypes.Relation
* fix(authz): satisfy go-lint — keyed Relation literal, drop redundant Verb selector
* refactor(coretypes): sync Kinds slice with full registry_kind declarations
* feat(coretypes): register metaresource and metaresources for all new kinds
Adds 21 metaresource and 21 metaresources entries (covering notification-channel,
route-policy, apdex-setting, auth-domain, session, cloud-integration,
cloud-integration-service, ingestion-key, ingestion-limit, pipeline,
user-preference, org-preference, quick-filter, ttl-setting, rule,
planned-maintenance, saved-view, trace-funnel, factor-password, factor-api-key,
license) so the authz schema covers every resource Kind declared in
registry_kind. Regenerates the static frontend permissions.type.ts to match.
* feat(coretypes): populate ManagedRoleToTransactions from signozapiserver routes
Enumerates every (verb, resource) tuple each managed role holds, derived
from the AdminAccess/EditAccess/ViewAccess middleware on routes in
pkg/apiserver/signozapiserver and the legacy http_handler in
pkg/query-service/app. Admin gets 123 transactions, editor 53, viewer 25,
anonymous keeps the single public-dashboard read.
* feat(coretypes): add integration kind with full CRUD for viewer/editor/admin
Install/uninstall/list integration routes (legacy /api/v1/integrations) all
sit behind ViewAccess, so every authenticated role gets the full CRUD
surface on (metaresource, integration) and (metaresources, integration).
Regenerates the static frontend permissions.type.ts to match.
* feat(coretypes): add subscription kind alongside license, document LCRUD shape
License covers the in-product license resource (Activate/Refresh/GetActive).
Subscription is the billing lifecycle (checkout/portal/billing) served by
ee/query-service routes. Both are admin-only and modeled with a uniform
LCRUD shape; comments call out which verbs actually map to routes versus
which are placeholders for shape parity (e.g. cancellation flows through
Stripe's portal, not an in-process delete).
* feat(coretypes): model telemetryresource for logs, traces, metrics
Mirrors the telemetryresource type from ee/authz/openfgaschema/base.fga
into coretypes: a read-only Type with three Kinds (logs, traces, metrics)
matching telemetrytypes.Signal. Selector is wildcard-only for v1; future
work can narrow per-service or per-environment when the use case lands.
Every managed role (admin/editor/viewer) gets read on each signal,
matching the schema's role#assignee grant. Anonymous stays unchanged.
Regenerates the static frontend permissions.type.ts.
* feat(coretypes): add audit-logs and meter-metrics kinds under telemetryresource
Audit logs (signal=logs, source=audit) and meter metrics (signal=metrics,
source=meter) are sensitive source-qualified telemetry streams that don't
belong under the broad read-grant every role gets on regular logs/traces/
metrics. Modeled as distinct Kinds so they can be permissioned
independently. Admin-only read for now; widen on explicit ask (e.g. an
auditor flow that needs viewer access to audit-logs). Regenerates the
static frontend permissions.type.ts.
* feat(coretypes): add logs-field and traces-field kinds for stored field config
GET/POST /logs/fields and /api/v2/traces/fields manage stored, mutable
field metadata (indexed/promoted columns) over each signal. They're
configuration, not telemetry data, so they sit under metaresource rather
than telemetryresource. Viewer reads, editor/admin update; no
create/delete since POST overwrites. Plural prefix (logs-field /
traces-field) matches the signal naming.
* chore(frontend): regenerate permissions.type.ts to match generate authz output
* feat(authz): add attach permissions to fga model
* fix(tests): use role permissions instead of dashboards
* fix(authz): couple of issues with register flow
* fix(authz): public dashboard read should be anomymous
* fix(tests): integration test for public dashboard access
---------
Co-authored-by: vikrantgupta25 <vikrant@signoz.io>
This commit is contained in:
17
.github/workflows/goci.yaml
vendored
17
.github/workflows/goci.yaml
vendored
@@ -102,3 +102,20 @@ jobs:
|
||||
run: |
|
||||
go run cmd/enterprise/*.go generate openapi
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in openapi spec. Run go run cmd/enterprise/*.go generate openapi locally and commit."; exit 1)
|
||||
authz:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: go-install
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
- name: generate-authz
|
||||
run: |
|
||||
go run cmd/enterprise/*.go generate authz
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in authz permissions. Run go run cmd/enterprise/*.go generate authz locally and commit."; exit 1)
|
||||
|
||||
40
.github/workflows/jsci.yaml
vendored
40
.github/workflows/jsci.yaml
vendored
@@ -63,46 +63,6 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
- name: run
|
||||
run: bash frontend/scripts/validate-md-languages.sh
|
||||
authz:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: node-install
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: deps-install
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
yarn install
|
||||
- name: uv-install
|
||||
uses: astral-sh/setup-uv@v5
|
||||
- name: uv-deps
|
||||
working-directory: ./tests/integration
|
||||
run: |
|
||||
uv sync
|
||||
- name: setup-test
|
||||
run: |
|
||||
make py-test-setup
|
||||
- name: generate
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
yarn generate:permissions-type
|
||||
- name: teardown-test
|
||||
if: always()
|
||||
run: |
|
||||
make py-test-teardown
|
||||
- name: validate
|
||||
run: |
|
||||
if ! git diff --exit-code frontend/src/hooks/useAuthZ/permissions.type.ts; then
|
||||
echo "::error::frontend/src/hooks/useAuthZ/permissions.type.ts is out of date. Please run the generator locally and commit the changes: npm run generate:permissions-type (from the frontend directory)"
|
||||
exit 1
|
||||
fi
|
||||
openapi:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
|
||||
117
cmd/authz.go
Normal file
117
cmd/authz.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"sort"
|
||||
"text/template"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const permissionsTypePath = "frontend/src/hooks/useAuthZ/permissions.type.ts"
|
||||
|
||||
var permissionsTypeTemplate = template.Must(template.New("permissions").Parse(
|
||||
`// AUTO GENERATED FILE - DO NOT EDIT - GENERATED BY cmd/enterprise/*.go generate authz
|
||||
export default {
|
||||
status: 'success',
|
||||
data: {
|
||||
resources: [
|
||||
{{- range .Resources }}
|
||||
{
|
||||
kind: '{{ .Kind }}',
|
||||
type: '{{ .Type }}',
|
||||
},
|
||||
{{- end }}
|
||||
],
|
||||
relations: {
|
||||
{{- range .Relations }}
|
||||
{{ .Verb }}: [{{ range $i, $t := .Types }}{{ if $i }}, {{ end }}'{{ $t }}'{{ end }}],
|
||||
{{- end }}
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
`))
|
||||
|
||||
type permissionsTypeRelation struct {
|
||||
Verb string
|
||||
Types []string
|
||||
}
|
||||
|
||||
type permissionsTypeResource struct {
|
||||
Kind string
|
||||
Type string
|
||||
}
|
||||
|
||||
type permissionsTypeData struct {
|
||||
Resources []permissionsTypeResource
|
||||
Relations []permissionsTypeRelation
|
||||
}
|
||||
|
||||
func registerGenerateAuthz(parentCmd *cobra.Command) {
|
||||
authzCmd := &cobra.Command{
|
||||
Use: "authz",
|
||||
Short: "Generate authz permissions for the frontend",
|
||||
RunE: func(currCmd *cobra.Command, args []string) error {
|
||||
return runGenerateAuthz(currCmd.Context())
|
||||
},
|
||||
}
|
||||
|
||||
parentCmd.AddCommand(authzCmd)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
allowedTypes := map[string]bool{}
|
||||
|
||||
refs := registry.ResourceRefs()
|
||||
resources := make([]permissionsTypeResource, 0, len(refs))
|
||||
for _, ref := range refs {
|
||||
if !allowedResources[ref.String()] {
|
||||
continue
|
||||
}
|
||||
allowedTypes[ref.Type.StringValue()] = true
|
||||
resources = append(resources, permissionsTypeResource{
|
||||
Kind: ref.Kind.String(),
|
||||
Type: ref.Type.StringValue(),
|
||||
})
|
||||
}
|
||||
|
||||
typesByVerb := registry.TypesByVerb()
|
||||
verbs := make([]coretypes.Verb, 0, len(typesByVerb))
|
||||
for verb := range typesByVerb {
|
||||
verbs = append(verbs, verb)
|
||||
}
|
||||
sort.Slice(verbs, func(i, j int) bool { return verbs[i].StringValue() < verbs[j].StringValue() })
|
||||
|
||||
relations := make([]permissionsTypeRelation, 0, len(verbs))
|
||||
for _, verb := range verbs {
|
||||
types := make([]string, 0, len(typesByVerb[verb]))
|
||||
for _, t := range typesByVerb[verb] {
|
||||
if !allowedTypes[t.StringValue()] {
|
||||
continue
|
||||
}
|
||||
types = append(types, t.StringValue())
|
||||
}
|
||||
relations = append(relations, permissionsTypeRelation{
|
||||
Verb: verb.StringValue(),
|
||||
Types: types,
|
||||
})
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := permissionsTypeTemplate.Execute(&buf, permissionsTypeData{Resources: resources, Relations: relations}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(permissionsTypePath, buf.Bytes(), 0o600)
|
||||
}
|
||||
@@ -92,13 +92,13 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete, _ dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, config authz.Config, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore), nil
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, authtypes.NewRegistry()), nil
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)
|
||||
|
||||
@@ -137,12 +137,12 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
|
||||
return authNs, nil
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, dashboardModule dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, config authz.Config, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, onBeforeRoleDelete, dashboardModule), nil
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, onBeforeRoleDelete, authtypes.NewRegistry()), nil
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)
|
||||
|
||||
@@ -16,6 +16,7 @@ func RegisterGenerate(parentCmd *cobra.Command, logger *slog.Logger) {
|
||||
}
|
||||
|
||||
registerGenerateOpenAPI(generateCmd)
|
||||
registerGenerateAuthz(generateCmd)
|
||||
|
||||
parentCmd.AddCommand(generateCmd)
|
||||
}
|
||||
|
||||
@@ -428,4 +428,4 @@ authz:
|
||||
provider: openfga
|
||||
openfga:
|
||||
# maximum tuples allowed per openfga write operation.
|
||||
max_tuples_per_write: 100
|
||||
max_tuples_per_write: 300
|
||||
|
||||
@@ -321,35 +321,6 @@ components:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
AuthtypesGettableObjects:
|
||||
properties:
|
||||
resource:
|
||||
$ref: '#/components/schemas/AuthtypesResource'
|
||||
selectors:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- resource
|
||||
- selectors
|
||||
type: object
|
||||
AuthtypesGettableResources:
|
||||
properties:
|
||||
relations:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
nullable: true
|
||||
type: object
|
||||
resources:
|
||||
items:
|
||||
$ref: '#/components/schemas/AuthtypesResource'
|
||||
type: array
|
||||
required:
|
||||
- resources
|
||||
- relations
|
||||
type: object
|
||||
AuthtypesGettableToken:
|
||||
properties:
|
||||
accessToken:
|
||||
@@ -366,9 +337,9 @@ components:
|
||||
authorized:
|
||||
type: boolean
|
||||
object:
|
||||
$ref: '#/components/schemas/AuthtypesObject'
|
||||
$ref: '#/components/schemas/CoretypesObject'
|
||||
relation:
|
||||
type: string
|
||||
$ref: '#/components/schemas/AuthtypesRelation'
|
||||
required:
|
||||
- relation
|
||||
- object
|
||||
@@ -416,16 +387,6 @@ components:
|
||||
issuerAlias:
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesObject:
|
||||
properties:
|
||||
resource:
|
||||
$ref: '#/components/schemas/AuthtypesResource'
|
||||
selector:
|
||||
type: string
|
||||
required:
|
||||
- resource
|
||||
- selector
|
||||
type: object
|
||||
AuthtypesOrgSessionContext:
|
||||
properties:
|
||||
authNSupport:
|
||||
@@ -442,22 +403,6 @@ components:
|
||||
provider:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
type: object
|
||||
AuthtypesPatchableObjects:
|
||||
properties:
|
||||
additions:
|
||||
items:
|
||||
$ref: '#/components/schemas/AuthtypesGettableObjects'
|
||||
nullable: true
|
||||
type: array
|
||||
deletions:
|
||||
items:
|
||||
$ref: '#/components/schemas/AuthtypesGettableObjects'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- additions
|
||||
- deletions
|
||||
type: object
|
||||
AuthtypesPatchableRole:
|
||||
properties:
|
||||
description:
|
||||
@@ -495,16 +440,15 @@ components:
|
||||
refreshToken:
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesResource:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
AuthtypesRelation:
|
||||
enum:
|
||||
- create
|
||||
- read
|
||||
- update
|
||||
- delete
|
||||
- list
|
||||
- assignee
|
||||
type: string
|
||||
AuthtypesRole:
|
||||
properties:
|
||||
createdAt:
|
||||
@@ -568,9 +512,9 @@ components:
|
||||
AuthtypesTransaction:
|
||||
properties:
|
||||
object:
|
||||
$ref: '#/components/schemas/AuthtypesObject'
|
||||
$ref: '#/components/schemas/CoretypesObject'
|
||||
relation:
|
||||
type: string
|
||||
$ref: '#/components/schemas/AuthtypesRelation'
|
||||
required:
|
||||
- relation
|
||||
- object
|
||||
@@ -2205,6 +2149,64 @@ components:
|
||||
to_user:
|
||||
type: string
|
||||
type: object
|
||||
CoretypesObject:
|
||||
properties:
|
||||
resource:
|
||||
$ref: '#/components/schemas/CoretypesResourceRef'
|
||||
selector:
|
||||
type: string
|
||||
required:
|
||||
- resource
|
||||
- selector
|
||||
type: object
|
||||
CoretypesObjectGroup:
|
||||
properties:
|
||||
resource:
|
||||
$ref: '#/components/schemas/CoretypesResourceRef'
|
||||
selectors:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- resource
|
||||
- selectors
|
||||
type: object
|
||||
CoretypesPatchableObjects:
|
||||
properties:
|
||||
additions:
|
||||
items:
|
||||
$ref: '#/components/schemas/CoretypesObjectGroup'
|
||||
nullable: true
|
||||
type: array
|
||||
deletions:
|
||||
items:
|
||||
$ref: '#/components/schemas/CoretypesObjectGroup'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- additions
|
||||
- deletions
|
||||
type: object
|
||||
CoretypesResourceRef:
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/CoretypesType'
|
||||
required:
|
||||
- type
|
||||
- kind
|
||||
type: object
|
||||
CoretypesType:
|
||||
enum:
|
||||
- user
|
||||
- serviceaccount
|
||||
- anonymous
|
||||
- role
|
||||
- organization
|
||||
- metaresource
|
||||
- metaresources
|
||||
type: string
|
||||
DashboardtypesDashboard:
|
||||
properties:
|
||||
createdAt:
|
||||
@@ -5704,35 +5706,6 @@ paths:
|
||||
summary: Check permissions
|
||||
tags:
|
||||
- authz
|
||||
/api/v1/authz/resources:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Gets all the available resources
|
||||
operationId: AuthzResources
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/AuthtypesGettableResources'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
summary: Get resources
|
||||
tags:
|
||||
- authz
|
||||
/api/v1/channels:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -8956,7 +8929,7 @@ paths:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/AuthtypesGettableObjects'
|
||||
$ref: '#/components/schemas/CoretypesObjectGroup'
|
||||
type: array
|
||||
status:
|
||||
type: string
|
||||
@@ -9029,7 +9002,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthtypesPatchableObjects'
|
||||
$ref: '#/components/schemas/CoretypesPatchableObjects'
|
||||
responses:
|
||||
"204":
|
||||
content:
|
||||
|
||||
@@ -2,7 +2,6 @@ package openfgaauthz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/authz/openfgaserver"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
|
||||
@@ -25,19 +25,19 @@ type provider struct {
|
||||
openfgaServer *openfgaserver.Server
|
||||
licensing licensing.Licensing
|
||||
store authtypes.RoleStore
|
||||
registry []authz.RegisterTypeable
|
||||
registry *authtypes.Registry
|
||||
settings factory.ScopedProviderSettings
|
||||
onBeforeRoleDelete []authz.OnBeforeRoleDelete
|
||||
}
|
||||
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry *authtypes.Registry) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) {
|
||||
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, licensing, onBeforeRoleDelete, registry)
|
||||
})
|
||||
}
|
||||
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
|
||||
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema, openfgaDataStore)
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry *authtypes.Registry) (authz.AuthZ, error) {
|
||||
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema, openfgaDataStore, registry)
|
||||
pkgAuthzService, err := pkgOpenfgaAuthzProvider.New(ctx, settings, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -74,11 +74,11 @@ func (provider *provider) Stop(ctx context.Context) error {
|
||||
return provider.openfgaServer.Stop(ctx)
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable coretypes.Resource, selectors []coretypes.Selector, roleSelectors []coretypes.Selector) error {
|
||||
return provider.openfgaServer.CheckWithTupleCreation(ctx, claims, orgID, relation, typeable, selectors, roleSelectors)
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable coretypes.Resource, selectors []coretypes.Selector, roleSelectors []coretypes.Selector) error {
|
||||
return provider.openfgaServer.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, typeable, selectors, roleSelectors)
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func (provider *provider) CheckTransactions(ctx context.Context, subject string,
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
|
||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType coretypes.Type) ([]*coretypes.Object, error) {
|
||||
return provider.openfgaServer.ListObjects(ctx, subject, relation, objectType)
|
||||
}
|
||||
|
||||
@@ -159,16 +159,10 @@ func (provider *provider) CreateManagedRoles(ctx context.Context, orgID valuer.U
|
||||
func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
grantTuples, err := provider.getManagedRoleGrantTuples(orgID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
grantTuples := provider.getManagedRoleGrantTuples(orgID, userID)
|
||||
tuples = append(tuples, grantTuples...)
|
||||
|
||||
managedRoleTuples, err := provider.getManagedRoleTransactionTuples(orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
managedRoleTuples := provider.getManagedRoleTransactionTuples(orgID)
|
||||
tuples = append(tuples, managedRoleTuples...)
|
||||
|
||||
return provider.Write(ctx, tuples, nil)
|
||||
@@ -208,21 +202,7 @@ func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, ro
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
resources := make([]*authtypes.Resource, 0)
|
||||
for _, register := range provider.registry {
|
||||
for _, typeable := range register.MustGetTypeables() {
|
||||
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
|
||||
}
|
||||
}
|
||||
for _, typeable := range provider.MustGetTypeables() {
|
||||
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*coretypes.Object, error) {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
@@ -233,16 +213,16 @@ func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects := make([]*authtypes.Object, 0)
|
||||
for _, objectType := range provider.getUniqueTypes() {
|
||||
if !slices.Contains(authtypes.TypeableRelations[objectType], relation) {
|
||||
objects := make([]*coretypes.Object, 0)
|
||||
for _, objectType := range provider.registry.Types() {
|
||||
if coretypes.ErrIfVerbNotValidForType(relation.Verb, objectType) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
resourceObjects, err := provider.
|
||||
ListObjects(
|
||||
ctx,
|
||||
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.Name, orgID, &authtypes.RelationAssignee),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceRole(), storableRole.Name, orgID, &coretypes.VerbAssignee),
|
||||
relation,
|
||||
objectType,
|
||||
)
|
||||
@@ -265,7 +245,7 @@ func (provider *provider) Patch(ctx context.Context, orgID valuer.UUID, role *au
|
||||
return provider.store.Update(ctx, orgID, role)
|
||||
}
|
||||
|
||||
func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, name string, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
|
||||
func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, name string, relation authtypes.Relation, additions, deletions []*coretypes.Object) error {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
@@ -318,84 +298,63 @@ func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return provider.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, authtypes.TypeableResourcesRoles}
|
||||
}
|
||||
|
||||
func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID valuer.UUID) []*openfgav1.TupleKey {
|
||||
tuples := []*openfgav1.TupleKey{}
|
||||
|
||||
// Grant the admin role to the user
|
||||
adminSubject := authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil)
|
||||
adminTuple, err := authtypes.TypeableRole.Tuples(
|
||||
adminSubject := authtypes.MustNewSubject(coretypes.NewResourceUser(), userID.String(), orgID, nil)
|
||||
adminTuple := authtypes.NewTuples(
|
||||
coretypes.NewResourceRole(),
|
||||
adminSubject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
},
|
||||
authtypes.Relation{Verb: coretypes.VerbAssignee},
|
||||
[]coretypes.Selector{coretypes.TypeRole.MustSelector(authtypes.SigNozAdminRoleName)},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, adminTuple...)
|
||||
|
||||
// Grant the admin role to the anonymous user
|
||||
anonymousSubject := authtypes.MustNewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
anonymousTuple, err := authtypes.TypeableRole.Tuples(
|
||||
anonymousSubject := authtypes.MustNewSubject(coretypes.NewResourceAnonymous(), coretypes.AnonymousUser.String(), orgID, nil)
|
||||
anonymousTuple := authtypes.NewTuples(
|
||||
coretypes.NewResourceRole(),
|
||||
anonymousSubject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAnonymousRoleName),
|
||||
},
|
||||
authtypes.Relation{Verb: coretypes.VerbAssignee},
|
||||
[]coretypes.Selector{coretypes.TypeRole.MustSelector(authtypes.SigNozAnonymousRoleName)},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, anonymousTuple...)
|
||||
|
||||
return tuples, nil
|
||||
return tuples
|
||||
}
|
||||
|
||||
func (provider *provider) getManagedRoleTransactionTuples(orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
transactionsByRole := make(map[string][]*authtypes.Transaction)
|
||||
for _, register := range provider.registry {
|
||||
for roleName, txns := range register.MustGetManagedRoleTransactions() {
|
||||
transactionsByRole[roleName] = append(transactionsByRole[roleName], txns...)
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) getManagedRoleTransactionTuples(orgID valuer.UUID) []*openfgav1.TupleKey {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for roleName, transactions := range transactionsByRole {
|
||||
for roleName, transactions := range provider.registry.ManagedRoleTransactions() {
|
||||
for _, txn := range transactions {
|
||||
typeable := authtypes.MustNewTypeableFromType(txn.Object.Resource.Type, txn.Object.Resource.Name)
|
||||
txnTuples, err := typeable.Tuples(
|
||||
resource := coretypes.MustNewResourceFromTypeAndKind(txn.Object.Resource.Type, txn.Object.Resource.Kind)
|
||||
txnTuples := authtypes.NewTuples(
|
||||
resource,
|
||||
authtypes.MustNewSubject(
|
||||
authtypes.TypeableRole,
|
||||
coretypes.NewResourceRole(),
|
||||
roleName,
|
||||
orgID,
|
||||
&authtypes.RelationAssignee,
|
||||
&coretypes.VerbAssignee,
|
||||
),
|
||||
txn.Relation,
|
||||
[]authtypes.Selector{txn.Object.Selector},
|
||||
[]coretypes.Selector{txn.Object.Selector},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, txnTuples...)
|
||||
}
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
return tuples
|
||||
}
|
||||
|
||||
func (provider *provider) deleteTuples(ctx context.Context, roleName string, orgID valuer.UUID) error {
|
||||
subject := authtypes.MustNewSubject(authtypes.TypeableRole, roleName, orgID, &authtypes.RelationAssignee)
|
||||
subject := authtypes.MustNewSubject(coretypes.NewResourceRole(), roleName, orgID, &coretypes.VerbAssignee)
|
||||
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for _, objectType := range provider.getUniqueTypes() {
|
||||
for _, objectType := range provider.registry.Types() {
|
||||
typeTuples, err := provider.ReadTuples(ctx, &openfgav1.ReadRequestTupleKey{
|
||||
User: subject,
|
||||
Object: objectType.StringValue() + ":",
|
||||
@@ -424,28 +383,3 @@ func (provider *provider) deleteTuples(ctx context.Context, roleName string, org
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) getUniqueTypes() []authtypes.Type {
|
||||
seen := make(map[string]struct{})
|
||||
uniqueTypes := make([]authtypes.Type, 0)
|
||||
for _, register := range provider.registry {
|
||||
for _, typeable := range register.MustGetTypeables() {
|
||||
typeKey := typeable.Type().StringValue()
|
||||
if _, ok := seen[typeKey]; ok {
|
||||
continue
|
||||
}
|
||||
seen[typeKey] = struct{}{}
|
||||
uniqueTypes = append(uniqueTypes, typeable.Type())
|
||||
}
|
||||
}
|
||||
for _, typeable := range provider.MustGetTypeables() {
|
||||
typeKey := typeable.Type().StringValue()
|
||||
if _, ok := seen[typeKey]; ok {
|
||||
continue
|
||||
}
|
||||
seen[typeKey] = struct{}{}
|
||||
uniqueTypes = append(uniqueTypes, typeable.Type())
|
||||
}
|
||||
|
||||
return uniqueTypes
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module base
|
||||
|
||||
type organisation
|
||||
type organization
|
||||
relations
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
@@ -10,12 +10,14 @@ type user
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
define attach: [user, serviceaccount, role#assignee]
|
||||
|
||||
type serviceaccount
|
||||
type serviceaccount
|
||||
relations
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
define attach: [user, serviceaccount, role#assignee]
|
||||
|
||||
type anonymous
|
||||
|
||||
@@ -26,6 +28,7 @@ type role
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
define attach: [user, serviceaccount, role#assignee]
|
||||
|
||||
type metaresources
|
||||
relations
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
@@ -33,18 +34,18 @@ func (server *Server) Stop(ctx context.Context) error {
|
||||
return server.pkgAuthzService.Stop(ctx)
|
||||
}
|
||||
|
||||
func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||
func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable coretypes.Resource, selectors []coretypes.Selector, _ []coretypes.Selector) error {
|
||||
subject := ""
|
||||
switch claims.Principal {
|
||||
case authtypes.PrincipalUser:
|
||||
user, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||
user, err := authtypes.NewSubject(coretypes.NewResourceUser(), claims.UserID, orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subject = user
|
||||
case authtypes.PrincipalServiceAccount:
|
||||
serviceAccount, err := authtypes.NewSubject(authtypes.TypeableServiceAccount, claims.ServiceAccountID, orgID, nil)
|
||||
serviceAccount, err := authtypes.NewSubject(coretypes.NewResourceServiceAccount(), claims.ServiceAccountID, orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -52,10 +53,7 @@ func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtyp
|
||||
subject = serviceAccount
|
||||
}
|
||||
|
||||
tupleSlice, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tupleSlice := authtypes.NewTuples(typeable, subject, relation, selectors, orgID)
|
||||
|
||||
tuples := make(map[string]*openfgav1.TupleKey, len(tupleSlice))
|
||||
for idx, tuple := range tupleSlice {
|
||||
@@ -76,16 +74,13 @@ func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtyp
|
||||
return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subjects are not authorized for requested access")
|
||||
}
|
||||
|
||||
func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable coretypes.Resource, selectors []coretypes.Selector, _ []coretypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(coretypes.NewResourceAnonymous(), coretypes.AnonymousUser.String(), orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tupleSlice, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tupleSlice := authtypes.NewTuples(typeable, subject, relation, selectors, orgID)
|
||||
|
||||
tuples := make(map[string]*openfgav1.TupleKey, len(tupleSlice))
|
||||
for idx, tuple := range tupleSlice {
|
||||
@@ -110,7 +105,7 @@ func (server *Server) BatchCheck(ctx context.Context, tupleReq map[string]*openf
|
||||
return server.pkgAuthzService.BatchCheck(ctx, tupleReq)
|
||||
}
|
||||
|
||||
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
|
||||
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType coretypes.Type) ([]*coretypes.Object, error) {
|
||||
return server.pkgAuthzService.ListObjects(ctx, subject, relation, objectType)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package openfgaserver
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/openfga/openfga/pkg/storage"
|
||||
@@ -10,11 +11,11 @@ import (
|
||||
"github.com/openfga/openfga/pkg/storage/sqlite"
|
||||
)
|
||||
|
||||
func NewSQLStore(store sqlstore.SQLStore) (storage.OpenFGADatastore, error) {
|
||||
func NewSQLStore(store sqlstore.SQLStore, config authz.Config) (storage.OpenFGADatastore, error) {
|
||||
switch store.BunDB().Dialect().Name().String() {
|
||||
case "sqlite":
|
||||
return sqlite.NewWithDB(store.SQLDB(), &sqlcommon.Config{
|
||||
MaxTuplesPerWriteField: 100,
|
||||
MaxTuplesPerWriteField: config.OpenFGA.MaxTuplesPerWrite,
|
||||
MaxTypesPerModelField: 100,
|
||||
})
|
||||
case "pg":
|
||||
@@ -24,7 +25,7 @@ func NewSQLStore(store sqlstore.SQLStore) (storage.OpenFGADatastore, error) {
|
||||
}
|
||||
|
||||
return postgres.NewWithDB(pgStore.Pool(), nil, &sqlcommon.Config{
|
||||
MaxTuplesPerWriteField: 100,
|
||||
MaxTuplesPerWriteField: config.OpenFGA.MaxTuplesPerWrite,
|
||||
MaxTypesPerModelField: 100,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"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/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
@@ -88,7 +88,7 @@ func (module *module) GetDashboardByPublicID(ctx context.Context, id valuer.UUID
|
||||
return dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard), nil
|
||||
}
|
||||
|
||||
func (module *module) GetPublicDashboardSelectorsAndOrg(ctx context.Context, id valuer.UUID, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
func (module *module) GetPublicDashboardSelectorsAndOrg(ctx context.Context, id valuer.UUID, orgs []*types.Organization) ([]coretypes.Selector, valuer.UUID, error) {
|
||||
orgIDs := make([]string, len(orgs))
|
||||
for idx, org := range orgs {
|
||||
orgIDs[idx] = org.ID.StringValue()
|
||||
@@ -99,9 +99,9 @@ func (module *module) GetPublicDashboardSelectorsAndOrg(ctx context.Context, id
|
||||
return nil, valuer.UUID{}, err
|
||||
}
|
||||
|
||||
return []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, id.StringValue()),
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, authtypes.WildCardSelectorString),
|
||||
return []coretypes.Selector{
|
||||
coretypes.TypeMetaResource.MustSelector(id.StringValue()),
|
||||
coretypes.TypeMetaResource.MustSelector(coretypes.WildCardSelectorString),
|
||||
}, storableDashboard.OrgID, nil
|
||||
}
|
||||
|
||||
@@ -217,28 +217,6 @@ func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, isAdmin, lock)
|
||||
}
|
||||
|
||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
return module.pkgDashboardModule.MustGetTypeables()
|
||||
}
|
||||
|
||||
func (module *module) MustGetManagedRoleTransactions() map[string][]*authtypes.Transaction {
|
||||
return map[string][]*authtypes.Transaction{
|
||||
authtypes.SigNozAnonymousRoleName: {
|
||||
{
|
||||
ID: valuer.GenerateUUID(),
|
||||
Relation: authtypes.RelationRead,
|
||||
Object: *authtypes.MustNewObject(
|
||||
authtypes.Resource{
|
||||
Type: authtypes.TypeMetaResource,
|
||||
Name: dashboardtypes.TypeableMetaResourcePublicDashboard.Name(),
|
||||
},
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, "*"),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) deletePublic(ctx context.Context, _ valuer.UUID, dashboardID valuer.UUID) error {
|
||||
return module.store.DeletePublic(ctx, dashboardID.StringValue())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/anomaly"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
@@ -15,7 +17,6 @@ import (
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -137,4 +138,3 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
aH.QueryRangeV4(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ import (
|
||||
|
||||
// Server runs HTTP, Mux and a grpc server
|
||||
type Server struct {
|
||||
config signoz.Config
|
||||
signoz *signoz.SigNoz
|
||||
config signoz.Config
|
||||
signoz *signoz.SigNoz
|
||||
|
||||
// public http router
|
||||
httpConn net.Listener
|
||||
@@ -148,7 +148,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
|
||||
s := &Server{
|
||||
config: config,
|
||||
signoz: signoz,
|
||||
signoz: signoz,
|
||||
httpHostPort: baseconst.HTTPHostPort,
|
||||
unavailableChannel: make(chan healthcheck.Status),
|
||||
usageManager: usageManager,
|
||||
@@ -317,4 +317,3 @@ func (s *Server) Stop(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
"commitlint": "commitlint --edit $1",
|
||||
"test": "jest",
|
||||
"test:changedsince": "jest --changedSince=main --coverage --silent",
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh",
|
||||
"generate:permissions-type": "node scripts/generate-permissions-type.cjs"
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const axios = require('axios');
|
||||
|
||||
const PERMISSIONS_TYPE_FILE = path.join(
|
||||
__dirname,
|
||||
'../src/hooks/useAuthZ/permissions.type.ts',
|
||||
);
|
||||
|
||||
const SIGNOZ_INTEGRATION_IMAGE = 'signoz:integration';
|
||||
const LOCAL_BACKEND_URL = 'http://localhost:8080';
|
||||
|
||||
function log(message) {
|
||||
console.log(`[generate-permissions-type] ${message}`);
|
||||
}
|
||||
|
||||
function getBackendUrlFromDocker() {
|
||||
try {
|
||||
const output = execSync(
|
||||
`docker ps --filter "ancestor=${SIGNOZ_INTEGRATION_IMAGE}" --format "{{.Ports}}"`,
|
||||
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] },
|
||||
).trim();
|
||||
|
||||
if (!output) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const portMatch = output.match(/0\.0\.0\.0:(\d+)->8080\/tcp/);
|
||||
if (portMatch) {
|
||||
return `http://localhost:${portMatch[1]}`;
|
||||
}
|
||||
|
||||
const ipv6Match = output.match(/:::(\d+)->8080\/tcp/);
|
||||
if (ipv6Match) {
|
||||
return `http://localhost:${ipv6Match[1]}`;
|
||||
}
|
||||
} catch (err) {
|
||||
log(`Warning: Could not get port from docker: ${err.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function checkBackendHealth(url, maxAttempts = 3, delayMs = 1000) {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
await axios.get(`${url}/api/v1/health`, {
|
||||
timeout: 5000,
|
||||
validateStatus: (status) => status === 200,
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (attempt < maxAttempts) {
|
||||
await new Promise((r) => setTimeout(r, delayMs));
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function discoverBackendUrl() {
|
||||
const dockerUrl = getBackendUrlFromDocker();
|
||||
if (dockerUrl) {
|
||||
log(`Found ${SIGNOZ_INTEGRATION_IMAGE} container, trying ${dockerUrl}...`);
|
||||
if (await checkBackendHealth(dockerUrl)) {
|
||||
log(`Backend found at ${dockerUrl} (from py-test-setup)`);
|
||||
return dockerUrl;
|
||||
}
|
||||
log(`Backend at ${dockerUrl} is not responding`);
|
||||
}
|
||||
|
||||
log(`Trying local backend at ${LOCAL_BACKEND_URL}...`);
|
||||
if (await checkBackendHealth(LOCAL_BACKEND_URL)) {
|
||||
log(`Backend found at ${LOCAL_BACKEND_URL}`);
|
||||
return LOCAL_BACKEND_URL;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function fetchResources(backendUrl) {
|
||||
log('Fetching resources from API...');
|
||||
const resourcesUrl = `${backendUrl}/api/v1/authz/resources`;
|
||||
|
||||
const { data: response } = await axios.get(resourcesUrl);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
function transformResponse(apiResponse) {
|
||||
if (!apiResponse.data) {
|
||||
throw new Error('Invalid API response: missing data field');
|
||||
}
|
||||
|
||||
const { resources, relations } = apiResponse.data;
|
||||
|
||||
return {
|
||||
status: apiResponse.status || 'success',
|
||||
data: {
|
||||
resources: resources,
|
||||
relations: relations,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function generateTypeScriptFile(data) {
|
||||
const resourcesStr = data.data.resources
|
||||
.map(
|
||||
(r) =>
|
||||
`\t\t\t{\n\t\t\t\tname: '${r.name}',\n\t\t\t\ttype: '${r.type}',\n\t\t\t},`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
const relationsStr = Object.entries(data.data.relations)
|
||||
.map(
|
||||
([type, relations]) =>
|
||||
`\t\t\t${type}: [${relations.map((r) => `'${r}'`).join(', ')}],`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
return `// AUTO GENERATED FILE - DO NOT EDIT - GENERATED BY scripts/generate-permissions-type
|
||||
export default {
|
||||
\tstatus: '${data.status}',
|
||||
\tdata: {
|
||||
\t\tresources: [
|
||||
${resourcesStr}
|
||||
\t\t],
|
||||
\t\trelations: {
|
||||
${relationsStr}
|
||||
\t\t},
|
||||
\t},
|
||||
} as const;
|
||||
`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
log('Starting permissions type generation...');
|
||||
|
||||
const backendUrl = await discoverBackendUrl();
|
||||
|
||||
if (!backendUrl) {
|
||||
console.error('\n' + '='.repeat(80));
|
||||
console.error('ERROR: No running SigNoz backend found!');
|
||||
console.error('='.repeat(80));
|
||||
console.error(
|
||||
'\nThe permissions type generator requires a running SigNoz backend.',
|
||||
);
|
||||
console.error('\nFor local development, start the backend with:');
|
||||
console.error(' make go-run-enterprise');
|
||||
console.error(
|
||||
'\nFor CI or integration testing, start the test environment with:',
|
||||
);
|
||||
console.error(' make py-test-setup');
|
||||
console.error(
|
||||
'\nIf running in CI and seeing this error, check that the py-test-setup',
|
||||
);
|
||||
console.error('step completed successfully before this step runs.');
|
||||
console.error('='.repeat(80) + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log('Fetching resources...');
|
||||
const apiResponse = await fetchResources(backendUrl);
|
||||
|
||||
log('Transforming response...');
|
||||
const transformed = transformResponse(apiResponse);
|
||||
|
||||
log('Generating TypeScript file...');
|
||||
const content = generateTypeScriptFile(transformed);
|
||||
|
||||
log(`Writing to ${PERMISSIONS_TYPE_FILE}...`);
|
||||
fs.writeFileSync(PERMISSIONS_TYPE_FILE, content, 'utf8');
|
||||
|
||||
const rootDir = path.join(__dirname, '../..');
|
||||
const relativePath = path.relative(
|
||||
path.join(rootDir, 'frontend'),
|
||||
PERMISSIONS_TYPE_FILE,
|
||||
);
|
||||
log('Linting generated file...');
|
||||
execSync(`cd frontend && yarn oxlint ${relativePath}`, {
|
||||
cwd: rootDir,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
log('Successfully generated permissions.type.ts');
|
||||
} catch (error) {
|
||||
log(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { main };
|
||||
@@ -4,23 +4,16 @@
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { useMutation } from 'react-query';
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
AuthtypesTransactionDTO,
|
||||
AuthzCheck200,
|
||||
AuthzResources200,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
@@ -110,88 +103,3 @@ export const useAuthzCheck = <
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Gets all the available resources
|
||||
* @summary Get resources
|
||||
*/
|
||||
export const authzResources = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<AuthzResources200>({
|
||||
url: `/api/v1/authz/resources`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getAuthzResourcesQueryKey = () => {
|
||||
return [`/api/v1/authz/resources`] as const;
|
||||
};
|
||||
|
||||
export const getAuthzResourcesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof authzResources>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof authzResources>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getAuthzResourcesQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof authzResources>>> = ({
|
||||
signal,
|
||||
}) => authzResources(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof authzResources>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type AuthzResourcesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof authzResources>>
|
||||
>;
|
||||
export type AuthzResourcesQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get resources
|
||||
*/
|
||||
|
||||
export function useAuthzResources<
|
||||
TData = Awaited<ReturnType<typeof authzResources>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof authzResources>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getAuthzResourcesQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get resources
|
||||
*/
|
||||
export const invalidateAuthzResources = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getAuthzResourcesQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
@@ -18,9 +18,9 @@ import type {
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
AuthtypesPatchableObjectsDTO,
|
||||
AuthtypesPatchableRoleDTO,
|
||||
AuthtypesPostableRoleDTO,
|
||||
CoretypesPatchableObjectsDTO,
|
||||
CreateRole201,
|
||||
DeleteRolePathParameters,
|
||||
GetObjects200,
|
||||
@@ -571,13 +571,13 @@ export const invalidateGetObjects = async (
|
||||
*/
|
||||
export const patchObjects = (
|
||||
{ id, relation }: PatchObjectsPathParameters,
|
||||
authtypesPatchableObjectsDTO: BodyType<AuthtypesPatchableObjectsDTO>,
|
||||
coretypesPatchableObjectsDTO: BodyType<CoretypesPatchableObjectsDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v1/roles/${id}/relations/${relation}/objects`,
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: authtypesPatchableObjectsDTO,
|
||||
data: coretypesPatchableObjectsDTO,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -590,7 +590,7 @@ export const getPatchObjectsMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: PatchObjectsPathParameters;
|
||||
data: BodyType<AuthtypesPatchableObjectsDTO>;
|
||||
data: BodyType<CoretypesPatchableObjectsDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -599,7 +599,7 @@ export const getPatchObjectsMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: PatchObjectsPathParameters;
|
||||
data: BodyType<AuthtypesPatchableObjectsDTO>;
|
||||
data: BodyType<CoretypesPatchableObjectsDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -616,7 +616,7 @@ export const getPatchObjectsMutationOptions = <
|
||||
Awaited<ReturnType<typeof patchObjects>>,
|
||||
{
|
||||
pathParams: PatchObjectsPathParameters;
|
||||
data: BodyType<AuthtypesPatchableObjectsDTO>;
|
||||
data: BodyType<CoretypesPatchableObjectsDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
@@ -630,7 +630,7 @@ export const getPatchObjectsMutationOptions = <
|
||||
export type PatchObjectsMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof patchObjects>>
|
||||
>;
|
||||
export type PatchObjectsMutationBody = BodyType<AuthtypesPatchableObjectsDTO>;
|
||||
export type PatchObjectsMutationBody = BodyType<CoretypesPatchableObjectsDTO>;
|
||||
export type PatchObjectsMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -645,7 +645,7 @@ export const usePatchObjects = <
|
||||
TError,
|
||||
{
|
||||
pathParams: PatchObjectsPathParameters;
|
||||
data: BodyType<AuthtypesPatchableObjectsDTO>;
|
||||
data: BodyType<CoretypesPatchableObjectsDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -654,7 +654,7 @@ export const usePatchObjects = <
|
||||
TError,
|
||||
{
|
||||
pathParams: PatchObjectsPathParameters;
|
||||
data: BodyType<AuthtypesPatchableObjectsDTO>;
|
||||
data: BodyType<CoretypesPatchableObjectsDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
|
||||
@@ -1668,33 +1668,6 @@ export interface AuthtypesGettableAuthDomainDTO {
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface AuthtypesGettableObjectsDTO {
|
||||
resource: AuthtypesResourceDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
selectors: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AuthtypesGettableResourcesDTORelations = {
|
||||
[key: string]: string[];
|
||||
} | null;
|
||||
|
||||
export interface AuthtypesGettableResourcesDTO {
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
relations: AuthtypesGettableResourcesDTORelations;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
resources: AuthtypesResourceDTO[];
|
||||
}
|
||||
|
||||
export interface AuthtypesGettableTokenDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -1719,11 +1692,8 @@ export interface AuthtypesGettableTransactionDTO {
|
||||
* @type boolean
|
||||
*/
|
||||
authorized: boolean;
|
||||
object: AuthtypesObjectDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
relation: string;
|
||||
object: CoretypesObjectDTO;
|
||||
relation: AuthtypesRelationDTO;
|
||||
}
|
||||
|
||||
export type AuthtypesGoogleConfigDTODomainToAdminEmail = {
|
||||
@@ -1797,14 +1767,6 @@ export interface AuthtypesOIDCConfigDTO {
|
||||
issuerAlias?: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesObjectDTO {
|
||||
resource: AuthtypesResourceDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
selector: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesOrgSessionContextDTO {
|
||||
authNSupport?: AuthtypesAuthNSupportDTO;
|
||||
/**
|
||||
@@ -1822,19 +1784,6 @@ export interface AuthtypesPasswordAuthNSupportDTO {
|
||||
provider?: AuthtypesAuthNProviderDTO;
|
||||
}
|
||||
|
||||
export interface AuthtypesPatchableObjectsDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
additions: AuthtypesGettableObjectsDTO[] | null;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
deletions: AuthtypesGettableObjectsDTO[] | null;
|
||||
}
|
||||
|
||||
export interface AuthtypesPatchableRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -1883,17 +1832,14 @@ export interface AuthtypesPostableRotateTokenDTO {
|
||||
refreshToken?: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesResourceDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type: string;
|
||||
export enum AuthtypesRelationDTO {
|
||||
create = 'create',
|
||||
read = 'read',
|
||||
update = 'update',
|
||||
delete = 'delete',
|
||||
list = 'list',
|
||||
assignee = 'assignee',
|
||||
}
|
||||
|
||||
export interface AuthtypesRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -1983,11 +1929,8 @@ export interface AuthtypesSessionContextDTO {
|
||||
}
|
||||
|
||||
export interface AuthtypesTransactionDTO {
|
||||
object: AuthtypesObjectDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
relation: string;
|
||||
object: CoretypesObjectDTO;
|
||||
relation: AuthtypesRelationDTO;
|
||||
}
|
||||
|
||||
export interface AuthtypesUpdatableAuthDomainDTO {
|
||||
@@ -4173,6 +4116,52 @@ export interface ConfigWechatConfigDTO {
|
||||
to_user?: string;
|
||||
}
|
||||
|
||||
export interface CoretypesObjectDTO {
|
||||
resource: CoretypesResourceRefDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
selector: string;
|
||||
}
|
||||
|
||||
export interface CoretypesObjectGroupDTO {
|
||||
resource: CoretypesResourceRefDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
selectors: string[];
|
||||
}
|
||||
|
||||
export interface CoretypesPatchableObjectsDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
additions: CoretypesObjectGroupDTO[] | null;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
deletions: CoretypesObjectGroupDTO[] | null;
|
||||
}
|
||||
|
||||
export interface CoretypesResourceRefDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
kind: string;
|
||||
type: CoretypesTypeDTO;
|
||||
}
|
||||
|
||||
export enum CoretypesTypeDTO {
|
||||
user = 'user',
|
||||
serviceaccount = 'serviceaccount',
|
||||
anonymous = 'anonymous',
|
||||
role = 'role',
|
||||
organization = 'organization',
|
||||
metaresource = 'metaresource',
|
||||
metaresources = 'metaresources',
|
||||
}
|
||||
export interface DashboardtypesDashboardDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -8110,14 +8099,6 @@ export type AuthzCheck200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type AuthzResources200 = {
|
||||
data: AuthtypesGettableResourcesDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListChannels200 = {
|
||||
/**
|
||||
* @type array
|
||||
@@ -8743,7 +8724,7 @@ export type GetObjects200 = {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data: AuthtypesGettableObjectsDTO[];
|
||||
data: CoretypesObjectGroupDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('GuardAuthZ', () => {
|
||||
);
|
||||
|
||||
render(
|
||||
<GuardAuthZ relation="read" object="dashboard:*">
|
||||
<GuardAuthZ relation="read" object="role:*">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
@@ -79,7 +79,7 @@ describe('GuardAuthZ', () => {
|
||||
render(
|
||||
<GuardAuthZ
|
||||
relation="read"
|
||||
object="dashboard:*"
|
||||
object="role:*"
|
||||
fallbackOnLoading={<LoadingFallback />}
|
||||
>
|
||||
<TestChild />
|
||||
@@ -102,7 +102,7 @@ describe('GuardAuthZ', () => {
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
<GuardAuthZ relation="read" object="dashboard:*">
|
||||
<GuardAuthZ relation="read" object="role:*">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
@@ -121,11 +121,7 @@ describe('GuardAuthZ', () => {
|
||||
);
|
||||
|
||||
render(
|
||||
<GuardAuthZ
|
||||
relation="read"
|
||||
object="dashboard:*"
|
||||
fallbackOnError={ErrorFallback}
|
||||
>
|
||||
<GuardAuthZ relation="read" object="role:*" fallbackOnError={ErrorFallback}>
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
@@ -155,7 +151,7 @@ describe('GuardAuthZ', () => {
|
||||
render(
|
||||
<GuardAuthZ
|
||||
relation="read"
|
||||
object="dashboard:*"
|
||||
object="role:*"
|
||||
fallbackOnError={errorFallbackWithCapture}
|
||||
>
|
||||
<TestChild />
|
||||
@@ -178,7 +174,7 @@ describe('GuardAuthZ', () => {
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
<GuardAuthZ relation="read" object="dashboard:*">
|
||||
<GuardAuthZ relation="read" object="role:*">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
@@ -201,7 +197,7 @@ describe('GuardAuthZ', () => {
|
||||
render(
|
||||
<GuardAuthZ
|
||||
relation="update"
|
||||
object="dashboard:123"
|
||||
object="role:123"
|
||||
fallbackOnNoPermissions={NoPermissionFallback}
|
||||
>
|
||||
<TestChild />
|
||||
@@ -224,7 +220,7 @@ describe('GuardAuthZ', () => {
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
<GuardAuthZ relation="update" object="dashboard:123">
|
||||
<GuardAuthZ relation="update" object="role:123">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
@@ -244,7 +240,7 @@ describe('GuardAuthZ', () => {
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
<GuardAuthZ relation="read" object="dashboard:*">
|
||||
<GuardAuthZ relation="read" object="role:*">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
@@ -257,7 +253,7 @@ describe('GuardAuthZ', () => {
|
||||
});
|
||||
|
||||
it('should pass requiredPermissionName to fallbackOnNoPermissions', async () => {
|
||||
const permission = buildPermission('update', 'dashboard:123');
|
||||
const permission = buildPermission('update', 'role:123');
|
||||
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
|
||||
@@ -269,7 +265,7 @@ describe('GuardAuthZ', () => {
|
||||
render(
|
||||
<GuardAuthZ
|
||||
relation="update"
|
||||
object="dashboard:123"
|
||||
object="role:123"
|
||||
fallbackOnNoPermissions={NoPermissionFallbackWithSuggestions}
|
||||
>
|
||||
<TestChild />
|
||||
@@ -299,7 +295,7 @@ describe('GuardAuthZ', () => {
|
||||
);
|
||||
|
||||
const { rerender } = render(
|
||||
<GuardAuthZ relation="read" object="dashboard:*">
|
||||
<GuardAuthZ relation="read" object="role:*">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
@@ -309,7 +305,7 @@ describe('GuardAuthZ', () => {
|
||||
});
|
||||
|
||||
rerender(
|
||||
<GuardAuthZ relation="delete" object="dashboard:456">
|
||||
<GuardAuthZ relation="delete" object="role:456">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
|
||||
@@ -41,11 +41,7 @@ describe('createGuardedRoute', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'read',
|
||||
'dashboard:*',
|
||||
);
|
||||
const GuardedComponent = createGuardedRoute(TestComponent, 'read', 'role:*');
|
||||
|
||||
const mockMatch = {
|
||||
params: {},
|
||||
@@ -79,7 +75,7 @@ describe('createGuardedRoute', () => {
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'read',
|
||||
'dashboard:{id}',
|
||||
'role:{id}',
|
||||
);
|
||||
|
||||
const mockMatch = {
|
||||
@@ -113,7 +109,7 @@ describe('createGuardedRoute', () => {
|
||||
relation: txn.relation,
|
||||
object: {
|
||||
resource: {
|
||||
name: txn.object.resource.name,
|
||||
kind: txn.object.resource.kind,
|
||||
type: txn.object.resource.type,
|
||||
},
|
||||
selector: '123:456',
|
||||
@@ -131,7 +127,7 @@ describe('createGuardedRoute', () => {
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'update',
|
||||
'dashboard:{id}:{version}',
|
||||
'role:{id}:{version}',
|
||||
);
|
||||
|
||||
const mockMatch = {
|
||||
@@ -166,7 +162,7 @@ describe('createGuardedRoute', () => {
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'read',
|
||||
'dashboard:{id}',
|
||||
'role:{id}',
|
||||
);
|
||||
|
||||
const mockMatch = {
|
||||
@@ -201,11 +197,7 @@ describe('createGuardedRoute', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'read',
|
||||
'dashboard:*',
|
||||
);
|
||||
const GuardedComponent = createGuardedRoute(TestComponent, 'read', 'role:*');
|
||||
|
||||
const mockMatch = {
|
||||
params: {},
|
||||
@@ -236,11 +228,7 @@ describe('createGuardedRoute', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'read',
|
||||
'dashboard:*',
|
||||
);
|
||||
const GuardedComponent = createGuardedRoute(TestComponent, 'read', 'role:*');
|
||||
|
||||
const mockMatch = {
|
||||
params: {},
|
||||
@@ -278,7 +266,7 @@ describe('createGuardedRoute', () => {
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'update',
|
||||
'dashboard:{id}',
|
||||
'role:{id}',
|
||||
);
|
||||
|
||||
const mockMatch = {
|
||||
@@ -304,7 +292,7 @@ describe('createGuardedRoute', () => {
|
||||
});
|
||||
|
||||
expect(screen.getByText('update')).toBeInTheDocument();
|
||||
expect(screen.getByText('dashboard:123')).toBeInTheDocument();
|
||||
expect(screen.getByText('role:123')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('Test Component: test-value'),
|
||||
).not.toBeInTheDocument();
|
||||
@@ -335,7 +323,7 @@ describe('createGuardedRoute', () => {
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
ComponentWithMultipleProps,
|
||||
'read',
|
||||
'dashboard:*',
|
||||
'role:*',
|
||||
);
|
||||
|
||||
const mockMatch = {
|
||||
@@ -370,10 +358,10 @@ describe('createGuardedRoute', () => {
|
||||
requestCount++;
|
||||
const payload = (await req.json()) as AuthtypesTransactionDTO[];
|
||||
const obj = payload[0]?.object;
|
||||
const name = obj?.resource?.name;
|
||||
const kind = obj?.resource?.kind;
|
||||
const selector = obj?.selector ?? '*';
|
||||
const objectStr =
|
||||
obj?.resource?.type === 'metaresources' ? name : `${name}:${selector}`;
|
||||
obj?.resource?.type === 'metaresources' ? kind : `${kind}:${selector}`;
|
||||
requestedObjects.push(objectStr ?? '');
|
||||
|
||||
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
|
||||
@@ -383,7 +371,7 @@ describe('createGuardedRoute', () => {
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'read',
|
||||
'dashboard:{id}',
|
||||
'role:{id}',
|
||||
);
|
||||
|
||||
const mockMatch1 = {
|
||||
@@ -407,7 +395,7 @@ describe('createGuardedRoute', () => {
|
||||
});
|
||||
|
||||
expect(requestCount).toBe(1);
|
||||
expect(requestedObjects).toContain('dashboard:123');
|
||||
expect(requestedObjects).toContain('role:123');
|
||||
|
||||
unmount();
|
||||
|
||||
@@ -432,7 +420,7 @@ describe('createGuardedRoute', () => {
|
||||
});
|
||||
|
||||
expect(requestCount).toBe(2);
|
||||
expect(requestedObjects).toContain('dashboard:456');
|
||||
expect(requestedObjects).toContain('role:456');
|
||||
});
|
||||
|
||||
it('should handle different relation types', async () => {
|
||||
@@ -446,7 +434,7 @@ describe('createGuardedRoute', () => {
|
||||
const GuardedComponent = createGuardedRoute(
|
||||
TestComponent,
|
||||
'delete',
|
||||
'dashboard:{id}',
|
||||
'role:{id}',
|
||||
);
|
||||
|
||||
const mockMatch = {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Table2, Trash2, Users } from '@signozhq/icons';
|
||||
import { Button, toast, ToggleGroup, ToggleGroupItem } from '@signozhq/ui';
|
||||
import { Skeleton } from 'antd';
|
||||
import { useAuthzResources } from 'api/generated/services/authz';
|
||||
import {
|
||||
getGetObjectsQueryKey,
|
||||
useDeleteRole,
|
||||
@@ -12,6 +11,9 @@ import {
|
||||
useGetRole,
|
||||
usePatchObjects,
|
||||
} from 'api/generated/services/role';
|
||||
import permissionsType from 'hooks/useAuthZ/permissions.type';
|
||||
|
||||
import type { AuthzResources } from '../utils';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { capitalize } from 'lodash-es';
|
||||
@@ -52,10 +54,7 @@ function RoleDetailsPage(): JSX.Element {
|
||||
const queryClient = useQueryClient();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const { data: authzResourcesResponse } = useAuthzResources({
|
||||
query: { enabled: true },
|
||||
});
|
||||
const authzResources = authzResourcesResponse?.data ?? null;
|
||||
const authzResources = permissionsType.data as unknown as AuthzResources;
|
||||
|
||||
// Extract channelId from URL pathname since useParams doesn't work in nested routing
|
||||
const roleIdMatch = pathname.match(ROLE_ID_REGEX);
|
||||
@@ -94,7 +93,7 @@ function RoleDetailsPage(): JSX.Element {
|
||||
|
||||
const initialConfig = useMemo(() => {
|
||||
if (!objectsData?.data || !activePermission) {
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
return objectsToPermissionConfig(
|
||||
objectsData.data,
|
||||
|
||||
@@ -15,15 +15,6 @@ const CUSTOM_ROLE_ID = '019c24aa-3333-0001-aaaa-111111111111';
|
||||
const MANAGED_ROLE_ID = '019c24aa-2248-756f-9833-984f1ab63819';
|
||||
|
||||
const rolesApiBase = 'http://localhost/api/v1/roles';
|
||||
const authzResourcesUrl = 'http://localhost/api/v1/authz/resources';
|
||||
|
||||
const authzResourcesResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
relations: { create: ['dashboard'], read: ['dashboard'] },
|
||||
resources: [{ name: 'dashboard', type: 'dashboard' }],
|
||||
},
|
||||
};
|
||||
|
||||
const emptyObjectsResponse = { status: 'success', data: [] };
|
||||
|
||||
@@ -45,9 +36,6 @@ function setupDefaultHandlers(roleId = CUSTOM_ROLE_ID): void {
|
||||
rest.get(`${rolesApiBase}/:id`, (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(roleResponse)),
|
||||
),
|
||||
rest.get(authzResourcesUrl, (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(authzResourcesResponse)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import type {
|
||||
AuthtypesGettableObjectsDTO,
|
||||
AuthtypesGettableResourcesDTO,
|
||||
CoretypesResourceRefDTO,
|
||||
CoretypesObjectGroupDTO,
|
||||
CoretypesTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import type {
|
||||
PermissionConfig,
|
||||
ResourceDefinition,
|
||||
} from '../PermissionSidePanel/PermissionSidePanel.types';
|
||||
|
||||
type AuthzResources = {
|
||||
resources: CoretypesResourceRefDTO[];
|
||||
relations: Record<string, string[]>;
|
||||
};
|
||||
import { PermissionScope } from '../PermissionSidePanel/PermissionSidePanel.types';
|
||||
import {
|
||||
buildConfig,
|
||||
@@ -33,17 +39,17 @@ jest.mock('../RoleDetails/constants', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const dashboardResource: AuthtypesGettableResourcesDTO['resources'][number] = {
|
||||
name: 'dashboard',
|
||||
type: 'metaresource',
|
||||
const dashboardResource: AuthzResources['resources'][number] = {
|
||||
kind: 'dashboard',
|
||||
type: 'metaresource' as CoretypesTypeDTO,
|
||||
};
|
||||
|
||||
const alertResource: AuthtypesGettableResourcesDTO['resources'][number] = {
|
||||
name: 'alert',
|
||||
type: 'metaresource',
|
||||
const alertResource: AuthzResources['resources'][number] = {
|
||||
kind: 'alert',
|
||||
type: 'metaresource' as CoretypesTypeDTO,
|
||||
};
|
||||
|
||||
const baseAuthzResources: AuthtypesGettableResourcesDTO = {
|
||||
const baseAuthzResources: AuthzResources = {
|
||||
resources: [dashboardResource, alertResource],
|
||||
relations: {
|
||||
create: ['metaresource'],
|
||||
@@ -220,7 +226,7 @@ describe('buildPatchPayload', () => {
|
||||
|
||||
describe('objectsToPermissionConfig', () => {
|
||||
it('maps a wildcard selector to ALL scope', () => {
|
||||
const objects: AuthtypesGettableObjectsDTO[] = [
|
||||
const objects: CoretypesObjectGroupDTO[] = [
|
||||
{ resource: dashboardResource, selectors: ['*'] },
|
||||
];
|
||||
|
||||
@@ -233,7 +239,7 @@ describe('objectsToPermissionConfig', () => {
|
||||
});
|
||||
|
||||
it('maps specific selectors to ONLY_SELECTED scope with the IDs', () => {
|
||||
const objects: AuthtypesGettableObjectsDTO[] = [
|
||||
const objects: CoretypesObjectGroupDTO[] = [
|
||||
{ resource: dashboardResource, selectors: [ID_A, ID_B] },
|
||||
];
|
||||
|
||||
@@ -338,7 +344,7 @@ describe('buildConfig', () => {
|
||||
|
||||
describe('derivePermissionTypes', () => {
|
||||
it('derives one PermissionType per relation key with correct key and capitalised label', () => {
|
||||
const relations: AuthtypesGettableResourcesDTO['relations'] = {
|
||||
const relations: AuthzResources['relations'] = {
|
||||
create: ['metaresource'],
|
||||
read: ['metaresource'],
|
||||
delete: ['metaresource'],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import type {
|
||||
AuthtypesGettableObjectsDTO,
|
||||
AuthtypesGettableResourcesDTO,
|
||||
CoretypesResourceRefDTO,
|
||||
CoretypesObjectGroupDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { capitalize } from 'lodash-es';
|
||||
@@ -19,6 +19,11 @@ import {
|
||||
PERMISSION_ICON_MAP,
|
||||
} from './RoleDetails/constants';
|
||||
|
||||
export type AuthzResources = {
|
||||
resources: ReadonlyArray<CoretypesResourceRefDTO>;
|
||||
relations: Readonly<Record<string, ReadonlyArray<string>>>;
|
||||
};
|
||||
|
||||
export interface PermissionType {
|
||||
key: string;
|
||||
label: string;
|
||||
@@ -29,11 +34,11 @@ export interface PatchPayloadOptions {
|
||||
newConfig: PermissionConfig;
|
||||
initialConfig: PermissionConfig;
|
||||
resources: ResourceDefinition[];
|
||||
authzRes: AuthtypesGettableResourcesDTO;
|
||||
authzRes: AuthzResources;
|
||||
}
|
||||
|
||||
export function derivePermissionTypes(
|
||||
relations: AuthtypesGettableResourcesDTO['relations'] | null,
|
||||
relations: AuthzResources['relations'] | null,
|
||||
): PermissionType[] {
|
||||
const iconSize = { size: 14 };
|
||||
|
||||
@@ -55,7 +60,7 @@ export function derivePermissionTypes(
|
||||
}
|
||||
|
||||
export function deriveResourcesForRelation(
|
||||
authzResources: AuthtypesGettableResourcesDTO | null,
|
||||
authzResources: AuthzResources | null,
|
||||
relation: string,
|
||||
): ResourceDefinition[] {
|
||||
if (!authzResources?.relations) {
|
||||
@@ -65,19 +70,19 @@ export function deriveResourcesForRelation(
|
||||
return authzResources.resources
|
||||
.filter((r) => supportedTypes.includes(r.type))
|
||||
.map((r) => ({
|
||||
id: r.name,
|
||||
label: capitalize(r.name).replace(/_/g, ' '),
|
||||
id: r.kind,
|
||||
label: capitalize(r.kind).replaceAll('_', ' '),
|
||||
options: [],
|
||||
}));
|
||||
}
|
||||
|
||||
export function objectsToPermissionConfig(
|
||||
objects: AuthtypesGettableObjectsDTO[],
|
||||
objects: CoretypesObjectGroupDTO[],
|
||||
resources: ResourceDefinition[],
|
||||
): PermissionConfig {
|
||||
const config: PermissionConfig = {};
|
||||
for (const res of resources) {
|
||||
const obj = objects.find((o) => o.resource.name === res.id);
|
||||
const obj = objects.find((o) => o.resource.kind === res.id);
|
||||
if (!obj) {
|
||||
config[res.id] = {
|
||||
scope: PermissionScope.ONLY_SELECTED,
|
||||
@@ -101,19 +106,19 @@ export function buildPatchPayload({
|
||||
resources,
|
||||
authzRes,
|
||||
}: PatchPayloadOptions): {
|
||||
additions: AuthtypesGettableObjectsDTO[] | null;
|
||||
deletions: AuthtypesGettableObjectsDTO[] | null;
|
||||
additions: CoretypesObjectGroupDTO[] | null;
|
||||
deletions: CoretypesObjectGroupDTO[] | null;
|
||||
} {
|
||||
if (!authzRes) {
|
||||
return { additions: null, deletions: null };
|
||||
}
|
||||
const additions: AuthtypesGettableObjectsDTO[] = [];
|
||||
const deletions: AuthtypesGettableObjectsDTO[] = [];
|
||||
const additions: CoretypesObjectGroupDTO[] = [];
|
||||
const deletions: CoretypesObjectGroupDTO[] = [];
|
||||
|
||||
for (const res of resources) {
|
||||
const initial = initialConfig[res.id];
|
||||
const current = newConfig[res.id];
|
||||
const resourceDef = authzRes.resources.find((r) => r.name === res.id);
|
||||
const resourceDef = authzRes.resources.find((r) => r.kind === res.id);
|
||||
if (!resourceDef) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
// AUTO GENERATED FILE - DO NOT EDIT - GENERATED BY scripts/generate-permissions-type
|
||||
// AUTO GENERATED FILE - DO NOT EDIT - GENERATED BY cmd/enterprise/*.go generate authz
|
||||
export default {
|
||||
status: 'success',
|
||||
data: {
|
||||
resources: [
|
||||
{
|
||||
name: 'dashboard',
|
||||
type: 'metaresource',
|
||||
},
|
||||
{
|
||||
name: 'dashboards',
|
||||
kind: 'role',
|
||||
type: 'metaresources',
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
kind: 'role',
|
||||
type: 'role',
|
||||
},
|
||||
{
|
||||
name: 'roles',
|
||||
type: 'metaresources',
|
||||
kind: 'serviceaccount',
|
||||
type: 'serviceaccount',
|
||||
},
|
||||
],
|
||||
relations: {
|
||||
assignee: ['role'],
|
||||
attach: ['role', 'serviceaccount'],
|
||||
create: ['metaresources'],
|
||||
delete: ['user', 'serviceaccount', 'role', 'organization', 'metaresource'],
|
||||
delete: ['role', 'serviceaccount'],
|
||||
list: ['metaresources'],
|
||||
read: ['user', 'serviceaccount', 'role', 'organization', 'metaresource'],
|
||||
update: ['user', 'serviceaccount', 'role', 'organization', 'metaresource'],
|
||||
read: ['role', 'serviceaccount'],
|
||||
update: ['role', 'serviceaccount'],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import permissionsType from './permissions.type';
|
||||
import { ObjectSeparator } from './utils';
|
||||
|
||||
const ObjectSeparator = ':';
|
||||
|
||||
type PermissionsData = typeof permissionsType.data;
|
||||
export type Resource = PermissionsData['resources'][number];
|
||||
export type ResourceName = Resource['name'];
|
||||
export type ResourceName = Resource['kind'];
|
||||
export type ResourceType = Resource['type'];
|
||||
|
||||
type RelationsByType = PermissionsData['relations'];
|
||||
|
||||
type ResourceTypeMap = {
|
||||
[K in ResourceName]: Extract<Resource, { name: K }>['type'];
|
||||
[K in ResourceName]: Extract<Resource, { kind: K }>['type'];
|
||||
};
|
||||
|
||||
type RelationName = keyof RelationsByType;
|
||||
@@ -17,7 +18,7 @@ type RelationName = keyof RelationsByType;
|
||||
export type ResourcesForRelation<R extends RelationName> = Extract<
|
||||
Resource,
|
||||
{ type: RelationsByType[R][number] }
|
||||
>['name'];
|
||||
>['kind'];
|
||||
|
||||
type IsPluralResource<R extends ResourceName> =
|
||||
ResourceTypeMap[R] extends 'metaresources' ? true : false;
|
||||
|
||||
@@ -36,8 +36,8 @@ const wrapper = ({ children }: { children: ReactElement }): ReactElement => (
|
||||
|
||||
describe('useAuthZ', () => {
|
||||
it('should fetch and return permissions successfully', async () => {
|
||||
const permission1 = buildPermission('read', 'dashboard:*');
|
||||
const permission2 = buildPermission('update', 'dashboard:123');
|
||||
const permission1 = buildPermission('read', 'role:*');
|
||||
const permission2 = buildPermission('update', 'role:123');
|
||||
|
||||
const expectedResponse = {
|
||||
[permission1]: {
|
||||
@@ -74,7 +74,7 @@ describe('useAuthZ', () => {
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
const permission = buildPermission('read', 'dashboard:*');
|
||||
const permission = buildPermission('read', 'role:*');
|
||||
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
|
||||
@@ -95,9 +95,9 @@ describe('useAuthZ', () => {
|
||||
});
|
||||
|
||||
it('should refetch when permissions array changes', async () => {
|
||||
const permission1 = buildPermission('read', 'dashboard:*');
|
||||
const permission2 = buildPermission('update', 'dashboard:123');
|
||||
const permission3 = buildPermission('delete', 'dashboard:456');
|
||||
const permission1 = buildPermission('read', 'role:*');
|
||||
const permission2 = buildPermission('update', 'role:123');
|
||||
const permission3 = buildPermission('delete', 'role:456');
|
||||
|
||||
let requestCount = 0;
|
||||
|
||||
@@ -161,8 +161,8 @@ describe('useAuthZ', () => {
|
||||
});
|
||||
|
||||
it('should not refetch when permissions array order changes but content is the same', async () => {
|
||||
const permission1 = buildPermission('read', 'dashboard:*');
|
||||
const permission2 = buildPermission('update', 'dashboard:123');
|
||||
const permission1 = buildPermission('read', 'role:*');
|
||||
const permission2 = buildPermission('update', 'role:123');
|
||||
|
||||
let requestCount = 0;
|
||||
|
||||
@@ -217,8 +217,8 @@ describe('useAuthZ', () => {
|
||||
});
|
||||
|
||||
it('should send correct payload format to API', async () => {
|
||||
const permission1 = buildPermission('read', 'dashboard:*');
|
||||
const permission2 = buildPermission('update', 'dashboard:123');
|
||||
const permission1 = buildPermission('read', 'role:*');
|
||||
const permission2 = buildPermission('update', 'role:123');
|
||||
|
||||
let receivedPayload: any = null;
|
||||
|
||||
@@ -244,23 +244,23 @@ describe('useAuthZ', () => {
|
||||
expect(receivedPayload[0]).toMatchObject({
|
||||
relation: 'read',
|
||||
object: {
|
||||
resource: { name: 'dashboard', type: 'metaresource' },
|
||||
resource: { kind: 'role', type: 'role' },
|
||||
selector: '*',
|
||||
},
|
||||
});
|
||||
expect(receivedPayload[1]).toMatchObject({
|
||||
relation: 'update',
|
||||
object: {
|
||||
resource: { name: 'dashboard', type: 'metaresource' },
|
||||
resource: { kind: 'role', type: 'role' },
|
||||
selector: '123',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should batch multiple hooks into single flight request', async () => {
|
||||
const permission1 = buildPermission('read', 'dashboard:*');
|
||||
const permission2 = buildPermission('update', 'dashboard:123');
|
||||
const permission3 = buildPermission('delete', 'dashboard:456');
|
||||
const permission1 = buildPermission('read', 'role:*');
|
||||
const permission2 = buildPermission('update', 'role:123');
|
||||
const permission3 = buildPermission('delete', 'role:456');
|
||||
|
||||
let requestCount = 0;
|
||||
const receivedPayloads: any[] = [];
|
||||
@@ -304,17 +304,17 @@ describe('useAuthZ', () => {
|
||||
expect(receivedPayloads[0][0]).toMatchObject({
|
||||
relation: 'read',
|
||||
object: {
|
||||
resource: { name: 'dashboard', type: 'metaresource' },
|
||||
resource: { kind: 'role', type: 'role' },
|
||||
selector: '*',
|
||||
},
|
||||
});
|
||||
expect(receivedPayloads[0][1]).toMatchObject({
|
||||
relation: 'update',
|
||||
object: { resource: { name: 'dashboard' }, selector: '123' },
|
||||
object: { resource: { kind: 'role' }, selector: '123' },
|
||||
});
|
||||
expect(receivedPayloads[0][2]).toMatchObject({
|
||||
relation: 'delete',
|
||||
object: { resource: { name: 'dashboard' }, selector: '456' },
|
||||
object: { resource: { kind: 'role' }, selector: '456' },
|
||||
});
|
||||
|
||||
expect(result1.current.permissions).toStrictEqual({
|
||||
@@ -329,9 +329,9 @@ describe('useAuthZ', () => {
|
||||
});
|
||||
|
||||
it('should create separate batches for calls after single flight window', async () => {
|
||||
const permission1 = buildPermission('read', 'dashboard:*');
|
||||
const permission2 = buildPermission('update', 'dashboard:123');
|
||||
const permission3 = buildPermission('delete', 'dashboard:456');
|
||||
const permission1 = buildPermission('read', 'role:*');
|
||||
const permission2 = buildPermission('update', 'role:123');
|
||||
const permission3 = buildPermission('delete', 'role:456');
|
||||
|
||||
let requestCount = 0;
|
||||
const receivedPayloads: any[] = [];
|
||||
@@ -386,18 +386,18 @@ describe('useAuthZ', () => {
|
||||
expect(receivedPayloads[1]).toHaveLength(2);
|
||||
expect(receivedPayloads[1][0]).toMatchObject({
|
||||
relation: 'update',
|
||||
object: { resource: { name: 'dashboard' }, selector: '123' },
|
||||
object: { resource: { kind: 'role' }, selector: '123' },
|
||||
});
|
||||
expect(receivedPayloads[1][1]).toMatchObject({
|
||||
relation: 'delete',
|
||||
object: { resource: { name: 'dashboard' }, selector: '456' },
|
||||
object: { resource: { kind: 'role' }, selector: '456' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should map permissions correctly when API returns response out of order', async () => {
|
||||
const permission1 = buildPermission('read', 'dashboard:*');
|
||||
const permission2 = buildPermission('update', 'dashboard:123');
|
||||
const permission3 = buildPermission('delete', 'dashboard:456');
|
||||
const permission1 = buildPermission('read', 'role:*');
|
||||
const permission2 = buildPermission('update', 'role:123');
|
||||
const permission3 = buildPermission('delete', 'role:456');
|
||||
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
|
||||
@@ -435,8 +435,8 @@ describe('useAuthZ', () => {
|
||||
});
|
||||
|
||||
it('should not leak state between separate batches', async () => {
|
||||
const permission1 = buildPermission('read', 'dashboard:*');
|
||||
const permission2 = buildPermission('update', 'dashboard:123');
|
||||
const permission1 = buildPermission('read', 'role:*');
|
||||
const permission2 = buildPermission('update', 'role:123');
|
||||
|
||||
let requestCount = 0;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { authzCheck } from 'api/generated/services/authz';
|
||||
import type {
|
||||
AuthtypesObjectDTO,
|
||||
CoretypesObjectDTO,
|
||||
AuthtypesTransactionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
@@ -34,7 +34,7 @@ function dispatchPermission(
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const copiedPermissions = pendingPermissions.slice();
|
||||
const copiedPermissions = [...pendingPermissions];
|
||||
pendingPermissions = [];
|
||||
ctx = null;
|
||||
|
||||
@@ -50,9 +50,9 @@ async function fetchManyPermissions(
|
||||
): Promise<AuthZCheckResponse> {
|
||||
const payload: AuthtypesTransactionDTO[] = permissions.map((permission) => {
|
||||
const dto = permissionToTransactionDto(permission);
|
||||
const object: AuthtypesObjectDTO = {
|
||||
const object: CoretypesObjectDTO = {
|
||||
resource: {
|
||||
name: dto.object.resource.name,
|
||||
kind: dto.object.resource.kind,
|
||||
type: dto.object.resource.type,
|
||||
},
|
||||
selector: dto.object.selector,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { AuthtypesTransactionDTO } from '../../api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
AuthtypesTransactionDTO,
|
||||
CoretypesTypeDTO,
|
||||
AuthtypesRelationDTO,
|
||||
} from '../../api/generated/services/sigNoz.schemas';
|
||||
import permissionsType from './permissions.type';
|
||||
import {
|
||||
AuthZObject,
|
||||
@@ -33,36 +37,72 @@ export function parsePermission(permission: BrandedPermission): {
|
||||
return { relation: relation as AuthZRelation, object };
|
||||
}
|
||||
|
||||
const resourceNameToType = permissionsType.data.resources.reduce(
|
||||
const kindsByType = permissionsType.data.resources.reduce(
|
||||
(acc, r) => {
|
||||
acc[r.name] = r.type;
|
||||
if (!acc[r.type]) {
|
||||
acc[r.type] = new Set();
|
||||
}
|
||||
acc[r.type].add(r.kind);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<ResourceName, ResourceType>,
|
||||
{} as Record<string, Set<string>>,
|
||||
);
|
||||
|
||||
function resolveType(
|
||||
relation: AuthZRelation,
|
||||
kind: string,
|
||||
): ResourceType | undefined {
|
||||
const candidates: readonly string[] =
|
||||
permissionsType.data.relations[relation] ?? [];
|
||||
for (const t of candidates) {
|
||||
if (kindsByType[t]?.has(kind)) {
|
||||
return t as ResourceType;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function splitObjectString(objectStr: string): {
|
||||
resourceName: string;
|
||||
selector: string;
|
||||
} {
|
||||
const idx = objectStr.indexOf(ObjectSeparator);
|
||||
if (idx === -1) {
|
||||
return { resourceName: objectStr, selector: '' };
|
||||
}
|
||||
return {
|
||||
resourceName: objectStr.slice(0, idx),
|
||||
selector: objectStr.slice(idx + 1),
|
||||
};
|
||||
}
|
||||
|
||||
export function permissionToTransactionDto(
|
||||
permission: BrandedPermission,
|
||||
): AuthtypesTransactionDTO {
|
||||
const { relation, object: objectStr } = parsePermission(permission);
|
||||
const directType = resourceNameToType[objectStr as ResourceName];
|
||||
const directType = resolveType(relation, objectStr);
|
||||
if (directType === 'metaresources') {
|
||||
return {
|
||||
relation,
|
||||
relation: relation as AuthtypesRelationDTO,
|
||||
object: {
|
||||
resource: { name: objectStr, type: directType },
|
||||
resource: {
|
||||
kind: objectStr as ResourceName,
|
||||
type: directType as CoretypesTypeDTO,
|
||||
},
|
||||
selector: '*',
|
||||
},
|
||||
};
|
||||
}
|
||||
const [resourceName, selector] = objectStr.split(ObjectSeparator);
|
||||
const type =
|
||||
resourceNameToType[resourceName as ResourceName] ?? 'metaresource';
|
||||
const { resourceName, selector } = splitObjectString(objectStr);
|
||||
const type = resolveType(relation, resourceName) ?? 'metaresource';
|
||||
|
||||
return {
|
||||
relation,
|
||||
relation: relation as AuthtypesRelationDTO,
|
||||
object: {
|
||||
resource: { name: resourceName, type },
|
||||
resource: {
|
||||
kind: resourceName as ResourceName,
|
||||
type: type as CoretypesTypeDTO,
|
||||
},
|
||||
selector: selector || '*',
|
||||
},
|
||||
};
|
||||
@@ -75,7 +115,7 @@ export function gettableTransactionToPermission(
|
||||
relation,
|
||||
object: { resource, selector },
|
||||
} = item;
|
||||
const resourceName = String(resource.name);
|
||||
const resourceName = String(resource.kind);
|
||||
const selectorStr = typeof selector === 'string' ? selector : '*';
|
||||
const objectStr =
|
||||
resource.type === 'metaresources'
|
||||
|
||||
@@ -26,22 +26,5 @@ func (provider *provider) addAuthzRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/authz/resources", handler.New(provider.authZ.OpenAccess(provider.authzHandler.GetResources), handler.OpenAPIDef{
|
||||
ID: "AuthzResources",
|
||||
Tags: []string{"authz"},
|
||||
Summary: "Get resources",
|
||||
Description: "Gets all the available resources",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(authtypes.GettableResources),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: nil,
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"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/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -83,9 +84,9 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}", handler.New(provider.authZ.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicData,
|
||||
authtypes.RelationRead,
|
||||
dashboardtypes.TypeableMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
authtypes.Relation{Verb: coretypes.VerbRead},
|
||||
coretypes.ResourceMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]coretypes.Selector, valuer.UUID, error) {
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
if err != nil {
|
||||
return nil, valuer.UUID{}, err
|
||||
@@ -104,16 +105,16 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newAnonymousSecuritySchemes([]string{dashboardtypes.TypeableMetaResourcePublicDashboard.Scope(authtypes.RelationRead)}),
|
||||
SecuritySchemes: newAnonymousSecuritySchemes([]string{coretypes.ResourceMetaResourcePublicDashboard.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}/widgets/{idx}/query_range", handler.New(provider.authZ.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicWidgetQueryRange,
|
||||
authtypes.RelationRead,
|
||||
dashboardtypes.TypeableMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
authtypes.Relation{Verb: coretypes.VerbRead},
|
||||
coretypes.ResourceMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]coretypes.Selector, valuer.UUID, error) {
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
if err != nil {
|
||||
return nil, valuer.UUID{}, err
|
||||
@@ -132,7 +133,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newAnonymousSecuritySchemes([]string{dashboardtypes.TypeableMetaResourcePublicDashboard.Scope(authtypes.RelationRead)}),
|
||||
SecuritySchemes: newAnonymousSecuritySchemes([]string{coretypes.ResourceMetaResourcePublicDashboard.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -68,7 +69,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
Description: "Gets all objects connected to the specified role via a given relation type",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*authtypes.GettableObjects, 0),
|
||||
Response: make([]*coretypes.ObjectGroup, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
@@ -100,7 +101,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch objects for a role by relation",
|
||||
Description: "Patches the objects connected to the specified role via a given relation type",
|
||||
Request: new(authtypes.PatchableObjects),
|
||||
Request: new(coretypes.PatchableObjects),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -19,16 +20,16 @@ func newTestSettings() factory.ScopedProviderSettings {
|
||||
return factory.NewScopedProviderSettings(instrumentationtest.New().ToProviderSettings(), "auditorserver_test")
|
||||
}
|
||||
|
||||
func newTestEvent(resource string, action audittypes.Action) audittypes.AuditEvent {
|
||||
func newTestEvent(resource string, action coretypes.Verb) audittypes.AuditEvent {
|
||||
return audittypes.AuditEvent{
|
||||
Timestamp: time.Now(),
|
||||
EventName: audittypes.NewEventName(resource, action),
|
||||
EventName: audittypes.NewEventName(coretypes.MustNewKind(resource), action),
|
||||
AuditAttributes: audittypes.AuditAttributes{
|
||||
Action: action,
|
||||
Outcome: audittypes.OutcomeSuccess,
|
||||
},
|
||||
ResourceAttributes: audittypes.ResourceAttributes{
|
||||
ResourceKind: resource,
|
||||
ResourceKind: coretypes.MustNewKind(resource),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -83,7 +84,7 @@ func TestAdd_FlushesOnBatchSize(t *testing.T) {
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
server.Add(ctx, newTestEvent("dashboard", audittypes.ActionCreate))
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbCreate))
|
||||
}
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
@@ -112,7 +113,7 @@ func TestAdd_FlushesOnInterval(t *testing.T) {
|
||||
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
server.Add(ctx, newTestEvent("user", audittypes.ActionUpdate))
|
||||
server.Add(ctx, newTestEvent("user", coretypes.VerbUpdate))
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
return exported.Load() == 1
|
||||
@@ -130,9 +131,9 @@ func TestAdd_DropsWhenBufferFull(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
server.Add(ctx, newTestEvent("dashboard", audittypes.ActionCreate))
|
||||
server.Add(ctx, newTestEvent("dashboard", audittypes.ActionUpdate))
|
||||
server.Add(ctx, newTestEvent("dashboard", audittypes.ActionDelete))
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbCreate))
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbUpdate))
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbDelete))
|
||||
|
||||
assert.Equal(t, 2, server.queueLen())
|
||||
}
|
||||
@@ -155,7 +156,7 @@ func TestStop_DrainsRemainingEvents(t *testing.T) {
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
server.Add(ctx, newTestEvent("alert-rule", audittypes.ActionCreate))
|
||||
server.Add(ctx, newTestEvent("alert-rule", coretypes.VerbCreate))
|
||||
}
|
||||
|
||||
require.NoError(t, server.Stop(ctx))
|
||||
@@ -180,8 +181,8 @@ func TestAdd_ContinuesAfterExportFailure(t *testing.T) {
|
||||
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
server.Add(ctx, newTestEvent("user", audittypes.ActionDelete))
|
||||
server.Add(ctx, newTestEvent("user", audittypes.ActionDelete))
|
||||
server.Add(ctx, newTestEvent("user", coretypes.VerbDelete))
|
||||
server.Add(ctx, newTestEvent("user", coretypes.VerbDelete))
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
return calls.Load() >= 1
|
||||
@@ -212,7 +213,7 @@ func TestAdd_ConcurrentSafety(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
server.Add(ctx, newTestEvent("dashboard", audittypes.ActionCreate))
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbCreate))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
@@ -14,10 +15,10 @@ type AuthZ interface {
|
||||
factory.ServiceWithHealthy
|
||||
|
||||
// CheckWithTupleCreation takes upon the responsibility for generating the tuples alongside everything Check does.
|
||||
CheckWithTupleCreation(context.Context, authtypes.Claims, valuer.UUID, authtypes.Relation, authtypes.Typeable, []authtypes.Selector, []authtypes.Selector) error
|
||||
CheckWithTupleCreation(context.Context, authtypes.Claims, valuer.UUID, authtypes.Relation, coretypes.Resource, []coretypes.Selector, []coretypes.Selector) error
|
||||
|
||||
// CheckWithTupleCreationWithoutClaims checks permissions for anonymous users.
|
||||
CheckWithTupleCreationWithoutClaims(context.Context, valuer.UUID, authtypes.Relation, authtypes.Typeable, []authtypes.Selector, []authtypes.Selector) error
|
||||
CheckWithTupleCreationWithoutClaims(context.Context, valuer.UUID, authtypes.Relation, coretypes.Resource, []coretypes.Selector, []coretypes.Selector) error
|
||||
|
||||
// BatchCheck accepts a map of ID → tuple and returns a map of ID → authorization result.
|
||||
BatchCheck(context.Context, map[string]*openfgav1.TupleKey) (map[string]*authtypes.TupleKeyAuthorization, error)
|
||||
@@ -30,7 +31,7 @@ type AuthZ interface {
|
||||
Write(context.Context, []*openfgav1.TupleKey, []*openfgav1.TupleKey) error
|
||||
|
||||
// Lists the selectors for objects assigned to subject (s) with relation (r) on resource (s)
|
||||
ListObjects(context.Context, string, authtypes.Relation, authtypes.Type) ([]*authtypes.Object, error)
|
||||
ListObjects(context.Context, string, authtypes.Relation, coretypes.Type) ([]*coretypes.Object, error)
|
||||
|
||||
// Creates the role.
|
||||
Create(context.Context, valuer.UUID, *authtypes.Role) error
|
||||
@@ -39,16 +40,13 @@ type AuthZ interface {
|
||||
GetOrCreate(context.Context, valuer.UUID, *authtypes.Role) (*authtypes.Role, error)
|
||||
|
||||
// Gets the objects associated with the given role and relation.
|
||||
GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*authtypes.Object, error)
|
||||
|
||||
// Gets all the typeable resources registered from role registry.
|
||||
GetResources(context.Context) []*authtypes.Resource
|
||||
GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*coretypes.Object, error)
|
||||
|
||||
// Patches the role.
|
||||
Patch(context.Context, valuer.UUID, *authtypes.Role) error
|
||||
|
||||
// Patches the objects in authorization server associated with the given role and relation
|
||||
PatchObjects(context.Context, valuer.UUID, string, authtypes.Relation, []*authtypes.Object, []*authtypes.Object) error
|
||||
PatchObjects(context.Context, valuer.UUID, string, authtypes.Relation, []*coretypes.Object, []*coretypes.Object) error
|
||||
|
||||
// Deletes the role and tuples in authorization server.
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
@@ -90,12 +88,6 @@ type AuthZ interface {
|
||||
// OnBeforeRoleDelete is a callback invoked before a role is deleted.
|
||||
type OnBeforeRoleDelete func(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
type RegisterTypeable interface {
|
||||
MustGetTypeables() []authtypes.Typeable
|
||||
|
||||
MustGetManagedRoleTransactions() map[string][]*authtypes.Transaction
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
Create(http.ResponseWriter, *http.Request)
|
||||
|
||||
@@ -103,8 +95,6 @@ type Handler interface {
|
||||
|
||||
GetObjects(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetResources(http.ResponseWriter, *http.Request)
|
||||
|
||||
List(http.ResponseWriter, *http.Request)
|
||||
|
||||
Patch(http.ResponseWriter, *http.Request)
|
||||
|
||||
@@ -25,7 +25,7 @@ func newConfig() factory.Config {
|
||||
return &Config{
|
||||
Provider: "openfga",
|
||||
OpenFGA: OpenFGAConfig{
|
||||
MaxTuplesPerWrite: 100,
|
||||
MaxTuplesPerWrite: 300,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/authz/openfgaserver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
@@ -18,31 +19,27 @@ import (
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
server *openfgaserver.Server
|
||||
store authtypes.RoleStore
|
||||
registry []authz.RegisterTypeable
|
||||
managedRolesByTransaction map[string][]string
|
||||
server *openfgaserver.Server
|
||||
store authtypes.RoleStore
|
||||
registry *authtypes.Registry
|
||||
}
|
||||
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, registry *authtypes.Registry) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) {
|
||||
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, registry)
|
||||
})
|
||||
}
|
||||
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, registry *authtypes.Registry) (authz.AuthZ, error) {
|
||||
server, err := openfgaserver.NewOpenfgaServer(ctx, settings, config, sqlstore, openfgaSchema, openfgaDataStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
managedRolesByTransaction := buildManagedRolesByTransaction(registry)
|
||||
|
||||
return &provider{
|
||||
server: server,
|
||||
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
|
||||
registry: registry,
|
||||
managedRolesByTransaction: managedRolesByTransaction,
|
||||
server: server,
|
||||
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
|
||||
registry: registry,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -62,11 +59,11 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq map[string]*o
|
||||
return provider.server.BatchCheck(ctx, tupleReq)
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable coretypes.Resource, selectors []coretypes.Selector, roleSelectors []coretypes.Selector) error {
|
||||
return provider.server.CheckWithTupleCreation(ctx, claims, orgID, relation, typeable, selectors, roleSelectors)
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable coretypes.Resource, selectors []coretypes.Selector, roleSelectors []coretypes.Selector) error {
|
||||
return provider.server.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, typeable, selectors, roleSelectors)
|
||||
}
|
||||
|
||||
@@ -78,7 +75,7 @@ func (provider *provider) ReadTuples(ctx context.Context, tupleKey *openfgav1.Re
|
||||
return provider.server.ReadTuples(ctx, tupleKey)
|
||||
}
|
||||
|
||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
|
||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType coretypes.Type) ([]*coretypes.Object, error) {
|
||||
return provider.server.ListObjects(ctx, subject, relation, objectType)
|
||||
}
|
||||
|
||||
@@ -103,22 +100,14 @@ func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UU
|
||||
}
|
||||
|
||||
func (provider *provider) Grant(ctx context.Context, orgID valuer.UUID, names []string, subject string) error {
|
||||
selectors := make([]authtypes.Selector, len(names))
|
||||
selectors := make([]coretypes.Selector, len(names))
|
||||
for idx, name := range names {
|
||||
selectors[idx] = authtypes.MustNewSelector(authtypes.TypeRole, name)
|
||||
selectors[idx] = coretypes.TypeRole.MustSelector(name)
|
||||
}
|
||||
|
||||
tuples, err := authtypes.TypeableRole.Tuples(
|
||||
subject,
|
||||
authtypes.RelationAssignee,
|
||||
selectors,
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tuples := authtypes.NewTuples(coretypes.NewResourceRole(), subject, authtypes.Relation{Verb: coretypes.VerbAssignee}, selectors, orgID)
|
||||
|
||||
err = provider.Write(ctx, tuples, nil)
|
||||
err := provider.Write(ctx, tuples, nil)
|
||||
if err != nil {
|
||||
return errors.WithAdditionalf(err, "failed to grant roles: %v to subject: %s", names, subject)
|
||||
}
|
||||
@@ -141,22 +130,14 @@ func (provider *provider) ModifyGrant(ctx context.Context, orgID valuer.UUID, ex
|
||||
}
|
||||
|
||||
func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, names []string, subject string) error {
|
||||
selectors := make([]authtypes.Selector, len(names))
|
||||
selectors := make([]coretypes.Selector, len(names))
|
||||
for idx, name := range names {
|
||||
selectors[idx] = authtypes.MustNewSelector(authtypes.TypeRole, name)
|
||||
selectors[idx] = coretypes.TypeRole.MustSelector(name)
|
||||
}
|
||||
|
||||
tuples, err := authtypes.TypeableRole.Tuples(
|
||||
subject,
|
||||
authtypes.RelationAssignee,
|
||||
selectors,
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tuples := authtypes.NewTuples(coretypes.NewResourceRole(), subject, authtypes.Relation{Verb: coretypes.VerbAssignee}, selectors, orgID)
|
||||
|
||||
err = provider.Write(ctx, nil, tuples)
|
||||
err := provider.Write(ctx, nil, tuples)
|
||||
if err != nil {
|
||||
return errors.WithAdditionalf(err, "failed to revoke roles: %v to subject: %s", names, subject)
|
||||
}
|
||||
@@ -184,7 +165,7 @@ func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID,
|
||||
}
|
||||
|
||||
func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||
return provider.Grant(ctx, orgID, []string{authtypes.SigNozAdminRoleName}, authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil))
|
||||
return provider.Grant(ctx, orgID, []string{authtypes.SigNozAdminRoleName}, authtypes.MustNewSubject(coretypes.NewResourceUser(), userID.String(), orgID, nil))
|
||||
}
|
||||
|
||||
func (setter *provider) Create(_ context.Context, _ valuer.UUID, _ *authtypes.Role) error {
|
||||
@@ -195,11 +176,7 @@ func (provider *provider) GetOrCreate(_ context.Context, _ valuer.UUID, _ *autht
|
||||
return nil, errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
return []*authtypes.Resource{}
|
||||
}
|
||||
|
||||
func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*coretypes.Object, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
@@ -207,7 +184,7 @@ func (provider *provider) Patch(_ context.Context, _ valuer.UUID, _ *authtypes.R
|
||||
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) PatchObjects(_ context.Context, _ valuer.UUID, _ string, _ authtypes.Relation, _, _ []*authtypes.Object) error {
|
||||
func (provider *provider) PatchObjects(_ context.Context, _ valuer.UUID, _ string, _ authtypes.Relation, _, _ []*coretypes.Object) error {
|
||||
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
@@ -220,7 +197,7 @@ func (provider *provider) CheckTransactions(ctx context.Context, subject string,
|
||||
return make([]*authtypes.TransactionWithAuthorization, 0), nil
|
||||
}
|
||||
|
||||
tuples, preResolved, roleCorrelations, err := authtypes.NewTuplesFromTransactionsWithManagedRoles(transactions, subject, orgID, provider.managedRolesByTransaction)
|
||||
tuples, preResolved, roleCorrelations, err := authtypes.NewTuplesFromTransactionsWithManagedRoles(transactions, subject, orgID, provider.registry.ManagedRolesByTransaction())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -236,21 +213,3 @@ func (provider *provider) CheckTransactions(ctx context.Context, subject string,
|
||||
|
||||
return authtypes.NewTransactionWithAuthorizationFromBatchResults(transactions, batchResults, preResolved, roleCorrelations), nil
|
||||
}
|
||||
|
||||
func buildManagedRolesByTransaction(registry []authz.RegisterTypeable) map[string][]string {
|
||||
managedRolesByTransaction := make(map[string][]string)
|
||||
for _, register := range registry {
|
||||
for roleName, transactions := range register.MustGetManagedRoleTransactions() {
|
||||
for _, txn := range transactions {
|
||||
key := txn.TransactionKey()
|
||||
managedRolesByTransaction[key] = append(managedRolesByTransaction[key], roleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return managedRolesByTransaction
|
||||
}
|
||||
|
||||
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ type role
|
||||
relations
|
||||
define assignee: [user, serviceaccount]
|
||||
|
||||
type organisation
|
||||
type organization
|
||||
relations
|
||||
define create: [role#assignee]
|
||||
define read: [role#assignee]
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
authz "github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
@@ -141,18 +142,18 @@ func (server *Server) BatchCheck(ctx context.Context, tupleReq map[string]*openf
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, _ coretypes.Resource, _ []coretypes.Selector, roleSelectors []coretypes.Selector) error {
|
||||
subject := ""
|
||||
switch claims.Principal {
|
||||
case authtypes.PrincipalUser:
|
||||
user, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||
user, err := authtypes.NewSubject(coretypes.NewResourceUser(), claims.UserID, orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subject = user
|
||||
case authtypes.PrincipalServiceAccount:
|
||||
serviceAccount, err := authtypes.NewSubject(authtypes.TypeableServiceAccount, claims.ServiceAccountID, orgID, nil)
|
||||
serviceAccount, err := authtypes.NewSubject(coretypes.NewResourceServiceAccount(), claims.ServiceAccountID, orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -160,10 +161,7 @@ func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtyp
|
||||
subject = serviceAccount
|
||||
}
|
||||
|
||||
tupleSlice, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tupleSlice := authtypes.NewTuples(coretypes.NewResourceRole(), subject, authtypes.Relation{Verb: coretypes.VerbAssignee}, roleSelectors, orgID)
|
||||
|
||||
// Convert slice to map with generated IDs for internal use
|
||||
tuples := make(map[string]*openfgav1.TupleKey, len(tupleSlice))
|
||||
@@ -185,16 +183,13 @@ func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtyp
|
||||
return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subjects are not authorized for requested access")
|
||||
}
|
||||
|
||||
func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, _ authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, _ authtypes.Relation, _ coretypes.Resource, _ []coretypes.Selector, roleSelectors []coretypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(coretypes.NewResourceAnonymous(), coretypes.AnonymousUser.String(), orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tupleSlice, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tupleSlice := authtypes.NewTuples(coretypes.NewResourceRole(), subject, authtypes.Relation{Verb: coretypes.VerbAssignee}, roleSelectors, orgID)
|
||||
|
||||
// Convert slice to map with generated IDs for internal use
|
||||
tuples := make(map[string]*openfgav1.TupleKey, len(tupleSlice))
|
||||
@@ -293,7 +288,7 @@ func (server *Server) ReadTuples(ctx context.Context, tupleKey *openfgav1.ReadRe
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
|
||||
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType coretypes.Type) ([]*coretypes.Object, error) {
|
||||
storeID, modelID := server.getStoreIDandModelID()
|
||||
response, err := server.openfgaServer.ListObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: storeID,
|
||||
@@ -306,7 +301,7 @@ func (server *Server) ListObjects(ctx context.Context, subject string, relation
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "cannot list objects for subject %s with relation %s for type %s", subject, relation.StringValue(), objectType.StringValue())
|
||||
}
|
||||
|
||||
return authtypes.MustNewObjectsFromStringSlice(response.Objects), nil
|
||||
return coretypes.MustNewObjectsFromStringSlice(response.Objects), nil
|
||||
}
|
||||
|
||||
func (server *Server) getOrCreateStore(ctx context.Context, name string) (string, error) {
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestProviderStartStop(t *testing.T) {
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
sqlstore := sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherRegexp)
|
||||
|
||||
openfgaDataStore, err := NewSQLStore(sqlstore)
|
||||
openfgaDataStore, err := NewSQLStore(sqlstore, authz.Config{OpenFGA: authz.OpenFGAConfig{MaxTuplesPerWrite: 100}})
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedModel := `module base
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package openfgaserver
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/openfga/openfga/pkg/storage"
|
||||
@@ -8,11 +9,11 @@ import (
|
||||
"github.com/openfga/openfga/pkg/storage/sqlite"
|
||||
)
|
||||
|
||||
func NewSQLStore(store sqlstore.SQLStore) (storage.OpenFGADatastore, error) {
|
||||
func NewSQLStore(store sqlstore.SQLStore, config authz.Config) (storage.OpenFGADatastore, error) {
|
||||
switch store.BunDB().Dialect().Name().String() {
|
||||
case "sqlite":
|
||||
return sqlite.NewWithDB(store.SQLDB(), &sqlcommon.Config{
|
||||
MaxTuplesPerWriteField: 100,
|
||||
MaxTuplesPerWriteField: config.OpenFGA.MaxTuplesPerWrite,
|
||||
MaxTypesPerModelField: 100,
|
||||
})
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"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"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -97,25 +98,20 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, authtypes.ErrCodeRoleInvalidInput, "relation is missing from the request"))
|
||||
return
|
||||
}
|
||||
relation, err := authtypes.NewRelation(relationStr)
|
||||
|
||||
relation, err := coretypes.NewVerb(relationStr)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
objects, err := handler.authz.GetObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, relation)
|
||||
objects, err := handler.authz.GetObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, authtypes.Relation{Verb: relation})
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, authtypes.NewGettableObjects(objects))
|
||||
}
|
||||
|
||||
func (handler *handler) GetResources(rw http.ResponseWriter, r *http.Request) {
|
||||
resources := handler.authz.GetResources(r.Context())
|
||||
|
||||
render.Success(rw, http.StatusOK, authtypes.NewGettableResources(resources))
|
||||
render.Success(rw, http.StatusOK, coretypes.NewObjectGroupsFromObjects(objects))
|
||||
}
|
||||
|
||||
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -190,7 +186,7 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
relation, err := authtypes.NewRelation(mux.Vars(r)["relation"])
|
||||
relation, err := coretypes.NewVerb(mux.Vars(r)["relation"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -207,19 +203,19 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
req := new(authtypes.PatchableObjects)
|
||||
req := new(coretypes.PatchableObjects)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
additions, deletions, err := authtypes.NewPatchableObjects(req.Additions, req.Deletions, relation)
|
||||
additions, deletions, err := coretypes.NewPatchableObjects(req.Additions, req.Deletions, relation)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.authz.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), role.Name, relation, additions, deletions)
|
||||
err = handler.authz.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), role.Name, authtypes.Relation{Verb: relation}, additions, deletions)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -266,7 +262,7 @@ func (handler *handler) Check(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||
subject, err := authtypes.NewSubject(coretypes.NewResourceUser(), claims.UserID, orgID, nil)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
@@ -171,7 +171,7 @@ func TestAwaitHealthy(t *testing.T) {
|
||||
func TestAwaitHealthyWithFailure(t *testing.T) {
|
||||
s1 := &failingHealthyService{
|
||||
healthyC: make(chan struct{}),
|
||||
err: errors.Newf(errors.TypeInternal, errors.CodeInternal,"startup failed"),
|
||||
err: errors.Newf(errors.TypeInternal, errors.CodeInternal, "startup failed"),
|
||||
}
|
||||
|
||||
registry, err := NewRegistry(context.Background(), slog.New(slog.DiscardHandler), NewNamedService(MustNewName("s1"), s1))
|
||||
@@ -245,7 +245,7 @@ func TestDependsOnStartsAfterDependency(t *testing.T) {
|
||||
func TestDependsOnFailsWhenDependencyFails(t *testing.T) {
|
||||
s1 := &failingHealthyService{
|
||||
healthyC: make(chan struct{}),
|
||||
err: errors.Newf(errors.TypeInternal, errors.CodeInternal,"s1 crashed"),
|
||||
err: errors.Newf(errors.TypeInternal, errors.CodeInternal, "s1 crashed"),
|
||||
}
|
||||
s2 := newTestService(t)
|
||||
|
||||
@@ -291,7 +291,7 @@ func TestDependsOnUnknownServiceIsIgnored(t *testing.T) {
|
||||
func TestServiceStateFailed(t *testing.T) {
|
||||
s1 := &failingHealthyService{
|
||||
healthyC: make(chan struct{}),
|
||||
err: errors.Newf(errors.TypeInternal, errors.CodeInternal,"fatal error"),
|
||||
err: errors.Newf(errors.TypeInternal, errors.CodeInternal, "fatal error"),
|
||||
}
|
||||
|
||||
registry, err := NewRegistry(context.Background(), slog.New(slog.DiscardHandler), NewNamedService(MustNewName("s1"), s1))
|
||||
|
||||
@@ -15,12 +15,24 @@ func TestQueryBinding_BindQuery(t *testing.T) {
|
||||
obj any
|
||||
expected any
|
||||
}{
|
||||
{name: "SingleIntField_NonEmptyValue", query: map[string][]string{"a": {"1"}}, obj: &struct{A int `query:"a"`}{}, expected: &struct{ A int }{A: 1}},
|
||||
{name: "SingleIntField_EmptyValue", query: map[string][]string{"a": {""}}, obj: &struct{A int `query:"a"`}{}, expected: &struct{ A int }{A: 0}},
|
||||
{name: "SingleIntField_MissingField", query: map[string][]string{}, obj: &struct{A int `query:"a"`}{}, expected: &struct{ A int }{A: 0}},
|
||||
{name: "SinglePointerIntField_NonEmptyValue", query: map[string][]string{"a": {"1"}}, obj: &struct{A *int `query:"a"`}{}, expected: &struct{ A *int }{A: &one}},
|
||||
{name: "SinglePointerIntField_EmptyValue", query: map[string][]string{"a": {""}}, obj: &struct{A *int `query:"a"`}{}, expected: &struct{ A *int }{A: &zero}},
|
||||
{name: "SinglePointerIntField_MissingField", query: map[string][]string{}, obj: &struct{A *int `query:"a"`}{}, expected: &struct{ A *int }{A: nil}},
|
||||
{name: "SingleIntField_NonEmptyValue", query: map[string][]string{"a": {"1"}}, obj: &struct {
|
||||
A int `query:"a"`
|
||||
}{}, expected: &struct{ A int }{A: 1}},
|
||||
{name: "SingleIntField_EmptyValue", query: map[string][]string{"a": {""}}, obj: &struct {
|
||||
A int `query:"a"`
|
||||
}{}, expected: &struct{ A int }{A: 0}},
|
||||
{name: "SingleIntField_MissingField", query: map[string][]string{}, obj: &struct {
|
||||
A int `query:"a"`
|
||||
}{}, expected: &struct{ A int }{A: 0}},
|
||||
{name: "SinglePointerIntField_NonEmptyValue", query: map[string][]string{"a": {"1"}}, obj: &struct {
|
||||
A *int `query:"a"`
|
||||
}{}, expected: &struct{ A *int }{A: &one}},
|
||||
{name: "SinglePointerIntField_EmptyValue", query: map[string][]string{"a": {""}}, obj: &struct {
|
||||
A *int `query:"a"`
|
||||
}{}, expected: &struct{ A *int }{A: &zero}},
|
||||
{name: "SinglePointerIntField_MissingField", query: map[string][]string{}, obj: &struct {
|
||||
A *int `query:"a"`
|
||||
}{}, expected: &struct{ A *int }{A: nil}},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
@@ -2,14 +2,15 @@ package handler
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
)
|
||||
|
||||
// Option configures optional behaviour on a handler created by New.
|
||||
type Option func(*handler)
|
||||
|
||||
type AuditDef struct {
|
||||
ResourceKind string // AuthZ Typeable.Kind() value, e.g. "dashboard", "user".
|
||||
Action audittypes.Action // create, update, delete, login, etc.
|
||||
ResourceKind coretypes.Kind // Typeable.Kind() value, e.g. "dashboard", "user".
|
||||
Action coretypes.Verb // create, update, delete, etc.
|
||||
Category audittypes.ActionCategory // access_control, configuration_change, etc.
|
||||
ResourceIDParam string // Gorilla mux path param name for the resource ID.
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -40,18 +41,18 @@ func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozEditorRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozViewerRoleName),
|
||||
selectors := []coretypes.Selector{
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozAdminRoleName),
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozEditorRoleName),
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozViewerRoleName),
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(
|
||||
ctx,
|
||||
claims,
|
||||
valuer.MustNewUUID(claims.OrgID),
|
||||
authtypes.RelationAssignee,
|
||||
authtypes.TypeableRole,
|
||||
authtypes.Relation{Verb: coretypes.VerbAssignee},
|
||||
coretypes.NewResourceRole(),
|
||||
selectors,
|
||||
selectors,
|
||||
)
|
||||
@@ -79,17 +80,17 @@ func (middleware *AuthZ) EditAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozEditorRoleName),
|
||||
selectors := []coretypes.Selector{
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozAdminRoleName),
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozEditorRoleName),
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(
|
||||
ctx,
|
||||
claims,
|
||||
valuer.MustNewUUID(claims.OrgID),
|
||||
authtypes.RelationAssignee,
|
||||
authtypes.TypeableRole,
|
||||
authtypes.Relation{Verb: coretypes.VerbAssignee},
|
||||
coretypes.NewResourceRole(),
|
||||
selectors,
|
||||
selectors,
|
||||
)
|
||||
@@ -117,16 +118,16 @@ func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
selectors := []coretypes.Selector{
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozAdminRoleName),
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(
|
||||
ctx,
|
||||
claims,
|
||||
valuer.MustNewUUID(claims.OrgID),
|
||||
authtypes.RelationAssignee,
|
||||
authtypes.TypeableRole,
|
||||
authtypes.Relation{Verb: coretypes.VerbAssignee},
|
||||
coretypes.NewResourceRole(),
|
||||
selectors,
|
||||
selectors,
|
||||
)
|
||||
@@ -153,16 +154,16 @@ func (middleware *AuthZ) SelfAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
selectors := []coretypes.Selector{
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozAdminRoleName),
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(
|
||||
req.Context(),
|
||||
claims,
|
||||
valuer.MustNewUUID(claims.OrgID),
|
||||
authtypes.RelationAssignee,
|
||||
authtypes.TypeableRole,
|
||||
authtypes.Relation{Verb: coretypes.VerbAssignee},
|
||||
coretypes.NewResourceRole(),
|
||||
selectors,
|
||||
selectors,
|
||||
)
|
||||
@@ -185,7 +186,7 @@ func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithClaimsFn, roles []string) http.HandlerFunc {
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, typeable coretypes.Resource, cb authtypes.SelectorCallbackWithClaimsFn, roles []string) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
@@ -200,9 +201,9 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
|
||||
return
|
||||
}
|
||||
|
||||
roleSelectors := []authtypes.Selector{}
|
||||
roleSelectors := []coretypes.Selector{}
|
||||
for _, role := range roles {
|
||||
roleSelectors = append(roleSelectors, authtypes.MustNewSelector(authtypes.TypeRole, role))
|
||||
roleSelectors = append(roleSelectors, coretypes.TypeRole.MustSelector(role))
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(ctx, claims, valuer.MustNewUUID(claims.OrgID), relation, typeable, selectors, roleSelectors)
|
||||
@@ -215,7 +216,7 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithoutClaimsFn, roles []string) http.HandlerFunc {
|
||||
func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, typeable coretypes.Resource, cb authtypes.SelectorCallbackWithoutClaimsFn, roles []string) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
orgs, err := middleware.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
@@ -230,9 +231,9 @@ func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation auth
|
||||
return
|
||||
}
|
||||
|
||||
roleSelectors := []authtypes.Selector{}
|
||||
roleSelectors := []coretypes.Selector{}
|
||||
for _, role := range roles {
|
||||
roleSelectors = append(roleSelectors, authtypes.MustNewSelector(authtypes.TypeRole, role))
|
||||
roleSelectors = append(roleSelectors, coretypes.TypeRole.MustSelector(role))
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreationWithoutClaims(ctx, orgId, relation, typeable, selectors, roleSelectors)
|
||||
|
||||
@@ -4,10 +4,9 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/statsreporter"
|
||||
"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/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -27,7 +26,7 @@ type Module interface {
|
||||
GetPublicWidgetQueryRange(context.Context, valuer.UUID, uint64, uint64, uint64) (*querybuildertypesv5.QueryRangeResponse, error)
|
||||
|
||||
// gets the selectors and org for the given public dashboard
|
||||
GetPublicDashboardSelectorsAndOrg(context.Context, valuer.UUID, []*types.Organization) ([]authtypes.Selector, valuer.UUID, error)
|
||||
GetPublicDashboardSelectorsAndOrg(context.Context, valuer.UUID, []*types.Organization) ([]coretypes.Selector, valuer.UUID, error)
|
||||
|
||||
// updates the public sharing config for a dashboard
|
||||
UpdatePublic(context.Context, valuer.UUID, *dashboardtypes.PublicDashboard) error
|
||||
@@ -50,8 +49,6 @@ type Module interface {
|
||||
GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]map[string]string, error)
|
||||
|
||||
statsreporter.StatsCollector
|
||||
|
||||
authz.RegisterTypeable
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/transition"
|
||||
"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/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
@@ -158,15 +159,15 @@ func (handler *handler) LockUnlock(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
isAdmin := false
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
selectors := []coretypes.Selector{
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozAdminRoleName),
|
||||
}
|
||||
err = handler.authz.CheckWithTupleCreation(
|
||||
ctx,
|
||||
claims,
|
||||
valuer.MustNewUUID(claims.OrgID),
|
||||
authtypes.RelationAssignee,
|
||||
authtypes.TypeableRole,
|
||||
authtypes.Relation{Verb: coretypes.VerbAssignee},
|
||||
coretypes.NewResourceRole(),
|
||||
selectors,
|
||||
selectors,
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"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/dashboardtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -202,14 +202,6 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
return dashboardtypes.NewStatsFromStorableDashboards(dashboards), nil
|
||||
}
|
||||
|
||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{dashboardtypes.TypeableMetaResourceDashboard, dashboardtypes.TypeableMetaResourcesDashboards}
|
||||
}
|
||||
|
||||
func (module *module) MustGetManagedRoleTransactions() map[string][]*authtypes.Transaction {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePublic is not supported.
|
||||
func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publicDashboard *dashboardtypes.PublicDashboard) error {
|
||||
return errors.Newf(errors.TypeUnsupported, dashboardtypes.ErrCodePublicDashboardUnsupported, "not implemented")
|
||||
@@ -227,7 +219,7 @@ func (module *module) GetPublicWidgetQueryRange(context.Context, valuer.UUID, ui
|
||||
return nil, errors.Newf(errors.TypeUnsupported, dashboardtypes.ErrCodePublicDashboardUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (module *module) GetPublicDashboardSelectorsAndOrg(_ context.Context, _ valuer.UUID, _ []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
func (module *module) GetPublicDashboardSelectorsAndOrg(_ context.Context, _ valuer.UUID, _ []*types.Organization) ([]coretypes.Selector, valuer.UUID, error) {
|
||||
return nil, valuer.UUID{}, errors.Newf(errors.TypeUnsupported, dashboardtypes.ErrCodePublicDashboardUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/cachetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
@@ -144,7 +145,7 @@ func (module *module) DeleteRole(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.authz.Revoke(ctx, orgID, []string{role.Name}, authtypes.MustNewSubject(authtypes.TypeableServiceAccount, id.String(), orgID, nil))
|
||||
err = module.authz.Revoke(ctx, orgID, []string{role.Name}, authtypes.MustNewSubject(coretypes.NewResourceServiceAccount(), id.String(), orgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -187,7 +188,7 @@ func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.authz.Revoke(ctx, orgID, serviceAccount.RoleNames(), authtypes.MustNewSubject(authtypes.TypeableServiceAccount, id.StringValue(), orgID, nil))
|
||||
err = module.authz.Revoke(ctx, orgID, serviceAccount.RoleNames(), authtypes.MustNewSubject(coretypes.NewResourceServiceAccount(), id.StringValue(), orgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -386,7 +387,7 @@ func (module *module) setRole(ctx context.Context, orgID valuer.UUID, id valuer.
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.authz.ModifyGrant(ctx, orgID, serviceAccount.RoleNames(), []string{role.Name}, authtypes.MustNewSubject(authtypes.TypeableServiceAccount, id.String(), orgID, nil))
|
||||
err = module.authz.ModifyGrant(ctx, orgID, serviceAccount.RoleNames(), []string{role.Name}, authtypes.MustNewSubject(coretypes.NewResourceServiceAccount(), id.String(), orgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package tracefunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -43,8 +44,8 @@ func NewService(
|
||||
orgGetter: orgGetter,
|
||||
authz: authz,
|
||||
config: config,
|
||||
stopC: make(chan struct{}),
|
||||
healthyC: make(chan struct{}),
|
||||
stopC: make(chan struct{}),
|
||||
healthyC: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +149,7 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
|
||||
orgID,
|
||||
existingUserRoleNames,
|
||||
[]string{authtypes.SigNozAdminRoleName},
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, existingUser.ID.StringValue(), orgID, nil),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceUser(), existingUser.ID.StringValue(), orgID, nil),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/tokenizer"
|
||||
"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/emailtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/integrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -176,7 +177,7 @@ func (module *setter) CreateUser(ctx context.Context, user *types.User, opts ...
|
||||
ctx,
|
||||
user.OrgID,
|
||||
createUserOpts.RoleNames,
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, user.ID.StringValue(), user.OrgID, nil),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceUser(), user.ID.StringValue(), user.OrgID, nil),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -236,15 +237,15 @@ func (module *setter) UpdateUserDeprecated(ctx context.Context, orgID valuer.UUI
|
||||
roleChange := user.Role != "" && user.Role != existingUser.Role
|
||||
|
||||
if roleChange {
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
selectors := []coretypes.Selector{
|
||||
coretypes.TypeRole.MustSelector(authtypes.SigNozAdminRoleName),
|
||||
}
|
||||
err = module.authz.CheckWithTupleCreation(
|
||||
ctx,
|
||||
claims,
|
||||
valuer.MustNewUUID(claims.OrgID),
|
||||
authtypes.RelationAssignee,
|
||||
authtypes.TypeableRole,
|
||||
authtypes.Relation{Verb: coretypes.VerbAssignee},
|
||||
coretypes.NewResourceRole(),
|
||||
selectors,
|
||||
selectors,
|
||||
)
|
||||
@@ -264,7 +265,7 @@ func (module *setter) UpdateUserDeprecated(ctx context.Context, orgID valuer.UUI
|
||||
orgID,
|
||||
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(existingUser.Role)},
|
||||
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceUser(), id, orgID, nil),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -400,7 +401,7 @@ func (module *setter) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
||||
ctx,
|
||||
orgID,
|
||||
roleNames,
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceUser(), id, orgID, nil),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -586,7 +587,7 @@ func (module *setter) UpdatePasswordByResetPasswordToken(ctx context.Context, to
|
||||
ctx,
|
||||
user.OrgID,
|
||||
roleNames,
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, user.ID.StringValue(), user.OrgID, nil),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceUser(), user.ID.StringValue(), user.OrgID, nil),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -719,7 +720,7 @@ func (module *setter) CreateFirstUser(ctx context.Context, organization *types.O
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.CreateUser(ctx, user, root.WithFactorPassword(password), root.WithRoleNames(roleNames))
|
||||
err = module.createUserWithoutGrant(ctx, user, root.WithFactorPassword(password), root.WithRoleNames(roleNames))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -796,7 +797,7 @@ func (module *setter) activatePendingUser(ctx context.Context, user *types.User,
|
||||
ctx,
|
||||
user.OrgID,
|
||||
createUserOpts.RoleNames,
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, user.ID.StringValue(), user.OrgID, nil),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceUser(), user.ID.StringValue(), user.OrgID, nil),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -890,7 +891,7 @@ func (module *setter) AddUserRole(ctx context.Context, orgID, userID valuer.UUID
|
||||
orgID,
|
||||
existingRoles,
|
||||
[]string{roleName},
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, existingUser.ID.StringValue(), existingUser.OrgID, nil),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceUser(), existingUser.ID.StringValue(), existingUser.OrgID, nil),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -953,7 +954,7 @@ func (module *setter) RemoveUserRole(ctx context.Context, orgID, userID valuer.U
|
||||
ctx,
|
||||
orgID,
|
||||
[]string{roleName},
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, existingUser.ID.StringValue(), existingUser.OrgID, nil),
|
||||
authtypes.MustNewSubject(coretypes.NewResourceUser(), existingUser.ID.StringValue(), existingUser.OrgID, nil),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
)
|
||||
|
||||
// Suppress unused import error
|
||||
|
||||
@@ -4,9 +4,10 @@ package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
)
|
||||
|
||||
// Suppress unused import error
|
||||
|
||||
@@ -2,11 +2,12 @@ package clickhouseprometheus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
|
||||
cmock "github.com/srikanthccv/ClickHouse-go-mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package prometheustest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/prometheus/prometheus/model/labels"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemoveExtraLabels(t *testing.T) {
|
||||
|
||||
@@ -251,10 +251,10 @@ func TestEnhancePromQLError(t *testing.T) {
|
||||
|
||||
t.Run("quoted metric outside braces patterns", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
wantHint bool
|
||||
wantMetricInHint string
|
||||
name string
|
||||
query string
|
||||
wantHint bool
|
||||
wantMetricInHint string
|
||||
}{
|
||||
{
|
||||
name: "quoted metric name followed by selector",
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
@@ -1425,7 +1426,7 @@ func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, params
|
||||
// we will change ttl for only the new parts and not the old ones
|
||||
query += " SETTINGS materialize_ttl_after_modify=0"
|
||||
|
||||
ttl := types.TTLSetting{
|
||||
ttl := retentiontypes.TTLSetting{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
@@ -1460,7 +1461,7 @@ func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, params
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusFailed).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -1480,7 +1481,7 @@ func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, params
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusFailed).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -1495,7 +1496,7 @@ func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, params
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusSuccess).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -1563,7 +1564,7 @@ func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, param
|
||||
timestamp = "end"
|
||||
}
|
||||
|
||||
ttl := types.TTLSetting{
|
||||
ttl := retentiontypes.TTLSetting{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
@@ -1610,7 +1611,7 @@ func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, param
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusFailed).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -1631,7 +1632,7 @@ func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, param
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusFailed).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -1646,7 +1647,7 @@ func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, param
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusSuccess).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -1838,7 +1839,7 @@ func (r *ClickHouseReader) SetTTLV2(ctx context.Context, orgID string, params *m
|
||||
}
|
||||
|
||||
for tableName, queries := range ttlPayload {
|
||||
customTTL := types.TTLSetting{
|
||||
customTTL := retentiontypes.TTLSetting{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
@@ -1977,7 +1978,7 @@ func (r *ClickHouseReader) GetCustomRetentionTTL(ctx context.Context, orgID stri
|
||||
response.Version = "v2"
|
||||
|
||||
// Get the latest custom retention TTL setting
|
||||
customTTL := new(types.TTLSetting)
|
||||
customTTL := new(retentiontypes.TTLSetting)
|
||||
err := r.sqlDB.BunDB().NewSelect().
|
||||
Model(customTTL).
|
||||
Where("org_id = ?", orgID).
|
||||
@@ -2046,8 +2047,8 @@ func (r *ClickHouseReader) GetCustomRetentionTTL(ctx context.Context, orgID stri
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) checkCustomRetentionTTLStatusItem(ctx context.Context, orgID string, tableName string) (*types.TTLSetting, error) {
|
||||
ttl := new(types.TTLSetting)
|
||||
func (r *ClickHouseReader) checkCustomRetentionTTLStatusItem(ctx context.Context, orgID string, tableName string) (*retentiontypes.TTLSetting, error) {
|
||||
ttl := new(retentiontypes.TTLSetting)
|
||||
err := r.sqlDB.BunDB().NewSelect().
|
||||
Model(ttl).
|
||||
Where("table_name = ?", tableName).
|
||||
@@ -2068,7 +2069,7 @@ func (r *ClickHouseReader) updateCustomRetentionTTLStatus(ctx context.Context, o
|
||||
statusItem, apiErr := r.checkCustomRetentionTTLStatusItem(ctx, orgID, tableName)
|
||||
if apiErr == nil && statusItem != nil {
|
||||
_, dbErr := r.sqlDB.BunDB().NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", status).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -2235,7 +2236,7 @@ func (r *ClickHouseReader) setTTLMetrics(ctx context.Context, orgID string, para
|
||||
}
|
||||
}
|
||||
metricTTL := func(tableName string) {
|
||||
ttl := types.TTLSetting{
|
||||
ttl := retentiontypes.TTLSetting{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
@@ -2282,7 +2283,7 @@ func (r *ClickHouseReader) setTTLMetrics(ctx context.Context, orgID string, para
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusFailed).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -2303,7 +2304,7 @@ func (r *ClickHouseReader) setTTLMetrics(ctx context.Context, orgID string, para
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusFailed).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -2318,7 +2319,7 @@ func (r *ClickHouseReader) setTTLMetrics(ctx context.Context, orgID string, para
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Set("updated_at = ?", time.Now()).
|
||||
Set("status = ?", constants.StatusSuccess).
|
||||
Where("id = ?", statusItem.ID.StringValue()).
|
||||
@@ -2341,7 +2342,7 @@ func (r *ClickHouseReader) deleteTtlTransactions(ctx context.Context, orgID stri
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Column("transaction_id").
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Where("org_id = ?", orgID).
|
||||
Group("transaction_id").
|
||||
OrderExpr("MAX(created_at) DESC").
|
||||
@@ -2356,7 +2357,7 @@ func (r *ClickHouseReader) deleteTtlTransactions(ctx context.Context, orgID stri
|
||||
sqlDB.
|
||||
BunDB().
|
||||
NewDelete().
|
||||
Model(new(types.TTLSetting)).
|
||||
Model(new(retentiontypes.TTLSetting)).
|
||||
Where("transaction_id NOT IN (?)", bun.In(limitTransactions)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -2365,9 +2366,9 @@ func (r *ClickHouseReader) deleteTtlTransactions(ctx context.Context, orgID stri
|
||||
}
|
||||
|
||||
// checkTTLStatusItem checks if ttl_status table has an entry for the given table name
|
||||
func (r *ClickHouseReader) checkTTLStatusItem(ctx context.Context, orgID string, tableName string) (*types.TTLSetting, *model.ApiError) {
|
||||
func (r *ClickHouseReader) checkTTLStatusItem(ctx context.Context, orgID string, tableName string) (*retentiontypes.TTLSetting, *model.ApiError) {
|
||||
r.logger.Info("checkTTLStatusItem query", "tableName", tableName)
|
||||
ttl := new(types.TTLSetting)
|
||||
ttl := new(retentiontypes.TTLSetting)
|
||||
err := r.
|
||||
sqlDB.
|
||||
BunDB().
|
||||
@@ -2391,7 +2392,7 @@ func (r *ClickHouseReader) getTTLQueryStatus(ctx context.Context, orgID string,
|
||||
status := constants.StatusSuccess
|
||||
for _, tableName := range tableNameArray {
|
||||
statusItem, apiErr := r.checkTTLStatusItem(ctx, orgID, tableName)
|
||||
emptyStatusStruct := new(types.TTLSetting)
|
||||
emptyStatusStruct := new(retentiontypes.TTLSetting)
|
||||
if statusItem == emptyStatusStruct {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
@@ -17,7 +19,6 @@ import (
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
@@ -2,6 +2,7 @@ package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ package querier
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
"strings"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ package v2
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
"strings"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
|
||||
s := &Server{
|
||||
config: config,
|
||||
signoz: signoz,
|
||||
signoz: signoz,
|
||||
httpHostPort: constants.HTTPHostPort,
|
||||
unavailableChannel: make(chan healthcheck.Status),
|
||||
}
|
||||
@@ -297,4 +297,3 @@ func (s *Server) Stop(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package model
|
||||
|
||||
import (
|
||||
json "encoding/json"
|
||||
|
||||
easyjson "github.com/mailru/easyjson"
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
|
||||
@@ -247,7 +247,11 @@ func ClickHouseFormattedMetricNames(v interface{}) string {
|
||||
|
||||
func AddBackTickToFormatTag(str string) string {
|
||||
if strings.Contains(str, ".") || strings.Contains(str, "-") {
|
||||
if strings.HasPrefix(str, "`") && strings.HasSuffix(str, "`") { return str } else { return "`" + str + "`" }
|
||||
if strings.HasPrefix(str, "`") && strings.HasSuffix(str, "`") {
|
||||
return str
|
||||
} else {
|
||||
return "`" + str + "`"
|
||||
}
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func New(
|
||||
sqlstoreProviderFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]],
|
||||
telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]],
|
||||
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error),
|
||||
authzCallback func(context.Context, sqlstore.SQLStore, licensing.Licensing, []authz.OnBeforeRoleDelete, dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error),
|
||||
authzCallback func(context.Context, sqlstore.SQLStore, authz.Config, licensing.Licensing, []authz.OnBeforeRoleDelete) (factory.ProviderFactory[authz.AuthZ, authz.Config], error),
|
||||
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module,
|
||||
gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config],
|
||||
auditorProviderFactories func(licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]],
|
||||
@@ -325,7 +325,7 @@ func New(
|
||||
// Initialize query parser (needed for dashboard module)
|
||||
queryParser := queryparser.New(providerSettings)
|
||||
|
||||
// Initialize dashboard module (needed for authz registry)
|
||||
// Initialize dashboard module
|
||||
dashboard := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, queryParser, querier, licensing)
|
||||
|
||||
// Initialize user getter
|
||||
@@ -341,7 +341,7 @@ func New(
|
||||
}
|
||||
|
||||
// Initialize authz
|
||||
authzProviderFactory, err := authzCallback(ctx, sqlstore, licensing, onBeforeRoleDelete, dashboard)
|
||||
authzProviderFactory, err := authzCallback(ctx, sqlstore, config.Authz, licensing, onBeforeRoleDelete)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
|
||||
@@ -5,6 +5,9 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
@@ -14,8 +17,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Shared types for migration
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
@@ -125,7 +125,7 @@ func (migration *migrateRbacToAuthz) Up(ctx context.Context, db *bun.DB) error {
|
||||
|
||||
tuples = append(tuples, tuple{
|
||||
OrgID: orgID,
|
||||
ID: authtypes.AnonymousUser.StringValue(),
|
||||
ID: coretypes.AnonymousUser.StringValue(),
|
||||
Type: "anonymous",
|
||||
RoleName: "signoz-anonymous",
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
|
||||
@@ -232,4 +232,3 @@ func (index *PartialUniqueIndex) ToDropSQL(fmter SQLFormatter) []byte {
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ type TelemetryStoreHook interface {
|
||||
AfterQuery(ctx context.Context, event *QueryEvent)
|
||||
}
|
||||
|
||||
|
||||
func WrapBeforeQuery(hooks []TelemetryStoreHook, ctx context.Context, event *QueryEvent) context.Context {
|
||||
for _, hook := range hooks {
|
||||
ctx = hook.BeforeQuery(ctx, event)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
settings telemetrystore.QuerySettings
|
||||
settings telemetrystore.QuerySettings
|
||||
}
|
||||
|
||||
func NewSettingsFactory() factory.ProviderFactory[telemetrystore.TelemetryStoreHook, telemetrystore.Config] {
|
||||
@@ -21,7 +21,7 @@ func NewSettingsFactory() factory.ProviderFactory[telemetrystore.TelemetryStoreH
|
||||
|
||||
func NewSettings(ctx context.Context, providerSettings factory.ProviderSettings, config telemetrystore.Config) (telemetrystore.TelemetryStoreHook, error) {
|
||||
return &provider{
|
||||
settings: config.Clickhouse.QuerySettings,
|
||||
settings: config.Clickhouse.QuerySettings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ package alertmanagertypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/expr-lang/expr"
|
||||
"time"
|
||||
|
||||
"github.com/expr-lang/expr"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package audittypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
// Action represents what was done.
|
||||
type Action struct {
|
||||
valuer.String
|
||||
pastTense string
|
||||
}
|
||||
|
||||
var (
|
||||
ActionCreate = Action{valuer.NewString("create"), "created"}
|
||||
ActionUpdate = Action{valuer.NewString("update"), "updated"}
|
||||
ActionDelete = Action{valuer.NewString("delete"), "deleted"}
|
||||
)
|
||||
|
||||
func (Action) Enum() []any {
|
||||
return []any{
|
||||
ActionCreate,
|
||||
ActionUpdate,
|
||||
ActionDelete,
|
||||
}
|
||||
}
|
||||
|
||||
// PastTense returns the past-tense form of the action for use in EventName.
|
||||
func (a Action) PastTense() string {
|
||||
return a.pastTense
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.opentelemetry.io/collector/pdata/pcommon"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
@@ -12,13 +13,13 @@ import (
|
||||
|
||||
// Audit attributes — Action (What).
|
||||
type AuditAttributes struct {
|
||||
Action Action // guaranteed to be present
|
||||
Action coretypes.Verb // guaranteed to be present
|
||||
ActionCategory ActionCategory // guaranteed to be present
|
||||
Outcome Outcome // guaranteed to be present
|
||||
IdentNProvider authtypes.IdentNProvider
|
||||
}
|
||||
|
||||
func NewAuditAttributesFromHTTP(statusCode int, action Action, category ActionCategory, claims authtypes.Claims) AuditAttributes {
|
||||
func NewAuditAttributesFromHTTP(statusCode int, action coretypes.Verb, category ActionCategory, claims authtypes.Claims) AuditAttributes {
|
||||
outcome := OutcomeFailure
|
||||
if statusCode >= 200 && statusCode < 400 {
|
||||
outcome = OutcomeSuccess
|
||||
@@ -71,10 +72,10 @@ func (attributes PrincipalAttributes) Put(dest pcommon.Map) {
|
||||
// These are OTel resource attributes (placed on the Resource, not event attributes).
|
||||
type ResourceAttributes struct {
|
||||
ResourceID string
|
||||
ResourceKind string // guaranteed to be present
|
||||
ResourceKind coretypes.Kind // guaranteed to be present
|
||||
}
|
||||
|
||||
func NewResourceAttributes(resourceID, resourceKind string) ResourceAttributes {
|
||||
func NewResourceAttributes(resourceID string, resourceKind coretypes.Kind) ResourceAttributes {
|
||||
return ResourceAttributes{
|
||||
ResourceID: resourceID,
|
||||
ResourceKind: resourceKind,
|
||||
@@ -85,7 +86,7 @@ func NewResourceAttributes(resourceID, resourceKind string) ResourceAttributes {
|
||||
// These are resource-level attributes (stored in the resource JSON column),
|
||||
// not event-level attributes (stored in attributes_string).
|
||||
func (attributes ResourceAttributes) PutResource(dest pcommon.Map) {
|
||||
putStrIfNotEmpty(dest, "signoz.audit.resource.kind", attributes.ResourceKind)
|
||||
putStrIfNotEmpty(dest, "signoz.audit.resource.kind", attributes.ResourceKind.String())
|
||||
putStrIfNotEmpty(dest, "signoz.audit.resource.id", attributes.ResourceID)
|
||||
}
|
||||
|
||||
@@ -192,7 +193,7 @@ func newBody(auditAttributes AuditAttributes, principalAttributes PrincipalAttri
|
||||
|
||||
// Resource: " kind (id)" or " kind".
|
||||
b.WriteString(" ")
|
||||
b.WriteString(resourceAttributes.ResourceKind)
|
||||
b.WriteString(resourceAttributes.ResourceKind.String())
|
||||
if resourceAttributes.ResourceID != "" {
|
||||
b.WriteString(" (")
|
||||
b.WriteString(resourceAttributes.ResourceID)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -35,7 +36,7 @@ func TestNewAuditAttributesFromHTTP_OutcomeBoundary(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
attrs := NewAuditAttributesFromHTTP(testCase.statusCode, ActionUpdate, ActionCategoryConfigurationChange, claims)
|
||||
attrs := NewAuditAttributesFromHTTP(testCase.statusCode, coretypes.VerbUpdate, ActionCategoryConfigurationChange, claims)
|
||||
assert.Equal(t, testCase.expectedOutcome, attrs.Outcome)
|
||||
})
|
||||
}
|
||||
@@ -53,7 +54,7 @@ func TestNewBody(t *testing.T) {
|
||||
{
|
||||
name: "Success_EmptyResourceID",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: ActionDelete,
|
||||
Action: coretypes.VerbDelete,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
@@ -63,7 +64,7 @@ func TestNewBody(t *testing.T) {
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "",
|
||||
ResourceKind: "dashboard",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "test@acme.com (019a1234-abcd-7000-8000-567800000001) deleted dashboard",
|
||||
@@ -71,7 +72,7 @@ func TestNewBody(t *testing.T) {
|
||||
{
|
||||
name: "Success_EmptyPrincipalEmail",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: ActionDelete,
|
||||
Action: coretypes.VerbDelete,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
@@ -81,7 +82,7 @@ func TestNewBody(t *testing.T) {
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "abd",
|
||||
ResourceKind: "dashboard",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "019a1234-abcd-7000-8000-567800000001 deleted dashboard (abd)",
|
||||
@@ -89,7 +90,7 @@ func TestNewBody(t *testing.T) {
|
||||
{
|
||||
name: "Success_EmptyPrincipalIDandEmail",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: ActionDelete,
|
||||
Action: coretypes.VerbDelete,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
@@ -99,7 +100,7 @@ func TestNewBody(t *testing.T) {
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "abd",
|
||||
ResourceKind: "dashboard",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "deleted dashboard (abd)",
|
||||
@@ -107,7 +108,7 @@ func TestNewBody(t *testing.T) {
|
||||
{
|
||||
name: "Success_AllPresent",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: ActionCreate,
|
||||
Action: coretypes.VerbCreate,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
@@ -117,7 +118,7 @@ func TestNewBody(t *testing.T) {
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "019b-5678",
|
||||
ResourceKind: "dashboard",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "alice@acme.com (019a1234-abcd-7000-8000-567800000001) created dashboard (019b-5678)",
|
||||
@@ -125,13 +126,13 @@ func TestNewBody(t *testing.T) {
|
||||
{
|
||||
name: "Success_EmptyEverythingOptional",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: ActionUpdate,
|
||||
Action: coretypes.VerbUpdate,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceKind: "alert-rule",
|
||||
ResourceKind: coretypes.MustNewKind("alert-rule"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "updated alert-rule",
|
||||
@@ -139,7 +140,7 @@ func TestNewBody(t *testing.T) {
|
||||
{
|
||||
name: "Failure_AllPresent",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: ActionUpdate,
|
||||
Action: coretypes.VerbUpdate,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeFailure,
|
||||
},
|
||||
@@ -149,7 +150,7 @@ func TestNewBody(t *testing.T) {
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "019b-5678",
|
||||
ResourceKind: "dashboard",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{
|
||||
ErrorType: "forbidden",
|
||||
@@ -160,7 +161,7 @@ func TestNewBody(t *testing.T) {
|
||||
{
|
||||
name: "Failure_ErrorTypeOnly",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: ActionDelete,
|
||||
Action: coretypes.VerbDelete,
|
||||
Outcome: OutcomeFailure,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
@@ -168,7 +169,7 @@ func TestNewBody(t *testing.T) {
|
||||
PrincipalEmail: valuer.MustNewEmail("test@acme.com"),
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceKind: "user",
|
||||
ResourceKind: coretypes.MustNewKind("user"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{
|
||||
ErrorType: "not-found",
|
||||
@@ -178,7 +179,7 @@ func TestNewBody(t *testing.T) {
|
||||
{
|
||||
name: "Failure_NoErrorDetails",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: ActionCreate,
|
||||
Action: coretypes.VerbCreate,
|
||||
Outcome: OutcomeFailure,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
@@ -187,7 +188,7 @@ func TestNewBody(t *testing.T) {
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "019b-5678",
|
||||
ResourceKind: "dashboard",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "test@acme.com (019a1234-abcd-7000-8000-567800000001) failed to create dashboard (019b-5678)",
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"go.opentelemetry.io/collector/pdata/pcommon"
|
||||
"go.opentelemetry.io/collector/pdata/plog"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
@@ -49,11 +50,11 @@ func NewAuditEventFromHTTPRequest(
|
||||
statusCode int,
|
||||
traceID oteltrace.TraceID,
|
||||
spanID oteltrace.SpanID,
|
||||
action Action,
|
||||
action coretypes.Verb,
|
||||
actionCategory ActionCategory,
|
||||
claims authtypes.Claims,
|
||||
resourceID string,
|
||||
resourceKind string,
|
||||
resourceKind coretypes.Kind,
|
||||
errorType string,
|
||||
errorCode string,
|
||||
) AuditEvent {
|
||||
@@ -88,7 +89,7 @@ func NewPLogsFromAuditEvents(events []AuditEvent, name string, version string, s
|
||||
groups := make(map[resourceKey][]int)
|
||||
order := make([]resourceKey, 0)
|
||||
for i, event := range events {
|
||||
key := resourceKey{kind: event.ResourceAttributes.ResourceKind, id: event.ResourceAttributes.ResourceID}
|
||||
key := resourceKey{kind: event.ResourceAttributes.ResourceKind.String(), id: event.ResourceAttributes.ResourceID}
|
||||
if _, exists := groups[key]; !exists {
|
||||
order = append(order, key)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package audittypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
|
||||
// EventName is a typed wrapper for audit event names, ensuring not every
|
||||
// string qualifies as an event name.
|
||||
type EventName struct {
|
||||
@@ -11,11 +13,10 @@ type EventName struct {
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// NewEventName("dashboard", ActionCreate) → "dashboard.created"
|
||||
// NewEventName("dashboard", ActionUpdate) → "dashboard.updated"
|
||||
// NewEventName("user.role", ActionUpdate) → "user.role.updated"
|
||||
func NewEventName(resourceKind string, action Action) EventName {
|
||||
return EventName{s: resourceKind + "." + action.PastTense()}
|
||||
// NewEventName("dashboard", coretypes.VerbCreate) → "dashboard.created"
|
||||
// NewEventName("dashboard", coretypes.VerbUpdate) → "dashboard.updated"
|
||||
func NewEventName(resourceKind coretypes.Kind, action coretypes.Verb) EventName {
|
||||
return EventName{s: resourceKind.String() + "." + action.PastTense()}
|
||||
}
|
||||
|
||||
// String returns the string representation of the event name.
|
||||
|
||||
@@ -6,10 +6,15 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
testDashboardKind = coretypes.MustNewKind("dashboard")
|
||||
)
|
||||
|
||||
func TestNewAuditEventFromHTTPRequest(t *testing.T) {
|
||||
traceID := oteltrace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
|
||||
spanID := oteltrace.SpanID{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
@@ -20,11 +25,11 @@ func TestNewAuditEventFromHTTPRequest(t *testing.T) {
|
||||
path string
|
||||
route string
|
||||
statusCode int
|
||||
action Action
|
||||
action coretypes.Verb
|
||||
category ActionCategory
|
||||
claims authtypes.Claims
|
||||
resourceID string
|
||||
resourceKind string
|
||||
resourceKind coretypes.Kind
|
||||
errorType string
|
||||
errorCode string
|
||||
expectedOutcome Outcome
|
||||
@@ -36,11 +41,11 @@ func TestNewAuditEventFromHTTPRequest(t *testing.T) {
|
||||
path: "/api/v1/dashboards",
|
||||
route: "/api/v1/dashboards",
|
||||
statusCode: http.StatusOK,
|
||||
action: ActionCreate,
|
||||
action: coretypes.VerbCreate,
|
||||
category: ActionCategoryConfigurationChange,
|
||||
claims: authtypes.Claims{UserID: "019a1234-abcd-7000-8000-567800000001", Email: "alice@acme.com", OrgID: "019a-0000-0000-0001", IdentNProvider: authtypes.IdentNProviderTokenizer},
|
||||
resourceID: "019b-5678-efgh-9012",
|
||||
resourceKind: "dashboard",
|
||||
resourceKind: testDashboardKind,
|
||||
expectedOutcome: OutcomeSuccess,
|
||||
expectedBody: "alice@acme.com (019a1234-abcd-7000-8000-567800000001) created dashboard (019b-5678-efgh-9012)",
|
||||
},
|
||||
@@ -50,11 +55,11 @@ func TestNewAuditEventFromHTTPRequest(t *testing.T) {
|
||||
path: "/api/v1/dashboards/019b-5678-efgh-9012",
|
||||
route: "/api/v1/dashboards/{id}",
|
||||
statusCode: http.StatusForbidden,
|
||||
action: ActionUpdate,
|
||||
action: coretypes.VerbUpdate,
|
||||
category: ActionCategoryConfigurationChange,
|
||||
claims: authtypes.Claims{UserID: "019aaaaa-bbbb-7000-8000-cccc00000002", Email: "viewer@acme.com", OrgID: "019a-0000-0000-0001", IdentNProvider: authtypes.IdentNProviderTokenizer},
|
||||
resourceID: "019b-5678-efgh-9012",
|
||||
resourceKind: "dashboard",
|
||||
resourceKind: testDashboardKind,
|
||||
errorType: "forbidden",
|
||||
errorCode: "authz_forbidden",
|
||||
expectedOutcome: OutcomeFailure,
|
||||
@@ -98,9 +103,9 @@ func TestNewAuditEventFromHTTPRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestEvent(resourceKind, resourceID string, action Action) AuditEvent {
|
||||
func newTestEvent(resourceKind coretypes.Kind, resourceID string, action coretypes.Verb) AuditEvent {
|
||||
return AuditEvent{
|
||||
Body: resourceKind + "." + action.PastTense(),
|
||||
Body: resourceKind.String() + "." + action.PastTense(),
|
||||
EventName: NewEventName(resourceKind, action),
|
||||
AuditAttributes: AuditAttributes{
|
||||
Action: action,
|
||||
@@ -131,7 +136,7 @@ func TestNewPLogsFromAuditEvents(t *testing.T) {
|
||||
{
|
||||
name: "SingleEvent",
|
||||
events: []AuditEvent{
|
||||
newTestEvent("dashboard", "d-001", ActionCreate),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbCreate),
|
||||
},
|
||||
expectedResourceLogs: 1,
|
||||
expectedResourceKinds: []string{"dashboard"},
|
||||
@@ -141,9 +146,9 @@ func TestNewPLogsFromAuditEvents(t *testing.T) {
|
||||
{
|
||||
name: "SameResource_MultipleEvents",
|
||||
events: []AuditEvent{
|
||||
newTestEvent("dashboard", "d-001", ActionCreate),
|
||||
newTestEvent("dashboard", "d-001", ActionUpdate),
|
||||
newTestEvent("dashboard", "d-001", ActionDelete),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbCreate),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbUpdate),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbDelete),
|
||||
},
|
||||
expectedResourceLogs: 1,
|
||||
expectedResourceKinds: []string{"dashboard"},
|
||||
@@ -153,8 +158,8 @@ func TestNewPLogsFromAuditEvents(t *testing.T) {
|
||||
{
|
||||
name: "DifferentResources_SeparateGroups",
|
||||
events: []AuditEvent{
|
||||
newTestEvent("dashboard", "d-001", ActionUpdate),
|
||||
newTestEvent("user", "u-001", ActionDelete),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbUpdate),
|
||||
newTestEvent(coretypes.MustNewKind("user"), "u-001", coretypes.VerbDelete),
|
||||
},
|
||||
expectedResourceLogs: 2,
|
||||
expectedResourceKinds: []string{"dashboard", "user"},
|
||||
@@ -164,8 +169,8 @@ func TestNewPLogsFromAuditEvents(t *testing.T) {
|
||||
{
|
||||
name: "SameKind_DifferentIDs_SeparateGroups",
|
||||
events: []AuditEvent{
|
||||
newTestEvent("dashboard", "d-001", ActionUpdate),
|
||||
newTestEvent("dashboard", "d-002", ActionDelete),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbUpdate),
|
||||
newTestEvent(testDashboardKind, "d-002", coretypes.VerbDelete),
|
||||
},
|
||||
expectedResourceLogs: 2,
|
||||
expectedResourceKinds: []string{"dashboard", "dashboard"},
|
||||
@@ -175,11 +180,11 @@ func TestNewPLogsFromAuditEvents(t *testing.T) {
|
||||
{
|
||||
name: "InterleavedResources_GroupedCorrectly",
|
||||
events: []AuditEvent{
|
||||
newTestEvent("dashboard", "d-001", ActionCreate),
|
||||
newTestEvent("user", "u-001", ActionUpdate),
|
||||
newTestEvent("dashboard", "d-001", ActionUpdate),
|
||||
newTestEvent("user", "u-001", ActionDelete),
|
||||
newTestEvent("dashboard", "d-001", ActionDelete),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbCreate),
|
||||
newTestEvent(coretypes.MustNewKind("user"), "u-001", coretypes.VerbUpdate),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbUpdate),
|
||||
newTestEvent(coretypes.MustNewKind("user"), "u-001", coretypes.VerbDelete),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbDelete),
|
||||
},
|
||||
expectedResourceLogs: 2,
|
||||
expectedResourceKinds: []string{"dashboard", "user"},
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
nameRegex = regexp.MustCompile("^[a-z-]{1,50}$")
|
||||
|
||||
_ json.Marshaler = new(Name)
|
||||
_ json.Unmarshaler = new(Name)
|
||||
_ encoding.TextMarshaler = new(Name)
|
||||
_ encoding.TextUnmarshaler = new(Name)
|
||||
)
|
||||
|
||||
type Name struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func NewName(name string) (Name, error) {
|
||||
if !nameRegex.MatchString(name) {
|
||||
return Name{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "name must conform to regex %s", nameRegex.String())
|
||||
}
|
||||
|
||||
return Name{val: name}, nil
|
||||
}
|
||||
|
||||
func MustNewName(name string) Name {
|
||||
named, err := NewName(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return named
|
||||
}
|
||||
|
||||
func (name Name) String() string {
|
||||
return name.val
|
||||
}
|
||||
|
||||
func (name *Name) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(name.val)
|
||||
}
|
||||
|
||||
func (name *Name) UnmarshalJSON(data []byte) error {
|
||||
nameStr := ""
|
||||
err := json.Unmarshal(data, &nameStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shadow, err := NewName(nameStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*name = shadow
|
||||
return nil
|
||||
}
|
||||
|
||||
func (name Name) MarshalText() ([]byte, error) {
|
||||
return []byte(name.val), nil
|
||||
}
|
||||
|
||||
func (name *Name) UnmarshalText(text []byte) error {
|
||||
shadow, err := NewName(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*name = shadow
|
||||
return nil
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
Name Name `json:"name" required:"true"`
|
||||
Type Type `json:"type" required:"true"`
|
||||
}
|
||||
|
||||
type GettableResources struct {
|
||||
Resources []*Resource `json:"resources" required:"true" nullable:"false"`
|
||||
Relations map[Relation][]Type `json:"relations" required:"true"`
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
Resource Resource `json:"resource" required:"true"`
|
||||
Selector Selector `json:"selector" required:"true"`
|
||||
}
|
||||
|
||||
type GettableObjects struct {
|
||||
Resource Resource `json:"resource" required:"true"`
|
||||
Selectors []Selector `json:"selectors" required:"true" nullable:"false"`
|
||||
}
|
||||
|
||||
type PatchableObjects struct {
|
||||
Additions []*GettableObjects `json:"additions" required:"true" nullable:"true"`
|
||||
Deletions []*GettableObjects `json:"deletions" required:"true" nullable:"true"`
|
||||
}
|
||||
|
||||
func NewObject(resource Resource, selector Selector) (*Object, error) {
|
||||
err := IsValidSelector(resource.Type, selector.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Object{Resource: resource, Selector: selector}, nil
|
||||
}
|
||||
|
||||
func NewObjectsFromGettableObjects(patchableObjects []*GettableObjects) ([]*Object, error) {
|
||||
objects := make([]*Object, 0)
|
||||
|
||||
for _, patchObject := range patchableObjects {
|
||||
for _, selector := range patchObject.Selectors {
|
||||
object, err := NewObject(patchObject.Resource, selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects = append(objects, object)
|
||||
}
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func NewPatchableObjects(additions []*GettableObjects, deletions []*GettableObjects, relation Relation) ([]*Object, []*Object, error) {
|
||||
if len(additions) == 0 && len(deletions) == 0 {
|
||||
return nil, nil, errors.New(errors.TypeInvalidInput, ErrCodeInvalidPatchObject, "empty object patch request received, at least one of additions or deletions must be present")
|
||||
}
|
||||
|
||||
for _, object := range additions {
|
||||
if !slices.Contains(TypeableRelations[object.Resource.Type], relation) {
|
||||
return nil, nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "relation %s is invalid for type %s", relation.StringValue(), object.Resource.Type.StringValue())
|
||||
}
|
||||
}
|
||||
|
||||
for _, object := range deletions {
|
||||
if !slices.Contains(TypeableRelations[object.Resource.Type], relation) {
|
||||
return nil, nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "relation %s is invalid for type %s", relation.StringValue(), object.Resource.Type.StringValue())
|
||||
}
|
||||
}
|
||||
|
||||
additionObjects, err := NewObjectsFromGettableObjects(additions)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
deletionsObjects, err := NewObjectsFromGettableObjects(deletions)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return additionObjects, deletionsObjects, nil
|
||||
}
|
||||
|
||||
func NewGettableResources(resources []*Resource) *GettableResources {
|
||||
return &GettableResources{
|
||||
Resources: resources,
|
||||
Relations: RelationsTypeable,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGettableObjects(objects []*Object) []*GettableObjects {
|
||||
grouped := make(map[Resource][]Selector)
|
||||
for _, obj := range objects {
|
||||
key := obj.Resource
|
||||
if _, ok := grouped[key]; !ok {
|
||||
grouped[key] = make([]Selector, 0)
|
||||
}
|
||||
|
||||
grouped[key] = append(grouped[key], obj.Selector)
|
||||
}
|
||||
|
||||
gettableObjects := make([]*GettableObjects, 0, len(grouped))
|
||||
for resource, selectors := range grouped {
|
||||
gettableObjects = append(gettableObjects, &GettableObjects{
|
||||
Resource: resource,
|
||||
Selectors: selectors,
|
||||
})
|
||||
}
|
||||
|
||||
return gettableObjects
|
||||
}
|
||||
|
||||
func MustNewObject(resource Resource, selector Selector) *Object {
|
||||
object, err := NewObject(resource, selector)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func MustNewObjectFromString(input string) *Object {
|
||||
parts := strings.Split(input, "/")
|
||||
if len(parts) != 4 {
|
||||
panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid input format: %s", input))
|
||||
}
|
||||
|
||||
typeParts := strings.Split(parts[0], ":")
|
||||
if len(typeParts) != 2 {
|
||||
panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid type format: %s", parts[0]))
|
||||
}
|
||||
|
||||
resource := Resource{
|
||||
Type: MustNewType(typeParts[0]),
|
||||
Name: MustNewName(parts[2]),
|
||||
}
|
||||
|
||||
selector := MustNewSelector(resource.Type, parts[3])
|
||||
|
||||
return &Object{Resource: resource, Selector: selector}
|
||||
}
|
||||
|
||||
func MustNewObjectsFromStringSlice(input []string) []*Object {
|
||||
objects := make([]*Object, 0, len(input))
|
||||
for _, str := range input {
|
||||
objects = append(objects, MustNewObjectFromString(str))
|
||||
}
|
||||
return objects
|
||||
}
|
||||
|
||||
func (object *Object) UnmarshalJSON(data []byte) error {
|
||||
var shadow = struct {
|
||||
Resource Resource
|
||||
Selector Selector
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(data, &shadow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj, err := NewObject(shadow.Resource, shadow.Selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*object = *obj
|
||||
return nil
|
||||
}
|
||||
77
pkg/types/authtypes/registry.go
Normal file
77
pkg/types/authtypes/registry.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
)
|
||||
|
||||
// Registry wraps the coretypes Registry and is the authz-aware view consumed
|
||||
// by the authz providers. It exposes the same static schema and adds any
|
||||
// authz-specific lookups built on top of it. Managed-role transactions are
|
||||
// bridged once here from coretypes.Transaction (data shape) to *Transaction
|
||||
// (operational shape with ID + TransactionKey).
|
||||
type Registry struct {
|
||||
coretypes *coretypes.Registry
|
||||
transactions map[string][]*Transaction
|
||||
managedRolesByTransaction map[string][]string
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
coreRegistry := coretypes.NewRegistry()
|
||||
transactions := buildManagedRoleTransactions(coreRegistry.ManagedRoleTransactions())
|
||||
return &Registry{
|
||||
coretypes: coreRegistry,
|
||||
transactions: transactions,
|
||||
managedRolesByTransaction: buildManagedRolesByTransaction(transactions),
|
||||
}
|
||||
}
|
||||
|
||||
func (registry *Registry) Types() []coretypes.Type {
|
||||
return registry.coretypes.Types()
|
||||
}
|
||||
|
||||
func (registry *Registry) Resources() []coretypes.Resource {
|
||||
return registry.coretypes.Resources()
|
||||
}
|
||||
|
||||
func (registry *Registry) ManagedRoles() []string {
|
||||
return registry.coretypes.ManagedRoles()
|
||||
}
|
||||
|
||||
func (registry *Registry) ManagedRoleTransactions() map[string][]*Transaction {
|
||||
return registry.transactions
|
||||
}
|
||||
|
||||
// ManagedRolesByTransaction returns the inverse of ManagedRoleTransactions:
|
||||
// keyed by Transaction.TransactionKey() ("verb:type:kind"), value is the list
|
||||
// of managed-role names that hold that transaction. Used by the BatchCheck
|
||||
// flow to expand a request transaction into role-assignee correlation checks.
|
||||
func (registry *Registry) ManagedRolesByTransaction() map[string][]string {
|
||||
return registry.managedRolesByTransaction
|
||||
}
|
||||
|
||||
func buildManagedRoleTransactions(input map[string][]coretypes.Transaction) map[string][]*Transaction {
|
||||
out := make(map[string][]*Transaction, len(input))
|
||||
for roleName, txns := range input {
|
||||
converted := make([]*Transaction, 0, len(txns))
|
||||
for _, txn := range txns {
|
||||
authzTxn, err := NewTransaction(Relation{Verb: txn.Verb}, txn.Object)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
converted = append(converted, authzTxn)
|
||||
}
|
||||
out[roleName] = converted
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func buildManagedRolesByTransaction(transactions map[string][]*Transaction) map[string][]string {
|
||||
out := make(map[string][]string)
|
||||
for roleName, txns := range transactions {
|
||||
for _, txn := range txns {
|
||||
key := txn.TransactionKey()
|
||||
out[key] = append(out[key], roleName)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -1,59 +1,31 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeAuthZInvalidRelation = errors.MustNewCode("authz_invalid_relation")
|
||||
ErrCodeInvalidPatchObject = errors.MustNewCode("authz_invalid_patch_objects")
|
||||
)
|
||||
|
||||
var (
|
||||
RelationCreate = Relation{valuer.NewString("create")}
|
||||
RelationRead = Relation{valuer.NewString("read")}
|
||||
RelationUpdate = Relation{valuer.NewString("update")}
|
||||
RelationDelete = Relation{valuer.NewString("delete")}
|
||||
RelationList = Relation{valuer.NewString("list")}
|
||||
RelationAssignee = Relation{valuer.NewString("assignee")}
|
||||
)
|
||||
|
||||
var TypeableRelations = map[Type][]Relation{
|
||||
TypeUser: {RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeServiceAccount: {RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeRole: {RelationAssignee, RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeOrganization: {RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeMetaResource: {RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeMetaResources: {RelationCreate, RelationList},
|
||||
type Relation struct {
|
||||
coretypes.Verb
|
||||
}
|
||||
|
||||
var RelationsTypeable = map[Relation][]Type{
|
||||
RelationCreate: {TypeMetaResources},
|
||||
RelationRead: {TypeUser, TypeServiceAccount, TypeRole, TypeOrganization, TypeMetaResource},
|
||||
RelationList: {TypeMetaResources},
|
||||
RelationUpdate: {TypeUser, TypeServiceAccount, TypeRole, TypeOrganization, TypeMetaResource},
|
||||
RelationDelete: {TypeUser, TypeServiceAccount, TypeRole, TypeOrganization, TypeMetaResource},
|
||||
RelationAssignee: {TypeRole},
|
||||
func (Relation) Enum() []any {
|
||||
return coretypes.Verb{}.Enum()
|
||||
}
|
||||
|
||||
type Relation struct{ valuer.String }
|
||||
|
||||
func NewRelation(relation string) (Relation, error) {
|
||||
switch relation {
|
||||
case "create":
|
||||
return RelationCreate, nil
|
||||
case "read":
|
||||
return RelationRead, nil
|
||||
case "update":
|
||||
return RelationUpdate, nil
|
||||
case "delete":
|
||||
return RelationDelete, nil
|
||||
case "list":
|
||||
return RelationList, nil
|
||||
case "assignee":
|
||||
return RelationAssignee, nil
|
||||
default:
|
||||
return Relation{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s", relation)
|
||||
func (rel *Relation) UnmarshalJSON(data []byte) error {
|
||||
str := ""
|
||||
err := json.Unmarshal(data, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
alias, err := coretypes.NewVerb(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*rel = Relation{alias}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"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"
|
||||
"github.com/uptrace/bun"
|
||||
@@ -34,13 +35,13 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
SigNozAnonymousRoleName = "signoz-anonymous"
|
||||
SigNozAnonymousRoleName = coretypes.SigNozAnonymousRoleName
|
||||
SigNozAnonymousRoleDescription = "Role assigned to anonymous users for access to public resources."
|
||||
SigNozAdminRoleName = "signoz-admin"
|
||||
SigNozAdminRoleName = coretypes.SigNozAdminRoleName
|
||||
SigNozAdminRoleDescription = "Role assigned to users who have full administrative access to SigNoz resources."
|
||||
SigNozEditorRoleName = "signoz-editor"
|
||||
SigNozEditorRoleName = coretypes.SigNozEditorRoleName
|
||||
SigNozEditorRoleDescription = "Role assigned to users who can create, edit, and manage SigNoz resources but do not have full administrative privileges."
|
||||
SigNozViewerRoleName = "signoz-viewer"
|
||||
SigNozViewerRoleName = coretypes.SigNozViewerRoleName
|
||||
SigNozViewerRoleDescription = "Role assigned to users who have read-only access to SigNoz resources."
|
||||
)
|
||||
|
||||
@@ -58,10 +59,6 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
TypeableResourcesRoles = MustNewTypeableMetaResources(MustNewName("roles"))
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
bun.BaseModel `bun:"table:role"`
|
||||
|
||||
@@ -171,25 +168,23 @@ func (role *PatchableRole) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAdditionTuples(name string, orgID valuer.UUID, relation Relation, additions []*Object) ([]*openfgav1.TupleKey, error) {
|
||||
func GetAdditionTuples(name string, orgID valuer.UUID, relation Relation, additions []*coretypes.Object) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, object := range additions {
|
||||
typeable := MustNewTypeableFromType(object.Resource.Type, object.Resource.Name)
|
||||
transactionTuples, err := typeable.Tuples(
|
||||
resource := coretypes.MustNewResourceFromTypeAndKind(object.Resource.Type, object.Resource.Kind)
|
||||
transactionTuples := NewTuples(
|
||||
resource,
|
||||
MustNewSubject(
|
||||
TypeableRole,
|
||||
resource,
|
||||
name,
|
||||
orgID,
|
||||
&RelationAssignee,
|
||||
&coretypes.VerbAssignee,
|
||||
),
|
||||
relation,
|
||||
[]Selector{object.Selector},
|
||||
[]coretypes.Selector{object.Selector},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tuples = append(tuples, transactionTuples...)
|
||||
}
|
||||
@@ -197,25 +192,23 @@ func GetAdditionTuples(name string, orgID valuer.UUID, relation Relation, additi
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func GetDeletionTuples(name string, orgID valuer.UUID, relation Relation, deletions []*Object) ([]*openfgav1.TupleKey, error) {
|
||||
func GetDeletionTuples(name string, orgID valuer.UUID, relation Relation, deletions []*coretypes.Object) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, object := range deletions {
|
||||
typeable := MustNewTypeableFromType(object.Resource.Type, object.Resource.Name)
|
||||
transactionTuples, err := typeable.Tuples(
|
||||
resource := coretypes.MustNewResourceFromTypeAndKind(object.Resource.Type, object.Resource.Kind)
|
||||
transactionTuples := NewTuples(
|
||||
resource,
|
||||
MustNewSubject(
|
||||
TypeableRole,
|
||||
resource,
|
||||
name,
|
||||
orgID,
|
||||
&RelationAssignee,
|
||||
&coretypes.VerbAssignee,
|
||||
),
|
||||
relation,
|
||||
[]Selector{object.Selector},
|
||||
[]coretypes.Selector{object.Selector},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tuples = append(tuples, transactionTuples...)
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeAuthZInvalidSelector = errors.MustNewCode("authz_invalid_selector")
|
||||
)
|
||||
|
||||
var (
|
||||
_ json.Marshaler = new(Selector)
|
||||
_ json.Unmarshaler = new(Selector)
|
||||
_ encoding.TextMarshaler = new(Selector)
|
||||
_ encoding.TextUnmarshaler = new(Selector)
|
||||
)
|
||||
|
||||
var (
|
||||
typeUserSelectorRegex = regexp.MustCompile(`^(^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$|\*)$`)
|
||||
typeServiceAccountSelectorRegex = regexp.MustCompile(`^(^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$|\*)$`)
|
||||
typeRoleSelectorRegex = regexp.MustCompile(`^([a-z-]{1,50}|\*)$`)
|
||||
typeAnonymousSelectorRegex = regexp.MustCompile(`^\*$`)
|
||||
typeOrganizationSelectorRegex = regexp.MustCompile(`^(^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$|\*)$`)
|
||||
typeMetaResourceSelectorRegex = regexp.MustCompile(`^(^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$|\*)$`)
|
||||
// metaresources selectors are used to select either all or none until we introduce some hierarchy here.
|
||||
typeMetaResourcesSelectorRegex = regexp.MustCompile(`^\*$`)
|
||||
)
|
||||
|
||||
var (
|
||||
WildCardSelectorString = "*"
|
||||
)
|
||||
|
||||
type SelectorCallbackWithClaimsFn func(*http.Request, Claims) ([]Selector, error)
|
||||
type SelectorCallbackWithoutClaimsFn func(*http.Request, []*types.Organization) ([]Selector, valuer.UUID, error)
|
||||
|
||||
type Selector struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func NewSelector(typed Type, selector string) (Selector, error) {
|
||||
err := IsValidSelector(typed, selector)
|
||||
if err != nil {
|
||||
return Selector{}, err
|
||||
}
|
||||
|
||||
return Selector{val: selector}, nil
|
||||
}
|
||||
|
||||
func MustNewSelector(typed Type, input string) Selector {
|
||||
selector, err := NewSelector(typed, input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return selector
|
||||
}
|
||||
|
||||
func (selector *Selector) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(selector.val)
|
||||
}
|
||||
|
||||
func (selector Selector) String() string {
|
||||
return selector.val
|
||||
}
|
||||
|
||||
func (typed *Selector) UnmarshalJSON(data []byte) error {
|
||||
str := ""
|
||||
err := json.Unmarshal(data, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
alias := Selector{val: str}
|
||||
*typed = alias
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (selector Selector) MarshalText() ([]byte, error) {
|
||||
return []byte(selector.val), nil
|
||||
}
|
||||
|
||||
func (selector *Selector) UnmarshalText(text []byte) error {
|
||||
*selector = Selector{val: string(text)}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsValidSelector(typed Type, selector string) error {
|
||||
switch typed {
|
||||
case TypeUser:
|
||||
if !typeUserSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeUserSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
case TypeServiceAccount:
|
||||
if !typeServiceAccountSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeServiceAccountSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
case TypeRole:
|
||||
if !typeRoleSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeRoleSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
case TypeAnonymous:
|
||||
if !typeAnonymousSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeAnonymousSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
case TypeOrganization:
|
||||
if !typeOrganizationSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeOrganizationSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
case TypeMetaResource:
|
||||
if !typeMetaResourceSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeMetaResourceSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
case TypeMetaResources:
|
||||
if !typeMetaResourcesSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeMetaResourcesSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type: %s", typed)
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
package authtypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func NewSubject(subjectType Typeable, selector string, orgID valuer.UUID, relation *Relation) (string, error) {
|
||||
func NewSubject(resource coretypes.Resource, selector string, orgID valuer.UUID, relation *coretypes.Verb) (string, error) {
|
||||
if relation == nil {
|
||||
return subjectType.Prefix(orgID) + "/" + selector, nil
|
||||
return resource.Prefix(orgID) + "/" + selector, nil
|
||||
}
|
||||
|
||||
return subjectType.Prefix(orgID) + "/" + selector + "#" + relation.StringValue(), nil
|
||||
return resource.Prefix(orgID) + "/" + selector + "#" + relation.StringValue(), nil
|
||||
}
|
||||
|
||||
func MustNewSubject(subjectType Typeable, selector string, orgID valuer.UUID, relation *Relation) string {
|
||||
subject, err := NewSubject(subjectType, selector, orgID, relation)
|
||||
func MustNewSubject(resource coretypes.Resource, selector string, orgID valuer.UUID, relation *coretypes.Verb) string {
|
||||
subject, err := NewSubject(resource, selector, orgID, relation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -2,22 +2,21 @@ package authtypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Transaction struct {
|
||||
ID valuer.UUID `json:"-"`
|
||||
Relation Relation `json:"relation" required:"true"`
|
||||
Object Object `json:"object" required:"true"`
|
||||
ID valuer.UUID `json:"-"`
|
||||
Relation Relation `json:"relation" required:"true"`
|
||||
Object coretypes.Object `json:"object" required:"true"`
|
||||
}
|
||||
|
||||
type GettableTransaction struct {
|
||||
Relation Relation `json:"relation" required:"true"`
|
||||
Object Object `json:"object" required:"true"`
|
||||
Authorized bool `json:"authorized" required:"true"`
|
||||
Relation Relation `json:"relation" required:"true"`
|
||||
Object coretypes.Object `json:"object" required:"true"`
|
||||
Authorized bool `json:"authorized" required:"true"`
|
||||
}
|
||||
|
||||
type TransactionWithAuthorization struct {
|
||||
@@ -25,9 +24,9 @@ type TransactionWithAuthorization struct {
|
||||
Authorized bool
|
||||
}
|
||||
|
||||
func NewTransaction(relation Relation, object Object) (*Transaction, error) {
|
||||
if !slices.Contains(TypeableRelations[object.Resource.Type], relation) {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s for type %s", relation.StringValue(), object.Resource.Type.StringValue())
|
||||
func NewTransaction(relation Relation, object coretypes.Object) (*Transaction, error) {
|
||||
if err := coretypes.ErrIfVerbNotValidForType(relation.Verb, object.Resource.Type); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Transaction{ID: valuer.GenerateUUID(), Relation: relation, Object: object}, nil
|
||||
@@ -46,58 +45,10 @@ func NewGettableTransaction(results []*TransactionWithAuthorization) []*Gettable
|
||||
return gettableTransactions
|
||||
}
|
||||
|
||||
// NewTransactionWithAuthorizationFromBatchResults merges batch check results into an ordered
|
||||
// slice of TransactionWithAuthorization matching the input transactions order.
|
||||
// preResolved contains txn IDs whose authorization was determined without BatchCheck.
|
||||
// roleCorrelations maps txn IDs to correlation IDs used for managed role checks.
|
||||
func NewTransactionWithAuthorizationFromBatchResults(
|
||||
transactions []*Transaction,
|
||||
batchResults map[string]*TupleKeyAuthorization,
|
||||
preResolved map[string]bool,
|
||||
roleCorrelations map[string][]string,
|
||||
) []*TransactionWithAuthorization {
|
||||
output := make([]*TransactionWithAuthorization, len(transactions))
|
||||
for i, txn := range transactions {
|
||||
txnID := txn.ID.StringValue()
|
||||
|
||||
if authorized, ok := preResolved[txnID]; ok {
|
||||
output[i] = &TransactionWithAuthorization{
|
||||
Transaction: txn,
|
||||
Authorized: authorized,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if txn.Object.Resource.Type == TypeRole && txn.Relation == RelationAssignee {
|
||||
output[i] = &TransactionWithAuthorization{
|
||||
Transaction: txn,
|
||||
Authorized: batchResults[txnID].Authorized,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
correlationIDs := roleCorrelations[txnID]
|
||||
authorized := false
|
||||
for _, correlationID := range correlationIDs {
|
||||
if result, exists := batchResults[correlationID]; exists && result.Authorized {
|
||||
authorized = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
output[i] = &TransactionWithAuthorization{
|
||||
Transaction: txn,
|
||||
Authorized: authorized,
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (transaction *Transaction) UnmarshalJSON(data []byte) error {
|
||||
var shadow = struct {
|
||||
Relation Relation
|
||||
Object Object
|
||||
Object coretypes.Object
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(data, &shadow)
|
||||
@@ -113,3 +64,7 @@ func (transaction *Transaction) UnmarshalJSON(data []byte) error {
|
||||
*transaction = *txn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (transaction *Transaction) TransactionKey() string {
|
||||
return transaction.Relation.StringValue() + ":" + transaction.Object.Resource.Type.StringValue() + ":" + transaction.Object.Resource.Kind.String()
|
||||
}
|
||||
|
||||
@@ -1,32 +1,50 @@
|
||||
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")
|
||||
ErrCodeAuthZInvalidType = errors.MustNewCode("authz_invalid_type")
|
||||
ErrCodeTypeableNotFound = errors.MustNewCode("typeable_not_found")
|
||||
)
|
||||
|
||||
type TupleKeyAuthorization struct {
|
||||
Tuple *openfgav1.TupleKey
|
||||
Authorized bool
|
||||
}
|
||||
|
||||
// TransactionKey returns a composite key for matching transactions to managed roles.
|
||||
func (transaction *Transaction) TransactionKey() string {
|
||||
return transaction.Relation.StringValue() + ":" + transaction.Object.Resource.Type.StringValue() + ":" + transaction.Object.Resource.Name.String()
|
||||
func NewTuples(resource coretypes.Resource, subject string, relation Relation, selectors []coretypes.Selector, orgID valuer.UUID) []*openfgav1.TupleKey {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, selector := range selectors {
|
||||
object := resource.Object(orgID, selector.String())
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples
|
||||
}
|
||||
|
||||
func NewTuplesFromTransactions(transactions []*Transaction, subject string, orgID valuer.UUID) (map[string]*openfgav1.TupleKey, error) {
|
||||
tuples := make(map[string]*openfgav1.TupleKey, len(transactions))
|
||||
for _, txn := range transactions {
|
||||
typeable, err := NewTypeableFromType(txn.Object.Resource.Type, txn.Object.Resource.Name)
|
||||
resource, err := coretypes.NewResourceFromTypeAndKind(txn.Object.Resource.Type, txn.Object.Resource.Kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txnTuples, err := typeable.Tuples(subject, txn.Relation, []Selector{txn.Object.Selector}, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txnTuples := NewTuples(resource, subject, txn.Relation, []coretypes.Selector{txn.Object.Selector}, orgID)
|
||||
|
||||
// Each transaction produces one tuple, keyed by transaction ID
|
||||
tuples[txn.ID.StringValue()] = txnTuples[0]
|
||||
@@ -36,7 +54,7 @@ func NewTuplesFromTransactions(transactions []*Transaction, subject string, orgI
|
||||
}
|
||||
|
||||
// NewTuplesFromTransactionsWithManagedRoles converts transactions to tuples for BatchCheck.
|
||||
// Direct role-assignment transactions (TypeRole + RelationAssignee) produce one tuple keyed by txn ID.
|
||||
// Direct role-assignment transactions (TypeRole + VerbAssignee) produce one tuple keyed by txn ID.
|
||||
// Other transactions are expanded via managedRolesByTransaction into role-assignee checks, keyed by "txnID:roleName".
|
||||
// Transactions with no managed role mapping are marked as pre-resolved (false) in the returned map.
|
||||
func NewTuplesFromTransactionsWithManagedRoles(
|
||||
@@ -52,16 +70,13 @@ func NewTuplesFromTransactionsWithManagedRoles(
|
||||
for _, txn := range transactions {
|
||||
txnID := txn.ID.StringValue()
|
||||
|
||||
if txn.Object.Resource.Type == TypeRole && txn.Relation == RelationAssignee {
|
||||
typeable, err := NewTypeableFromType(txn.Object.Resource.Type, txn.Object.Resource.Name)
|
||||
if txn.Object.Resource.Type.Equals(coretypes.TypeRole) && txn.Relation.Verb == coretypes.VerbAssignee {
|
||||
resource, err := coretypes.NewResourceFromTypeAndKind(txn.Object.Resource.Type, txn.Object.Resource.Kind)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
txnTuples, err := typeable.Tuples(subject, txn.Relation, []Selector{txn.Object.Selector}, orgID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
txnTuples := NewTuples(resource, subject, txn.Relation, []coretypes.Selector{txn.Object.Selector}, orgID)
|
||||
|
||||
tuples[txnID] = txnTuples[0]
|
||||
continue
|
||||
@@ -74,11 +89,8 @@ func NewTuplesFromTransactionsWithManagedRoles(
|
||||
}
|
||||
|
||||
for _, roleName := range roleNames {
|
||||
roleSelector := MustNewSelector(TypeRole, roleName)
|
||||
roleTuples, err := TypeableRole.Tuples(subject, RelationAssignee, []Selector{roleSelector}, orgID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
roleSelector := coretypes.TypeRole.MustSelector(roleName)
|
||||
roleTuples := NewTuples(coretypes.ResourceRole, subject, Relation{Verb: coretypes.VerbAssignee}, []coretypes.Selector{roleSelector}, orgID)
|
||||
|
||||
correlationID := valuer.GenerateUUID().StringValue()
|
||||
tuples[correlationID] = roleTuples[0]
|
||||
@@ -88,3 +100,51 @@ func NewTuplesFromTransactionsWithManagedRoles(
|
||||
|
||||
return tuples, preResolved, roleCorrelations, nil
|
||||
}
|
||||
|
||||
// NewTransactionWithAuthorizationFromBatchResults merges batch check results into an ordered
|
||||
// slice of TransactionWithAuthorization matching the input transactions order.
|
||||
// preResolved contains txn IDs whose authorization was determined without BatchCheck.
|
||||
// roleCorrelations maps txn IDs to correlation IDs used for managed role checks.
|
||||
func NewTransactionWithAuthorizationFromBatchResults(
|
||||
transactions []*Transaction,
|
||||
batchResults map[string]*TupleKeyAuthorization,
|
||||
preResolved map[string]bool,
|
||||
roleCorrelations map[string][]string,
|
||||
) []*TransactionWithAuthorization {
|
||||
output := make([]*TransactionWithAuthorization, len(transactions))
|
||||
for i, txn := range transactions {
|
||||
txnID := txn.ID.StringValue()
|
||||
|
||||
if authorized, ok := preResolved[txnID]; ok {
|
||||
output[i] = &TransactionWithAuthorization{
|
||||
Transaction: txn,
|
||||
Authorized: authorized,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if txn.Object.Resource.Type.Equals(coretypes.TypeRole) && txn.Relation.Verb == coretypes.VerbAssignee {
|
||||
output[i] = &TransactionWithAuthorization{
|
||||
Transaction: txn,
|
||||
Authorized: batchResults[txnID].Authorized,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
correlationIDs := roleCorrelations[txnID]
|
||||
authorized := false
|
||||
for _, correlationID := range correlationIDs {
|
||||
if result, exists := batchResults[correlationID]; exists && result.Authorized {
|
||||
authorized = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
output[i] = &TransactionWithAuthorization{
|
||||
Transaction: txn,
|
||||
Authorized: authorized,
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeAuthZUnavailable = errors.MustNewCode("authz_unavailable")
|
||||
ErrCodeAuthZForbidden = errors.MustNewCode("authz_forbidden")
|
||||
ErrCodeAuthZInvalidType = errors.MustNewCode("authz_invalid_type")
|
||||
)
|
||||
|
||||
var (
|
||||
TypeUser = Type{valuer.NewString("user")}
|
||||
TypeServiceAccount = Type{valuer.NewString("serviceaccount")}
|
||||
TypeAnonymous = Type{valuer.NewString("anonymous")}
|
||||
TypeRole = Type{valuer.NewString("role")}
|
||||
TypeOrganization = Type{valuer.NewString("organization")}
|
||||
TypeMetaResource = Type{valuer.NewString("metaresource")}
|
||||
TypeMetaResources = Type{valuer.NewString("metaresources")}
|
||||
)
|
||||
|
||||
var (
|
||||
TypeableUser = &typeableUser{}
|
||||
TypeableServiceAccount = &typeableServiceAccount{}
|
||||
TypeableAnonymous = &typeableAnonymous{}
|
||||
TypeableRole = &typeableRole{}
|
||||
TypeableOrganization = &typeableOrganization{}
|
||||
)
|
||||
|
||||
type Typeable interface {
|
||||
Type() Type
|
||||
Name() Name
|
||||
Prefix(orgId valuer.UUID) string
|
||||
Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error)
|
||||
Scope(relation Relation) string
|
||||
}
|
||||
|
||||
type Type struct{ valuer.String }
|
||||
|
||||
func MustNewType(input string) Type {
|
||||
typed, err := NewType(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return typed
|
||||
}
|
||||
|
||||
func NewType(input string) (Type, error) {
|
||||
switch input {
|
||||
case "user":
|
||||
return TypeUser, nil
|
||||
case "serviceaccount":
|
||||
return TypeServiceAccount, nil
|
||||
case "role":
|
||||
return TypeRole, nil
|
||||
case "organization":
|
||||
return TypeOrganization, nil
|
||||
case "metaresource":
|
||||
return TypeMetaResource, nil
|
||||
case "metaresources":
|
||||
return TypeMetaResources, nil
|
||||
default:
|
||||
return Type{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type: %s", input)
|
||||
}
|
||||
}
|
||||
|
||||
func (typed *Type) UnmarshalJSON(data []byte) error {
|
||||
str := ""
|
||||
err := json.Unmarshal(data, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
alias, err := NewType(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*typed = alias
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTypeableFromType(typed Type, name Name) (Typeable, error) {
|
||||
switch typed {
|
||||
case TypeRole:
|
||||
return TypeableRole, nil
|
||||
case TypeUser:
|
||||
return TypeableUser, nil
|
||||
case TypeServiceAccount:
|
||||
return TypeableServiceAccount, nil
|
||||
case TypeOrganization:
|
||||
return TypeableOrganization, nil
|
||||
case TypeMetaResource:
|
||||
resource, err := NewTypeableMetaResource(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource, nil
|
||||
case TypeMetaResources:
|
||||
resources, err := NewTypeableMetaResources(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type %s", typed)
|
||||
}
|
||||
|
||||
func MustNewTypeableFromType(typed Type, name Name) Typeable {
|
||||
typeable, err := NewTypeableFromType(typed, name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return typeable
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableAnonymous)
|
||||
|
||||
var (
|
||||
AnonymousUser = valuer.UUID{}
|
||||
)
|
||||
|
||||
type typeableAnonymous struct{}
|
||||
|
||||
func (typeableAnonymous *typeableAnonymous) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for _, selector := range selector {
|
||||
object := typeableAnonymous.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableAnonymous *typeableAnonymous) Type() Type {
|
||||
return TypeAnonymous
|
||||
}
|
||||
|
||||
func (typeableAnonymous *typeableAnonymous) Name() Name {
|
||||
return MustNewName("anonymous")
|
||||
}
|
||||
|
||||
// example: anonymous:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/anonymous
|
||||
func (typeableAnonymous *typeableAnonymous) Prefix(orgID valuer.UUID) string {
|
||||
return typeableAnonymous.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableAnonymous.Name().String()
|
||||
}
|
||||
|
||||
func (typeableAnonymous *typeableAnonymous) Scope(relation Relation) string {
|
||||
return typeableAnonymous.Name().String() + ":" + relation.StringValue()
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableMetaResource)
|
||||
|
||||
type typeableMetaResource struct {
|
||||
name Name
|
||||
}
|
||||
|
||||
func NewTypeableMetaResource(name Name) (Typeable, error) {
|
||||
return &typeableMetaResource{name: name}, nil
|
||||
}
|
||||
|
||||
func MustNewTypeableMetaResource(name Name) Typeable {
|
||||
typeableesource, err := NewTypeableMetaResource(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return typeableesource
|
||||
}
|
||||
|
||||
func (typeableMetaResource *typeableMetaResource) Tuples(subject string, relation Relation, selectors []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, selector := range selectors {
|
||||
object := typeableMetaResource.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableMetaResource *typeableMetaResource) Type() Type {
|
||||
return TypeMetaResource
|
||||
}
|
||||
|
||||
func (typeableMetaResource *typeableMetaResource) Name() Name {
|
||||
return typeableMetaResource.name
|
||||
}
|
||||
|
||||
// example: metaresource:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboard
|
||||
func (typeableMetaResource *typeableMetaResource) Prefix(orgID valuer.UUID) string {
|
||||
return typeableMetaResource.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableMetaResource.Name().String()
|
||||
}
|
||||
|
||||
func (typeableMetaResource *typeableMetaResource) Scope(relation Relation) string {
|
||||
return typeableMetaResource.Name().String() + ":" + relation.StringValue()
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableMetaResources)
|
||||
|
||||
type typeableMetaResources struct {
|
||||
name Name
|
||||
}
|
||||
|
||||
func NewTypeableMetaResources(name Name) (Typeable, error) {
|
||||
return &typeableMetaResources{name: name}, nil
|
||||
}
|
||||
|
||||
func MustNewTypeableMetaResources(name Name) Typeable {
|
||||
resources, err := NewTypeableMetaResources(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func (typeableResources *typeableMetaResources) Tuples(subject string, relation Relation, selectors []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, selector := range selectors {
|
||||
object := typeableResources.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableMetaResources *typeableMetaResources) Type() Type {
|
||||
return TypeMetaResources
|
||||
}
|
||||
|
||||
func (typeableMetaResources *typeableMetaResources) Name() Name {
|
||||
return typeableMetaResources.name
|
||||
}
|
||||
|
||||
// example: metaresources:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboards
|
||||
func (typeableMetaResources *typeableMetaResources) Prefix(orgID valuer.UUID) string {
|
||||
return typeableMetaResources.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableMetaResources.Name().String()
|
||||
}
|
||||
|
||||
func (typeableMetaResources *typeableMetaResources) Scope(relation Relation) string {
|
||||
return typeableMetaResources.Name().String() + ":" + relation.StringValue()
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableOrganization)
|
||||
|
||||
type typeableOrganization struct{}
|
||||
|
||||
func (typeableOrganization *typeableOrganization) Tuples(subject string, relation Relation, selectors []Selector, _ valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, selector := range selectors {
|
||||
object := strings.Join([]string{typeableOrganization.Type().StringValue(), selector.String()}, ":")
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableOrganization *typeableOrganization) Type() Type {
|
||||
return TypeOrganization
|
||||
}
|
||||
|
||||
func (typeableOrganization *typeableOrganization) Name() Name {
|
||||
return MustNewName("organization")
|
||||
}
|
||||
|
||||
func (typeableOrganization *typeableOrganization) Prefix(_ valuer.UUID) string {
|
||||
return typeableOrganization.Type().StringValue()
|
||||
}
|
||||
|
||||
func (typeableOrganization *typeableOrganization) Scope(relation Relation) string {
|
||||
return typeableOrganization.Name().String() + ":" + relation.StringValue()
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableRole)
|
||||
|
||||
type typeableRole struct{}
|
||||
|
||||
func (typeableRole *typeableRole) Tuples(subject string, relation Relation, selectors []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, selector := range selectors {
|
||||
object := typeableRole.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableRole *typeableRole) Type() Type {
|
||||
return TypeRole
|
||||
}
|
||||
|
||||
func (typeableRole *typeableRole) Name() Name {
|
||||
return MustNewName("role")
|
||||
}
|
||||
|
||||
// example: role:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/role
|
||||
func (typeableRole *typeableRole) Prefix(orgID valuer.UUID) string {
|
||||
return typeableRole.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableRole.Name().String()
|
||||
}
|
||||
|
||||
func (typeableRole *typeableRole) Scope(relation Relation) string {
|
||||
return typeableRole.Name().String() + ":" + relation.StringValue()
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableServiceAccount)
|
||||
|
||||
type typeableServiceAccount struct{}
|
||||
|
||||
func (typeableServiceAccount *typeableServiceAccount) Tuples(subject string, relation Relation, selectors []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, selector := range selectors {
|
||||
object := typeableServiceAccount.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableServiceAccount *typeableServiceAccount) Type() Type {
|
||||
return TypeServiceAccount
|
||||
}
|
||||
|
||||
func (typeableServiceAccount *typeableServiceAccount) Name() Name {
|
||||
return MustNewName("serviceaccount")
|
||||
}
|
||||
|
||||
// example: serviceaccount:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/serviceaccount
|
||||
func (typeableServiceAccount *typeableServiceAccount) Prefix(orgID valuer.UUID) string {
|
||||
return typeableServiceAccount.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableServiceAccount.Name().String()
|
||||
}
|
||||
|
||||
func (typeableServiceAccount *typeableServiceAccount) Scope(relation Relation) string {
|
||||
return typeableServiceAccount.Name().String() + ":" + relation.StringValue()
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableUser)
|
||||
|
||||
type typeableUser struct{}
|
||||
|
||||
func (typeableUser *typeableUser) Tuples(subject string, relation Relation, selectors []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
for _, selector := range selectors {
|
||||
object := typeableUser.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableUser *typeableUser) Type() Type {
|
||||
return TypeUser
|
||||
}
|
||||
|
||||
func (typeableUser *typeableUser) Name() Name {
|
||||
return MustNewName("user")
|
||||
}
|
||||
|
||||
// example: user:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/user
|
||||
func (typeableUser *typeableUser) Prefix(orgID valuer.UUID) string {
|
||||
return typeableUser.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableUser.Name().String()
|
||||
}
|
||||
|
||||
func (typeableUser *typeableUser) Scope(relation Relation) string {
|
||||
return typeableUser.Name().String() + ":" + relation.StringValue()
|
||||
}
|
||||
77
pkg/types/coretypes/kind.go
Normal file
77
pkg/types/coretypes/kind.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package coretypes
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
kindRegex = regexp.MustCompile("^[a-z-]{1,50}$")
|
||||
)
|
||||
|
||||
var (
|
||||
_ json.Marshaler = new(Kind)
|
||||
_ json.Unmarshaler = new(Kind)
|
||||
_ encoding.TextMarshaler = new(Kind)
|
||||
_ encoding.TextUnmarshaler = new(Kind)
|
||||
)
|
||||
|
||||
// Kind represents a specific kind of a Type. For example, for Type "metaresource", we can have Kinds like "dashboard", "alert", etc.
|
||||
type Kind struct{ val string }
|
||||
|
||||
func NewKind(str string) (Kind, error) {
|
||||
if !kindRegex.MatchString(str) {
|
||||
return Kind{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "kind must conform to regex %s", kindRegex.String())
|
||||
}
|
||||
|
||||
return Kind{val: str}, nil
|
||||
}
|
||||
|
||||
func MustNewKind(str string) Kind {
|
||||
kind, err := NewKind(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return kind
|
||||
}
|
||||
|
||||
func (name Kind) String() string {
|
||||
return name.val
|
||||
}
|
||||
|
||||
func (name *Kind) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(name.val)
|
||||
}
|
||||
|
||||
func (name *Kind) UnmarshalJSON(data []byte) error {
|
||||
str := ""
|
||||
err := json.Unmarshal(data, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shadow, err := NewKind(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*name = shadow
|
||||
return nil
|
||||
}
|
||||
|
||||
func (name Kind) MarshalText() ([]byte, error) {
|
||||
return []byte(name.val), nil
|
||||
}
|
||||
|
||||
func (name *Kind) UnmarshalText(text []byte) error {
|
||||
shadow, err := NewKind(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*name = shadow
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user