mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-22 16:37:31 +00:00
Compare commits
5 Commits
invite-val
...
fix/remove
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43509681fa | ||
|
|
ff5fcc0e98 | ||
|
|
122d88c4d2 | ||
|
|
0dd42ec076 | ||
|
|
34ba5bab28 |
@@ -92,6 +92,19 @@ components:
|
|||||||
tokenType:
|
tokenType:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
AuthtypesGettableTransaction:
|
||||||
|
properties:
|
||||||
|
authorized:
|
||||||
|
type: boolean
|
||||||
|
object:
|
||||||
|
$ref: '#/components/schemas/AuthtypesObject'
|
||||||
|
relation:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- relation
|
||||||
|
- object
|
||||||
|
- authorized
|
||||||
|
type: object
|
||||||
AuthtypesGoogleConfig:
|
AuthtypesGoogleConfig:
|
||||||
properties:
|
properties:
|
||||||
allowedGroups:
|
allowedGroups:
|
||||||
@@ -117,6 +130,8 @@ components:
|
|||||||
serviceAccountJson:
|
serviceAccountJson:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
AuthtypesName:
|
||||||
|
type: object
|
||||||
AuthtypesOIDCConfig:
|
AuthtypesOIDCConfig:
|
||||||
properties:
|
properties:
|
||||||
claimMapping:
|
claimMapping:
|
||||||
@@ -134,6 +149,16 @@ components:
|
|||||||
issuerAlias:
|
issuerAlias:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
AuthtypesObject:
|
||||||
|
properties:
|
||||||
|
resource:
|
||||||
|
$ref: '#/components/schemas/AuthtypesResource'
|
||||||
|
selector:
|
||||||
|
$ref: '#/components/schemas/AuthtypesSelector'
|
||||||
|
required:
|
||||||
|
- resource
|
||||||
|
- selector
|
||||||
|
type: object
|
||||||
AuthtypesOrgSessionContext:
|
AuthtypesOrgSessionContext:
|
||||||
properties:
|
properties:
|
||||||
authNSupport:
|
authNSupport:
|
||||||
@@ -171,6 +196,16 @@ components:
|
|||||||
refreshToken:
|
refreshToken:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
AuthtypesResource:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
$ref: '#/components/schemas/AuthtypesName'
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
AuthtypesRoleMapping:
|
AuthtypesRoleMapping:
|
||||||
properties:
|
properties:
|
||||||
defaultRole:
|
defaultRole:
|
||||||
@@ -196,6 +231,8 @@ components:
|
|||||||
samlIdp:
|
samlIdp:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
AuthtypesSelector:
|
||||||
|
type: object
|
||||||
AuthtypesSessionContext:
|
AuthtypesSessionContext:
|
||||||
properties:
|
properties:
|
||||||
exists:
|
exists:
|
||||||
@@ -206,6 +243,18 @@ components:
|
|||||||
nullable: true
|
nullable: true
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
AuthtypesTransaction:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
object:
|
||||||
|
$ref: '#/components/schemas/AuthtypesObject'
|
||||||
|
relation:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- relation
|
||||||
|
- object
|
||||||
|
type: object
|
||||||
AuthtypesUpdateableAuthDomain:
|
AuthtypesUpdateableAuthDomain:
|
||||||
properties:
|
properties:
|
||||||
config:
|
config:
|
||||||
@@ -1613,6 +1662,56 @@ components:
|
|||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
RoletypesGettableResources:
|
||||||
|
properties:
|
||||||
|
relations:
|
||||||
|
additionalProperties:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
type: object
|
||||||
|
resources:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/AuthtypesResource'
|
||||||
|
nullable: true
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- resources
|
||||||
|
- relations
|
||||||
|
type: object
|
||||||
|
RoletypesPatchableObjects:
|
||||||
|
properties:
|
||||||
|
additions:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/AuthtypesObject'
|
||||||
|
nullable: true
|
||||||
|
type: array
|
||||||
|
deletions:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/AuthtypesObject'
|
||||||
|
nullable: true
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- additions
|
||||||
|
- deletions
|
||||||
|
type: object
|
||||||
|
RoletypesPatchableRole:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- description
|
||||||
|
type: object
|
||||||
|
RoletypesPostableRole:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
RoletypesRole:
|
RoletypesRole:
|
||||||
properties:
|
properties:
|
||||||
createdAt:
|
createdAt:
|
||||||
@@ -1631,6 +1730,11 @@ components:
|
|||||||
updatedAt:
|
updatedAt:
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- description
|
||||||
|
- type
|
||||||
|
- orgId
|
||||||
type: object
|
type: object
|
||||||
TelemetrytypesFieldContext:
|
TelemetrytypesFieldContext:
|
||||||
enum:
|
enum:
|
||||||
@@ -2022,6 +2126,41 @@ info:
|
|||||||
version: ""
|
version: ""
|
||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
paths:
|
paths:
|
||||||
|
/api/v1/authz/check:
|
||||||
|
post:
|
||||||
|
deprecated: false
|
||||||
|
description: Checks if the authenticated user has permissions for given transactions
|
||||||
|
operationId: AuthzCheck
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/AuthtypesTransaction'
|
||||||
|
type: array
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/AuthtypesGettableTransaction'
|
||||||
|
type: array
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
description: OK
|
||||||
|
"500":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Internal Server Error
|
||||||
|
summary: Check permissions
|
||||||
|
tags:
|
||||||
|
- authz
|
||||||
/api/v1/changePassword/{id}:
|
/api/v1/changePassword/{id}:
|
||||||
post:
|
post:
|
||||||
deprecated: false
|
deprecated: false
|
||||||
@@ -3851,6 +3990,11 @@ paths:
|
|||||||
deprecated: false
|
deprecated: false
|
||||||
description: This endpoint creates a role
|
description: This endpoint creates a role
|
||||||
operationId: CreateRole
|
operationId: CreateRole
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RoletypesPostableRole'
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
content:
|
content:
|
||||||
@@ -3863,6 +4007,12 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
description: Created
|
description: Created
|
||||||
|
"400":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Bad Request
|
||||||
"401":
|
"401":
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -3875,12 +4025,30 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/RenderErrorResponse'
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
description: Forbidden
|
description: Forbidden
|
||||||
|
"409":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Conflict
|
||||||
|
"451":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Unavailable For Legal Reasons
|
||||||
"500":
|
"500":
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/RenderErrorResponse'
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
|
"501":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Implemented
|
||||||
security:
|
security:
|
||||||
- api_key:
|
- api_key:
|
||||||
- ADMIN
|
- ADMIN
|
||||||
@@ -3919,12 +4087,30 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/RenderErrorResponse'
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
description: Forbidden
|
description: Forbidden
|
||||||
|
"404":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Found
|
||||||
|
"451":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Unavailable For Legal Reasons
|
||||||
"500":
|
"500":
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/RenderErrorResponse'
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
|
"501":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Implemented
|
||||||
security:
|
security:
|
||||||
- api_key:
|
- api_key:
|
||||||
- ADMIN
|
- ADMIN
|
||||||
@@ -3991,6 +4177,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RoletypesPatchableRole'
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
content:
|
content:
|
||||||
@@ -4010,6 +4201,220 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/RenderErrorResponse'
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
description: Forbidden
|
description: Forbidden
|
||||||
|
"404":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Found
|
||||||
|
"451":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Unavailable For Legal Reasons
|
||||||
|
"500":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Internal Server Error
|
||||||
|
"501":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Implemented
|
||||||
|
security:
|
||||||
|
- api_key:
|
||||||
|
- ADMIN
|
||||||
|
- tokenizer:
|
||||||
|
- ADMIN
|
||||||
|
summary: Patch role
|
||||||
|
tags:
|
||||||
|
- role
|
||||||
|
/api/v1/roles/{id}/relation/{relation}/objects:
|
||||||
|
get:
|
||||||
|
deprecated: false
|
||||||
|
description: Gets all objects connected to the specified role via a given relation
|
||||||
|
type
|
||||||
|
operationId: GetObjects
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: path
|
||||||
|
name: relation
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/AuthtypesObject'
|
||||||
|
type: array
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
description: OK
|
||||||
|
"401":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Unauthorized
|
||||||
|
"403":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Forbidden
|
||||||
|
"404":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Found
|
||||||
|
"451":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Unavailable For Legal Reasons
|
||||||
|
"500":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Internal Server Error
|
||||||
|
"501":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Implemented
|
||||||
|
security:
|
||||||
|
- api_key:
|
||||||
|
- ADMIN
|
||||||
|
- tokenizer:
|
||||||
|
- ADMIN
|
||||||
|
summary: Get objects for a role by relation
|
||||||
|
tags:
|
||||||
|
- role
|
||||||
|
patch:
|
||||||
|
deprecated: false
|
||||||
|
description: Patches the objects connected to the specified role via a given
|
||||||
|
relation type
|
||||||
|
operationId: PatchObjects
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: path
|
||||||
|
name: relation
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RoletypesPatchableObjects'
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: No Content
|
||||||
|
"400":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Bad Request
|
||||||
|
"401":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Unauthorized
|
||||||
|
"403":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Forbidden
|
||||||
|
"404":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Found
|
||||||
|
"451":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Unavailable For Legal Reasons
|
||||||
|
"500":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Internal Server Error
|
||||||
|
"501":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Not Implemented
|
||||||
|
security:
|
||||||
|
- api_key:
|
||||||
|
- ADMIN
|
||||||
|
- tokenizer:
|
||||||
|
- ADMIN
|
||||||
|
summary: Patch objects for a role by relation
|
||||||
|
tags:
|
||||||
|
- role
|
||||||
|
/api/v1/roles/resources:
|
||||||
|
get:
|
||||||
|
deprecated: false
|
||||||
|
description: Gets all the available resources for role assignment
|
||||||
|
operationId: GetResources
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/components/schemas/RoletypesGettableResources'
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
description: OK
|
||||||
|
"401":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Unauthorized
|
||||||
|
"403":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RenderErrorResponse'
|
||||||
|
description: Forbidden
|
||||||
"500":
|
"500":
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -4021,7 +4426,7 @@ paths:
|
|||||||
- ADMIN
|
- ADMIN
|
||||||
- tokenizer:
|
- tokenizer:
|
||||||
- ADMIN
|
- ADMIN
|
||||||
summary: Patch role
|
summary: Get resources
|
||||||
tags:
|
tags:
|
||||||
- role
|
- role
|
||||||
/api/v1/user:
|
/api/v1/user:
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ func (provider *provider) Stop(ctx context.Context) error {
|
|||||||
return provider.openfgaServer.Stop(ctx)
|
return provider.openfgaServer.Stop(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *provider) Check(ctx context.Context, tuple *openfgav1.TupleKey) error {
|
|
||||||
return provider.openfgaServer.Check(ctx, tuple)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 authtypes.Typeable, selectors []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||||
return provider.openfgaServer.CheckWithTupleCreation(ctx, claims, orgID, relation, typeable, selectors, roleSelectors)
|
return provider.openfgaServer.CheckWithTupleCreation(ctx, claims, orgID, relation, typeable, selectors, roleSelectors)
|
||||||
}
|
}
|
||||||
@@ -74,8 +70,8 @@ func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Contex
|
|||||||
return provider.openfgaServer.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, typeable, selectors, roleSelectors)
|
return provider.openfgaServer.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, typeable, selectors, roleSelectors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *provider) BatchCheck(ctx context.Context, tuples []*openfgav1.TupleKey) error {
|
func (provider *provider) BatchCheck(ctx context.Context, tupleReq map[string]*openfgav1.TupleKey) (map[string]*authtypes.TupleKeyAuthorization, error) {
|
||||||
return provider.openfgaServer.BatchCheck(ctx, tuples)
|
return provider.openfgaServer.BatchCheck(ctx, tupleReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
||||||
@@ -187,6 +183,11 @@ func (provider *provider) GetResources(_ context.Context) []*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) ([]*authtypes.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())
|
||||||
|
}
|
||||||
|
|
||||||
storableRole, err := provider.store.Get(ctx, orgID, id)
|
storableRole, err := provider.store.Get(ctx, orgID, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -198,7 +199,7 @@ func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id
|
|||||||
resourceObjects, err := provider.
|
resourceObjects, err := provider.
|
||||||
ListObjects(
|
ListObjects(
|
||||||
ctx,
|
ctx,
|
||||||
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.ID.String(), orgID, &authtypes.RelationAssignee),
|
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.Name, orgID, &authtypes.RelationAssignee),
|
||||||
relation,
|
relation,
|
||||||
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
|
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package openfgaserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/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/authtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||||
@@ -28,27 +30,34 @@ func (server *Server) Stop(ctx context.Context) error {
|
|||||||
return server.pkgAuthzService.Stop(ctx)
|
return server.pkgAuthzService.Stop(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Check(ctx context.Context, tuple *openfgav1.TupleKey) error {
|
|
||||||
return server.pkgAuthzService.Check(ctx, tuple)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||||
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tuples, err := typeable.Tuples(subject, relation, selectors, orgID)
|
tupleSlice, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.BatchCheck(ctx, tuples)
|
tuples := make(map[string]*openfgav1.TupleKey, len(tupleSlice))
|
||||||
|
for idx, tuple := range tupleSlice {
|
||||||
|
tuples[strconv.Itoa(idx)] = tuple
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := server.BatchCheck(ctx, tuples)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
for _, resp := range response {
|
||||||
|
if resp.Authorized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||||
@@ -57,21 +66,32 @@ func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, o
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tuples, err := typeable.Tuples(subject, relation, selectors, orgID)
|
tupleSlice, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.BatchCheck(ctx, tuples)
|
tuples := make(map[string]*openfgav1.TupleKey, len(tupleSlice))
|
||||||
|
for idx, tuple := range tupleSlice {
|
||||||
|
tuples[strconv.Itoa(idx)] = tuple
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := server.BatchCheck(ctx, tuples)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
for _, resp := range response {
|
||||||
|
if resp.Authorized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subjects are not authorized for requested access")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) BatchCheck(ctx context.Context, tuples []*openfgav1.TupleKey) error {
|
func (server *Server) BatchCheck(ctx context.Context, tupleReq map[string]*openfgav1.TupleKey) (map[string]*authtypes.TupleKeyAuthorization, error) {
|
||||||
return server.pkgAuthzService.BatchCheck(ctx, tuples)
|
return server.pkgAuthzService.BatchCheck(ctx, tupleReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ func (module *module) MustGetManagedRoleTransactions() map[string][]*authtypes.T
|
|||||||
return map[string][]*authtypes.Transaction{
|
return map[string][]*authtypes.Transaction{
|
||||||
roletypes.SigNozAnonymousRoleName: {
|
roletypes.SigNozAnonymousRoleName: {
|
||||||
{
|
{
|
||||||
|
ID: valuer.GenerateUUID(),
|
||||||
Relation: authtypes.RelationRead,
|
Relation: authtypes.RelationRead,
|
||||||
Object: *authtypes.MustNewObject(
|
Object: *authtypes.MustNewObject(
|
||||||
authtypes.Resource{
|
authtypes.Resource{
|
||||||
|
|||||||
107
frontend/src/api/generated/services/authz/index.ts
Normal file
107
frontend/src/api/generated/services/authz/index.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* ! Do not edit manually
|
||||||
|
* * The file has been auto-generated using Orval for SigNoz
|
||||||
|
* * regenerate with 'yarn generate:api'
|
||||||
|
* SigNoz
|
||||||
|
*/
|
||||||
|
import type {
|
||||||
|
MutationFunction,
|
||||||
|
UseMutationOptions,
|
||||||
|
UseMutationResult,
|
||||||
|
} from 'react-query';
|
||||||
|
import { useMutation } from 'react-query';
|
||||||
|
|
||||||
|
import { GeneratedAPIInstance } from '../../../index';
|
||||||
|
import type {
|
||||||
|
AuthtypesTransactionDTO,
|
||||||
|
AuthzCheck200,
|
||||||
|
RenderErrorResponseDTO,
|
||||||
|
} from '../sigNoz.schemas';
|
||||||
|
|
||||||
|
type AwaitedInput<T> = PromiseLike<T> | T;
|
||||||
|
|
||||||
|
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the authenticated user has permissions for given transactions
|
||||||
|
* @summary Check permissions
|
||||||
|
*/
|
||||||
|
export const authzCheck = (
|
||||||
|
authtypesTransactionDTO: AuthtypesTransactionDTO[],
|
||||||
|
signal?: AbortSignal,
|
||||||
|
) => {
|
||||||
|
return GeneratedAPIInstance<AuthzCheck200>({
|
||||||
|
url: `/api/v1/authz/check`,
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
data: authtypesTransactionDTO,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAuthzCheckMutationOptions = <
|
||||||
|
TError = RenderErrorResponseDTO,
|
||||||
|
TContext = unknown
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof authzCheck>>,
|
||||||
|
TError,
|
||||||
|
{ data: AuthtypesTransactionDTO[] },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof authzCheck>>,
|
||||||
|
TError,
|
||||||
|
{ data: AuthtypesTransactionDTO[] },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationKey = ['authzCheck'];
|
||||||
|
const { mutation: mutationOptions } = options
|
||||||
|
? options.mutation &&
|
||||||
|
'mutationKey' in options.mutation &&
|
||||||
|
options.mutation.mutationKey
|
||||||
|
? options
|
||||||
|
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||||
|
: { mutation: { mutationKey } };
|
||||||
|
|
||||||
|
const mutationFn: MutationFunction<
|
||||||
|
Awaited<ReturnType<typeof authzCheck>>,
|
||||||
|
{ data: AuthtypesTransactionDTO[] }
|
||||||
|
> = (props) => {
|
||||||
|
const { data } = props ?? {};
|
||||||
|
|
||||||
|
return authzCheck(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AuthzCheckMutationResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof authzCheck>>
|
||||||
|
>;
|
||||||
|
export type AuthzCheckMutationBody = AuthtypesTransactionDTO[];
|
||||||
|
export type AuthzCheckMutationError = RenderErrorResponseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Check permissions
|
||||||
|
*/
|
||||||
|
export const useAuthzCheck = <
|
||||||
|
TError = RenderErrorResponseDTO,
|
||||||
|
TContext = unknown
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof authzCheck>>,
|
||||||
|
TError,
|
||||||
|
{ data: AuthtypesTransactionDTO[] },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof authzCheck>>,
|
||||||
|
TError,
|
||||||
|
{ data: AuthtypesTransactionDTO[] },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationOptions = getAuthzCheckMutationOptions(options);
|
||||||
|
|
||||||
|
return useMutation(mutationOptions);
|
||||||
|
};
|
||||||
@@ -21,11 +21,18 @@ import { GeneratedAPIInstance } from '../../../index';
|
|||||||
import type {
|
import type {
|
||||||
CreateRole201,
|
CreateRole201,
|
||||||
DeleteRolePathParameters,
|
DeleteRolePathParameters,
|
||||||
|
GetObjects200,
|
||||||
|
GetObjectsPathParameters,
|
||||||
|
GetResources200,
|
||||||
GetRole200,
|
GetRole200,
|
||||||
GetRolePathParameters,
|
GetRolePathParameters,
|
||||||
ListRoles200,
|
ListRoles200,
|
||||||
|
PatchObjectsPathParameters,
|
||||||
PatchRolePathParameters,
|
PatchRolePathParameters,
|
||||||
RenderErrorResponseDTO,
|
RenderErrorResponseDTO,
|
||||||
|
RoletypesPatchableObjectsDTO,
|
||||||
|
RoletypesPatchableRoleDTO,
|
||||||
|
RoletypesPostableRoleDTO,
|
||||||
} from '../sigNoz.schemas';
|
} from '../sigNoz.schemas';
|
||||||
|
|
||||||
type AwaitedInput<T> = PromiseLike<T> | T;
|
type AwaitedInput<T> = PromiseLike<T> | T;
|
||||||
@@ -114,10 +121,15 @@ export const invalidateListRoles = async (
|
|||||||
* This endpoint creates a role
|
* This endpoint creates a role
|
||||||
* @summary Create role
|
* @summary Create role
|
||||||
*/
|
*/
|
||||||
export const createRole = (signal?: AbortSignal) => {
|
export const createRole = (
|
||||||
|
roletypesPostableRoleDTO: RoletypesPostableRoleDTO,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
) => {
|
||||||
return GeneratedAPIInstance<CreateRole201>({
|
return GeneratedAPIInstance<CreateRole201>({
|
||||||
url: `/api/v1/roles`,
|
url: `/api/v1/roles`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
data: roletypesPostableRoleDTO,
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -129,13 +141,13 @@ export const getCreateRoleMutationOptions = <
|
|||||||
mutation?: UseMutationOptions<
|
mutation?: UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof createRole>>,
|
Awaited<ReturnType<typeof createRole>>,
|
||||||
TError,
|
TError,
|
||||||
void,
|
{ data: RoletypesPostableRoleDTO },
|
||||||
TContext
|
TContext
|
||||||
>;
|
>;
|
||||||
}): UseMutationOptions<
|
}): UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof createRole>>,
|
Awaited<ReturnType<typeof createRole>>,
|
||||||
TError,
|
TError,
|
||||||
void,
|
{ data: RoletypesPostableRoleDTO },
|
||||||
TContext
|
TContext
|
||||||
> => {
|
> => {
|
||||||
const mutationKey = ['createRole'];
|
const mutationKey = ['createRole'];
|
||||||
@@ -149,9 +161,11 @@ export const getCreateRoleMutationOptions = <
|
|||||||
|
|
||||||
const mutationFn: MutationFunction<
|
const mutationFn: MutationFunction<
|
||||||
Awaited<ReturnType<typeof createRole>>,
|
Awaited<ReturnType<typeof createRole>>,
|
||||||
void
|
{ data: RoletypesPostableRoleDTO }
|
||||||
> = () => {
|
> = (props) => {
|
||||||
return createRole();
|
const { data } = props ?? {};
|
||||||
|
|
||||||
|
return createRole(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { mutationFn, ...mutationOptions };
|
return { mutationFn, ...mutationOptions };
|
||||||
@@ -160,7 +174,7 @@ export const getCreateRoleMutationOptions = <
|
|||||||
export type CreateRoleMutationResult = NonNullable<
|
export type CreateRoleMutationResult = NonNullable<
|
||||||
Awaited<ReturnType<typeof createRole>>
|
Awaited<ReturnType<typeof createRole>>
|
||||||
>;
|
>;
|
||||||
|
export type CreateRoleMutationBody = RoletypesPostableRoleDTO;
|
||||||
export type CreateRoleMutationError = RenderErrorResponseDTO;
|
export type CreateRoleMutationError = RenderErrorResponseDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,13 +187,13 @@ export const useCreateRole = <
|
|||||||
mutation?: UseMutationOptions<
|
mutation?: UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof createRole>>,
|
Awaited<ReturnType<typeof createRole>>,
|
||||||
TError,
|
TError,
|
||||||
void,
|
{ data: RoletypesPostableRoleDTO },
|
||||||
TContext
|
TContext
|
||||||
>;
|
>;
|
||||||
}): UseMutationResult<
|
}): UseMutationResult<
|
||||||
Awaited<ReturnType<typeof createRole>>,
|
Awaited<ReturnType<typeof createRole>>,
|
||||||
TError,
|
TError,
|
||||||
void,
|
{ data: RoletypesPostableRoleDTO },
|
||||||
TContext
|
TContext
|
||||||
> => {
|
> => {
|
||||||
const mutationOptions = getCreateRoleMutationOptions(options);
|
const mutationOptions = getCreateRoleMutationOptions(options);
|
||||||
@@ -358,10 +372,15 @@ export const invalidateGetRole = async (
|
|||||||
* This endpoint patches a role
|
* This endpoint patches a role
|
||||||
* @summary Patch role
|
* @summary Patch role
|
||||||
*/
|
*/
|
||||||
export const patchRole = ({ id }: PatchRolePathParameters) => {
|
export const patchRole = (
|
||||||
|
{ id }: PatchRolePathParameters,
|
||||||
|
roletypesPatchableRoleDTO: RoletypesPatchableRoleDTO,
|
||||||
|
) => {
|
||||||
return GeneratedAPIInstance<string>({
|
return GeneratedAPIInstance<string>({
|
||||||
url: `/api/v1/roles/${id}`,
|
url: `/api/v1/roles/${id}`,
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
data: roletypesPatchableRoleDTO,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -372,13 +391,13 @@ export const getPatchRoleMutationOptions = <
|
|||||||
mutation?: UseMutationOptions<
|
mutation?: UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof patchRole>>,
|
Awaited<ReturnType<typeof patchRole>>,
|
||||||
TError,
|
TError,
|
||||||
{ pathParams: PatchRolePathParameters },
|
{ pathParams: PatchRolePathParameters; data: RoletypesPatchableRoleDTO },
|
||||||
TContext
|
TContext
|
||||||
>;
|
>;
|
||||||
}): UseMutationOptions<
|
}): UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof patchRole>>,
|
Awaited<ReturnType<typeof patchRole>>,
|
||||||
TError,
|
TError,
|
||||||
{ pathParams: PatchRolePathParameters },
|
{ pathParams: PatchRolePathParameters; data: RoletypesPatchableRoleDTO },
|
||||||
TContext
|
TContext
|
||||||
> => {
|
> => {
|
||||||
const mutationKey = ['patchRole'];
|
const mutationKey = ['patchRole'];
|
||||||
@@ -392,11 +411,11 @@ export const getPatchRoleMutationOptions = <
|
|||||||
|
|
||||||
const mutationFn: MutationFunction<
|
const mutationFn: MutationFunction<
|
||||||
Awaited<ReturnType<typeof patchRole>>,
|
Awaited<ReturnType<typeof patchRole>>,
|
||||||
{ pathParams: PatchRolePathParameters }
|
{ pathParams: PatchRolePathParameters; data: RoletypesPatchableRoleDTO }
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { pathParams } = props ?? {};
|
const { pathParams, data } = props ?? {};
|
||||||
|
|
||||||
return patchRole(pathParams);
|
return patchRole(pathParams, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { mutationFn, ...mutationOptions };
|
return { mutationFn, ...mutationOptions };
|
||||||
@@ -405,7 +424,7 @@ export const getPatchRoleMutationOptions = <
|
|||||||
export type PatchRoleMutationResult = NonNullable<
|
export type PatchRoleMutationResult = NonNullable<
|
||||||
Awaited<ReturnType<typeof patchRole>>
|
Awaited<ReturnType<typeof patchRole>>
|
||||||
>;
|
>;
|
||||||
|
export type PatchRoleMutationBody = RoletypesPatchableRoleDTO;
|
||||||
export type PatchRoleMutationError = RenderErrorResponseDTO;
|
export type PatchRoleMutationError = RenderErrorResponseDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -418,16 +437,292 @@ export const usePatchRole = <
|
|||||||
mutation?: UseMutationOptions<
|
mutation?: UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof patchRole>>,
|
Awaited<ReturnType<typeof patchRole>>,
|
||||||
TError,
|
TError,
|
||||||
{ pathParams: PatchRolePathParameters },
|
{ pathParams: PatchRolePathParameters; data: RoletypesPatchableRoleDTO },
|
||||||
TContext
|
TContext
|
||||||
>;
|
>;
|
||||||
}): UseMutationResult<
|
}): UseMutationResult<
|
||||||
Awaited<ReturnType<typeof patchRole>>,
|
Awaited<ReturnType<typeof patchRole>>,
|
||||||
TError,
|
TError,
|
||||||
{ pathParams: PatchRolePathParameters },
|
{ pathParams: PatchRolePathParameters; data: RoletypesPatchableRoleDTO },
|
||||||
TContext
|
TContext
|
||||||
> => {
|
> => {
|
||||||
const mutationOptions = getPatchRoleMutationOptions(options);
|
const mutationOptions = getPatchRoleMutationOptions(options);
|
||||||
|
|
||||||
return useMutation(mutationOptions);
|
return useMutation(mutationOptions);
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Gets all objects connected to the specified role via a given relation type
|
||||||
|
* @summary Get objects for a role by relation
|
||||||
|
*/
|
||||||
|
export const getObjects = (
|
||||||
|
{ id, relation }: GetObjectsPathParameters,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
) => {
|
||||||
|
return GeneratedAPIInstance<GetObjects200>({
|
||||||
|
url: `/api/v1/roles/${id}/relation/${relation}/objects`,
|
||||||
|
method: 'GET',
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetObjectsQueryKey = ({
|
||||||
|
id,
|
||||||
|
relation,
|
||||||
|
}: GetObjectsPathParameters) => {
|
||||||
|
return ['getObjects'] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetObjectsQueryOptions = <
|
||||||
|
TData = Awaited<ReturnType<typeof getObjects>>,
|
||||||
|
TError = RenderErrorResponseDTO
|
||||||
|
>(
|
||||||
|
{ id, relation }: GetObjectsPathParameters,
|
||||||
|
options?: {
|
||||||
|
query?: UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getObjects>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const { query: queryOptions } = options ?? {};
|
||||||
|
|
||||||
|
const queryKey =
|
||||||
|
queryOptions?.queryKey ?? getGetObjectsQueryKey({ id, relation });
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<Awaited<ReturnType<typeof getObjects>>> = ({
|
||||||
|
signal,
|
||||||
|
}) => getObjects({ id, relation }, signal);
|
||||||
|
|
||||||
|
return {
|
||||||
|
queryKey,
|
||||||
|
queryFn,
|
||||||
|
enabled: !!(id && relation),
|
||||||
|
...queryOptions,
|
||||||
|
} as UseQueryOptions<Awaited<ReturnType<typeof getObjects>>, TError, TData> & {
|
||||||
|
queryKey: QueryKey;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetObjectsQueryResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof getObjects>>
|
||||||
|
>;
|
||||||
|
export type GetObjectsQueryError = RenderErrorResponseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get objects for a role by relation
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useGetObjects<
|
||||||
|
TData = Awaited<ReturnType<typeof getObjects>>,
|
||||||
|
TError = RenderErrorResponseDTO
|
||||||
|
>(
|
||||||
|
{ id, relation }: GetObjectsPathParameters,
|
||||||
|
options?: {
|
||||||
|
query?: UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getObjects>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>;
|
||||||
|
},
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||||
|
const queryOptions = getGetObjectsQueryOptions({ id, relation }, options);
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: QueryKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
query.queryKey = queryOptions.queryKey;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get objects for a role by relation
|
||||||
|
*/
|
||||||
|
export const invalidateGetObjects = async (
|
||||||
|
queryClient: QueryClient,
|
||||||
|
{ id, relation }: GetObjectsPathParameters,
|
||||||
|
options?: InvalidateOptions,
|
||||||
|
): Promise<QueryClient> => {
|
||||||
|
await queryClient.invalidateQueries(
|
||||||
|
{ queryKey: getGetObjectsQueryKey({ id, relation }) },
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return queryClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patches the objects connected to the specified role via a given relation type
|
||||||
|
* @summary Patch objects for a role by relation
|
||||||
|
*/
|
||||||
|
export const patchObjects = (
|
||||||
|
{ id, relation }: PatchObjectsPathParameters,
|
||||||
|
roletypesPatchableObjectsDTO: RoletypesPatchableObjectsDTO,
|
||||||
|
) => {
|
||||||
|
return GeneratedAPIInstance<string>({
|
||||||
|
url: `/api/v1/roles/${id}/relation/${relation}/objects`,
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
data: roletypesPatchableObjectsDTO,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPatchObjectsMutationOptions = <
|
||||||
|
TError = RenderErrorResponseDTO,
|
||||||
|
TContext = unknown
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof patchObjects>>,
|
||||||
|
TError,
|
||||||
|
{
|
||||||
|
pathParams: PatchObjectsPathParameters;
|
||||||
|
data: RoletypesPatchableObjectsDTO;
|
||||||
|
},
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof patchObjects>>,
|
||||||
|
TError,
|
||||||
|
{ pathParams: PatchObjectsPathParameters; data: RoletypesPatchableObjectsDTO },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationKey = ['patchObjects'];
|
||||||
|
const { mutation: mutationOptions } = options
|
||||||
|
? options.mutation &&
|
||||||
|
'mutationKey' in options.mutation &&
|
||||||
|
options.mutation.mutationKey
|
||||||
|
? options
|
||||||
|
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||||
|
: { mutation: { mutationKey } };
|
||||||
|
|
||||||
|
const mutationFn: MutationFunction<
|
||||||
|
Awaited<ReturnType<typeof patchObjects>>,
|
||||||
|
{ pathParams: PatchObjectsPathParameters; data: RoletypesPatchableObjectsDTO }
|
||||||
|
> = (props) => {
|
||||||
|
const { pathParams, data } = props ?? {};
|
||||||
|
|
||||||
|
return patchObjects(pathParams, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PatchObjectsMutationResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof patchObjects>>
|
||||||
|
>;
|
||||||
|
export type PatchObjectsMutationBody = RoletypesPatchableObjectsDTO;
|
||||||
|
export type PatchObjectsMutationError = RenderErrorResponseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Patch objects for a role by relation
|
||||||
|
*/
|
||||||
|
export const usePatchObjects = <
|
||||||
|
TError = RenderErrorResponseDTO,
|
||||||
|
TContext = unknown
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof patchObjects>>,
|
||||||
|
TError,
|
||||||
|
{
|
||||||
|
pathParams: PatchObjectsPathParameters;
|
||||||
|
data: RoletypesPatchableObjectsDTO;
|
||||||
|
},
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof patchObjects>>,
|
||||||
|
TError,
|
||||||
|
{ pathParams: PatchObjectsPathParameters; data: RoletypesPatchableObjectsDTO },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationOptions = getPatchObjectsMutationOptions(options);
|
||||||
|
|
||||||
|
return useMutation(mutationOptions);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Gets all the available resources for role assignment
|
||||||
|
* @summary Get resources
|
||||||
|
*/
|
||||||
|
export const getResources = (signal?: AbortSignal) => {
|
||||||
|
return GeneratedAPIInstance<GetResources200>({
|
||||||
|
url: `/api/v1/roles/resources`,
|
||||||
|
method: 'GET',
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetResourcesQueryKey = () => {
|
||||||
|
return ['getResources'] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetResourcesQueryOptions = <
|
||||||
|
TData = Awaited<ReturnType<typeof getResources>>,
|
||||||
|
TError = RenderErrorResponseDTO
|
||||||
|
>(options?: {
|
||||||
|
query?: UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getResources>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>;
|
||||||
|
}) => {
|
||||||
|
const { query: queryOptions } = options ?? {};
|
||||||
|
|
||||||
|
const queryKey = queryOptions?.queryKey ?? getGetResourcesQueryKey();
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<Awaited<ReturnType<typeof getResources>>> = ({
|
||||||
|
signal,
|
||||||
|
}) => getResources(signal);
|
||||||
|
|
||||||
|
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getResources>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
> & { queryKey: QueryKey };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetResourcesQueryResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof getResources>>
|
||||||
|
>;
|
||||||
|
export type GetResourcesQueryError = RenderErrorResponseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get resources
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useGetResources<
|
||||||
|
TData = Awaited<ReturnType<typeof getResources>>,
|
||||||
|
TError = RenderErrorResponseDTO
|
||||||
|
>(options?: {
|
||||||
|
query?: UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getResources>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>;
|
||||||
|
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||||
|
const queryOptions = getGetResourcesQueryOptions(options);
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: QueryKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
query.queryKey = queryOptions.queryKey;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get resources
|
||||||
|
*/
|
||||||
|
export const invalidateGetResources = async (
|
||||||
|
queryClient: QueryClient,
|
||||||
|
options?: InvalidateOptions,
|
||||||
|
): Promise<QueryClient> => {
|
||||||
|
await queryClient.invalidateQueries(
|
||||||
|
{ queryKey: getGetResourcesQueryKey() },
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return queryClient;
|
||||||
|
};
|
||||||
|
|||||||
@@ -127,6 +127,18 @@ export interface AuthtypesGettableTokenDTO {
|
|||||||
tokenType?: string;
|
tokenType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthtypesGettableTransactionDTO {
|
||||||
|
/**
|
||||||
|
* @type boolean
|
||||||
|
*/
|
||||||
|
authorized: boolean;
|
||||||
|
object: AuthtypesObjectDTO;
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
relation: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type AuthtypesGoogleConfigDTODomainToAdminEmail = {
|
export type AuthtypesGoogleConfigDTODomainToAdminEmail = {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
@@ -170,6 +182,10 @@ export interface AuthtypesGoogleConfigDTO {
|
|||||||
serviceAccountJson?: string;
|
serviceAccountJson?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthtypesNameDTO {
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AuthtypesOIDCConfigDTO {
|
export interface AuthtypesOIDCConfigDTO {
|
||||||
claimMapping?: AuthtypesAttributeMappingDTO;
|
claimMapping?: AuthtypesAttributeMappingDTO;
|
||||||
/**
|
/**
|
||||||
@@ -198,6 +214,11 @@ export interface AuthtypesOIDCConfigDTO {
|
|||||||
issuerAlias?: string;
|
issuerAlias?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthtypesObjectDTO {
|
||||||
|
resource: AuthtypesResourceDTO;
|
||||||
|
selector: AuthtypesSelectorDTO;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AuthtypesOrgSessionContextDTO {
|
export interface AuthtypesOrgSessionContextDTO {
|
||||||
authNSupport?: AuthtypesAuthNSupportDTO;
|
authNSupport?: AuthtypesAuthNSupportDTO;
|
||||||
/**
|
/**
|
||||||
@@ -248,6 +269,14 @@ export interface AuthtypesPostableRotateTokenDTO {
|
|||||||
refreshToken?: string;
|
refreshToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthtypesResourceDTO {
|
||||||
|
name: AuthtypesNameDTO;
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @nullable
|
* @nullable
|
||||||
*/
|
*/
|
||||||
@@ -291,6 +320,10 @@ export interface AuthtypesSamlConfigDTO {
|
|||||||
samlIdp?: string;
|
samlIdp?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthtypesSelectorDTO {
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AuthtypesSessionContextDTO {
|
export interface AuthtypesSessionContextDTO {
|
||||||
/**
|
/**
|
||||||
* @type boolean
|
* @type boolean
|
||||||
@@ -303,6 +336,18 @@ export interface AuthtypesSessionContextDTO {
|
|||||||
orgs?: AuthtypesOrgSessionContextDTO[] | null;
|
orgs?: AuthtypesOrgSessionContextDTO[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthtypesTransactionDTO {
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
id?: string;
|
||||||
|
object: AuthtypesObjectDTO;
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
relation: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AuthtypesUpdateableAuthDomainDTO {
|
export interface AuthtypesUpdateableAuthDomainDTO {
|
||||||
config?: AuthtypesAuthDomainConfigDTO;
|
config?: AuthtypesAuthDomainConfigDTO;
|
||||||
}
|
}
|
||||||
@@ -1947,6 +1992,57 @@ export interface RenderErrorResponseDTO {
|
|||||||
status?: string;
|
status?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @nullable
|
||||||
|
*/
|
||||||
|
export type RoletypesGettableResourcesDTORelations = {
|
||||||
|
[key: string]: string[];
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
export interface RoletypesGettableResourcesDTO {
|
||||||
|
/**
|
||||||
|
* @type object
|
||||||
|
* @nullable true
|
||||||
|
*/
|
||||||
|
relations: RoletypesGettableResourcesDTORelations;
|
||||||
|
/**
|
||||||
|
* @type array
|
||||||
|
* @nullable true
|
||||||
|
*/
|
||||||
|
resources: AuthtypesResourceDTO[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoletypesPatchableObjectsDTO {
|
||||||
|
/**
|
||||||
|
* @type array
|
||||||
|
* @nullable true
|
||||||
|
*/
|
||||||
|
additions: AuthtypesObjectDTO[] | null;
|
||||||
|
/**
|
||||||
|
* @type array
|
||||||
|
* @nullable true
|
||||||
|
*/
|
||||||
|
deletions: AuthtypesObjectDTO[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoletypesPatchableRoleDTO {
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoletypesPostableRoleDTO {
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
description?: string;
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RoletypesRoleDTO {
|
export interface RoletypesRoleDTO {
|
||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
@@ -1956,7 +2052,7 @@ export interface RoletypesRoleDTO {
|
|||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
description?: string;
|
description: string;
|
||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
@@ -1964,15 +2060,15 @@ export interface RoletypesRoleDTO {
|
|||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name: string;
|
||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
orgId?: string;
|
orgId: string;
|
||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
type?: string;
|
type: string;
|
||||||
/**
|
/**
|
||||||
* @type string
|
* @type string
|
||||||
* @format date-time
|
* @format date-time
|
||||||
@@ -2499,6 +2595,17 @@ export interface ZeustypesPostableProfileDTO {
|
|||||||
where_did_you_discover_signoz: string;
|
where_did_you_discover_signoz: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AuthzCheck200 = {
|
||||||
|
/**
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
data?: AuthtypesGettableTransactionDTO[];
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ChangePasswordPathParameters = {
|
export type ChangePasswordPathParameters = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
@@ -2902,6 +3009,33 @@ export type GetRole200 = {
|
|||||||
export type PatchRolePathParameters = {
|
export type PatchRolePathParameters = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
export type GetObjectsPathParameters = {
|
||||||
|
id: string;
|
||||||
|
relation: string;
|
||||||
|
};
|
||||||
|
export type GetObjects200 = {
|
||||||
|
/**
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
data?: AuthtypesObjectDTO[];
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PatchObjectsPathParameters = {
|
||||||
|
id: string;
|
||||||
|
relation: string;
|
||||||
|
};
|
||||||
|
export type GetResources200 = {
|
||||||
|
data?: RoletypesGettableResourcesDTO;
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ListUsers200 = {
|
export type ListUsers200 = {
|
||||||
/**
|
/**
|
||||||
* @type array
|
* @type array
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -103,9 +103,10 @@ function K8sClustersList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -105,9 +105,10 @@ function K8sDaemonSetsList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -106,9 +106,10 @@ function K8sDeploymentsList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -101,9 +101,10 @@ function K8sJobsList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe
|
|||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { safeParseJSON } from './commonUtils';
|
||||||
import { INFRA_MONITORING_K8S_PARAMS_KEYS, K8sCategory } from './constants';
|
import { INFRA_MONITORING_K8S_PARAMS_KEYS, K8sCategory } from './constants';
|
||||||
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
|
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
|
||||||
import { IEntityColumn } from './utils';
|
import { IEntityColumn } from './utils';
|
||||||
@@ -58,9 +59,10 @@ function K8sHeader({
|
|||||||
const urlFilters = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS);
|
const urlFilters = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS);
|
||||||
let { filters } = currentQuery.builder.queryData[0];
|
let { filters } = currentQuery.builder.queryData[0];
|
||||||
if (urlFilters) {
|
if (urlFilters) {
|
||||||
const decoded = decodeURIComponent(urlFilters);
|
const parsed = safeParseJSON<IBuilderQuery['filters']>(urlFilters);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
filters = parsed;
|
filters = parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...currentQuery,
|
...currentQuery,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -104,9 +104,10 @@ function K8sNamespacesList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -99,9 +99,10 @@ function K8sNodesList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -92,9 +92,10 @@ function K8sPodsList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -105,9 +105,10 @@ function K8sStatefulSetsList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import { FeatureKeys } from '../../../constants/features';
|
import { FeatureKeys } from '../../../constants/features';
|
||||||
import { useAppContext } from '../../../providers/App/App';
|
import { useAppContext } from '../../../providers/App/App';
|
||||||
import { getOrderByFromParams } from '../commonUtils';
|
import { getOrderByFromParams, safeParseJSON } from '../commonUtils';
|
||||||
import {
|
import {
|
||||||
GetK8sEntityToAggregateAttribute,
|
GetK8sEntityToAggregateAttribute,
|
||||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||||
@@ -105,9 +105,10 @@ function K8sVolumesList({
|
|||||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||||
if (groupBy) {
|
if (groupBy) {
|
||||||
const decoded = decodeURIComponent(groupBy);
|
const parsed = safeParseJSON<IBuilderQuery['groupBy']>(groupBy);
|
||||||
const parsed = JSON.parse(decoded);
|
if (parsed) {
|
||||||
return parsed as IBuilderQuery['groupBy'];
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Table, Tooltip, Typography } from 'antd';
|
import { Table, Tooltip, Typography } from 'antd';
|
||||||
import { Progress } from 'antd/lib';
|
import { Progress } from 'antd/lib';
|
||||||
@@ -260,6 +261,19 @@ export const filterDuplicateFilters = (
|
|||||||
return uniqueFilters;
|
return uniqueFilters;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const safeParseJSON = <T,>(value: string): T | null => {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(value) as T;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing JSON from URL parameter:', e);
|
||||||
|
// TODO: Should we capture this error in Sentry?
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getOrderByFromParams = (
|
export const getOrderByFromParams = (
|
||||||
searchParams: URLSearchParams,
|
searchParams: URLSearchParams,
|
||||||
returnNullAsDefault = false,
|
returnNullAsDefault = false,
|
||||||
@@ -271,9 +285,12 @@ export const getOrderByFromParams = (
|
|||||||
INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY,
|
INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY,
|
||||||
);
|
);
|
||||||
if (orderByFromParams) {
|
if (orderByFromParams) {
|
||||||
const decoded = decodeURIComponent(orderByFromParams);
|
const parsed = safeParseJSON<{ columnName: string; order: 'asc' | 'desc' }>(
|
||||||
const parsed = JSON.parse(decoded);
|
orderByFromParams,
|
||||||
return parsed as { columnName: string; order: 'asc' | 'desc' };
|
);
|
||||||
|
if (parsed) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (returnNullAsDefault) {
|
if (returnNullAsDefault) {
|
||||||
return null;
|
return null;
|
||||||
@@ -287,13 +304,7 @@ export const getFiltersFromParams = (
|
|||||||
): IBuilderQuery['filters'] | null => {
|
): IBuilderQuery['filters'] | null => {
|
||||||
const filtersFromParams = searchParams.get(queryKey);
|
const filtersFromParams = searchParams.get(queryKey);
|
||||||
if (filtersFromParams) {
|
if (filtersFromParams) {
|
||||||
try {
|
return safeParseJSON<IBuilderQuery['filters']>(filtersFromParams);
|
||||||
const decoded = decodeURIComponent(filtersFromParams);
|
|
||||||
const parsed = JSON.parse(decoded);
|
|
||||||
return parsed as IBuilderQuery['filters'];
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,10 +88,7 @@ function InviteTeamMembers({
|
|||||||
setTeamMembersToInvite((prev) => (prev || []).filter((m) => m.id !== id));
|
setTeamMembersToInvite((prev) => (prev || []).filter((m) => m.id !== id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMemberTouched = (member: TeamMember): boolean =>
|
// Validation function to check all users
|
||||||
member.email.trim() !== '' ||
|
|
||||||
Boolean(member.role && member.role.trim() !== '');
|
|
||||||
|
|
||||||
const validateAllUsers = (): boolean => {
|
const validateAllUsers = (): boolean => {
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
let hasEmailErrors = false;
|
let hasEmailErrors = false;
|
||||||
@@ -99,9 +96,7 @@ function InviteTeamMembers({
|
|||||||
|
|
||||||
const updatedEmailValidity: Record<string, boolean> = {};
|
const updatedEmailValidity: Record<string, boolean> = {};
|
||||||
|
|
||||||
const touchedMembers = teamMembersToInvite?.filter(isMemberTouched) ?? [];
|
teamMembersToInvite?.forEach((member) => {
|
||||||
|
|
||||||
touchedMembers?.forEach((member) => {
|
|
||||||
const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(member.email);
|
const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(member.email);
|
||||||
const roleValid = Boolean(member.role && member.role.trim() !== '');
|
const roleValid = Boolean(member.role && member.role.trim() !== '');
|
||||||
|
|
||||||
@@ -155,12 +150,12 @@ function InviteTeamMembers({
|
|||||||
|
|
||||||
const handleNext = (): void => {
|
const handleNext = (): void => {
|
||||||
if (validateAllUsers()) {
|
if (validateAllUsers()) {
|
||||||
setTeamMembers(teamMembersToInvite?.filter(isMemberTouched) ?? []);
|
setTeamMembers(teamMembersToInvite || []);
|
||||||
setHasInvalidEmails(false);
|
setHasInvalidEmails(false);
|
||||||
setHasInvalidRoles(false);
|
setHasInvalidRoles(false);
|
||||||
setInviteError(null);
|
setInviteError(null);
|
||||||
sendInvites({
|
sendInvites({
|
||||||
invites: teamMembersToInvite?.filter(isMemberTouched) ?? [],
|
invites: teamMembersToInvite || [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -235,12 +230,12 @@ function InviteTeamMembers({
|
|||||||
|
|
||||||
const getValidationErrorMessage = (): string => {
|
const getValidationErrorMessage = (): string => {
|
||||||
if (hasInvalidEmails && hasInvalidRoles) {
|
if (hasInvalidEmails && hasInvalidRoles) {
|
||||||
return 'Please enter valid emails and select roles for team members';
|
return 'Please enter valid emails and select roles for all team members';
|
||||||
}
|
}
|
||||||
if (hasInvalidEmails) {
|
if (hasInvalidEmails) {
|
||||||
return 'Please enter valid emails for team members';
|
return 'Please enter valid emails for all team members';
|
||||||
}
|
}
|
||||||
return 'Please select roles for team members';
|
return 'Please select roles for all team members';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDoLater = (): void => {
|
const handleDoLater = (): void => {
|
||||||
|
|||||||
@@ -1,485 +0,0 @@
|
|||||||
import { rest, server } from 'mocks-server/server';
|
|
||||||
import {
|
|
||||||
fireEvent,
|
|
||||||
render,
|
|
||||||
screen,
|
|
||||||
userEvent,
|
|
||||||
waitFor,
|
|
||||||
} from 'tests/test-utils';
|
|
||||||
|
|
||||||
import InviteTeamMembers from '../InviteTeamMembers';
|
|
||||||
|
|
||||||
jest.mock('api/common/logEvent', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mockNotificationSuccess = jest.fn() as jest.MockedFunction<
|
|
||||||
(args: { message: string }) => void
|
|
||||||
>;
|
|
||||||
const mockNotificationError = jest.fn() as jest.MockedFunction<
|
|
||||||
(args: { message: string }) => void
|
|
||||||
>;
|
|
||||||
|
|
||||||
jest.mock('hooks/useNotifications', () => ({
|
|
||||||
useNotifications: (): any => ({
|
|
||||||
notifications: {
|
|
||||||
success: mockNotificationSuccess,
|
|
||||||
error: mockNotificationError,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const INVITE_USERS_ENDPOINT = '*/api/v1/invite/bulk';
|
|
||||||
|
|
||||||
interface TeamMember {
|
|
||||||
email: string;
|
|
||||||
role: string;
|
|
||||||
name: string;
|
|
||||||
frontendBaseUrl: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InviteRequestBody {
|
|
||||||
invites: { email: string; role: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RenderProps {
|
|
||||||
isLoading?: boolean;
|
|
||||||
teamMembers?: TeamMember[] | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockOnNext = jest.fn() as jest.MockedFunction<() => void>;
|
|
||||||
const mockSetTeamMembers = jest.fn() as jest.MockedFunction<
|
|
||||||
(members: TeamMember[]) => void
|
|
||||||
>;
|
|
||||||
|
|
||||||
function renderComponent({
|
|
||||||
isLoading = false,
|
|
||||||
teamMembers = null,
|
|
||||||
}: RenderProps = {}): ReturnType<typeof render> {
|
|
||||||
return render(
|
|
||||||
<InviteTeamMembers
|
|
||||||
isLoading={isLoading}
|
|
||||||
teamMembers={teamMembers}
|
|
||||||
setTeamMembers={mockSetTeamMembers}
|
|
||||||
onNext={mockOnNext}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectRole(
|
|
||||||
user: ReturnType<typeof userEvent.setup>,
|
|
||||||
selectIndex: number,
|
|
||||||
optionLabel: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const placeholders = screen.getAllByText(/select roles/i);
|
|
||||||
await user.click(placeholders[selectIndex]);
|
|
||||||
const optionContent = await screen.findByText(optionLabel);
|
|
||||||
fireEvent.click(optionContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('InviteTeamMembers', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
server.use(
|
|
||||||
rest.post(INVITE_USERS_ENDPOINT, (_, res, ctx) =>
|
|
||||||
res(ctx.status(200), ctx.json({ status: 'success' })),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.useRealTimers();
|
|
||||||
server.resetHandlers();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Initial rendering', () => {
|
|
||||||
it('renders the page header, column labels, default rows, and action buttons', () => {
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getByRole('heading', { name: /invite your team/i }),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByText(/signoz is a lot more useful with collaborators/i),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getAllByPlaceholderText(/e\.g\. john@signoz\.io/i),
|
|
||||||
).toHaveLength(3);
|
|
||||||
expect(screen.getByText('Email address')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Roles')).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByRole('button', { name: /complete/i }),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByRole('button', { name: /i'll do this later/i }),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('disables both action buttons while isLoading is true', () => {
|
|
||||||
renderComponent({ isLoading: true });
|
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: /complete/i })).toBeDisabled();
|
|
||||||
expect(
|
|
||||||
screen.getByRole('button', { name: /i'll do this later/i }),
|
|
||||||
).toBeDisabled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Row management', () => {
|
|
||||||
it('adds a new empty row when "Add another" is clicked', async () => {
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getAllByPlaceholderText(/e\.g\. john@signoz\.io/i),
|
|
||||||
).toHaveLength(3);
|
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /add another/i }));
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getAllByPlaceholderText(/e\.g\. john@signoz\.io/i),
|
|
||||||
).toHaveLength(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes the correct row when its trash icon is clicked', async () => {
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const emailInputs = screen.getAllByPlaceholderText(
|
|
||||||
/e\.g\. john@signoz\.io/i,
|
|
||||||
);
|
|
||||||
await user.type(emailInputs[0], 'first@example.com');
|
|
||||||
await screen.findByDisplayValue('first@example.com');
|
|
||||||
|
|
||||||
await user.click(
|
|
||||||
screen.getAllByRole('button', { name: /remove team member/i })[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.queryByDisplayValue('first@example.com'),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getAllByPlaceholderText(/e\.g\. john@signoz\.io/i),
|
|
||||||
).toHaveLength(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('hides remove buttons when only one row remains', async () => {
|
|
||||||
renderComponent();
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
|
|
||||||
let removeButtons = screen.getAllByRole('button', {
|
|
||||||
name: /remove team member/i,
|
|
||||||
});
|
|
||||||
while (removeButtons.length > 0) {
|
|
||||||
await user.click(removeButtons[0]);
|
|
||||||
removeButtons = screen.queryAllByRole('button', {
|
|
||||||
name: /remove team member/i,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.queryByRole('button', { name: /remove team member/i }),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Inline email validation', () => {
|
|
||||||
it('shows an inline error after typing an invalid email and clears it when a valid email is entered', async () => {
|
|
||||||
jest.useFakeTimers();
|
|
||||||
const user = userEvent.setup({
|
|
||||||
advanceTimers: (ms) => jest.advanceTimersByTime(ms),
|
|
||||||
});
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const [firstInput] = screen.getAllByPlaceholderText(
|
|
||||||
/e\.g\. john@signoz\.io/i,
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.type(firstInput, 'not-an-email');
|
|
||||||
jest.advanceTimersByTime(600);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByText(/invalid email address/i)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
await user.clear(firstInput);
|
|
||||||
await user.type(firstInput, 'good@example.com');
|
|
||||||
jest.advanceTimersByTime(600);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/invalid email address/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not show an inline error when the field is cleared back to empty', async () => {
|
|
||||||
jest.useFakeTimers();
|
|
||||||
const user = userEvent.setup({
|
|
||||||
advanceTimers: (ms) => jest.advanceTimersByTime(ms),
|
|
||||||
});
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const [firstInput] = screen.getAllByPlaceholderText(
|
|
||||||
/e\.g\. john@signoz\.io/i,
|
|
||||||
);
|
|
||||||
await user.type(firstInput, 'a');
|
|
||||||
await user.clear(firstInput);
|
|
||||||
jest.advanceTimersByTime(600);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/invalid email address/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Validation callout on Complete', () => {
|
|
||||||
it('shows the correct callout message for each combination of email/role validity', async () => {
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const removeButtons = screen.getAllByRole('button', {
|
|
||||||
name: /remove team member/i,
|
|
||||||
});
|
|
||||||
await user.click(removeButtons[0]);
|
|
||||||
await user.click(
|
|
||||||
screen.getAllByRole('button', { name: /remove team member/i })[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [firstInput] = screen.getAllByPlaceholderText(
|
|
||||||
/e\.g\. john@signoz\.io/i,
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.type(firstInput, 'bad-email');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.getByText(
|
|
||||||
/please enter valid emails and select roles for team members/i,
|
|
||||||
),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails for team members/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please select roles for team members/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
await selectRole(user, 0, 'Viewer');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.getByText(/please enter valid emails for team members/i),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please select roles for team members/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails and select roles/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
await user.clear(firstInput);
|
|
||||||
await user.type(firstInput, 'valid@example.com');
|
|
||||||
await user.click(screen.getByRole('button', { name: /add another/i }));
|
|
||||||
const allInputs = screen.getAllByPlaceholderText(/e\.g\. john@signoz\.io/i);
|
|
||||||
await user.type(allInputs[1], 'norole@example.com');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.getByText(/please select roles for team members/i),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails for team members/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails and select roles/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('treats whitespace as untouched, clears the callout on fix-and-resubmit, and clears role error on role select', async () => {
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const removeButtons = screen.getAllByRole('button', {
|
|
||||||
name: /remove team member/i,
|
|
||||||
});
|
|
||||||
await user.click(removeButtons[0]);
|
|
||||||
await user.click(
|
|
||||||
screen.getAllByRole('button', { name: /remove team member/i })[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [firstInput] = screen.getAllByPlaceholderText(
|
|
||||||
/e\.g\. john@signoz\.io/i,
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.type(firstInput, ' ');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(screen.queryByText(/please select roles/i)).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
await user.clear(firstInput);
|
|
||||||
await user.type(firstInput, 'bad-email');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.getByText(
|
|
||||||
/please enter valid emails and select roles for team members/i,
|
|
||||||
),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails for team members/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please select roles for team members/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
await user.clear(firstInput);
|
|
||||||
await user.type(firstInput, 'good@example.com');
|
|
||||||
await selectRole(user, 0, 'Admin');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails and select roles/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails for team members/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please select roles for team members/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => expect(mockOnNext).toHaveBeenCalledTimes(1), {
|
|
||||||
timeout: 1200,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not show a validation callout when all rows are untouched (empty)', async () => {
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.queryByText(/please enter valid emails/i),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(screen.queryByText(/please select roles/i)).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(
|
|
||||||
() => {
|
|
||||||
expect(mockOnNext).toHaveBeenCalled();
|
|
||||||
},
|
|
||||||
{ timeout: 1200 },
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('API integration', () => {
|
|
||||||
it('only sends touched (non-empty) rows — empty rows are excluded from the invite payload', async () => {
|
|
||||||
let capturedBody: InviteRequestBody | null = null;
|
|
||||||
|
|
||||||
server.use(
|
|
||||||
rest.post(INVITE_USERS_ENDPOINT, async (req, res, ctx) => {
|
|
||||||
capturedBody = await req.json<InviteRequestBody>();
|
|
||||||
return res(ctx.status(200), ctx.json({ status: 'success' }));
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const [firstInput] = screen.getAllByPlaceholderText(
|
|
||||||
/e\.g\. john@signoz\.io/i,
|
|
||||||
);
|
|
||||||
await user.type(firstInput, 'only@example.com');
|
|
||||||
await selectRole(user, 0, 'Admin');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(capturedBody).not.toBeNull();
|
|
||||||
expect(capturedBody?.invites).toHaveLength(1);
|
|
||||||
expect(capturedBody?.invites[0]).toMatchObject({
|
|
||||||
email: 'only@example.com',
|
|
||||||
role: 'ADMIN',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await waitFor(() => expect(mockOnNext).toHaveBeenCalled(), {
|
|
||||||
timeout: 1200,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls the invite API, shows a success notification, and calls onNext after the 1 s delay', async () => {
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const [firstInput] = screen.getAllByPlaceholderText(
|
|
||||||
/e\.g\. john@signoz\.io/i,
|
|
||||||
);
|
|
||||||
await user.type(firstInput, 'alice@example.com');
|
|
||||||
await selectRole(user, 0, 'Admin');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockNotificationSuccess).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ message: 'Invites sent successfully!' }),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(
|
|
||||||
() => {
|
|
||||||
expect(mockOnNext).toHaveBeenCalledTimes(1);
|
|
||||||
},
|
|
||||||
{ timeout: 1200 },
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders an API error container when the invite request fails', async () => {
|
|
||||||
server.use(
|
|
||||||
rest.post(INVITE_USERS_ENDPOINT, (_, res, ctx) =>
|
|
||||||
res(
|
|
||||||
ctx.status(500),
|
|
||||||
ctx.json({
|
|
||||||
errors: [{ code: 'INTERNAL_ERROR', msg: 'Something went wrong' }],
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const [firstInput] = screen.getAllByPlaceholderText(
|
|
||||||
/e\.g\. john@signoz\.io/i,
|
|
||||||
);
|
|
||||||
await user.type(firstInput, 'fail@example.com');
|
|
||||||
await selectRole(user, 0, 'Viewer');
|
|
||||||
await user.click(screen.getByRole('button', { name: /complete/i }));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(document.querySelector('.auth-error-container')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
await user.type(firstInput, 'x');
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
document.querySelector('.auth-error-container'),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
30
pkg/apiserver/signozapiserver/authz.go
Normal file
30
pkg/apiserver/signozapiserver/authz.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package signozapiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (provider *provider) addAuthzRoutes(router *mux.Router) error {
|
||||||
|
if err := router.Handle("/api/v1/authz/check", handler.New(provider.authzHandler.Check, handler.OpenAPIDef{
|
||||||
|
ID: "AuthzCheck",
|
||||||
|
Tags: []string{"authz"},
|
||||||
|
Summary: "Check permissions",
|
||||||
|
Description: "Checks if the authenticated user has permissions for given transactions",
|
||||||
|
Request: make([]*authtypes.Transaction, 0),
|
||||||
|
RequestContentType: "",
|
||||||
|
Response: make([]*authtypes.GettableTransaction, 0),
|
||||||
|
ResponseContentType: "application/json",
|
||||||
|
SuccessStatusCode: http.StatusOK,
|
||||||
|
ErrorStatusCodes: []int{},
|
||||||
|
Deprecated: false,
|
||||||
|
SecuritySchemes: nil,
|
||||||
|
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -207,6 +207,10 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := provider.addAuthzRoutes(router); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := provider.addFieldsRoutes(router); err != nil {
|
if err := provider.addFieldsRoutes(router); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
@@ -15,12 +16,12 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
|||||||
Tags: []string{"role"},
|
Tags: []string{"role"},
|
||||||
Summary: "Create role",
|
Summary: "Create role",
|
||||||
Description: "This endpoint creates a role",
|
Description: "This endpoint creates a role",
|
||||||
Request: nil,
|
Request: new(roletypes.PostableRole),
|
||||||
RequestContentType: "",
|
RequestContentType: "",
|
||||||
Response: new(types.Identifiable),
|
Response: new(types.Identifiable),
|
||||||
ResponseContentType: "application/json",
|
ResponseContentType: "application/json",
|
||||||
SuccessStatusCode: http.StatusCreated,
|
SuccessStatusCode: http.StatusCreated,
|
||||||
ErrorStatusCodes: []int{},
|
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||||
Deprecated: false,
|
Deprecated: false,
|
||||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||||
@@ -44,6 +45,23 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := router.Handle("/api/v1/roles/resources", handler.New(provider.authZ.AdminAccess(provider.authzHandler.GetResources), handler.OpenAPIDef{
|
||||||
|
ID: "GetResources",
|
||||||
|
Tags: []string{"role"},
|
||||||
|
Summary: "Get resources",
|
||||||
|
Description: "Gets all the available resources for role assignment",
|
||||||
|
Request: nil,
|
||||||
|
RequestContentType: "",
|
||||||
|
Response: new(roletypes.GettableResources),
|
||||||
|
ResponseContentType: "application/json",
|
||||||
|
SuccessStatusCode: http.StatusOK,
|
||||||
|
ErrorStatusCodes: []int{},
|
||||||
|
Deprecated: false,
|
||||||
|
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||||
|
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.authzHandler.Get), handler.OpenAPIDef{
|
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.authzHandler.Get), handler.OpenAPIDef{
|
||||||
ID: "GetRole",
|
ID: "GetRole",
|
||||||
Tags: []string{"role"},
|
Tags: []string{"role"},
|
||||||
@@ -61,17 +79,51 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := router.Handle("/api/v1/roles/{id}/relation/{relation}/objects", handler.New(provider.authZ.AdminAccess(provider.authzHandler.GetObjects), handler.OpenAPIDef{
|
||||||
|
ID: "GetObjects",
|
||||||
|
Tags: []string{"role"},
|
||||||
|
Summary: "Get objects for a role by relation",
|
||||||
|
Description: "Gets all objects connected to the specified role via a given relation type",
|
||||||
|
Request: nil,
|
||||||
|
RequestContentType: "",
|
||||||
|
Response: make([]*authtypes.Object, 0),
|
||||||
|
ResponseContentType: "application/json",
|
||||||
|
SuccessStatusCode: http.StatusOK,
|
||||||
|
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||||
|
Deprecated: false,
|
||||||
|
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||||
|
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.authzHandler.Patch), handler.OpenAPIDef{
|
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.authzHandler.Patch), handler.OpenAPIDef{
|
||||||
ID: "PatchRole",
|
ID: "PatchRole",
|
||||||
Tags: []string{"role"},
|
Tags: []string{"role"},
|
||||||
Summary: "Patch role",
|
Summary: "Patch role",
|
||||||
Description: "This endpoint patches a role",
|
Description: "This endpoint patches a role",
|
||||||
Request: nil,
|
Request: new(roletypes.PatchableRole),
|
||||||
RequestContentType: "",
|
RequestContentType: "",
|
||||||
Response: nil,
|
Response: nil,
|
||||||
ResponseContentType: "application/json",
|
ResponseContentType: "application/json",
|
||||||
SuccessStatusCode: http.StatusNoContent,
|
SuccessStatusCode: http.StatusNoContent,
|
||||||
ErrorStatusCodes: []int{},
|
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||||
|
Deprecated: false,
|
||||||
|
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||||
|
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := router.Handle("/api/v1/roles/{id}/relation/{relation}/objects", handler.New(provider.authZ.AdminAccess(provider.authzHandler.PatchObjects), handler.OpenAPIDef{
|
||||||
|
ID: "PatchObjects",
|
||||||
|
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(roletypes.PatchableObjects),
|
||||||
|
RequestContentType: "",
|
||||||
|
Response: nil,
|
||||||
|
ResponseContentType: "application/json",
|
||||||
|
SuccessStatusCode: http.StatusNoContent,
|
||||||
|
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||||
Deprecated: false,
|
Deprecated: false,
|
||||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||||
@@ -88,7 +140,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
|||||||
Response: nil,
|
Response: nil,
|
||||||
ResponseContentType: "application/json",
|
ResponseContentType: "application/json",
|
||||||
SuccessStatusCode: http.StatusNoContent,
|
SuccessStatusCode: http.StatusNoContent,
|
||||||
ErrorStatusCodes: []int{},
|
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||||
Deprecated: false,
|
Deprecated: false,
|
||||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||||
|
|||||||
@@ -14,17 +14,14 @@ import (
|
|||||||
type AuthZ interface {
|
type AuthZ interface {
|
||||||
factory.Service
|
factory.Service
|
||||||
|
|
||||||
// Check returns error when the upstream authorization server is unavailable or the subject (s) doesn't have relation (r) on object (o).
|
|
||||||
Check(context.Context, *openfgav1.TupleKey) error
|
|
||||||
|
|
||||||
// CheckWithTupleCreation takes upon the responsibility for generating the tuples alongside everything Check does.
|
// 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, authtypes.Typeable, []authtypes.Selector, []authtypes.Selector) error
|
||||||
|
|
||||||
// CheckWithTupleCreationWithoutClaims checks permissions for anonymous users.
|
// 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, authtypes.Typeable, []authtypes.Selector, []authtypes.Selector) error
|
||||||
|
|
||||||
// Batch Check returns error when the upstream authorization server is unavailable or for all the tuples of subject (s) doesn't have relation (r) on object (o).
|
// BatchCheck accepts a map of ID → tuple and returns a map of ID → authorization result.
|
||||||
BatchCheck(context.Context, []*openfgav1.TupleKey) error
|
BatchCheck(context.Context, map[string]*openfgav1.TupleKey) (map[string]*authtypes.TupleKeyAuthorization, error)
|
||||||
|
|
||||||
// Write accepts the insertion tuples and the deletion tuples.
|
// Write accepts the insertion tuples and the deletion tuples.
|
||||||
Write(context.Context, []*openfgav1.TupleKey, []*openfgav1.TupleKey) error
|
Write(context.Context, []*openfgav1.TupleKey, []*openfgav1.TupleKey) error
|
||||||
@@ -102,5 +99,7 @@ type Handler interface {
|
|||||||
|
|
||||||
PatchObjects(http.ResponseWriter, *http.Request)
|
PatchObjects(http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
|
Check(http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
Delete(http.ResponseWriter, *http.Request)
|
Delete(http.ResponseWriter, *http.Request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) er
|
|||||||
Model(role).
|
Model(role).
|
||||||
Exec(ctx)
|
Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "role with id: %s already exists", role.ID)
|
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "role with name: %s already exists", role.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -48,11 +48,7 @@ func (provider *provider) Stop(ctx context.Context) error {
|
|||||||
return provider.server.Stop(ctx)
|
return provider.server.Stop(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *provider) Check(ctx context.Context, tupleReq *openfgav1.TupleKey) error {
|
func (provider *provider) BatchCheck(ctx context.Context, tupleReq map[string]*openfgav1.TupleKey) (map[string]*authtypes.TupleKeyAuthorization, error) {
|
||||||
return provider.server.Check(ctx, tupleReq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.TupleKey) error {
|
|
||||||
return provider.server.BatchCheck(ctx, tupleReq)
|
return provider.server.BatchCheck(ctx, tupleReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,10 +177,6 @@ func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *provider) SetManagedRoleTransactions(context.Context, valuer.UUID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||||
return provider.Grant(ctx, orgID, roletypes.SigNozAdminRoleName, authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil))
|
return provider.Grant(ctx, orgID, roletypes.SigNozAdminRoleName, authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,42 +90,18 @@ func (server *Server) Stop(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Check(ctx context.Context, tupleReq *openfgav1.TupleKey) error {
|
func (server *Server) BatchCheck(ctx context.Context, tupleReq map[string]*openfgav1.TupleKey) (map[string]*authtypes.TupleKeyAuthorization, error) {
|
||||||
storeID, modelID := server.getStoreIDandModelID()
|
storeID, modelID := server.getStoreIDandModelID()
|
||||||
checkResponse, err := server.openfgaServer.Check(
|
batchCheckItems := make([]*openfgav1.BatchCheckItem, 0, len(tupleReq))
|
||||||
ctx,
|
for id, tuple := range tupleReq {
|
||||||
&openfgav1.CheckRequest{
|
|
||||||
StoreId: storeID,
|
|
||||||
AuthorizationModelId: modelID,
|
|
||||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
|
||||||
User: tupleReq.User,
|
|
||||||
Relation: tupleReq.Relation,
|
|
||||||
Object: tupleReq.Object,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !checkResponse.Allowed {
|
|
||||||
return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subject %s cannot %s object %s", tupleReq.User, tupleReq.Relation, tupleReq.Object)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *Server) BatchCheck(ctx context.Context, tupleReq []*openfgav1.TupleKey) error {
|
|
||||||
storeID, modelID := server.getStoreIDandModelID()
|
|
||||||
batchCheckItems := make([]*openfgav1.BatchCheckItem, 0)
|
|
||||||
for idx, tuple := range tupleReq {
|
|
||||||
batchCheckItems = append(batchCheckItems, &openfgav1.BatchCheckItem{
|
batchCheckItems = append(batchCheckItems, &openfgav1.BatchCheckItem{
|
||||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||||
User: tuple.User,
|
User: tuple.User,
|
||||||
Relation: tuple.Relation,
|
Relation: tuple.Relation,
|
||||||
Object: tuple.Object,
|
Object: tuple.Object,
|
||||||
},
|
},
|
||||||
// the batch check response is map[string] keyed by correlationID.
|
// Use transaction ID as correlation ID for deterministic mapping
|
||||||
CorrelationId: strconv.Itoa(idx),
|
CorrelationId: id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,17 +113,18 @@ func (server *Server) BatchCheck(ctx context.Context, tupleReq []*openfgav1.Tupl
|
|||||||
Checks: batchCheckItems,
|
Checks: batchCheckItems,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error())
|
return nil, errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, checkResponse := range checkResponse.Result {
|
response := make(map[string]*authtypes.TupleKeyAuthorization, len(tupleReq))
|
||||||
if checkResponse.GetAllowed() {
|
for id, tuple := range tupleReq {
|
||||||
return nil
|
response[id] = &authtypes.TupleKeyAuthorization{
|
||||||
|
Tuple: tuple,
|
||||||
|
Authorized: checkResponse.Result[id].GetAllowed(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subjects are not authorized for requested access")
|
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, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||||
@@ -156,17 +133,29 @@ func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtyp
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tuples, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
|
tupleSlice, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.BatchCheck(ctx, tuples)
|
// Convert slice to map with generated IDs for internal use
|
||||||
|
tuples := make(map[string]*openfgav1.TupleKey, len(tupleSlice))
|
||||||
|
for idx, tuple := range tupleSlice {
|
||||||
|
tuples[strconv.Itoa(idx)] = tuple
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := server.BatchCheck(ctx, tuples)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
for _, resp := range response {
|
||||||
|
if resp.Authorized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, _ authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||||
@@ -175,17 +164,29 @@ func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, o
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tuples, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
|
tupleSlice, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.BatchCheck(ctx, tuples)
|
// Convert slice to map with generated IDs for internal use
|
||||||
|
tuples := make(map[string]*openfgav1.TupleKey, len(tupleSlice))
|
||||||
|
for idx, tuple := range tupleSlice {
|
||||||
|
tuples[strconv.Itoa(idx)] = tuple
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := server.BatchCheck(ctx, tuples)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
for _, resp := range response {
|
||||||
|
if resp.Authorized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subjects are not authorized for requested access")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
|
func (server *Server) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/http/binding"
|
"github.com/SigNoz/signoz/pkg/http/binding"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"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/authtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
@@ -35,13 +36,14 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.authz.Create(ctx, valuer.MustNewUUID(claims.OrgID), roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom, valuer.MustNewUUID(claims.OrgID)))
|
role := roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom, valuer.MustNewUUID(claims.OrgID))
|
||||||
|
err = handler.authz.Create(ctx, valuer.MustNewUUID(claims.OrgID), role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw, err)
|
render.Error(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
render.Success(rw, http.StatusCreated, nil)
|
render.Success(rw, http.StatusCreated, types.Identifiable{ID: role.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||||
@@ -112,17 +114,9 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (handler *handler) GetResources(rw http.ResponseWriter, r *http.Request) {
|
func (handler *handler) GetResources(rw http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
resources := handler.authz.GetResources(r.Context())
|
||||||
resources := handler.authz.GetResources(ctx)
|
|
||||||
|
|
||||||
var resourceRelations = struct {
|
render.Success(rw, http.StatusOK, roletypes.NewGettableResources(resources))
|
||||||
Resources []*authtypes.Resource `json:"resources"`
|
|
||||||
Relations map[authtypes.Type][]authtypes.Relation `json:"relations"`
|
|
||||||
}{
|
|
||||||
Resources: resources,
|
|
||||||
Relations: authtypes.TypeableRelations,
|
|
||||||
}
|
|
||||||
render.Success(rw, http.StatusOK, resourceRelations)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
||||||
@@ -227,7 +221,7 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
render.Success(rw, http.StatusAccepted, nil)
|
render.Success(rw, http.StatusNoContent, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||||
@@ -252,3 +246,39 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
render.Success(rw, http.StatusNoContent, nil)
|
render.Success(rw, http.StatusNoContent, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (handler *handler) Check(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions := make([]*authtypes.Transaction, 0)
|
||||||
|
if err := binding.JSON.BindBody(r.Body, &transactions); err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||||
|
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tuples, err := authtypes.NewTuplesFromTransactions(transactions, subject, orgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := handler.authz.BatchCheck(ctx, tuples)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(rw, http.StatusOK, authtypes.NewGettableTransaction(transactions, results))
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,15 +15,14 @@ var (
|
|||||||
RelationUpdate = Relation{valuer.NewString("update")}
|
RelationUpdate = Relation{valuer.NewString("update")}
|
||||||
RelationDelete = Relation{valuer.NewString("delete")}
|
RelationDelete = Relation{valuer.NewString("delete")}
|
||||||
RelationList = Relation{valuer.NewString("list")}
|
RelationList = Relation{valuer.NewString("list")}
|
||||||
RelationBlock = Relation{valuer.NewString("block")}
|
|
||||||
RelationAssignee = Relation{valuer.NewString("assignee")}
|
RelationAssignee = Relation{valuer.NewString("assignee")}
|
||||||
)
|
)
|
||||||
|
|
||||||
var TypeableRelations = map[Type][]Relation{
|
var TypeableRelations = map[Type][]Relation{
|
||||||
TypeUser: {RelationRead, RelationUpdate, RelationDelete},
|
TypeUser: {RelationRead, RelationUpdate, RelationDelete},
|
||||||
TypeRole: {RelationAssignee, RelationRead, RelationUpdate, RelationDelete},
|
TypeRole: {RelationAssignee, RelationRead, RelationUpdate, RelationDelete},
|
||||||
TypeOrganization: {RelationCreate, RelationRead, RelationUpdate, RelationDelete, RelationList},
|
TypeOrganization: {RelationRead, RelationUpdate, RelationDelete},
|
||||||
TypeMetaResource: {RelationRead, RelationUpdate, RelationDelete, RelationBlock},
|
TypeMetaResource: {RelationRead, RelationUpdate, RelationDelete},
|
||||||
TypeMetaResources: {RelationCreate, RelationList},
|
TypeMetaResources: {RelationCreate, RelationList},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +40,6 @@ func NewRelation(relation string) (Relation, error) {
|
|||||||
return RelationDelete, nil
|
return RelationDelete, nil
|
||||||
case "list":
|
case "list":
|
||||||
return RelationList, nil
|
return RelationList, nil
|
||||||
case "block":
|
|
||||||
return RelationBlock, nil
|
|
||||||
case "assignee":
|
case "assignee":
|
||||||
return RelationAssignee, nil
|
return RelationAssignee, nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -6,21 +6,29 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Name Name `json:"name"`
|
Name Name `json:"name" required:"true"`
|
||||||
Type Type `json:"type"`
|
Type Type `json:"type" required:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Object struct {
|
type Object struct {
|
||||||
Resource Resource `json:"resource"`
|
Resource Resource `json:"resource" required:"true"`
|
||||||
Selector Selector `json:"selector"`
|
Selector Selector `json:"selector" required:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Transaction struct {
|
type Transaction struct {
|
||||||
Relation Relation `json:"relation"`
|
ID valuer.UUID `json:"id"`
|
||||||
Object Object `json:"object"`
|
Relation Relation `json:"relation" required:"true"`
|
||||||
|
Object 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewObject(resource Resource, selector Selector) (*Object, error) {
|
func NewObject(resource Resource, selector Selector) (*Object, error) {
|
||||||
@@ -75,7 +83,21 @@ func NewTransaction(relation Relation, object Object) (*Transaction, error) {
|
|||||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s for type %s", relation.StringValue(), object.Resource.Type.StringValue())
|
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s for type %s", relation.StringValue(), object.Resource.Type.StringValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Transaction{Relation: relation, Object: object}, nil
|
return &Transaction{ID: valuer.GenerateUUID(), Relation: relation, Object: object}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGettableTransaction(transactions []*Transaction, results map[string]*TupleKeyAuthorization) []*GettableTransaction {
|
||||||
|
gettableTransactions := make([]*GettableTransaction, len(transactions))
|
||||||
|
for i, txn := range transactions {
|
||||||
|
result := results[txn.ID.StringValue()]
|
||||||
|
gettableTransactions[i] = &GettableTransaction{
|
||||||
|
Relation: txn.Relation,
|
||||||
|
Object: txn.Object,
|
||||||
|
Authorized: result.Authorized,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gettableTransactions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (object *Object) UnmarshalJSON(data []byte) error {
|
func (object *Object) UnmarshalJSON(data []byte) error {
|
||||||
|
|||||||
31
pkg/types/authtypes/tuple.go
Normal file
31
pkg/types/authtypes/tuple.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package authtypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TupleKeyAuthorization struct {
|
||||||
|
Tuple *openfgav1.TupleKey
|
||||||
|
Authorized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txnTuples, err := typeable.Tuples(subject, txn.Relation, []Selector{txn.Object.Selector}, orgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each transaction produces one tuple, keyed by transaction ID
|
||||||
|
tuples[txn.ID.StringValue()] = txnTuples[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return tuples, nil
|
||||||
|
}
|
||||||
@@ -69,24 +69,29 @@ type StorableRole struct {
|
|||||||
type Role struct {
|
type Role struct {
|
||||||
types.Identifiable
|
types.Identifiable
|
||||||
types.TimeAuditable
|
types.TimeAuditable
|
||||||
Name string `json:"name"`
|
Name string `json:"name" required:"true"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description" required:"true"`
|
||||||
Type valuer.String `json:"type"`
|
Type valuer.String `json:"type" required:"true"`
|
||||||
OrgID valuer.UUID `json:"orgId"`
|
OrgID valuer.UUID `json:"orgId" required:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostableRole struct {
|
type PostableRole struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name" required:"true"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PatchableRole struct {
|
type PatchableRole struct {
|
||||||
Description *string `json:"description"`
|
Description string `json:"description" required:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PatchableObjects struct {
|
type PatchableObjects struct {
|
||||||
Additions []*authtypes.Object `json:"additions"`
|
Additions []*authtypes.Object `json:"additions" required:"true"`
|
||||||
Deletions []*authtypes.Object `json:"deletions"`
|
Deletions []*authtypes.Object `json:"deletions" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableResources struct {
|
||||||
|
Resources []*authtypes.Resource `json:"resources" required:"true"`
|
||||||
|
Relations map[authtypes.Type][]authtypes.Relation `json:"relations" required:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStorableRoleFromRole(role *Role) *StorableRole {
|
func NewStorableRoleFromRole(role *Role) *StorableRole {
|
||||||
@@ -137,15 +142,20 @@ func NewManagedRoles(orgID valuer.UUID) []*Role {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (role *Role) PatchMetadata(description *string) error {
|
func NewGettableResources(resources []*authtypes.Resource) *GettableResources {
|
||||||
|
return &GettableResources{
|
||||||
|
Resources: resources,
|
||||||
|
Relations: authtypes.TypeableRelations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (role *Role) PatchMetadata(description string) error {
|
||||||
err := role.CanEditDelete()
|
err := role.CanEditDelete()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if description != nil {
|
role.Description = description
|
||||||
role.Description = *description
|
|
||||||
}
|
|
||||||
role.UpdatedAt = time.Now()
|
role.UpdatedAt = time.Now()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -210,7 +220,7 @@ func (role *PostableRole) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
func (role *PatchableRole) UnmarshalJSON(data []byte) error {
|
func (role *PatchableRole) UnmarshalJSON(data []byte) error {
|
||||||
type shadowPatchableRole struct {
|
type shadowPatchableRole struct {
|
||||||
Description *string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var shadowRole shadowPatchableRole
|
var shadowRole shadowPatchableRole
|
||||||
@@ -218,7 +228,7 @@ func (role *PatchableRole) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if shadowRole.Description == nil {
|
if shadowRole.Description == "" {
|
||||||
return errors.New(errors.TypeInvalidInput, ErrCodeRoleEmptyPatch, "empty role patch request received, description must be present")
|
return errors.New(errors.TypeInvalidInput, ErrCodeRoleEmptyPatch, "empty role patch request received, description must be present")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user