Compare commits

..

21 Commits

Author SHA1 Message Date
Karan Balani
259a53ac70 feat: api for fetch all users with a role id 2026-03-24 13:57:28 +05:30
Karan Balani
d263e3d00d feat: statsreporter to use new list functions 2026-03-24 13:57:28 +05:30
Karan Balani
bebde04363 feat: add get user by id v2 api 2026-03-24 13:57:28 +05:30
Karan Balani
546edf12fa fix: fmtlint for integration tests 2026-03-24 13:57:28 +05:30
Karan Balani
3266e0a293 fix: role integration test 2026-03-24 13:57:28 +05:30
Karan Balani
a1c650374b fix: tests lint and remove unreachable code 2026-03-24 13:57:28 +05:30
Karan Balani
843b4e2886 feat: add more tests for role checks 2026-03-24 13:57:28 +05:30
Karan Balani
fad87b6d3c fix: empty array entry in user roles 2026-03-24 13:57:28 +05:30
Karan Balani
e45b296236 fix: openapi 2026-03-24 13:57:28 +05:30
Karan Balani
71d25adecd fix: allow empty roles for users 2026-03-24 13:57:28 +05:30
Karan Balani
6889d7837c fix: add nil checks 2026-03-24 13:57:28 +05:30
Karan Balani
6df2b50e29 fix: request type for update my user api 2026-03-24 13:57:28 +05:30
Karan Balani
6a2bd68795 chore: integration tests use new v2 apis 2026-03-24 13:57:28 +05:30
Karan Balani
28654dae64 fix: noop handler and params fixes 2026-03-24 13:57:28 +05:30
Karan Balani
bddc878143 feat: no need to check user roles in update user since it is now behind admin access 2026-03-24 13:57:28 +05:30
Karan Balani
c84d521a10 fix: use deprecated method for deprecated api handler 2026-03-24 13:57:28 +05:30
Karan Balani
1b570bffb5 fix: openapi schema 2026-03-24 13:57:28 +05:30
Karan Balani
560fe9a072 feat: update my user and update user v2 api 2026-03-24 13:57:28 +05:30
Karan Balani
95c0c77d7c feat: get my user v2 api including roles 2026-03-24 13:57:28 +05:30
Karan Balani
0e0a87505e feat: add get user roles api 2026-03-24 13:57:28 +05:30
Karan Balani
216d01f2c2 feat: list users v2 api 2026-03-24 13:57:28 +05:30
38 changed files with 2051 additions and 3349 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -437,152 +437,41 @@ export interface AuthtypesUpdateableAuthDomainDTO {
config?: AuthtypesAuthDomainConfigDTO;
}
export interface CloudintegrationtypesAWSAccountConfigDTO {
/**
* @type array
*/
regions: string[];
}
export type CloudintegrationtypesAWSCollectionStrategyDTOS3Buckets = {
[key: string]: string[];
};
export interface CloudintegrationtypesAWSCollectionStrategyDTO {
aws_logs?: CloudintegrationtypesAWSLogsStrategyDTO;
aws_metrics?: CloudintegrationtypesAWSMetricsStrategyDTO;
/**
* @type object
*/
s3_buckets?: CloudintegrationtypesAWSCollectionStrategyDTOS3Buckets;
}
export interface CloudintegrationtypesAWSConnectionArtifactDTO {
/**
* @type string
*/
connectionURL: string;
}
export interface CloudintegrationtypesAWSConnectionArtifactRequestDTO {
/**
* @type string
*/
deploymentRegion: string;
/**
* @type array
*/
regions: string[];
}
export interface CloudintegrationtypesAWSIntegrationConfigDTO {
/**
* @type array
*/
enabledRegions: string[];
telemetry: CloudintegrationtypesAWSCollectionStrategyDTO;
}
export type CloudintegrationtypesAWSLogsStrategyDTOCloudwatchLogsSubscriptionsItem = {
/**
* @type string
*/
filter_pattern?: string;
/**
* @type string
*/
log_group_name_prefix?: string;
};
export interface CloudintegrationtypesAWSLogsStrategyDTO {
/**
* @type array
* @nullable true
*/
cloudwatch_logs_subscriptions?:
| CloudintegrationtypesAWSLogsStrategyDTOCloudwatchLogsSubscriptionsItem[]
| null;
}
export type CloudintegrationtypesAWSMetricsStrategyDTOCloudwatchMetricStreamFiltersItem = {
/**
* @type array
*/
MetricNames?: string[];
/**
* @type string
*/
Namespace?: string;
};
export interface CloudintegrationtypesAWSMetricsStrategyDTO {
/**
* @type array
* @nullable true
*/
cloudwatch_metric_stream_filters?:
| CloudintegrationtypesAWSMetricsStrategyDTOCloudwatchMetricStreamFiltersItem[]
| null;
}
export interface CloudintegrationtypesAWSServiceConfigDTO {
logs?: CloudintegrationtypesAWSServiceLogsConfigDTO;
metrics?: CloudintegrationtypesAWSServiceMetricsConfigDTO;
}
export type CloudintegrationtypesAWSServiceLogsConfigDTOS3Buckets = {
[key: string]: string[];
};
export interface CloudintegrationtypesAWSServiceLogsConfigDTO {
/**
* @type boolean
*/
enabled?: boolean;
/**
* @type object
*/
s3_buckets?: CloudintegrationtypesAWSServiceLogsConfigDTOS3Buckets;
}
export interface CloudintegrationtypesAWSServiceMetricsConfigDTO {
/**
* @type boolean
*/
enabled?: boolean;
}
export interface CloudintegrationtypesAccountDTO {
agentReport: CloudintegrationtypesAgentReportDTO;
config: CloudintegrationtypesAccountConfigDTO;
export interface AuthtypesUserWithRolesDTO {
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
displayName?: string;
/**
* @type string
*/
email?: string;
/**
* @type string
*/
id: string;
/**
* @type string
* @type boolean
*/
orgId: string;
isRoot?: boolean;
/**
* @type string
*/
provider: string;
orgId?: string;
/**
* @type string
* @type array
* @nullable true
*/
providerAccountId: string | null;
roles?: AuthtypesRoleDTO[] | null;
/**
* @type string
* @format date-time
* @nullable true
*/
removedAt: Date | null;
status?: string;
/**
* @type string
* @format date-time
@@ -590,283 +479,6 @@ export interface CloudintegrationtypesAccountDTO {
updatedAt?: Date;
}
export interface CloudintegrationtypesAccountConfigDTO {
aws: CloudintegrationtypesAWSAccountConfigDTO;
}
/**
* @nullable
*/
export type CloudintegrationtypesAgentReportDTOData = {
[key: string]: unknown;
} | null;
/**
* @nullable
*/
export type CloudintegrationtypesAgentReportDTO = {
/**
* @type object
* @nullable true
*/
data: CloudintegrationtypesAgentReportDTOData;
/**
* @type integer
* @format int64
*/
timestampMillis: number;
} | null;
export interface CloudintegrationtypesAssetsDTO {
/**
* @type array
* @nullable true
*/
dashboards?: CloudintegrationtypesDashboardDTO[] | null;
}
export interface CloudintegrationtypesCollectedLogAttributeDTO {
/**
* @type string
*/
name?: string;
/**
* @type string
*/
path?: string;
/**
* @type string
*/
type?: string;
}
export interface CloudintegrationtypesCollectedMetricDTO {
/**
* @type string
*/
description?: string;
/**
* @type string
*/
name?: string;
/**
* @type string
*/
type?: string;
/**
* @type string
*/
unit?: string;
}
export interface CloudintegrationtypesCollectionStrategyDTO {
aws: CloudintegrationtypesAWSCollectionStrategyDTO;
}
export interface CloudintegrationtypesConnectionArtifactDTO {
aws: CloudintegrationtypesAWSConnectionArtifactDTO;
}
export interface CloudintegrationtypesConnectionArtifactRequestDTO {
aws: CloudintegrationtypesAWSConnectionArtifactRequestDTO;
}
export interface CloudintegrationtypesDashboardDTO {
definition?: DashboardtypesStorableDashboardDataDTO;
/**
* @type string
*/
description?: string;
/**
* @type string
*/
id?: string;
/**
* @type string
*/
title?: string;
}
export interface CloudintegrationtypesDataCollectedDTO {
/**
* @type array
* @nullable true
*/
logs?: CloudintegrationtypesCollectedLogAttributeDTO[] | null;
/**
* @type array
* @nullable true
*/
metrics?: CloudintegrationtypesCollectedMetricDTO[] | null;
}
export interface CloudintegrationtypesGettableAccountWithArtifactDTO {
connectionArtifact: CloudintegrationtypesConnectionArtifactDTO;
/**
* @type string
*/
id: string;
}
export interface CloudintegrationtypesGettableAccountsDTO {
/**
* @type array
*/
accounts: CloudintegrationtypesAccountDTO[];
}
export interface CloudintegrationtypesGettableAgentCheckInResponseDTO {
/**
* @type string
*/
account_id: string;
/**
* @type string
*/
cloud_account_id: string;
/**
* @type string
*/
cloudIntegrationId: string;
integration_config: CloudintegrationtypesIntegrationConfigDTO;
integrationConfig: CloudintegrationtypesProviderIntegrationConfigDTO;
/**
* @type string
*/
providerAccountId: string;
/**
* @type string
* @format date-time
* @nullable true
*/
removed_at: Date | null;
/**
* @type string
* @format date-time
* @nullable true
*/
removedAt: Date | null;
}
export interface CloudintegrationtypesGettableServicesMetadataDTO {
/**
* @type array
*/
services: CloudintegrationtypesServiceMetadataDTO[];
}
/**
* @nullable
*/
export type CloudintegrationtypesIntegrationConfigDTO = {
/**
* @type array
*/
enabled_regions: string[];
telemetry: CloudintegrationtypesAWSCollectionStrategyDTO;
} | null;
/**
* @nullable
*/
export type CloudintegrationtypesPostableAgentCheckInRequestDTOData = {
[key: string]: unknown;
} | null;
export interface CloudintegrationtypesPostableAgentCheckInRequestDTO {
/**
* @type string
*/
account_id?: string;
/**
* @type string
*/
cloud_account_id?: string;
/**
* @type string
*/
cloudIntegrationId?: string;
/**
* @type object
* @nullable true
*/
data: CloudintegrationtypesPostableAgentCheckInRequestDTOData;
/**
* @type string
*/
providerAccountId?: string;
}
export interface CloudintegrationtypesProviderIntegrationConfigDTO {
aws: CloudintegrationtypesAWSIntegrationConfigDTO;
}
export interface CloudintegrationtypesServiceDTO {
assets: CloudintegrationtypesAssetsDTO;
dataCollected: CloudintegrationtypesDataCollectedDTO;
/**
* @type string
*/
icon: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
overview: string;
serviceConfig?: CloudintegrationtypesServiceConfigDTO;
supported_signals: CloudintegrationtypesSupportedSignalsDTO;
telemetryCollectionStrategy: CloudintegrationtypesCollectionStrategyDTO;
/**
* @type string
*/
title: string;
}
export interface CloudintegrationtypesServiceConfigDTO {
aws: CloudintegrationtypesAWSServiceConfigDTO;
}
export interface CloudintegrationtypesServiceMetadataDTO {
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
*/
icon: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
title: string;
}
export interface CloudintegrationtypesSupportedSignalsDTO {
/**
* @type boolean
*/
logs?: boolean;
/**
* @type boolean
*/
metrics?: boolean;
}
export interface CloudintegrationtypesUpdatableAccountDTO {
config: CloudintegrationtypesAccountConfigDTO;
}
export interface CloudintegrationtypesUpdatableServiceDTO {
config: CloudintegrationtypesServiceConfigDTO;
}
export interface DashboardtypesDashboardDTO {
/**
* @type string
@@ -3144,6 +2756,24 @@ export interface TypesStorableAPIKeyDTO {
userId?: string;
}
export interface TypesUpdatableSelfUserDTO {
/**
* @type string
*/
displayName: string;
}
export interface TypesUpdatableUserDTO {
/**
* @type string
*/
displayName: string;
/**
* @type array
*/
roleNames: string[];
}
export interface TypesUserDTO {
/**
* @type string
@@ -3288,97 +2918,6 @@ export type AuthzResources200 = {
export type ChangePasswordPathParameters = {
id: string;
};
export type AgentCheckInDeprecatedPathParameters = {
cloudProvider: string;
};
export type AgentCheckInDeprecated200 = {
data: CloudintegrationtypesGettableAgentCheckInResponseDTO;
/**
* @type string
*/
status: string;
};
export type ListAccountsPathParameters = {
cloudProvider: string;
};
export type ListAccounts200 = {
data: CloudintegrationtypesGettableAccountsDTO;
/**
* @type string
*/
status: string;
};
export type CreateAccountPathParameters = {
cloudProvider: string;
};
export type CreateAccount200 = {
data: CloudintegrationtypesGettableAccountWithArtifactDTO;
/**
* @type string
*/
status: string;
};
export type DisconnectAccountPathParameters = {
cloudProvider: string;
id: string;
};
export type GetAccountPathParameters = {
cloudProvider: string;
id: string;
};
export type GetAccount200 = {
data: CloudintegrationtypesAccountDTO;
/**
* @type string
*/
status: string;
};
export type UpdateAccountPathParameters = {
cloudProvider: string;
id: string;
};
export type AgentCheckInPathParameters = {
cloudProvider: string;
};
export type AgentCheckIn200 = {
data: CloudintegrationtypesGettableAgentCheckInResponseDTO;
/**
* @type string
*/
status: string;
};
export type ListServicesMetadataPathParameters = {
cloudProvider: string;
};
export type ListServicesMetadata200 = {
data: CloudintegrationtypesGettableServicesMetadataDTO;
/**
* @type string
*/
status: string;
};
export type GetServicePathParameters = {
cloudProvider: string;
serviceId: string;
};
export type GetService200 = {
data: CloudintegrationtypesServiceDTO;
/**
* @type string
*/
status: string;
};
export type UpdateServicePathParameters = {
cloudProvider: string;
serviceId: string;
};
export type CreateSessionByGoogleCallback303 = {
data: AuthtypesGettableTokenDTO;
/**
@@ -4207,6 +3746,42 @@ export type RotateSession200 = {
status: string;
};
export type ListUsersV2200 = {
/**
* @type array
*/
data: TypesUserDTO[];
/**
* @type string
*/
status: string;
};
export type UpdateUserV2PathParameters = {
id: string;
};
export type GetUserRolesPathParameters = {
id: string;
};
export type GetUserRoles200 = {
/**
* @type array
*/
data: AuthtypesRoleDTO[];
/**
* @type string
*/
status: string;
};
export type GetMyUserV2200 = {
data: AuthtypesUserWithRolesDTO;
/**
* @type string
*/
status: string;
};
export type GetHosts200 = {
data: ZeustypesGettableHostDTO;
/**

View File

@@ -25,12 +25,16 @@ import type {
CreateInvite201,
DeleteUserPathParameters,
GetMyUser200,
GetMyUserV2200,
GetResetPasswordToken200,
GetResetPasswordTokenPathParameters,
GetUser200,
GetUserPathParameters,
GetUserRoles200,
GetUserRolesPathParameters,
ListAPIKeys200,
ListUsers200,
ListUsersV2200,
RenderErrorResponseDTO,
RevokeAPIKeyPathParameters,
TypesChangePasswordRequestDTO,
@@ -41,9 +45,12 @@ import type {
TypesPostableInviteDTO,
TypesPostableResetPasswordDTO,
TypesStorableAPIKeyDTO,
TypesUpdatableSelfUserDTO,
TypesUpdatableUserDTO,
UpdateAPIKeyPathParameters,
UpdateUser200,
UpdateUserPathParameters,
UpdateUserV2PathParameters,
} from '../sigNoz.schemas';
/**
@@ -1345,3 +1352,454 @@ export const useForgotPassword = <
return useMutation(mutationOptions);
};
/**
* This endpoint lists all users for the organization
* @summary List users v2
*/
export const listUsersV2 = (signal?: AbortSignal) => {
return GeneratedAPIInstance<ListUsersV2200>({
url: `/api/v2/users`,
method: 'GET',
signal,
});
};
export const getListUsersV2QueryKey = () => {
return [`/api/v2/users`] as const;
};
export const getListUsersV2QueryOptions = <
TData = Awaited<ReturnType<typeof listUsersV2>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listUsersV2>>,
TError,
TData
>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListUsersV2QueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof listUsersV2>>> = ({
signal,
}) => listUsersV2(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listUsersV2>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListUsersV2QueryResult = NonNullable<
Awaited<ReturnType<typeof listUsersV2>>
>;
export type ListUsersV2QueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List users v2
*/
export function useListUsersV2<
TData = Awaited<ReturnType<typeof listUsersV2>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listUsersV2>>,
TError,
TData
>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListUsersV2QueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List users v2
*/
export const invalidateListUsersV2 = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListUsersV2QueryKey() },
options,
);
return queryClient;
};
/**
* This endpoint updates the user by id
* @summary Update user v2
*/
export const updateUserV2 = (
{ id }: UpdateUserV2PathParameters,
typesUpdatableUserDTO: BodyType<TypesUpdatableUserDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/users/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: typesUpdatableUserDTO,
});
};
export const getUpdateUserV2MutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateUserV2>>,
TError,
{
pathParams: UpdateUserV2PathParameters;
data: BodyType<TypesUpdatableUserDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateUserV2>>,
TError,
{
pathParams: UpdateUserV2PathParameters;
data: BodyType<TypesUpdatableUserDTO>;
},
TContext
> => {
const mutationKey = ['updateUserV2'];
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 updateUserV2>>,
{
pathParams: UpdateUserV2PathParameters;
data: BodyType<TypesUpdatableUserDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateUserV2(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateUserV2MutationResult = NonNullable<
Awaited<ReturnType<typeof updateUserV2>>
>;
export type UpdateUserV2MutationBody = BodyType<TypesUpdatableUserDTO>;
export type UpdateUserV2MutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update user v2
*/
export const useUpdateUserV2 = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateUserV2>>,
TError,
{
pathParams: UpdateUserV2PathParameters;
data: BodyType<TypesUpdatableUserDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateUserV2>>,
TError,
{
pathParams: UpdateUserV2PathParameters;
data: BodyType<TypesUpdatableUserDTO>;
},
TContext
> => {
const mutationOptions = getUpdateUserV2MutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint returns the user roles by user id
* @summary Get user roles
*/
export const getUserRoles = (
{ id }: GetUserRolesPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetUserRoles200>({
url: `/api/v2/users/${id}/roles`,
method: 'GET',
signal,
});
};
export const getGetUserRolesQueryKey = ({ id }: GetUserRolesPathParameters) => {
return [`/api/v2/users/${id}/roles`] as const;
};
export const getGetUserRolesQueryOptions = <
TData = Awaited<ReturnType<typeof getUserRoles>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetUserRolesPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getUserRoles>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getGetUserRolesQueryKey({ id });
const queryFn: QueryFunction<Awaited<ReturnType<typeof getUserRoles>>> = ({
signal,
}) => getUserRoles({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getUserRoles>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetUserRolesQueryResult = NonNullable<
Awaited<ReturnType<typeof getUserRoles>>
>;
export type GetUserRolesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get user roles
*/
export function useGetUserRoles<
TData = Awaited<ReturnType<typeof getUserRoles>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetUserRolesPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getUserRoles>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetUserRolesQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get user roles
*/
export const invalidateGetUserRoles = async (
queryClient: QueryClient,
{ id }: GetUserRolesPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetUserRolesQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint returns the user I belong to
* @summary Get my user v2
*/
export const getMyUserV2 = (signal?: AbortSignal) => {
return GeneratedAPIInstance<GetMyUserV2200>({
url: `/api/v2/users/me`,
method: 'GET',
signal,
});
};
export const getGetMyUserV2QueryKey = () => {
return [`/api/v2/users/me`] as const;
};
export const getGetMyUserV2QueryOptions = <
TData = Awaited<ReturnType<typeof getMyUserV2>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getMyUserV2>>,
TError,
TData
>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getGetMyUserV2QueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof getMyUserV2>>> = ({
signal,
}) => getMyUserV2(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof getMyUserV2>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetMyUserV2QueryResult = NonNullable<
Awaited<ReturnType<typeof getMyUserV2>>
>;
export type GetMyUserV2QueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get my user v2
*/
export function useGetMyUserV2<
TData = Awaited<ReturnType<typeof getMyUserV2>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getMyUserV2>>,
TError,
TData
>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetMyUserV2QueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get my user v2
*/
export const invalidateGetMyUserV2 = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetMyUserV2QueryKey() },
options,
);
return queryClient;
};
/**
* This endpoint updates the user I belong to
* @summary Update my user v2
*/
export const updateMyUserV2 = (
typesUpdatableSelfUserDTO: BodyType<TypesUpdatableSelfUserDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/users/me`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: typesUpdatableSelfUserDTO,
});
};
export const getUpdateMyUserV2MutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateMyUserV2>>,
TError,
{ data: BodyType<TypesUpdatableSelfUserDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateMyUserV2>>,
TError,
{ data: BodyType<TypesUpdatableSelfUserDTO> },
TContext
> => {
const mutationKey = ['updateMyUserV2'];
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 updateMyUserV2>>,
{ data: BodyType<TypesUpdatableSelfUserDTO> }
> = (props) => {
const { data } = props ?? {};
return updateMyUserV2(data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateMyUserV2MutationResult = NonNullable<
Awaited<ReturnType<typeof updateMyUserV2>>
>;
export type UpdateMyUserV2MutationBody = BodyType<TypesUpdatableSelfUserDTO>;
export type UpdateMyUserV2MutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update my user v2
*/
export const useUpdateMyUserV2 = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateMyUserV2>>,
TError,
{ data: BodyType<TypesUpdatableSelfUserDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateMyUserV2>>,
TError,
{ data: BodyType<TypesUpdatableSelfUserDTO> },
TContext
> => {
const mutationOptions = getUpdateMyUserV2MutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -62,6 +62,9 @@ export const getVolumeQueryPayload = (
const k8sPVCNameKey = dotMetricsEnabled
? 'k8s.persistentvolumeclaim.name'
: 'k8s_persistentvolumeclaim_name';
const legendTemplate = dotMetricsEnabled
? '{{k8s.namespace.name}}-{{k8s.pod.name}}'
: '{{k8s_namespace_name}}-{{k8s_pod_name}}';
return [
{
@@ -133,7 +136,7 @@ export const getVolumeQueryPayload = (
functions: [],
groupBy: [],
having: [],
legend: 'Available',
legend: legendTemplate,
limit: null,
orderBy: [],
queryName: 'A',
@@ -225,7 +228,7 @@ export const getVolumeQueryPayload = (
functions: [],
groupBy: [],
having: [],
legend: 'Capacity',
legend: legendTemplate,
limit: null,
orderBy: [],
queryName: 'A',
@@ -316,7 +319,7 @@ export const getVolumeQueryPayload = (
},
groupBy: [],
having: [],
legend: 'Inodes Used',
legend: legendTemplate,
limit: null,
orderBy: [],
queryName: 'A',
@@ -408,7 +411,7 @@ export const getVolumeQueryPayload = (
},
groupBy: [],
having: [],
legend: 'Total Inodes',
legend: legendTemplate,
limit: null,
orderBy: [],
queryName: 'A',
@@ -500,7 +503,7 @@ export const getVolumeQueryPayload = (
},
groupBy: [],
having: [],
legend: 'Inodes Free',
legend: legendTemplate,
limit: null,
orderBy: [],
queryName: 'A',

View File

@@ -1,216 +0,0 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/gorilla/mux"
)
func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.CreateAccount),
handler.OpenAPIDef{
ID: "CreateAccount",
Tags: []string{"cloudintegration"},
Summary: "Create account",
Description: "This endpoint creates a new cloud integration account for the specified cloud provider",
Request: new(citypes.PostableConnectionArtifact),
RequestContentType: "application/json",
Response: new(citypes.GettableAccountWithArtifact),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.ListAccounts),
handler.OpenAPIDef{
ID: "ListAccounts",
Tags: []string{"cloudintegration"},
Summary: "List accounts",
Description: "This endpoint lists the accounts for the specified cloud provider",
Request: nil,
RequestContentType: "",
Response: new(citypes.GettableAccounts),
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/cloud_integrations/{cloud_provider}/accounts/{id}", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.GetAccount),
handler.OpenAPIDef{
ID: "GetAccount",
Tags: []string{"cloudintegration"},
Summary: "Get account",
Description: "This endpoint gets an account for the specified cloud provider",
Request: nil,
RequestContentType: "",
Response: new(citypes.GettableAccount),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.UpdateAccount),
handler.OpenAPIDef{
ID: "UpdateAccount",
Tags: []string{"cloudintegration"},
Summary: "Update account",
Description: "This endpoint updates an account for the specified cloud provider",
Request: new(citypes.UpdatableAccount),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/{id}", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.DisconnectAccount),
handler.OpenAPIDef{
ID: "DisconnectAccount",
Tags: []string{"cloudintegration"},
Summary: "Disconnect account",
Description: "This endpoint disconnects an account for the specified cloud provider",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/services", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.ListServicesMetadata),
handler.OpenAPIDef{
ID: "ListServicesMetadata",
Tags: []string{"cloudintegration"},
Summary: "List services metadata",
Description: "This endpoint lists the services metadata for the specified cloud provider",
Request: nil,
RequestContentType: "",
Response: new(citypes.GettableServicesMetadata),
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/cloud_integrations/{cloud_provider}/services/{service_id}", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.GetService),
handler.OpenAPIDef{
ID: "GetService",
Tags: []string{"cloudintegration"},
Summary: "Get service",
Description: "This endpoint gets a service for the specified cloud provider",
Request: nil,
RequestContentType: "",
Response: new(citypes.GettableService),
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/cloud_integrations/{cloud_provider}/services/{service_id}", handler.New(
provider.authZ.AdminAccess(provider.cloudIntegrationHandler.UpdateService),
handler.OpenAPIDef{
ID: "UpdateService",
Tags: []string{"cloudintegration"},
Summary: "Update service",
Description: "This endpoint updates a service for the specified cloud provider",
Request: new(citypes.UpdatableService),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPut).GetError(); err != nil {
return err
}
// Agent check-in endpoint is kept same as older one to maintain backward compatibility with already deployed agents.
// In the future, this endpoint will be deprecated and a new endpoint will be introduced for consistency with above endpoints.
if err := router.Handle("/api/v1/cloud-integrations/{cloud_provider}/agent-check-in", handler.New(
provider.authZ.ViewAccess(provider.cloudIntegrationHandler.AgentCheckIn),
handler.OpenAPIDef{
ID: "AgentCheckInDeprecated",
Tags: []string{"cloudintegration"},
Summary: "Agent check-in",
Description: "[Deprecated] This endpoint is called by the deployed agent to check in",
Request: new(citypes.PostableAgentCheckInRequest),
RequestContentType: "application/json",
Response: new(citypes.GettableAgentCheckInResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: true, // this endpoint will be deprecated in future
SecuritySchemes: newSecuritySchemes(types.RoleViewer), // agent role is viewer
},
)).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/cloud_integrations/{cloud_provider}/accounts/check_in", handler.New(
provider.authZ.ViewAccess(provider.cloudIntegrationHandler.AgentCheckIn),
handler.OpenAPIDef{
ID: "AgentCheckIn",
Tags: []string{"cloudintegration"},
Summary: "Agent check-in",
Description: "This endpoint is called by the deployed agent to check in",
Request: new(citypes.PostableAgentCheckInRequest),
RequestContentType: "application/json",
Response: new(citypes.GettableAgentCheckInResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer), // agent role is viewer
},
)).Methods(http.MethodPost).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/authdomain"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
@@ -31,30 +30,29 @@ import (
)
type provider struct {
config apiserver.Config
settings factory.ScopedProviderSettings
router *mux.Router
authZ *middleware.AuthZ
orgHandler organization.Handler
userHandler user.Handler
sessionHandler session.Handler
authDomainHandler authdomain.Handler
preferenceHandler preference.Handler
globalHandler global.Handler
promoteHandler promote.Handler
flaggerHandler flagger.Handler
dashboardModule dashboard.Module
dashboardHandler dashboard.Handler
metricsExplorerHandler metricsexplorer.Handler
gatewayHandler gateway.Handler
fieldsHandler fields.Handler
authzHandler authz.Handler
rawDataExportHandler rawdataexport.Handler
zeusHandler zeus.Handler
querierHandler querier.Handler
serviceAccountHandler serviceaccount.Handler
factoryHandler factory.Handler
cloudIntegrationHandler cloudintegration.Handler
config apiserver.Config
settings factory.ScopedProviderSettings
router *mux.Router
authZ *middleware.AuthZ
orgHandler organization.Handler
userHandler user.Handler
sessionHandler session.Handler
authDomainHandler authdomain.Handler
preferenceHandler preference.Handler
globalHandler global.Handler
promoteHandler promote.Handler
flaggerHandler flagger.Handler
dashboardModule dashboard.Module
dashboardHandler dashboard.Handler
metricsExplorerHandler metricsexplorer.Handler
gatewayHandler gateway.Handler
fieldsHandler fields.Handler
authzHandler authz.Handler
rawDataExportHandler rawdataexport.Handler
zeusHandler zeus.Handler
querierHandler querier.Handler
serviceAccountHandler serviceaccount.Handler
factoryHandler factory.Handler
}
func NewFactory(
@@ -79,7 +77,6 @@ func NewFactory(
querierHandler querier.Handler,
serviceAccountHandler serviceaccount.Handler,
factoryHandler factory.Handler,
cloudIntegrationHandler cloudintegration.Handler,
) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] {
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config apiserver.Config) (apiserver.APIServer, error) {
return newProvider(
@@ -107,7 +104,6 @@ func NewFactory(
querierHandler,
serviceAccountHandler,
factoryHandler,
cloudIntegrationHandler,
)
})
}
@@ -137,35 +133,33 @@ func newProvider(
querierHandler querier.Handler,
serviceAccountHandler serviceaccount.Handler,
factoryHandler factory.Handler,
cloudIntegrationHandler cloudintegration.Handler,
) (apiserver.APIServer, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver")
router := mux.NewRouter().UseEncodedPath()
provider := &provider{
config: config,
settings: settings,
router: router,
orgHandler: orgHandler,
userHandler: userHandler,
sessionHandler: sessionHandler,
authDomainHandler: authDomainHandler,
preferenceHandler: preferenceHandler,
globalHandler: globalHandler,
promoteHandler: promoteHandler,
flaggerHandler: flaggerHandler,
dashboardModule: dashboardModule,
dashboardHandler: dashboardHandler,
metricsExplorerHandler: metricsExplorerHandler,
gatewayHandler: gatewayHandler,
fieldsHandler: fieldsHandler,
authzHandler: authzHandler,
rawDataExportHandler: rawDataExportHandler,
zeusHandler: zeusHandler,
querierHandler: querierHandler,
serviceAccountHandler: serviceAccountHandler,
factoryHandler: factoryHandler,
cloudIntegrationHandler: cloudIntegrationHandler,
config: config,
settings: settings,
router: router,
orgHandler: orgHandler,
userHandler: userHandler,
sessionHandler: sessionHandler,
authDomainHandler: authDomainHandler,
preferenceHandler: preferenceHandler,
globalHandler: globalHandler,
promoteHandler: promoteHandler,
flaggerHandler: flaggerHandler,
dashboardModule: dashboardModule,
dashboardHandler: dashboardHandler,
metricsExplorerHandler: metricsExplorerHandler,
gatewayHandler: gatewayHandler,
fieldsHandler: fieldsHandler,
authzHandler: authzHandler,
rawDataExportHandler: rawDataExportHandler,
zeusHandler: zeusHandler,
querierHandler: querierHandler,
serviceAccountHandler: serviceAccountHandler,
factoryHandler: factoryHandler,
}
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz)
@@ -258,10 +252,6 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return err
}
if err := provider.addCloudIntegrationRoutes(router); err != nil {
return err
}
return nil
}

View File

@@ -111,7 +111,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v1/user", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListUsers), handler.OpenAPIDef{
if err := router.Handle("/api/v1/user", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListUsersDeprecated), handler.OpenAPIDef{
ID: "ListUsers",
Tags: []string{"users"},
Summary: "List users",
@@ -128,7 +128,24 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v1/user/me", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetMyUser), handler.OpenAPIDef{
if err := router.Handle("/api/v2/users", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListUsers), handler.OpenAPIDef{
ID: "ListUsersV2",
Tags: []string{"users"},
Summary: "List users v2",
Description: "This endpoint lists all users for the organization",
Request: nil,
RequestContentType: "",
Response: make([]*types.User, 0),
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/user/me", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetMyUserDeprecated), handler.OpenAPIDef{
ID: "GetMyUser",
Tags: []string{"users"},
Summary: "Get my user",
@@ -145,7 +162,41 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.GetUser), handler.OpenAPIDef{
if err := router.Handle("/api/v2/users/me", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetMyUser), handler.OpenAPIDef{
ID: "GetMyUserV2",
Tags: []string{"users"},
Summary: "Get my user v2",
Description: "This endpoint returns the user I belong to",
Request: nil,
RequestContentType: "",
Response: new(authtypes.UserWithRoles),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderTokenizer.StringValue()}},
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/users/me", handler.New(provider.authZ.OpenAccess(provider.userHandler.UpdateMyUser), handler.OpenAPIDef{
ID: "UpdateMyUserV2",
Tags: []string{"users"},
Summary: "Update my user v2",
Description: "This endpoint updates the user I belong to",
Request: new(types.UpdatableSelfUser),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderTokenizer.StringValue()}},
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.GetUserDeprecated), handler.OpenAPIDef{
ID: "GetUser",
Tags: []string{"users"},
Summary: "Get user",
@@ -162,7 +213,24 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.UpdateUser), handler.OpenAPIDef{
if err := router.Handle("/api/v2/users/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetUser), handler.OpenAPIDef{
ID: "GetUserV2",
Tags: []string{"users"},
Summary: "Get user by user id",
Description: "This endpoint returns the user by id",
Request: nil,
RequestContentType: "",
Response: new(types.User),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.UpdateUserDeprecated), handler.OpenAPIDef{
ID: "UpdateUser",
Tags: []string{"users"},
Summary: "Update user",
@@ -179,6 +247,23 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v2/users/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.UpdateUser), handler.OpenAPIDef{
ID: "UpdateUserV2",
Tags: []string{"users"},
Summary: "Update user v2",
Description: "This endpoint updates the user by id",
Request: new(types.UpdatableUser),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.DeleteUser), handler.OpenAPIDef{
ID: "DeleteUser",
Tags: []string{"users"},
@@ -264,5 +349,39 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v2/users/{id}/roles", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetUserRoles), handler.OpenAPIDef{
ID: "GetUserRoles",
Tags: []string{"users"},
Summary: "Get user roles",
Description: "This endpoint returns the user roles by user id",
Request: nil,
RequestContentType: "",
Response: make([]*authtypes.Role, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/roles/{id}/users", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetUsersByRoleID), handler.OpenAPIDef{
ID: "GetUsersByRoleID",
Tags: []string{"users"},
Summary: "Get users by role id",
Description: "This endpoint returns the users having the role by role id",
Request: nil,
RequestContentType: "",
Response: make([]*types.User, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -53,7 +53,7 @@ type Module interface {
}
type Handler interface {
CreateAccount(http.ResponseWriter, *http.Request)
GetConnectionArtifact(http.ResponseWriter, *http.Request)
ListAccounts(http.ResponseWriter, *http.Request)
GetAccount(http.ResponseWriter, *http.Request)
UpdateAccount(http.ResponseWriter, *http.Request)

View File

@@ -1,58 +0,0 @@
package implcloudintegration
import (
"net/http"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
)
type handler struct{}
func NewHandler() cloudintegration.Handler {
return &handler{}
}
func (handler *handler) CreateAccount(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}
func (handler *handler) ListAccounts(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}
func (handler *handler) GetAccount(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}
func (handler *handler) UpdateAccount(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}
func (handler *handler) DisconnectAccount(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}
func (handler *handler) ListServicesMetadata(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}
func (handler *handler) GetService(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}
func (handler *handler) UpdateService(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}
func (handler *handler) AgentCheckIn(writer http.ResponseWriter, request *http.Request) {
// TODO implement me
panic("implement me")
}

View File

@@ -37,7 +37,7 @@ func (module *getter) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID)
return rootUser, userRoles, nil
}
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.DeprecatedUser, error) {
func (module *getter) ListByOrgIDDeprecated(ctx context.Context, orgID valuer.UUID) ([]*types.DeprecatedUser, error) {
users, err := module.store.ListUsersByOrgID(ctx, orgID)
if err != nil {
return nil, err
@@ -84,6 +84,23 @@ func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*ty
return deprecatedUsers, nil
}
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.User, error) {
users, err := module.store.ListUsersByOrgID(ctx, orgID)
if err != nil {
return nil, err
}
// filter root users if feature flag `hide_root_users` is true
evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
hideRootUsers := module.flagger.BooleanOrEmpty(ctx, flagger.FeatureHideRootUser, evalCtx)
if hideRootUsers {
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.IsRoot })
}
return users, nil
}
func (module *getter) GetDeprecatedUserByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.DeprecatedUser, error) {
user, err := module.store.GetByOrgIDAndID(ctx, orgID, id)
if err != nil {
@@ -99,11 +116,19 @@ func (module *getter) GetDeprecatedUserByOrgIDAndID(ctx context.Context, orgID v
return nil, errors.New(errors.TypeUnexpected, authtypes.ErrCodeUserRolesNotFound, "no user roles entries found")
}
if userRoles[0].Role == nil {
return nil, errors.New(errors.TypeUnexpected, authtypes.ErrCodeRoleNotFound, "role not found for user role entry")
}
role := authtypes.SigNozManagedRoleToExistingLegacyRole[userRoles[0].Role.Name]
return types.NewDeprecatedUserFromUserAndRole(user, role), nil
}
func (module *getter) GetUserByOrgIDAndID(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) (*types.User, error) {
return module.store.GetByOrgIDAndID(ctx, orgID, userID)
}
func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.DeprecatedUser, error) {
user, err := module.store.GetUser(ctx, id)
if err != nil {
@@ -119,6 +144,10 @@ func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.Deprecate
return nil, errors.New(errors.TypeUnexpected, authtypes.ErrCodeUserRolesNotFound, "no user roles entries found")
}
if userRoles[0].Role == nil {
return nil, errors.New(errors.TypeUnexpected, authtypes.ErrCodeRoleNotFound, "role not found for user role entry")
}
role := authtypes.SigNozManagedRoleToExistingLegacyRole[userRoles[0].Role.Name]
return types.NewDeprecatedUserFromUserAndRole(user, role), nil
@@ -180,5 +209,15 @@ func (module *getter) GetUserRoles(ctx context.Context, userID valuer.UUID) ([]*
return nil, err
}
for _, ur := range userRoles {
if ur.Role == nil {
return nil, errors.New(errors.TypeUnexpected, authtypes.ErrCodeRoleNotFound, "role not found for user role entry")
}
}
return userRoles, nil
}
func (module *getter) GetUsersByOrgIDAndRoleID(ctx context.Context, orgID valuer.UUID, roleID valuer.UUID) ([]*types.User, error) {
return module.store.GetUsersByOrgIDAndRoleID(ctx, orgID, roleID)
}

View File

@@ -85,7 +85,7 @@ func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusCreated, nil)
}
func (h *handler) GetUser(w http.ResponseWriter, r *http.Request) {
func (h *handler) GetUserDeprecated(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -106,7 +106,28 @@ func (h *handler) GetUser(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusOK, user)
}
func (h *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
func (h *handler) GetUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
userID := mux.Vars(r)["id"]
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
user, err := h.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID))
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, user)
}
func (h *handler) GetMyUserDeprecated(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -125,6 +146,85 @@ func (h *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusOK, user)
}
func (h *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
user, err := h.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
if err != nil {
render.Error(w, err)
return
}
userRoles, err := h.getter.GetUserRoles(ctx, user.ID)
if err != nil {
render.Error(w, err)
return
}
roles := make([]*authtypes.Role, len(userRoles))
for idx, userRole := range userRoles {
roles[idx] = authtypes.NewRoleFromStorableRole(userRole.Role)
}
userWithRoles := &authtypes.UserWithRoles{
User: user,
Roles: roles,
}
render.Success(w, http.StatusOK, userWithRoles)
}
func (h *handler) UpdateMyUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
updatableSelfUser := new(types.UpdatableSelfUser)
if err := json.NewDecoder(r.Body).Decode(&updatableSelfUser); err != nil {
render.Error(w, err)
return
}
_, err = h.setter.UpdateMyUser(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), updatableSelfUser)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusNoContent, nil)
}
func (h *handler) ListUsersDeprecated(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
users, err := h.getter.ListByOrgIDDeprecated(ctx, valuer.MustNewUUID(claims.OrgID))
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, users)
}
func (h *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -144,7 +244,7 @@ func (h *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusOK, users)
}
func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
func (h *handler) UpdateUserDeprecated(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -162,7 +262,7 @@ func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
return
}
updatedUser, err := h.setter.UpdateUser(ctx, valuer.MustNewUUID(claims.OrgID), id, &user, claims.UserID)
updatedUser, err := h.setter.UpdateUserDeprecated(ctx, valuer.MustNewUUID(claims.OrgID), id, &user, claims.UserID)
if err != nil {
render.Error(w, err)
return
@@ -171,6 +271,33 @@ func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusOK, updatedUser)
}
func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
userID := mux.Vars(r)["id"]
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
updatableUser := new(types.UpdatableUser)
if err := json.NewDecoder(r.Body).Decode(&updatableUser); err != nil {
render.Error(w, err)
return
}
_, err = h.setter.UpdateUser(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID), updatableUser, valuer.MustNewUUID(claims.UserID))
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusNoContent, nil)
}
func (h *handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -443,3 +570,56 @@ func (h *handler) RevokeAPIKey(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusNoContent, nil)
}
func (h *handler) GetUserRoles(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
userID := mux.Vars(r)["id"]
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
user, err := h.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID))
if err != nil {
render.Error(w, err)
return
}
userRoles, err := h.getter.GetUserRoles(ctx, user.ID)
if err != nil {
render.Error(w, err)
return
}
roles := make([]*authtypes.Role, len(userRoles))
for idx, userRole := range userRoles {
roles[idx] = authtypes.NewRoleFromStorableRole(userRole.Role)
}
render.Success(w, http.StatusOK, roles)
}
func (h *handler) GetUsersByRoleID(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
roleID := mux.Vars(r)["id"]
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
users, err := h.getter.GetUsersByOrgIDAndRoleID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(roleID))
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, users)
}

View File

@@ -156,9 +156,7 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
existingUser.PromoteToRoot()
err = s.store.RunInTx(ctx, func(ctx context.Context) error {
// update users table
deprecatedUser := types.NewDeprecatedUserFromUserAndRole(existingUser, types.RoleAdmin)
if err := s.setter.UpdateAnyUser(ctx, orgID, deprecatedUser); err != nil {
if err := s.setter.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
return err
}
@@ -201,8 +199,7 @@ func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID,
if existingRoot.Email != s.config.Email {
existingRoot.UpdateEmail(s.config.Email)
deprecatedUser := types.NewDeprecatedUserFromUserAndRole(existingRoot, types.RoleAdmin)
if err := s.setter.UpdateAnyUser(ctx, orgID, deprecatedUser); err != nil {
if err := s.setter.UpdateAnyUser(ctx, orgID, existingRoot); err != nil {
return err
}
}

View File

@@ -220,7 +220,7 @@ func (module *setter) CreateUser(ctx context.Context, user *types.User, opts ...
return nil
}
func (module *setter) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.DeprecatedUser, updatedBy string) (*types.DeprecatedUser, error) {
func (module *setter) UpdateUserDeprecated(ctx context.Context, orgID valuer.UUID, id string, user *types.DeprecatedUser, updatedBy string) (*types.DeprecatedUser, error) {
existingUser, err := module.getter.GetDeprecatedUserByOrgIDAndID(ctx, orgID, valuer.MustNewUUID(id))
if err != nil {
return nil, err
@@ -265,7 +265,7 @@ func (module *setter) UpdateUser(ctx context.Context, orgID valuer.UUID, id stri
existingUser.Update(user.DisplayName, user.Role)
// update the user - idempotent (this does analytics too so keeping it outside txn)
if err := module.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
if err := module.UpdateAnyUserDeprecated(ctx, orgID, existingUser); err != nil {
return nil, err
}
@@ -291,7 +291,113 @@ func (module *setter) UpdateUser(ctx context.Context, orgID valuer.UUID, id stri
return existingUser, nil
}
func (module *setter) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, deprecateUser *types.DeprecatedUser) error {
func (module *setter) UpdateMyUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, updatable *types.UpdatableSelfUser) (*types.User, error) {
existingUser, err := module.getter.GetUserByOrgIDAndID(ctx, orgID, userID)
if err != nil {
return nil, err
}
if err := existingUser.ErrIfRoot(); err != nil {
return nil, errors.WithAdditionalf(err, "cannot update root user")
}
if err := existingUser.ErrIfDeleted(); err != nil {
return nil, errors.WithAdditionalf(err, "cannot update deleted user")
}
existingUser.Update(updatable.DisplayName)
if err := module.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
return nil, err
}
return existingUser, nil
}
func (module *setter) UpdateUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, updatable *types.UpdatableUser, updatedBy valuer.UUID) (*types.User, error) {
existingUser, err := module.getter.GetUserByOrgIDAndID(ctx, orgID, userID)
if err != nil {
return nil, err
}
if err := existingUser.ErrIfRoot(); err != nil {
return nil, errors.WithAdditionalf(err, "cannot update root user")
}
if err := existingUser.ErrIfDeleted(); err != nil {
return nil, errors.WithAdditionalf(err, "cannot update deleted user")
}
existingUserRoles, err := module.getter.GetUserRoles(ctx, existingUser.ID)
if err != nil {
return nil, err
}
existingUserRoleNames := roleNamesFromUserRoles(existingUserRoles)
var grants, revokes []string
var rolesChanged bool
if len(updatable.RoleNames) > 0 {
// validate that all requested role names exist before making any changes
_, err := module.authz.ListByOrgIDAndNames(ctx, orgID, updatable.RoleNames)
if err != nil {
return nil, err
}
}
grants, revokes = module.patchRolesNames(existingUserRoleNames, updatable.RoleNames)
rolesChanged = (len(grants) > 0) || (len(revokes) > 0)
if rolesChanged && existingUser.ID == updatedBy {
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot change self roles")
}
if rolesChanged {
err = module.authz.ModifyGrant(
ctx,
orgID,
revokes,
grants,
authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil),
)
if err != nil {
return nil, err
}
}
existingUser.Update(updatable.DisplayName)
if err := module.UpdateAnyUser(ctx, existingUser.OrgID, existingUser); err != nil {
return nil, err
}
if rolesChanged {
// this by default runs in txn
if err := module.UpdateUserRoles(ctx, existingUser.OrgID, existingUser.ID, updatable.RoleNames); err != nil {
return nil, err
}
}
return existingUser, nil
}
func (module *setter) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
if err := module.store.UpdateUser(ctx, orgID, user); err != nil {
return err
}
if err := module.tokenizer.DeleteIdentity(ctx, user.ID); err != nil {
return err
}
// stats collector things
traits := types.NewTraitsFromUser(user)
module.analytics.IdentifyUser(ctx, user.OrgID.String(), user.ID.String(), traits)
module.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Updated", traits)
return nil
}
func (module *setter) UpdateAnyUserDeprecated(ctx context.Context, orgID valuer.UUID, deprecateUser *types.DeprecatedUser) error {
user := types.NewUserFromDeprecatedUser(deprecateUser)
if err := module.store.UpdateUser(ctx, orgID, user); err != nil {
return err
@@ -801,13 +907,17 @@ func (module *setter) activatePendingUser(ctx context.Context, user *types.User,
func (module *setter) UpdateUserRoles(ctx context.Context, orgID, userID valuer.UUID, finalRoleNames []string) error {
return module.store.RunInTx(ctx, func(ctx context.Context) error {
// delete old user_role entries and create new ones from SSO
// delete old user_role entries
if err := module.userRoleStore.DeleteUserRoles(ctx, userID); err != nil {
return err
}
// create fresh ones
return module.createUserRoleEntries(ctx, orgID, userID, finalRoleNames)
// create fresh ones only if there are roles to assign
if len(finalRoleNames) > 0 {
return module.createUserRoleEntries(ctx, orgID, userID, finalRoleNames)
}
return nil
})
}
@@ -820,3 +930,33 @@ func roleNamesFromUserRoles(userRoles []*authtypes.UserRole) []string {
}
return names
}
func (module *setter) patchRolesNames(currentRolesNames, targetRoleNames []string) ([]string, []string) {
currentRolesSet := make(map[string]struct{}, len(currentRolesNames))
targetRolesSet := make(map[string]struct{}, len(targetRoleNames))
for _, role := range currentRolesNames {
currentRolesSet[role] = struct{}{}
}
for _, role := range targetRoleNames {
targetRolesSet[role] = struct{}{}
}
// additions: roles present in input but not in current
additions := []string{}
for _, role := range targetRoleNames {
if _, exists := currentRolesSet[role]; !exists {
additions = append(additions, role)
}
}
// deletions: roles present in current but not in input
deletions := []string{}
for _, role := range currentRolesNames {
if _, exists := targetRolesSet[role]; !exists {
deletions = append(deletions, role)
}
}
return additions, deletions
}

View File

@@ -667,3 +667,22 @@ func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID
return users, nil
}
func (store *store) GetUsersByOrgIDAndRoleID(ctx context.Context, orgID valuer.UUID, roleID valuer.UUID) ([]*types.User, error) {
users := []*types.User{}
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(&users).
Join(`JOIN user_role ON user_role.user_id = "users".id`).
Where(`"users".org_id = ?`, orgID).
Where("user_role.role_id = ?", roleID).
Scan(ctx)
if err != nil {
return nil, err
}
return users, nil
}

View File

@@ -34,10 +34,13 @@ type Setter interface {
// Initiate forgot password flow for a user
ForgotPassword(ctx context.Context, orgID valuer.UUID, email valuer.Email, frontendBaseURL string) error
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.DeprecatedUser, updatedBy string) (*types.DeprecatedUser, error)
UpdateUserDeprecated(ctx context.Context, orgID valuer.UUID, id string, user *types.DeprecatedUser, updatedBy string) (*types.DeprecatedUser, error)
UpdateMyUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, updatable *types.UpdatableSelfUser) (*types.User, error)
UpdateUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, updatable *types.UpdatableUser, updatedBy valuer.UUID) (*types.User, error)
// UpdateAnyUser updates a user and persists the changes to the database along with the analytics and identity deletion.
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.DeprecatedUser) error
UpdateAnyUserDeprecated(ctx context.Context, orgID valuer.UUID, deprecateUser *types.DeprecatedUser) error
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error
DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error
// invite
@@ -60,11 +63,13 @@ type Getter interface {
// Get root user by org id.
GetRootUserByOrgID(context.Context, valuer.UUID) (*types.User, []*authtypes.UserRole, error)
// Get gets the users based on the given id
ListByOrgID(context.Context, valuer.UUID) ([]*types.DeprecatedUser, error)
// Get gets the users based on the given org id
ListByOrgIDDeprecated(context.Context, valuer.UUID) ([]*types.DeprecatedUser, error)
ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.User, error)
// Get deprecated user object by orgID and id.
GetDeprecatedUserByOrgIDAndID(context.Context, valuer.UUID, valuer.UUID) (*types.DeprecatedUser, error)
GetUserByOrgIDAndID(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) (*types.User, error)
// Get user by id.
Get(context.Context, valuer.UUID) (*types.DeprecatedUser, error)
@@ -86,6 +91,9 @@ type Getter interface {
// Gets user_role with roles entries from db
GetUserRoles(ctx context.Context, userID valuer.UUID) ([]*authtypes.UserRole, error)
// Gets all the user with role using role id in an org id
GetUsersByOrgIDAndRoleID(ctx context.Context, orgID valuer.UUID, roleID valuer.UUID) ([]*types.User, error)
}
type Handler interface {
@@ -93,11 +101,19 @@ type Handler interface {
CreateInvite(http.ResponseWriter, *http.Request)
CreateBulkInvite(http.ResponseWriter, *http.Request)
// users
ListUsersDeprecated(http.ResponseWriter, *http.Request)
ListUsers(http.ResponseWriter, *http.Request)
UpdateUserDeprecated(http.ResponseWriter, *http.Request)
UpdateUser(http.ResponseWriter, *http.Request)
DeleteUser(http.ResponseWriter, *http.Request)
GetUserDeprecated(http.ResponseWriter, *http.Request)
GetUser(http.ResponseWriter, *http.Request)
GetMyUserDeprecated(http.ResponseWriter, *http.Request)
GetMyUser(http.ResponseWriter, *http.Request)
UpdateMyUser(http.ResponseWriter, *http.Request)
GetUserRoles(http.ResponseWriter, *http.Request)
GetUsersByRoleID(http.ResponseWriter, *http.Request)
// Reset Password
GetResetPasswordToken(http.ResponseWriter, *http.Request)

View File

@@ -12,8 +12,6 @@ import (
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/apdex"
"github.com/SigNoz/signoz/pkg/modules/apdex/implapdex"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
@@ -40,25 +38,24 @@ import (
)
type Handlers struct {
SavedView savedview.Handler
Apdex apdex.Handler
Dashboard dashboard.Handler
QuickFilter quickfilter.Handler
TraceFunnel tracefunnel.Handler
RawDataExport rawdataexport.Handler
SpanPercentile spanpercentile.Handler
Services services.Handler
MetricsExplorer metricsexplorer.Handler
Global global.Handler
FlaggerHandler flagger.Handler
GatewayHandler gateway.Handler
Fields fields.Handler
AuthzHandler authz.Handler
ZeusHandler zeus.Handler
QuerierHandler querier.Handler
ServiceAccountHandler serviceaccount.Handler
RegistryHandler factory.Handler
CloudIntegrationHandler cloudintegration.Handler
SavedView savedview.Handler
Apdex apdex.Handler
Dashboard dashboard.Handler
QuickFilter quickfilter.Handler
TraceFunnel tracefunnel.Handler
RawDataExport rawdataexport.Handler
SpanPercentile spanpercentile.Handler
Services services.Handler
MetricsExplorer metricsexplorer.Handler
Global global.Handler
FlaggerHandler flagger.Handler
GatewayHandler gateway.Handler
Fields fields.Handler
AuthzHandler authz.Handler
ZeusHandler zeus.Handler
QuerierHandler querier.Handler
ServiceAccountHandler serviceaccount.Handler
RegistryHandler factory.Handler
}
func NewHandlers(
@@ -76,24 +73,23 @@ func NewHandlers(
registryHandler factory.Handler,
) Handlers {
return Handlers{
SavedView: implsavedview.NewHandler(modules.SavedView),
Apdex: implapdex.NewHandler(modules.Apdex),
Dashboard: impldashboard.NewHandler(modules.Dashboard, providerSettings),
QuickFilter: implquickfilter.NewHandler(modules.QuickFilter),
TraceFunnel: impltracefunnel.NewHandler(modules.TraceFunnel),
RawDataExport: implrawdataexport.NewHandler(modules.RawDataExport),
Services: implservices.NewHandler(modules.Services),
MetricsExplorer: implmetricsexplorer.NewHandler(modules.MetricsExplorer),
SpanPercentile: implspanpercentile.NewHandler(modules.SpanPercentile),
Global: signozglobal.NewHandler(global),
FlaggerHandler: flagger.NewHandler(flaggerService),
GatewayHandler: gateway.NewHandler(gatewayService),
Fields: implfields.NewHandler(providerSettings, telemetryMetadataStore),
AuthzHandler: signozauthzapi.NewHandler(authz),
ZeusHandler: zeus.NewHandler(zeusService, licensing),
QuerierHandler: querierHandler,
ServiceAccountHandler: implserviceaccount.NewHandler(modules.ServiceAccount),
RegistryHandler: registryHandler,
CloudIntegrationHandler: implcloudintegration.NewHandler(),
SavedView: implsavedview.NewHandler(modules.SavedView),
Apdex: implapdex.NewHandler(modules.Apdex),
Dashboard: impldashboard.NewHandler(modules.Dashboard, providerSettings),
QuickFilter: implquickfilter.NewHandler(modules.QuickFilter),
TraceFunnel: impltracefunnel.NewHandler(modules.TraceFunnel),
RawDataExport: implrawdataexport.NewHandler(modules.RawDataExport),
Services: implservices.NewHandler(modules.Services),
MetricsExplorer: implmetricsexplorer.NewHandler(modules.MetricsExplorer),
SpanPercentile: implspanpercentile.NewHandler(modules.SpanPercentile),
Global: signozglobal.NewHandler(global),
FlaggerHandler: flagger.NewHandler(flaggerService),
GatewayHandler: gateway.NewHandler(gatewayService),
Fields: implfields.NewHandler(providerSettings, telemetryMetadataStore),
AuthzHandler: signozauthzapi.NewHandler(authz),
ZeusHandler: zeus.NewHandler(zeusService, licensing),
QuerierHandler: querierHandler,
ServiceAccountHandler: implserviceaccount.NewHandler(modules.ServiceAccount),
RegistryHandler: registryHandler,
}
}

View File

@@ -17,7 +17,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/instrumentation"
"github.com/SigNoz/signoz/pkg/modules/authdomain"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
@@ -66,7 +65,6 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
struct{ querier.Handler }{},
struct{ serviceaccount.Handler }{},
struct{ factory.Handler }{},
struct{ cloudintegration.Handler }{},
).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{})
if err != nil {
return nil, err

View File

@@ -279,7 +279,6 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
handlers.QuerierHandler,
handlers.ServiceAccountHandler,
handlers.RegistryHandler,
handlers.CloudIntegrationHandler,
),
)
}

View File

@@ -178,7 +178,7 @@ func (provider *provider) Report(ctx context.Context) error {
}
for _, user := range users {
traits := types.NewTraitsFromDeprecatedUser(user)
traits := types.NewTraitsFromUser(user)
if maxLastObservedAt, ok := maxLastObservedAtPerUserID[user.ID]; ok {
traits["auth_token.last_observed_at.max.time"] = maxLastObservedAt.UTC()
traits["auth_token.last_observed_at.max.time_unix"] = maxLastObservedAt.Unix()

View File

@@ -5,13 +5,14 @@ import (
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
var (
ErrCodeUserRoleAlreadyExists = errors.MustNewCode("user_role_already_exists")
ErrCodeUserRolesNotFound = errors.MustNewCode("user_roles_not_found")
ErrCodeUserRolesNotFound = errors.MustNewCode("user_roles_not_found")
)
type UserRole struct {
@@ -47,6 +48,11 @@ func NewUserRoles(userID valuer.UUID, roles []*Role) []*UserRole {
return userRoles
}
type UserWithRoles struct {
*types.User
Roles []*Role `json:"roles"`
}
type UserRoleStore interface {
// create user roles in bulk
CreateUserRoles(ctx context.Context, userRoles []*UserRole) error

View File

@@ -10,35 +10,34 @@ import (
type Account struct {
types.Identifiable
types.TimeAuditable
ProviderAccountID *string `json:"providerAccountId" required:"true" nullable:"true"`
Provider CloudProviderType `json:"provider" required:"true"`
RemovedAt *time.Time `json:"removedAt" required:"true" nullable:"true"`
AgentReport *AgentReport `json:"agentReport" required:"true" nullable:"true"`
OrgID valuer.UUID `json:"orgId" required:"true"`
Config *AccountConfig `json:"config" required:"true" nullable:"false"`
ProviderAccountId *string `json:"providerAccountID,omitempty"`
Provider CloudProviderType `json:"provider"`
RemovedAt *time.Time `json:"removedAt,omitempty"`
AgentReport *AgentReport `json:"agentReport,omitempty"`
OrgID valuer.UUID `json:"orgID"`
Config *AccountConfig `json:"config,omitempty"`
}
// AgentReport represents heartbeats sent by the agent.
type AgentReport struct {
TimestampMillis int64 `json:"timestampMillis" required:"true"`
Data map[string]any `json:"data" required:"true" nullable:"true"`
}
type AccountConfig struct {
// required till new providers are added
AWS *AWSAccountConfig `json:"aws" required:"true" nullable:"false"`
TimestampMillis int64 `json:"timestampMillis"`
Data map[string]any `json:"data"`
}
type GettableAccounts struct {
Accounts []*Account `json:"accounts" required:"true" nullable:"false"`
Accounts []*Account `json:"accounts"`
}
type GettableAccount = Account
type UpdatableAccount struct {
Config *AccountConfig `json:"config" required:"true" nullable:"false"`
Config *AccountConfig `json:"config"`
}
type AccountConfig struct {
AWS *AWSAccountConfig `json:"aws,omitempty"`
}
type AWSAccountConfig struct {
Regions []string `json:"regions" required:"true" nullable:"false"`
Regions []string `json:"regions"`
}

View File

@@ -1,81 +1,88 @@
package cloudintegrationtypes
import (
"time"
"github.com/SigNoz/signoz/pkg/valuer"
)
import "github.com/SigNoz/signoz/pkg/types/integrationtypes"
type ConnectionArtifactRequest struct {
// required till new providers are added
Aws *AWSConnectionArtifactRequest `json:"aws" required:"true" nullable:"false"`
Aws *AWSConnectionArtifactRequest `json:"aws"`
}
type AWSConnectionArtifactRequest struct {
DeploymentRegion string `json:"deploymentRegion" required:"true"`
Regions []string `json:"regions" required:"true" nullable:"false"`
DeploymentRegion string `json:"deploymentRegion"`
Regions []string `json:"regions"`
}
type PostableConnectionArtifact = ConnectionArtifactRequest
type ConnectionArtifact struct {
// required till new providers are added
Aws *AWSConnectionArtifact `json:"aws" required:"true" nullable:"false"`
Aws *AWSConnectionArtifact `json:"aws"`
}
type AWSConnectionArtifact struct {
ConnectionURL string `json:"connectionURL" required:"true"`
ConnectionUrl string `json:"connectionURL"`
}
type GettableAccountWithArtifact struct {
ID valuer.UUID `json:"id" required:"true"`
Artifact *ConnectionArtifact `json:"connectionArtifact" required:"true"`
type GettableConnectionArtifact = ConnectionArtifact
type AccountStatus struct {
Id string `json:"id"`
ProviderAccountId *string `json:"providerAccountID,omitempty"`
Status integrationtypes.AccountStatus `json:"status"`
}
type GettableAccountStatus = AccountStatus
type AgentCheckInRequest struct {
ProviderAccountID string `json:"providerAccountId" required:"false"`
CloudIntegrationID string `json:"cloudIntegrationId" required:"false"`
// older backward compatible fields are mapped to new fields
// CloudIntegrationId string `json:"cloudIntegrationId"`
// AccountId string `json:"accountId"`
Data map[string]any `json:"data" required:"true" nullable:"true"`
// New fields
ProviderAccountId string `json:"providerAccountId"`
CloudAccountId string `json:"cloudAccountId"`
Data map[string]any `json:"data,omitempty"`
}
type PostableAgentCheckInRequest struct {
AgentCheckInRequest
// following are backward compatible fields for older running agents
// which gets mapped to new fields in AgentCheckInRequest
ID string `json:"account_id" required:"false"` // => CloudIntegrationID
AccountID string `json:"cloud_account_id" required:"false"` // => ProviderAccountID
}
type AgentCheckInResponse struct {
CloudIntegrationID string `json:"cloudIntegrationId" required:"true"`
ProviderAccountID string `json:"providerAccountId" required:"true"`
IntegrationConfig *ProviderIntegrationConfig `json:"integrationConfig" required:"true"`
RemovedAt *time.Time `json:"removedAt" required:"true" nullable:"true"`
CloudIntegrationId string `json:"cloud_integration_id"`
CloudAccountId string `json:"cloud_account_id"`
}
type GettableAgentCheckInResponse struct {
// Older fields for backward compatibility with existing AWS agents
AccountID string `json:"account_id" required:"true"`
CloudAccountID string `json:"cloud_account_id" required:"true"`
OlderIntegrationConfig *IntegrationConfig `json:"integration_config" required:"true" nullable:"true"`
OlderRemovedAt *time.Time `json:"removed_at" required:"true" nullable:"true"`
AgentCheckInResponse
// For backward compatibility
CloudIntegrationId string `json:"cloud_integration_id"`
AccountId string `json:"account_id"`
}
type AgentCheckInResponse struct {
// Older fields for backward compatibility are mapped to new fields below
// CloudIntegrationId string `json:"cloud_integration_id"`
// AccountId string `json:"account_id"`
// New fields
ProviderAccountId string `json:"providerAccountId"`
CloudAccountId string `json:"cloudAccountId"`
// IntegrationConfig populates data related to integration that is required for an agent
// to start collecting telemetry data
// keeping JSON key snake_case for backward compatibility
IntegrationConfig *IntegrationConfig `json:"integration_config,omitempty"`
}
// IntegrationConfig older integration config struct for backward compatibility,
// this will be eventually removed once agents are updated to use new struct.
type IntegrationConfig struct {
EnabledRegions []string `json:"enabled_regions" required:"true" nullable:"false"` // backward compatible
Telemetry *AWSCollectionStrategy `json:"telemetry" required:"true" nullable:"false"` // backward compatible
}
EnabledRegions []string `json:"enabledRegions"` // backward compatible
Telemetry *AWSCollectionStrategy `json:"telemetry,omitempty"` // backward compatible
type ProviderIntegrationConfig struct {
AWS *AWSIntegrationConfig `json:"aws" required:"true" nullable:"false"`
// new fields
AWS *AWSIntegrationConfig `json:"aws,omitempty"`
}
type AWSIntegrationConfig struct {
EnabledRegions []string `json:"enabledRegions" required:"true" nullable:"false"`
Telemetry *AWSCollectionStrategy `json:"telemetry" required:"true" nullable:"false"`
EnabledRegions []string `json:"enabledRegions"`
Telemetry *AWSCollectionStrategy `json:"telemetry,omitempty"`
}

View File

@@ -10,19 +10,20 @@ import (
"github.com/SigNoz/signoz/pkg/valuer"
)
var ErrCodeInvalidServiceID = errors.MustNewCode("invalid_service_id")
var (
S3Sync = valuer.NewString("s3sync")
// ErrCodeInvalidServiceID is the error code for invalid service id.
ErrCodeInvalidServiceID = errors.MustNewCode("invalid_service_id")
)
type ServiceID struct{ valuer.String }
type CloudIntegrationService struct {
types.Identifiable
types.TimeAuditable
Type ServiceID `json:"type"`
Config *ServiceConfig `json:"config"`
CloudIntegrationID valuer.UUID `json:"cloudIntegrationId"`
}
type ServiceConfig struct {
// required till new providers are added
AWS *AWSServiceConfig `json:"aws" required:"true" nullable:"false"`
CloudIntegrationID valuer.UUID `json:"cloudIntegrationID"`
}
// ServiceMetadata helps to quickly list available services and whether it is enabled or not.
@@ -31,56 +32,26 @@ type ServiceConfig struct {
type ServiceMetadata struct {
ServiceDefinitionMetadata
// if the service is enabled for the account
Enabled bool `json:"enabled" required:"true"`
}
// ServiceDefinitionMetadata represents service definition metadata. This is useful for showing service tab in frontend.
type ServiceDefinitionMetadata struct {
ID string `json:"id" required:"true"`
Title string `json:"title" required:"true"`
Icon string `json:"icon" required:"true"`
Enabled bool `json:"enabled"`
}
type GettableServicesMetadata struct {
Services []*ServiceMetadata `json:"services" required:"true" nullable:"false"`
Services []*ServiceMetadata `json:"services"`
}
type Service struct {
ServiceDefinition
ServiceConfig *ServiceConfig `json:"serviceConfig" required:"false" nullable:"false"`
ServiceConfig *ServiceConfig `json:"serviceConfig"`
}
type GettableService = Service
type UpdatableService struct {
Config *ServiceConfig `json:"config" required:"true" nullable:"false"`
Config *ServiceConfig `json:"config"`
}
type ServiceDefinition struct {
ServiceDefinitionMetadata
Overview string `json:"overview" required:"true"` // markdown
Assets Assets `json:"assets" required:"true"`
SupportedSignals SupportedSignals `json:"supported_signals" required:"true"`
DataCollected DataCollected `json:"dataCollected" required:"true"`
Strategy *CollectionStrategy `json:"telemetryCollectionStrategy" required:"true" nullable:"false"`
}
// SupportedSignals for cloud provider's service.
type SupportedSignals struct {
Logs bool `json:"logs"`
Metrics bool `json:"metrics"`
}
// DataCollected is curated static list of metrics and logs, this is shown as part of service overview.
type DataCollected struct {
Logs []CollectedLogAttribute `json:"logs"`
Metrics []CollectedMetric `json:"metrics"`
}
// CollectionStrategy is cloud provider specific configuration for signal collection,
// this is used by agent to understand the nitty-gritty for collecting telemetry for the cloud provider.
type CollectionStrategy struct {
AWS *AWSCollectionStrategy `json:"aws" required:"true" nullable:"false"`
type ServiceConfig struct {
AWS *AWSServiceConfig `json:"aws,omitempty"`
}
type AWSServiceConfig struct {
@@ -99,11 +70,45 @@ type AWSServiceMetricsConfig struct {
Enabled bool `json:"enabled"`
}
// ServiceDefinitionMetadata represents service definition metadata. This is useful for showing service tab in frontend.
type ServiceDefinitionMetadata struct {
Id string `json:"id"`
Title string `json:"title"`
Icon string `json:"icon"`
}
type ServiceDefinition struct {
ServiceDefinitionMetadata
Overview string `json:"overview"` // markdown
Assets Assets `json:"assets"`
SupportedSignals SupportedSignals `json:"supported_signals"`
DataCollected DataCollected `json:"dataCollected"`
Strategy *CollectionStrategy `json:"telemetryCollectionStrategy"`
}
// CollectionStrategy is cloud provider specific configuration for signal collection,
// this is used by agent to understand the nitty-gritty for collecting telemetry for the cloud provider.
type CollectionStrategy struct {
AWS *AWSCollectionStrategy `json:"aws,omitempty"`
}
// Assets represents the collection of dashboards.
type Assets struct {
Dashboards []Dashboard `json:"dashboards"`
}
// SupportedSignals for cloud provider's service.
type SupportedSignals struct {
Logs bool `json:"logs"`
Metrics bool `json:"metrics"`
}
// DataCollected is curated static list of metrics and logs, this is shown as part of service overview.
type DataCollected struct {
Logs []CollectedLogAttribute `json:"logs"`
Metrics []CollectedMetric `json:"metrics"`
}
// CollectedLogAttribute represents a log attribute that is present in all log entries for a service,
// this is shown as part of service overview.
type CollectedLogAttribute struct {
@@ -164,23 +169,56 @@ type AWSLogsStrategy struct {
// This is used to show available pre-made dashboards for a service,
// hence has additional fields like id, title and description
type Dashboard struct {
ID string `json:"id"`
Id string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Definition dashboardtypes.StorableDashboardData `json:"definition,omitempty"`
}
// SupportedServices is the map of supported services for each cloud provider.
var SupportedServices = map[CloudProviderType][]ServiceID{
CloudProviderTypeAWS: {
{valuer.NewString("alb")},
{valuer.NewString("api-gateway")},
{valuer.NewString("dynamodb")},
{valuer.NewString("ec2")},
{valuer.NewString("ecs")},
{valuer.NewString("eks")},
{valuer.NewString("elasticache")},
{valuer.NewString("lambda")},
{valuer.NewString("msk")},
{valuer.NewString("rds")},
{valuer.NewString("s3sync")},
{valuer.NewString("sns")},
{valuer.NewString("sqs")},
},
}
// NewServiceID returns a new ServiceID from a string, validated against the supported services for the given cloud provider.
func NewServiceID(provider CloudProviderType, service string) (ServiceID, error) {
services, ok := SupportedServices[provider]
if !ok {
return ServiceID{}, errors.NewInvalidInputf(ErrCodeInvalidServiceID, "no services defined for cloud provider: %s", provider)
}
for _, s := range services {
if s.StringValue() == service {
return s, nil
}
}
return ServiceID{}, errors.NewInvalidInputf(ErrCodeInvalidServiceID, "invalid service id %q for cloud provider %s", service, provider)
}
// UTILS
// GetCloudIntegrationDashboardID returns the dashboard id for a cloud integration, given the cloud provider, service id, and dashboard id.
// This is used to generate unique dashboard ids for cloud integration, and also to parse the dashboard id to get the cloud provider and service id when needed.
func GetCloudIntegrationDashboardID(cloudProvider CloudProviderType, svcID, dashboardID string) string {
return fmt.Sprintf("cloud-integration--%s--%s--%s", cloudProvider, svcID, dashboardID)
func GetCloudIntegrationDashboardID(cloudProvider CloudProviderType, svcId, dashboardId string) string {
return fmt.Sprintf("cloud-integration--%s--%s--%s", cloudProvider, svcId, dashboardId)
}
// GetDashboardsFromAssets returns the list of dashboards for the cloud provider service from definition.
func GetDashboardsFromAssets(
svcID string,
svcId string,
orgID valuer.UUID,
cloudProvider CloudProviderType,
createdAt time.Time,
@@ -191,7 +229,7 @@ func GetDashboardsFromAssets(
for _, d := range assets.Dashboards {
author := fmt.Sprintf("%s-integration", cloudProvider)
dashboards = append(dashboards, &dashboardtypes.Dashboard{
ID: GetCloudIntegrationDashboardID(cloudProvider, svcID, d.ID),
ID: GetCloudIntegrationDashboardID(cloudProvider, svcId, d.Id),
Locked: true,
OrgID: orgID,
Data: d.Definition,

View File

@@ -1,75 +0,0 @@
package cloudintegrationtypes
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/valuer"
)
type ServiceID struct{ valuer.String }
var (
AWSServiceALB = ServiceID{valuer.NewString("alb")}
AWSServiceAPIGateway = ServiceID{valuer.NewString("api-gateway")}
AWSServiceDynamoDB = ServiceID{valuer.NewString("dynamodb")}
AWSServiceEC2 = ServiceID{valuer.NewString("ec2")}
AWSServiceECS = ServiceID{valuer.NewString("ecs")}
AWSServiceEKS = ServiceID{valuer.NewString("eks")}
AWSServiceElastiCache = ServiceID{valuer.NewString("elasticache")}
AWSServiceLambda = ServiceID{valuer.NewString("lambda")}
AWSServiceMSK = ServiceID{valuer.NewString("msk")}
AWSServiceRDS = ServiceID{valuer.NewString("rds")}
AWSServiceS3Sync = ServiceID{valuer.NewString("s3sync")}
AWSServiceSNS = ServiceID{valuer.NewString("sns")}
AWSServiceSQS = ServiceID{valuer.NewString("sqs")}
)
func (ServiceID) Enum() []any {
return []any{
AWSServiceALB,
AWSServiceAPIGateway,
AWSServiceDynamoDB,
AWSServiceEC2,
AWSServiceECS,
AWSServiceEKS,
AWSServiceElastiCache,
AWSServiceLambda,
AWSServiceMSK,
AWSServiceRDS,
AWSServiceS3Sync,
AWSServiceSNS,
AWSServiceSQS,
}
}
// SupportedServices is the map of supported services for each cloud provider.
var SupportedServices = map[CloudProviderType][]ServiceID{
CloudProviderTypeAWS: {
AWSServiceALB,
AWSServiceAPIGateway,
AWSServiceDynamoDB,
AWSServiceEC2,
AWSServiceECS,
AWSServiceEKS,
AWSServiceElastiCache,
AWSServiceLambda,
AWSServiceMSK,
AWSServiceRDS,
AWSServiceS3Sync,
AWSServiceSNS,
AWSServiceSQS,
},
}
// NewServiceID returns a new ServiceID from a string, validated against the supported services for the given cloud provider.
func NewServiceID(provider CloudProviderType, service string) (ServiceID, error) {
services, ok := SupportedServices[provider]
if !ok {
return ServiceID{}, errors.NewInvalidInputf(ErrCodeInvalidServiceID, "no services defined for cloud provider: %s", provider)
}
for _, s := range services {
if s.StringValue() == service {
return s, nil
}
}
return ServiceID{}, errors.NewInvalidInputf(ErrCodeInvalidServiceID, "invalid service id %q for cloud provider %s", service, provider)
}

View File

@@ -51,6 +51,15 @@ type DeprecatedUser struct {
Role Role `json:"role"`
}
type UpdatableSelfUser struct {
DisplayName string `json:"displayName" required:"true"`
}
type UpdatableUser struct {
DisplayName string `json:"displayName" required:"true"`
RoleNames []string `json:"roleNames" required:"true" nullable:"false"`
}
type PostableRegisterOrgAndAdmin struct {
Name string `json:"name"`
Email valuer.Email `json:"email"`
@@ -298,6 +307,9 @@ type UserStore interface {
// Get user by reset password token
GetUserByResetPasswordToken(ctx context.Context, token string) (*User, error)
// Get users having role by org id and role id
GetUsersByOrgIDAndRoleID(ctx context.Context, orgID valuer.UUID, roleID valuer.UUID) ([]*User, error)
// Transaction
RunInTx(ctx context.Context, cb func(ctx context.Context) error) error
}

View File

@@ -654,19 +654,6 @@ def get_oidc_domain(signoz: types.SigNoz, admin_token: str) -> dict:
)
def get_user_by_email(signoz: types.SigNoz, admin_token: str, email: str) -> dict:
"""Helper to get a user by email."""
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
return next(
(user for user in response.json()["data"] if user["email"] == email),
None,
)
def perform_oidc_login(
signoz: types.SigNoz, # pylint: disable=unused-argument
idp: types.TestContainerIDP,

View File

@@ -3,6 +3,9 @@ import os
from typing import Any
import isodate
import requests
from fixtures import types
# parses the given timestamp string from ISO format to datetime.datetime
@@ -31,3 +34,33 @@ def parse_duration(duration: Any) -> datetime.timedelta:
def get_testdata_file_path(file: str) -> str:
testdata_dir = os.path.join(os.path.dirname(__file__), "..", "testdata")
return os.path.join(testdata_dir, file)
def get_user_by_email(signoz: types.SigNoz, admin_token: str, email: str) -> dict:
"""Helper to get a user by email."""
headers = {"Authorization": f"Bearer {admin_token}"} if admin_token else {}
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers=headers,
)
return next(
(user for user in response.json()["data"] if user["email"] == email),
None,
)
def get_user_role_names(signoz: types.SigNoz, admin_token: str, user_id: str) -> list:
"""Helper to get the user roles by user ID"""
headers = {"Authorization": f"Bearer {admin_token}"} if admin_token else {}
response = requests.get(
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles"),
timeout=2,
headers=headers,
)
roles = response.json()["data"]
if not roles:
return []
return [role["name"] for role in roles]

View File

@@ -12,11 +12,8 @@ from fixtures.auth import (
USER_ADMIN_PASSWORD,
add_license,
)
from fixtures.idputils import (
get_saml_domain,
get_user_by_email,
perform_saml_login,
)
from fixtures.idputils import get_saml_domain, perform_saml_login
from fixtures.utils import get_user_by_email, get_user_role_names
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
@@ -131,26 +128,12 @@ def test_saml_authn(
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Assert that the user was created in signoz.
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(
user
for user in user_response
if user["email"] == "viewer@saml.integration.test"
),
None,
)
found_user = get_user_by_email(signoz, admin_token, "viewer@saml.integration.test")
assert found_user is not None
assert found_user["role"] == "VIEWER"
# Confirm role
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_idp_initiated_saml_authn(
@@ -182,26 +165,14 @@ def test_idp_initiated_saml_authn(
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Assert that the user was created in signoz.
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
found_user = get_user_by_email(
signoz, admin_token, "viewer.idp.initiated@saml.integration.test"
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(
user
for user in user_response
if user["email"] == "viewer.idp.initiated@saml.integration.test"
),
None,
)
assert found_user is not None
assert found_user["role"] == "VIEWER"
# Confirm role
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_saml_update_domain_with_group_mappings(
@@ -271,7 +242,8 @@ def test_saml_role_mapping_single_group_admin(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "ADMIN"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-admin" in found_user_role_names
def test_saml_role_mapping_single_group_editor(
@@ -297,7 +269,8 @@ def test_saml_role_mapping_single_group_editor(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "EDITOR"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names
def test_saml_role_mapping_multiple_groups_highest_wins(
@@ -327,7 +300,8 @@ def test_saml_role_mapping_multiple_groups_highest_wins(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "EDITOR"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names
def test_saml_role_mapping_explicit_viewer_group(
@@ -354,7 +328,8 @@ def test_saml_role_mapping_explicit_viewer_group(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_saml_role_mapping_unmapped_group_uses_default(
@@ -380,7 +355,8 @@ def test_saml_role_mapping_unmapped_group_uses_default(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_saml_update_domain_with_use_role_claim(
@@ -457,7 +433,8 @@ def test_saml_role_mapping_role_claim_takes_precedence(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "ADMIN"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-admin" in found_user_role_names
def test_saml_role_mapping_invalid_role_claim_fallback(
@@ -487,7 +464,8 @@ def test_saml_role_mapping_invalid_role_claim_fallback(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "EDITOR"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names
def test_saml_role_mapping_case_insensitive(
@@ -517,7 +495,8 @@ def test_saml_role_mapping_case_insensitive(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "ADMIN"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-admin" in found_user_role_names
def test_saml_name_mapping(
@@ -545,7 +524,8 @@ def test_saml_name_mapping(
assert (
found_user["displayName"] == "Jane"
) # We are only mapping the first name here
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_saml_empty_name_fallback(
@@ -570,7 +550,8 @@ def test_saml_empty_name_fallback(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_saml_sso_login_activates_pending_invite_user(
@@ -613,7 +594,8 @@ def test_saml_sso_login_activates_pending_invite_user(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["status"] == "active"
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_saml_sso_deleted_user_gets_new_user_on_login(
@@ -680,7 +662,7 @@ def test_saml_sso_deleted_user_gets_new_user_on_login(
# Verify a NEW active user was auto-provisioned via SSO
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
@@ -694,4 +676,7 @@ def test_saml_sso_deleted_user_gets_new_user_on_login(
)
assert found_user is not None
assert found_user["status"] == "active"
assert found_user["role"] == "VIEWER" # default role from SSO domain config
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert (
"signoz-viewer" in found_user_role_names
) # default role from SSO domain config

View File

@@ -11,11 +11,8 @@ from fixtures.auth import (
USER_ADMIN_PASSWORD,
add_license,
)
from fixtures.idputils import (
get_oidc_domain,
get_user_by_email,
perform_oidc_login,
)
from fixtures.idputils import get_oidc_domain, perform_oidc_login
from fixtures.utils import get_user_by_email, get_user_role_names
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
@@ -112,26 +109,12 @@ def test_oidc_authn(
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Assert that the user was created in signoz.
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(
user
for user in user_response
if user["email"] == "viewer@oidc.integration.test"
),
None,
)
found_user = get_user_by_email(signoz, admin_token, "viewer@oidc.integration.test")
assert found_user is not None
assert found_user["role"] == "VIEWER"
# Confirm role
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_oidc_update_domain_with_group_mappings(
@@ -208,7 +191,8 @@ def test_oidc_role_mapping_single_group_admin(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "ADMIN"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-admin" in found_user_role_names
def test_oidc_role_mapping_single_group_editor(
@@ -234,7 +218,8 @@ def test_oidc_role_mapping_single_group_editor(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "EDITOR"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names
def test_oidc_role_mapping_multiple_groups_highest_wins(
@@ -264,7 +249,8 @@ def test_oidc_role_mapping_multiple_groups_highest_wins(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "ADMIN"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-admin" in found_user_role_names
def test_oidc_role_mapping_explicit_viewer_group(
@@ -291,7 +277,8 @@ def test_oidc_role_mapping_explicit_viewer_group(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_oidc_role_mapping_unmapped_group_uses_default(
@@ -317,7 +304,8 @@ def test_oidc_role_mapping_unmapped_group_uses_default(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
def test_oidc_update_domain_with_use_role_claim(
@@ -397,7 +385,8 @@ def test_oidc_role_mapping_role_claim_takes_precedence(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "ADMIN"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-admin" in found_user_role_names
def test_oidc_role_mapping_invalid_role_claim_fallback(
@@ -429,7 +418,8 @@ def test_oidc_role_mapping_invalid_role_claim_fallback(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "EDITOR"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names
def test_oidc_role_mapping_case_insensitive(
@@ -459,7 +449,8 @@ def test_oidc_role_mapping_case_insensitive(
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["role"] == "EDITOR"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names
def test_oidc_name_mapping(
@@ -482,20 +473,13 @@ def test_oidc_name_mapping(
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=5,
)
assert response.status_code == HTTPStatus.OK
users = response.json()["data"]
found_user = next((u for u in users if u["email"] == email), None)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
# Keycloak concatenates firstName + lastName into "name" claim
assert found_user["displayName"] == "John Doe"
assert found_user["role"] == "VIEWER" # Default role
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names # Default role
def test_oidc_empty_name_uses_fallback(
@@ -518,19 +502,12 @@ def test_oidc_empty_name_uses_fallback(
)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=5,
)
assert response.status_code == HTTPStatus.OK
users = response.json()["data"]
found_user = next((u for u in users if u["email"] == email), None)
found_user = get_user_by_email(signoz, admin_token, email)
# User should still be created even with empty name
assert found_user is not None
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names
# Note: displayName may be empty - this is a known limitation
@@ -570,16 +547,9 @@ def test_oidc_sso_login_activates_pending_invite_user(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
# User should be active with ADMIN role from invite, not VIEWER from SSO
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
found_user = next(
(user for user in response.json()["data"] if user["email"] == email),
None,
)
# User should be active with VIEWER role from SSO
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
assert found_user["status"] == "active"
assert found_user["role"] == "VIEWER"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-viewer" in found_user_role_names

View File

@@ -5,6 +5,7 @@ import requests
from fixtures import types
from fixtures.logger import setup_logger
from fixtures.utils import get_user_by_email, get_user_role_names
logger = setup_logger(__name__)
@@ -74,31 +75,10 @@ def test_register(signoz: types.SigNoz, get_token: Callable[[str, str], str]) ->
admin_token = get_token("admin@integration.test", "password123Z$")
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == "admin@integration.test"),
None,
)
found_user = get_user_by_email(signoz, admin_token, "admin@integration.test")
assert found_user is not None
assert found_user["role"] == "ADMIN"
response = requests.get(
signoz.self.host_configs["8080"].get(f"/api/v1/user/{found_user["id"]}"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
assert response.json()["data"]["role"] == "ADMIN"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-admin" in found_user_role_names
def test_invite_and_register(
@@ -120,21 +100,11 @@ def test_invite_and_register(
assert invited_user["role"] == "EDITOR"
# Verify the user user appears in the users list but as pending_invite status
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == "editor@integration.test"),
None,
)
found_user = get_user_by_email(signoz, admin_token, "editor@integration.test")
assert found_user is not None
assert found_user["status"] == "pending_invite"
assert found_user["role"] == "EDITOR"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names
reset_token = invited_user["token"]
@@ -152,7 +122,7 @@ def test_invite_and_register(
# Verify that an admin endpoint cannot be called by the editor user
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"Authorization": f"Bearer {editor_token}"},
)
@@ -160,24 +130,12 @@ def test_invite_and_register(
assert response.status_code == HTTPStatus.FORBIDDEN
# Verify that the editor user status has been updated to ACTIVE
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={
"Authorization": f"Bearer {get_token("admin@integration.test", "password123Z$")}"
},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == "editor@integration.test"),
None,
)
admin_token = get_token("admin@integration.test", "password123Z$")
found_user = get_user_by_email(signoz, admin_token, "editor@integration.test")
assert found_user is not None
assert found_user["role"] == "EDITOR"
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names
assert found_user["displayName"] == "editor"
assert found_user["email"] == "editor@integration.test"
assert found_user["status"] == "active"
@@ -221,25 +179,7 @@ def test_self_access(
) -> None:
admin_token = get_token("admin@integration.test", "password123Z$")
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == "editor@integration.test"),
None,
)
response = requests.get(
signoz.self.host_configs["8080"].get(f"/api/v1/user/{found_user['id']}"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
assert response.json()["data"]["role"] == "EDITOR"
found_user = get_user_by_email(signoz, admin_token, "editor@integration.test")
assert found_user is not None
found_user_role_names = get_user_role_names(signoz, admin_token, found_user["id"])
assert "signoz-editor" in found_user_role_names

View File

@@ -26,7 +26,7 @@ def test_api_key(signoz: types.SigNoz, get_token: Callable[[str, str], str]) ->
assert "token" in pat_response["data"]
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"SIGNOZ-API-KEY": f"{pat_response["data"]["token"]}"},
)
@@ -85,7 +85,7 @@ def test_api_key_role(
assert "token" in pat_response["data"]
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"SIGNOZ-API-KEY": f"{pat_response["data"]["token"]}"},
)
@@ -109,7 +109,7 @@ def test_api_key_role(
assert "token" in pat_response["data"]
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"SIGNOZ-API-KEY": f"{pat_response["data"]["token"]}"},
)

View File

@@ -6,6 +6,7 @@ from sqlalchemy import sql
from fixtures import types
from fixtures.logger import setup_logger
from fixtures.utils import get_user_by_email
logger = setup_logger(__name__)
@@ -35,23 +36,10 @@ def test_change_password(
assert response.status_code == HTTPStatus.NO_CONTENT
# Get the user id
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(
user
for user in user_response
if user["email"] == "admin+password@integration.test"
),
None,
found_user = get_user_by_email(
signoz, admin_token, "admin+password@integration.test"
)
assert found_user is not None
# Try logging in with the password
token = get_token("admin+password@integration.test", "password123Z$")
@@ -100,23 +88,10 @@ def test_reset_password(
admin_token = get_token("admin@integration.test", "password123Z$")
# Get the user id for admin+password@integration.test
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(
user
for user in user_response
if user["email"] == "admin+password@integration.test"
),
None,
found_user = get_user_by_email(
signoz, admin_token, "admin+password@integration.test"
)
assert found_user is not None
response = requests.get(
signoz.self.host_configs["8080"].get(
@@ -158,23 +133,10 @@ def test_reset_password_with_no_password(
admin_token = get_token("admin@integration.test", "password123Z$")
# Get the user id for admin+password@integration.test
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(
user
for user in user_response
if user["email"] == "admin+password@integration.test"
),
None,
found_user = get_user_by_email(
signoz, admin_token, "admin+password@integration.test"
)
assert found_user is not None
with signoz.sqlstore.conn.connect() as conn:
result = conn.execute(
@@ -305,17 +267,7 @@ def test_forgot_password_creates_reset_token(
# Verify reset password token was created by querying the database
# First, get the user ID
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == "forgot@integration.test"),
None,
)
found_user = get_user_by_email(signoz, admin_token, "forgot@integration.test")
assert found_user is not None
reset_token = None
@@ -371,17 +323,7 @@ def test_reset_password_with_expired_token(
admin_token = get_token("admin@integration.test", "password123Z$")
# Get user ID for the forgot@integration.test user
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.OK
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == "forgot@integration.test"),
None,
)
found_user = get_user_by_email(signoz, admin_token, "forgot@integration.test")
assert found_user is not None
# Get org ID

View File

@@ -4,6 +4,7 @@ from typing import Callable, Tuple
import requests
from fixtures import types
from fixtures.utils import get_user_role_names
def test_change_role(
@@ -40,7 +41,7 @@ def test_change_role(
)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user/me"),
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
@@ -60,20 +61,20 @@ def test_change_role(
# Change the new user's role - move to ADMIN
response = requests.put(
signoz.self.host_configs["8080"].get(f"/api/v1/user/{new_user_id}"),
signoz.self.host_configs["8080"].get(f"/api/v2/users/{new_user_id}"),
json={
"displayName": "role change user",
"role": "ADMIN",
"roleNames": ["signoz-admin"],
},
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
assert response.status_code == HTTPStatus.NO_CONTENT
# Make some API calls again
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user/me"),
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
@@ -106,3 +107,302 @@ def test_change_role(
)
assert response.status_code == HTTPStatus.OK
def test_remove_all_roles(
signoz: types.SigNoz,
get_token: Callable[[str, str], str],
get_tokens: Callable[[str, str], Tuple[str, str]],
):
admin_token = get_token("admin@integration.test", "password123Z$")
# Create a new user as EDITOR
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/invite"),
json={"email": "admin+noroles@integration.test", "role": "EDITOR"},
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.CREATED
invited_user = response.json()["data"]
reset_token = invited_user["token"]
# Activate user via reset password
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
json={"password": "password123Z$", "token": reset_token},
timeout=2,
)
assert response.status_code == HTTPStatus.NO_CONTENT
# Login and get user id
new_user_token, new_user_refresh_token = get_tokens(
"admin+noroles@integration.test", "password123Z$"
)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.OK
new_user_id = response.json()["data"]["id"]
# Validate the user has the editor role
role_names = get_user_role_names(signoz, admin_token, new_user_id)
assert role_names is not None
assert "signoz-editor" in role_names
# Remove all roles
response = requests.put(
signoz.self.host_configs["8080"].get(f"/api/v2/users/{new_user_id}"),
json={
"displayName": "no roles user",
"roleNames": [],
},
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.NO_CONTENT
# Validate the user has no roles
role_names = get_user_role_names(signoz, admin_token, new_user_id)
assert role_names is None or len(role_names) == 0
# Old token should be invalidated after role change
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.UNAUTHORIZED
# Token rotation should also fail for a user with no roles
# (the session endpoint requires roles to build an identity)
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v2/sessions/rotate"),
json={
"refreshToken": new_user_refresh_token,
},
headers={"Authorization": f"Bearer {new_user_token}"},
timeout=2,
)
assert (
response.status_code != HTTPStatus.OK
), "token rotation should fail for user with no roles"
def test_multiple_roles(
signoz: types.SigNoz,
get_token: Callable[[str, str], str],
get_tokens: Callable[[str, str], Tuple[str, str]],
):
admin_token = get_token("admin@integration.test", "password123Z$")
# Create a new user as VIEWER
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/invite"),
json={"email": "admin+multirole@integration.test", "role": "VIEWER"},
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == HTTPStatus.CREATED
invited_user = response.json()["data"]
reset_token = invited_user["token"]
# Activate user via reset password
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
json={"password": "password123Z$", "token": reset_token},
timeout=2,
)
assert response.status_code == HTTPStatus.NO_CONTENT
# Login and get user id
new_user_token, new_user_refresh_token = get_tokens(
"admin+multirole@integration.test", "password123Z$"
)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.OK
new_user_id = response.json()["data"]["id"]
# Validate user starts with viewer role
role_names = get_user_role_names(signoz, admin_token, new_user_id)
assert role_names is not None
assert role_names == [
"signoz-viewer"
], f"expected ['signoz-viewer'], got {role_names}"
# As viewer, admin-only APIs should be forbidden
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/org/preferences"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.FORBIDDEN
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.FORBIDDEN
# Assign multiple roles: editor + viewer
response = requests.put(
signoz.self.host_configs["8080"].get(f"/api/v2/users/{new_user_id}"),
json={
"displayName": "multi role user",
"roleNames": ["signoz-editor", "signoz-viewer"],
},
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.NO_CONTENT
# Validate user has both roles
role_names = get_user_role_names(signoz, admin_token, new_user_id)
assert role_names is not None
assert sorted(role_names) == [
"signoz-editor",
"signoz-viewer",
], f"expected ['signoz-editor', 'signoz-viewer'], got {sorted(role_names)}"
# Rotate token to pick up new roles
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v2/sessions/rotate"),
json={"refreshToken": new_user_refresh_token},
headers={"Authorization": f"Bearer {new_user_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
rotate_response = response.json()["data"]
new_user_token = rotate_response["accessToken"]
new_user_refresh_token = rotate_response["refreshToken"]
# Verify /me includes both roles
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.OK
me_role_names = sorted(r["name"] for r in response.json()["data"]["roles"])
assert me_role_names == [
"signoz-editor",
"signoz-viewer",
], f"expected ['signoz-editor', 'signoz-viewer'] in /me, got {me_role_names}"
# Editor+viewer still cannot access admin-only APIs
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/org/preferences"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.FORBIDDEN
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.FORBIDDEN
# Assign all three roles including admin
response = requests.put(
signoz.self.host_configs["8080"].get(f"/api/v2/users/{new_user_id}"),
json={
"displayName": "multi role user",
"roleNames": ["signoz-admin", "signoz-editor", "signoz-viewer"],
},
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.NO_CONTENT
role_names = get_user_role_names(signoz, admin_token, new_user_id)
assert sorted(role_names) == [
"signoz-admin",
"signoz-editor",
"signoz-viewer",
], f"expected all three roles, got {sorted(role_names)}"
# Rotate token to pick up admin role
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v2/sessions/rotate"),
json={"refreshToken": new_user_refresh_token},
headers={"Authorization": f"Bearer {new_user_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
rotate_response = response.json()["data"]
new_user_token = rotate_response["accessToken"]
new_user_refresh_token = rotate_response["refreshToken"]
# Now with admin role, admin-only APIs should succeed
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/org/preferences"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.OK
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.OK
# Reduce back to single viewer role
response = requests.put(
signoz.self.host_configs["8080"].get(f"/api/v2/users/{new_user_id}"),
json={
"displayName": "multi role user",
"roleNames": ["signoz-viewer"],
},
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.NO_CONTENT
role_names = get_user_role_names(signoz, admin_token, new_user_id)
assert role_names == [
"signoz-viewer"
], f"expected ['signoz-viewer'] after reduction, got {role_names}"
# Rotate token to pick up reduced roles
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v2/sessions/rotate"),
json={"refreshToken": new_user_refresh_token},
headers={"Authorization": f"Bearer {new_user_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
rotate_response = response.json()["data"]
new_user_token = rotate_response["accessToken"]
# After reducing to viewer, admin-only APIs should be forbidden again
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/org/preferences"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.FORBIDDEN
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
headers={"Authorization": f"Bearer {new_user_token}"},
)
assert response.status_code == HTTPStatus.FORBIDDEN

View File

@@ -52,7 +52,7 @@ def test_root_user_signoz_admin_assignment(
# Get the user from the /user/me endpoint and extract the id
user_response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user/me"),
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)

View File

@@ -47,7 +47,7 @@ def test_user_invite_accept_role_grant(
# Login with editor email and password
editor_token = get_token(USER_EDITOR_EMAIL, USER_EDITOR_PASSWORD)
user_me_response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user/me"),
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
headers={"Authorization": f"Bearer {editor_token}"},
timeout=2,
)
@@ -102,7 +102,7 @@ def test_user_update_role_grant(
# Get the editor user's id
editor_token = get_token(USER_EDITOR_EMAIL, USER_EDITOR_PASSWORD)
user_me_response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user/me"),
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
headers={"Authorization": f"Bearer {editor_token}"},
timeout=2,
)
@@ -121,14 +121,14 @@ def test_user_update_role_grant(
org_id = roles_data[0]["orgId"]
# Update the user's role to viewer
update_payload = {"role": "VIEWER"}
update_payload = {"roleNames": ["signoz-viewer"]}
update_response = requests.put(
signoz.self.host_configs["8080"].get(f"/api/v1/user/{editor_id}"),
signoz.self.host_configs["8080"].get(f"/api/v2/users/{editor_id}"),
json=update_payload,
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert update_response.status_code == HTTPStatus.OK
assert update_response.status_code == HTTPStatus.NO_CONTENT
# Check that user no longer has the editor role in the db
with signoz.sqlstore.conn.connect() as conn:
@@ -179,7 +179,7 @@ def test_user_delete_role_revoke(
# login with editor to get the user_id and check if user exists
editor_token = get_token(USER_EDITOR_EMAIL, USER_EDITOR_PASSWORD)
user_me_response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user/me"),
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
headers={"Authorization": f"Bearer {editor_token}"},
timeout=2,
)

View File

@@ -14,7 +14,7 @@ def test_root_user_created(signoz: types.SigNoz) -> None:
The root user service reconciles asynchronously after startup.
Phase 1: Poll /api/v1/version until setupCompleted=true.
Phase 2: Poll /api/v1/user until it returns 200, confirming the root
Phase 2: Poll /api/v2/users until it returns 200, confirming the root
user actually exists and the impersonation provider works.
"""
# Phase 1: wait for setupCompleted
@@ -39,13 +39,13 @@ def test_root_user_created(signoz: types.SigNoz) -> None:
# Phase 2: wait for root user to be fully resolved
for attempt in range(15):
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
)
if response.status_code == HTTPStatus.OK:
return
logger.info(
"Attempt %s: /api/v1/user returned %s, retrying ...",
"Attempt %s: /api/v2/users returned %s, retrying ...",
attempt + 1,
response.status_code,
)

View File

@@ -4,6 +4,7 @@ import requests
from fixtures import types
from fixtures.logger import setup_logger
from fixtures.utils import get_user_role_names
logger = setup_logger(__name__)
@@ -32,7 +33,7 @@ def test_impersonated_user_is_admin(signoz: types.SigNoz) -> None:
Listing users is an admin-only endpoint.
"""
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
signoz.self.host_configs["8080"].get("/api/v2/users"),
timeout=2,
)
@@ -46,4 +47,5 @@ def test_impersonated_user_is_admin(signoz: types.SigNoz) -> None:
None,
)
assert root_user is not None
assert root_user["role"] == "ADMIN"
root_user_role_names = get_user_role_names(signoz, None, root_user["id"])
assert "signoz-admin" in root_user_role_names