mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-08 18:10:27 +01:00
Compare commits
2 Commits
main
...
sso-form-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f647cbc78 | ||
|
|
8749523cb4 |
3
.github/workflows/integrationci.yaml
vendored
3
.github/workflows/integrationci.yaml
vendored
@@ -39,7 +39,6 @@ jobs:
|
||||
matrix:
|
||||
suite:
|
||||
- alerts
|
||||
- basepath
|
||||
- callbackauthn
|
||||
- cloudintegrations
|
||||
- dashboard
|
||||
@@ -84,7 +83,7 @@ jobs:
|
||||
run: |
|
||||
cd tests && uv sync
|
||||
- name: webdriver
|
||||
if: matrix.suite == 'callbackauthn' || matrix.suite == 'basepath'
|
||||
if: matrix.suite == 'callbackauthn'
|
||||
run: |
|
||||
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
||||
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee -a /etc/apt/sources.list.d/google-chrome.list
|
||||
|
||||
@@ -91,7 +91,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
sqlstoreProviderFactories(),
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing, config.Global)
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, config authz.Config, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore, config)
|
||||
|
||||
@@ -107,17 +107,17 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
sqlstoreProviderFactories(),
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
samlCallbackAuthN, err := samlcallbackauthn.New(ctx, store, licensing, config.Global)
|
||||
samlCallbackAuthN, err := samlcallbackauthn.New(ctx, store, licensing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oidcCallbackAuthN, err := oidccallbackauthn.New(store, licensing, providerSettings, config.Global)
|
||||
oidcCallbackAuthN, err := oidccallbackauthn.New(store, licensing, providerSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing, config.Global)
|
||||
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,12 +5,10 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/http/client"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
@@ -28,14 +26,13 @@ var defaultScopes []string = []string{"email", "profile", oidc.ScopeOpenID}
|
||||
var _ authn.CallbackAuthN = (*AuthN)(nil)
|
||||
|
||||
type AuthN struct {
|
||||
settings factory.ScopedProviderSettings
|
||||
store authtypes.AuthNStore
|
||||
licensing licensing.Licensing
|
||||
httpClient *client.Client
|
||||
globalConfig global.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
store authtypes.AuthNStore
|
||||
licensing licensing.Licensing
|
||||
httpClient *client.Client
|
||||
}
|
||||
|
||||
func New(store authtypes.AuthNStore, licensing licensing.Licensing, providerSettings factory.ProviderSettings, globalConfig global.Config) (*AuthN, error) {
|
||||
func New(store authtypes.AuthNStore, licensing licensing.Licensing, providerSettings factory.ProviderSettings) (*AuthN, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn")
|
||||
|
||||
httpClient, err := client.New(providerSettings.Logger, providerSettings.TracerProvider, providerSettings.MeterProvider)
|
||||
@@ -44,11 +41,10 @@ func New(store authtypes.AuthNStore, licensing licensing.Licensing, providerSett
|
||||
}
|
||||
|
||||
return &AuthN{
|
||||
settings: settings,
|
||||
store: store,
|
||||
licensing: licensing,
|
||||
httpClient: httpClient,
|
||||
globalConfig: globalConfig,
|
||||
settings: settings,
|
||||
store: store,
|
||||
licensing: licensing,
|
||||
httpClient: httpClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -201,7 +197,7 @@ func (a *AuthN) oidcProviderAndoauth2Config(ctx context.Context, siteURL *url.UR
|
||||
RedirectURL: (&url.URL{
|
||||
Scheme: siteURL.Scheme,
|
||||
Host: siteURL.Host,
|
||||
Path: path.Join(a.globalConfig.ExternalPath(), redirectPath),
|
||||
Path: redirectPath,
|
||||
}).String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -6,12 +6,10 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -26,16 +24,14 @@ const (
|
||||
var _ authn.CallbackAuthN = (*AuthN)(nil)
|
||||
|
||||
type AuthN struct {
|
||||
store authtypes.AuthNStore
|
||||
licensing licensing.Licensing
|
||||
globalConfig global.Config
|
||||
store authtypes.AuthNStore
|
||||
licensing licensing.Licensing
|
||||
}
|
||||
|
||||
func New(ctx context.Context, store authtypes.AuthNStore, licensing licensing.Licensing, globalConfig global.Config) (*AuthN, error) {
|
||||
func New(ctx context.Context, store authtypes.AuthNStore, licensing licensing.Licensing) (*AuthN, error) {
|
||||
return &AuthN{
|
||||
store: store,
|
||||
licensing: licensing,
|
||||
globalConfig: globalConfig,
|
||||
store: store,
|
||||
licensing: licensing,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -136,7 +132,7 @@ func (a *AuthN) serviceProvider(siteURL *url.URL, authDomain *authtypes.AuthDoma
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acsURL := &url.URL{Scheme: siteURL.Scheme, Host: siteURL.Host, Path: path.Join(a.globalConfig.ExternalPath(), redirectPath)}
|
||||
acsURL := &url.URL{Scheme: siteURL.Scheme, Host: siteURL.Host, Path: redirectPath}
|
||||
|
||||
// Note:
|
||||
// The ServiceProviderIssuer is the client id in case of keycloak. Since we set it to the host here, we need to set the client id == host in keycloak.
|
||||
|
||||
@@ -96,14 +96,28 @@ function CreateOrEdit(props: CreateOrEditProps): JSX.Element {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { domainToAdminEmailList, ...rest } = config;
|
||||
const {
|
||||
domainToAdminEmailList,
|
||||
allowedGroups,
|
||||
serviceAccountJson,
|
||||
domainToAdminEmail: _domainToAdminEmail,
|
||||
fetchTransitiveGroupMembership,
|
||||
...rest
|
||||
} = config;
|
||||
const domainToAdminEmail = convertDomainMappingsToRecord(
|
||||
domainToAdminEmailList,
|
||||
);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
domainToAdminEmail: domainToAdminEmail ?? {},
|
||||
...(rest.fetchGroups
|
||||
? {
|
||||
allowedGroups,
|
||||
serviceAccountJson,
|
||||
domainToAdminEmail: domainToAdminEmail ?? {},
|
||||
fetchTransitiveGroupMembership,
|
||||
}
|
||||
: { domainToAdminEmail: {} }),
|
||||
};
|
||||
}, [form]);
|
||||
|
||||
@@ -129,7 +143,7 @@ function CreateOrEdit(props: CreateOrEditProps): JSX.Element {
|
||||
|
||||
return {
|
||||
...rest,
|
||||
groupMappings: groupMappings ?? {},
|
||||
groupMappings: rest.useRoleAttribute ? undefined : (groupMappings ?? {}),
|
||||
};
|
||||
}, [form]);
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { AuthtypesGettableAuthDomainDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import CreateEdit from '../CreateEdit/CreateEdit';
|
||||
import {
|
||||
AUTH_DOMAINS_UPDATE_ENDPOINT,
|
||||
mockDomainWithRoleMapping,
|
||||
mockGoogleAuthDomain,
|
||||
mockGoogleAuthWithWorkspaceGroups,
|
||||
mockOidcWithClaimMapping,
|
||||
mockSamlWithAttributeMapping,
|
||||
mockUpdateSuccessResponse,
|
||||
} from './mocks';
|
||||
|
||||
// @signozhq/ui/button internal effects block form.validateFields() in tests
|
||||
jest.mock('@signozhq/ui/button', () => ({
|
||||
...jest.requireActual('@signozhq/ui/button'),
|
||||
Button: ({
|
||||
children,
|
||||
onClick,
|
||||
loading,
|
||||
disabled,
|
||||
'aria-label': ariaLabel,
|
||||
prefix,
|
||||
suffix,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
'aria-label'?: string;
|
||||
prefix?: React.ReactNode;
|
||||
suffix?: React.ReactNode;
|
||||
}) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
disabled={disabled || loading}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{prefix}
|
||||
{children}
|
||||
{suffix}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
type SavedPayload = {
|
||||
config: {
|
||||
googleAuthConfig?: Record<string, unknown>;
|
||||
samlConfig?: Record<string, unknown>;
|
||||
oidcConfig?: Record<string, unknown>;
|
||||
roleMapping?: Record<string, unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
async function submitForm(
|
||||
record: AuthtypesGettableAuthDomainDTO,
|
||||
): Promise<SavedPayload> {
|
||||
const requests: SavedPayload[] = [];
|
||||
|
||||
server.use(
|
||||
rest.put(AUTH_DOMAINS_UPDATE_ENDPOINT, async (req, res, ctx) => {
|
||||
requests.push((await req.json()) as SavedPayload);
|
||||
return res(ctx.status(200), ctx.json(mockUpdateSuccessResponse));
|
||||
}),
|
||||
);
|
||||
|
||||
render(<CreateEdit isCreate={false} record={record} onClose={jest.fn()} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /save changes/i }));
|
||||
await waitFor(() => expect(requests).toHaveLength(1));
|
||||
|
||||
return requests[0];
|
||||
}
|
||||
|
||||
describe('CreateEdit — payload sanitization', () => {
|
||||
afterEach(() => server.resetHandlers());
|
||||
|
||||
describe('Google Auth', () => {
|
||||
it('sends core fields and omits workspace fields when fetchGroups is not set', async () => {
|
||||
const payload = await submitForm(mockGoogleAuthDomain);
|
||||
|
||||
const g = payload.config.googleAuthConfig;
|
||||
expect(g?.clientId).toBe('test-client-id');
|
||||
expect(g?.clientSecret).toBe('test-client-secret');
|
||||
expect(g?.allowedGroups).toBeUndefined();
|
||||
expect(g?.serviceAccountJson).toBeUndefined();
|
||||
expect(g?.fetchTransitiveGroupMembership).toBeUndefined();
|
||||
expect(g?.domainToAdminEmail).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('strips workspace fields when fetchGroups is false', async () => {
|
||||
const payload = await submitForm({
|
||||
...mockGoogleAuthWithWorkspaceGroups,
|
||||
config: {
|
||||
...mockGoogleAuthWithWorkspaceGroups.config,
|
||||
googleAuthConfig: {
|
||||
...mockGoogleAuthWithWorkspaceGroups.config?.googleAuthConfig,
|
||||
fetchGroups: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const g = payload.config.googleAuthConfig;
|
||||
expect(g?.fetchGroups).toBe(false);
|
||||
expect(g?.allowedGroups).toBeUndefined();
|
||||
expect(g?.serviceAccountJson).toBeUndefined();
|
||||
expect(g?.fetchTransitiveGroupMembership).toBeUndefined();
|
||||
expect(g?.domainToAdminEmail).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('includes all workspace fields when fetchGroups is true', async () => {
|
||||
const payload = await submitForm(mockGoogleAuthWithWorkspaceGroups);
|
||||
|
||||
const g = payload.config.googleAuthConfig;
|
||||
expect(g?.fetchGroups).toBe(true);
|
||||
expect(g?.serviceAccountJson).toBe('{"type": "service_account"}');
|
||||
expect(g?.fetchTransitiveGroupMembership).toBe(true);
|
||||
expect(g?.allowedGroups).toStrictEqual([
|
||||
'allowed-group-1',
|
||||
'allowed-group-2',
|
||||
]);
|
||||
expect(g?.domainToAdminEmail).toStrictEqual({
|
||||
'google-groups.com': 'admin@google-groups.com',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SAML', () => {
|
||||
it('sends core and attributeMapping fields', async () => {
|
||||
const payload = await submitForm(mockSamlWithAttributeMapping);
|
||||
|
||||
const s = payload.config.samlConfig;
|
||||
expect(s?.samlIdp).toBe('https://idp.saml-attrs.com/sso');
|
||||
expect(s?.samlEntity).toBe('urn:saml-attrs:idp');
|
||||
expect(s?.samlCert).toBe('MOCK_CERTIFICATE_ATTRS');
|
||||
expect(s?.insecureSkipAuthNRequestsSigned).toBe(true);
|
||||
|
||||
const attr = s?.attributeMapping as Record<string, unknown>;
|
||||
expect(attr?.name).toBe('user_display_name');
|
||||
expect(attr?.groups).toBe('member_of');
|
||||
expect(attr?.role).toBe('signoz_role');
|
||||
});
|
||||
});
|
||||
|
||||
describe('OIDC', () => {
|
||||
it('sends all fields including claimMapping', async () => {
|
||||
const payload = await submitForm(mockOidcWithClaimMapping);
|
||||
|
||||
const o = payload.config.oidcConfig;
|
||||
expect(o?.issuer).toBe('https://oidc.claims.com');
|
||||
expect(o?.issuerAlias).toBe('https://alias.claims.com');
|
||||
expect(o?.clientId).toBe('claims-client-id');
|
||||
expect(o?.clientSecret).toBe('claims-client-secret');
|
||||
expect(o?.insecureSkipEmailVerified).toBe(true);
|
||||
expect(o?.getUserInfo).toBe(true);
|
||||
|
||||
const claim = o?.claimMapping as Record<string, unknown>;
|
||||
expect(claim?.email).toBe('user_email');
|
||||
expect(claim?.name).toBe('display_name');
|
||||
expect(claim?.groups).toBe('user_groups');
|
||||
expect(claim?.role).toBe('user_role');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Role Mapping', () => {
|
||||
it('strips groupMappings when useRoleAttribute is true', async () => {
|
||||
const payload = await submitForm({
|
||||
...mockDomainWithRoleMapping,
|
||||
config: {
|
||||
...mockDomainWithRoleMapping.config,
|
||||
roleMapping: {
|
||||
...mockDomainWithRoleMapping.config?.roleMapping,
|
||||
useRoleAttribute: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(payload.config.roleMapping?.useRoleAttribute).toBe(true);
|
||||
expect(payload.config.roleMapping?.groupMappings).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sends groupMappings when useRoleAttribute is false', async () => {
|
||||
const payload = await submitForm(mockDomainWithRoleMapping);
|
||||
|
||||
expect(payload.config.roleMapping?.useRoleAttribute).toBe(false);
|
||||
expect(payload.config.roleMapping?.groupMappings).toStrictEqual({
|
||||
'admin-group': 'ADMIN',
|
||||
'dev-team': 'EDITOR',
|
||||
viewers: 'VIEWER',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
@@ -15,7 +14,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/http/client"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -31,13 +29,12 @@ var scopes []string = []string{"email", "profile"}
|
||||
var _ authn.CallbackAuthN = (*AuthN)(nil)
|
||||
|
||||
type AuthN struct {
|
||||
store authtypes.AuthNStore
|
||||
settings factory.ScopedProviderSettings
|
||||
httpClient *client.Client
|
||||
globalConfig global.Config
|
||||
store authtypes.AuthNStore
|
||||
settings factory.ScopedProviderSettings
|
||||
httpClient *client.Client
|
||||
}
|
||||
|
||||
func New(ctx context.Context, store authtypes.AuthNStore, providerSettings factory.ProviderSettings, globalConfig global.Config) (*AuthN, error) {
|
||||
func New(ctx context.Context, store authtypes.AuthNStore, providerSettings factory.ProviderSettings) (*AuthN, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/authn/callbackauthn/googlecallbackauthn")
|
||||
|
||||
httpClient, err := client.New(settings.Logger(), providerSettings.TracerProvider, providerSettings.MeterProvider)
|
||||
@@ -46,10 +43,9 @@ func New(ctx context.Context, store authtypes.AuthNStore, providerSettings facto
|
||||
}
|
||||
|
||||
return &AuthN{
|
||||
store: store,
|
||||
settings: settings,
|
||||
httpClient: httpClient,
|
||||
globalConfig: globalConfig,
|
||||
store: store,
|
||||
settings: settings,
|
||||
httpClient: httpClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -182,7 +178,7 @@ func (a *AuthN) oauth2Config(siteURL *url.URL, authDomain *authtypes.AuthDomain,
|
||||
RedirectURL: (&url.URL{
|
||||
Scheme: siteURL.Scheme,
|
||||
Host: siteURL.Host,
|
||||
Path: path.Join(a.globalConfig.ExternalPath(), redirectPath),
|
||||
Path: redirectPath,
|
||||
}).String(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/http/binding"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
@@ -17,12 +15,11 @@ import (
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
module session.Module
|
||||
globalConfig global.Config
|
||||
module session.Module
|
||||
}
|
||||
|
||||
func NewHandler(module session.Module, globalConfig global.Config) session.Handler {
|
||||
return &handler{module: module, globalConfig: globalConfig}
|
||||
func NewHandler(module session.Module) session.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) GetSessionContext(rw http.ResponseWriter, req *http.Request) {
|
||||
@@ -161,13 +158,13 @@ func (handler *handler) DeleteSession(rw http.ResponseWriter, req *http.Request)
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) getRedirectURLFromErr(err error) string {
|
||||
func (*handler) getRedirectURLFromErr(err error) string {
|
||||
values := errors.AsURLValues(err)
|
||||
values.Add("callbackauthnerr", "true")
|
||||
|
||||
return (&url.URL{
|
||||
// When UI is being served on a prefix, we need to redirect to the login page on the prefix.
|
||||
Path: path.Join(handler.globalConfig.ExternalPath(), "/login"),
|
||||
Path: "/login",
|
||||
RawQuery: values.Encode(),
|
||||
}).String()
|
||||
}
|
||||
|
||||
@@ -7,15 +7,14 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/authn/callbackauthn/googlecallbackauthn"
|
||||
"github.com/SigNoz/signoz/pkg/authn/passwordauthn/emailpasswordauthn"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
func NewAuthNs(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, globalConfig global.Config) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
func NewAuthNs(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
emailPasswordAuthN := emailpasswordauthn.New(store)
|
||||
|
||||
googleCallbackAuthN, err := googlecallbackauthn.New(ctx, store, providerSettings, globalConfig)
|
||||
googleCallbackAuthN, err := googlecallbackauthn.New(ctx, store, providerSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -275,14 +275,14 @@ func NewQuerierProviderFactories(telemetryStore telemetrystore.TelemetryStore, p
|
||||
)
|
||||
}
|
||||
|
||||
func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.AuthZ, modules Modules, handlers Handlers, globalConfig global.Config) factory.NamedMap[factory.ProviderFactory[apiserver.APIServer, apiserver.Config]] {
|
||||
func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.AuthZ, modules Modules, handlers Handlers) factory.NamedMap[factory.ProviderFactory[apiserver.APIServer, apiserver.Config]] {
|
||||
return factory.MustNewNamedMap(
|
||||
signozapiserver.NewFactory(
|
||||
orgGetter,
|
||||
authz,
|
||||
implorganization.NewHandler(modules.OrgGetter, modules.OrgSetter),
|
||||
impluser.NewHandler(modules.UserSetter, modules.UserGetter),
|
||||
implsession.NewHandler(modules.Session, globalConfig),
|
||||
implsession.NewHandler(modules.Session),
|
||||
implauthdomain.NewHandler(modules.AuthDomain),
|
||||
implpreference.NewHandler(modules.Preference),
|
||||
handlers.Global,
|
||||
|
||||
@@ -95,7 +95,6 @@ func TestNewProviderFactories(t *testing.T) {
|
||||
nil,
|
||||
Modules{},
|
||||
Handlers{},
|
||||
global.Config{},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ func New(
|
||||
ctx,
|
||||
providerSettings,
|
||||
config.APIServer,
|
||||
NewAPIServerProviderFactories(orgGetter, authz, modules, handlers, config.Global),
|
||||
NewAPIServerProviderFactories(orgGetter, authz, modules, handlers),
|
||||
"signoz",
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
140
tests/fixtures/auth.py
vendored
140
tests/fixtures/auth.py
vendored
@@ -56,18 +56,11 @@ def _login(signoz: types.SigNoz, email: str, password: str) -> str:
|
||||
return login.json()["data"]["accessToken"]
|
||||
|
||||
|
||||
def register_admin(
|
||||
signoz: types.SigNoz,
|
||||
request: pytest.FixtureRequest,
|
||||
pytestconfig: pytest.Config,
|
||||
cache_key: str = "create_user_admin",
|
||||
base_path: str = "",
|
||||
) -> types.Operation:
|
||||
"""Register the first admin (creates the org), under base_path. Reuse-wrapped."""
|
||||
|
||||
def create() -> types.Operation:
|
||||
@pytest.fixture(name="create_user_admin", scope="package")
|
||||
def create_user_admin(signoz: types.SigNoz, request: pytest.FixtureRequest, pytestconfig: pytest.Config) -> types.Operation:
|
||||
def create() -> None:
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{base_path}/api/v1/register"),
|
||||
signoz.self.host_configs["8080"].get("/api/v1/register"),
|
||||
json={
|
||||
"name": USER_ADMIN_NAME,
|
||||
"orgName": "",
|
||||
@@ -90,7 +83,7 @@ def register_admin(
|
||||
return reuse.wrap(
|
||||
request,
|
||||
pytestconfig,
|
||||
cache_key,
|
||||
"create_user_admin",
|
||||
lambda: types.Operation(name=""),
|
||||
create,
|
||||
delete,
|
||||
@@ -98,86 +91,86 @@ def register_admin(
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="create_user_admin", scope="package")
|
||||
def create_user_admin(signoz: types.SigNoz, request: pytest.FixtureRequest, pytestconfig: pytest.Config) -> types.Operation:
|
||||
return register_admin(signoz, request, pytestconfig)
|
||||
|
||||
|
||||
def session_context_getter(signoz: types.SigNoz, base_path: str = "") -> Callable[[str], dict]:
|
||||
"""Build a callable that fetches the session context for an email (under base_path)."""
|
||||
|
||||
def fetch_session_context(email: str) -> dict:
|
||||
@pytest.fixture(name="get_session_context", scope="function")
|
||||
def get_session_context(signoz: types.SigNoz) -> Callable[[str, str], str]:
|
||||
def _get_session_context(email: str) -> str:
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{base_path}/api/v2/sessions/context"),
|
||||
params={"email": email, "ref": f"{signoz.self.host_configs['8080'].base()}"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/sessions/context"),
|
||||
params={
|
||||
"email": email,
|
||||
"ref": f"{signoz.self.host_configs['8080'].base()}",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
return response.json()["data"]
|
||||
|
||||
return fetch_session_context
|
||||
|
||||
|
||||
@pytest.fixture(name="get_session_context", scope="function")
|
||||
def get_session_context(signoz: types.SigNoz) -> Callable[[str], dict]:
|
||||
return session_context_getter(signoz)
|
||||
|
||||
|
||||
def token_getter(signoz: types.SigNoz, base_path: str = "") -> Callable[[str, str], str]:
|
||||
"""Build a callable that logs in (email/password) and returns the access token (under base_path)."""
|
||||
|
||||
def fetch_token(email: str, password: str) -> str:
|
||||
context = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{base_path}/api/v2/sessions/context"),
|
||||
params={"email": email, "ref": f"{signoz.self.host_configs['8080'].base()}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert context.status_code == HTTPStatus.OK
|
||||
org_id = context.json()["data"]["orgs"][0]["id"]
|
||||
|
||||
login = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{base_path}/api/v2/sessions/email_password"),
|
||||
json={"email": email, "password": password, "orgId": org_id},
|
||||
timeout=5,
|
||||
)
|
||||
assert login.status_code == HTTPStatus.OK
|
||||
return login.json()["data"]["accessToken"]
|
||||
|
||||
return fetch_token
|
||||
return _get_session_context
|
||||
|
||||
|
||||
@pytest.fixture(name="get_token", scope="function")
|
||||
def get_token(signoz: types.SigNoz) -> Callable[[str, str], str]:
|
||||
return token_getter(signoz)
|
||||
|
||||
|
||||
def tokens_getter(signoz: types.SigNoz, base_path: str = "") -> Callable[[str, str], tuple[str, str]]:
|
||||
"""Build a callable that logs in and returns the (access, refresh) token pair (under base_path)."""
|
||||
|
||||
def fetch_tokens(email: str, password: str) -> tuple[str, str]:
|
||||
context = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{base_path}/api/v2/sessions/context"),
|
||||
params={"email": email, "ref": f"{signoz.self.host_configs['8080'].base()}"},
|
||||
def _get_token(email: str, password: str) -> str:
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/sessions/context"),
|
||||
params={
|
||||
"email": email,
|
||||
"ref": f"{signoz.self.host_configs['8080'].base()}",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
assert context.status_code == HTTPStatus.OK
|
||||
org_id = context.json()["data"]["orgs"][0]["id"]
|
||||
|
||||
login = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{base_path}/api/v2/sessions/email_password"),
|
||||
json={"email": email, "password": password, "orgId": org_id},
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
org_id = response.json()["data"]["orgs"][0]["id"]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/sessions/email_password"),
|
||||
json={
|
||||
"email": email,
|
||||
"password": password,
|
||||
"orgId": org_id,
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
assert login.status_code == HTTPStatus.OK
|
||||
data = login.json()["data"]
|
||||
return data["accessToken"], data["refreshToken"]
|
||||
|
||||
return fetch_tokens
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
return response.json()["data"]["accessToken"]
|
||||
|
||||
return _get_token
|
||||
|
||||
|
||||
@pytest.fixture(name="get_tokens", scope="function")
|
||||
def get_tokens(signoz: types.SigNoz) -> Callable[[str, str], tuple[str, str]]:
|
||||
return tokens_getter(signoz)
|
||||
def _get_tokens(email: str, password: str) -> str:
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/sessions/context"),
|
||||
params={
|
||||
"email": email,
|
||||
"ref": f"{signoz.self.host_configs['8080'].base()}",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
org_id = response.json()["data"]["orgs"][0]["id"]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/sessions/email_password"),
|
||||
json={
|
||||
"email": email,
|
||||
"password": password,
|
||||
"orgId": org_id,
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
access_token = response.json()["data"]["accessToken"]
|
||||
refresh_token = response.json()["data"]["refreshToken"]
|
||||
return access_token, refresh_token
|
||||
|
||||
return _get_tokens
|
||||
|
||||
|
||||
@pytest.fixture(name="apply_license", scope="package")
|
||||
@@ -277,7 +270,6 @@ def add_license(
|
||||
signoz: types.SigNoz,
|
||||
make_http_mocks: Callable[[types.TestContainerDocker, list[Mapping]], None],
|
||||
get_token: Callable[[str, str], str], # pylint: disable=redefined-outer-name
|
||||
base_path: str = "",
|
||||
) -> None:
|
||||
make_http_mocks(
|
||||
signoz.zeus,
|
||||
@@ -316,7 +308,7 @@ def add_license(
|
||||
access_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
url=signoz.self.host_configs["8080"].get(f"{base_path}/api/v3/licenses"),
|
||||
url=signoz.self.host_configs["8080"].get("/api/v3/licenses"),
|
||||
json={"key": "secret-key"},
|
||||
headers={"Authorization": "Bearer " + access_token},
|
||||
timeout=5,
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from selenium import webdriver
|
||||
from wiremock.resources.mappings import Mapping
|
||||
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license, assert_user_has_role
|
||||
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
|
||||
|
||||
# SigNoz is served under /signoz, so the OIDC callback registered with the IdP
|
||||
# must include the prefix to match the backend-generated redirect URI.
|
||||
BASE_PATH = "/signoz"
|
||||
OIDC_CALLBACK_PATH = f"{BASE_PATH}/api/v1/complete/oidc"
|
||||
|
||||
|
||||
def test_apply_license(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
make_http_mocks: Callable[[TestContainerDocker, list[Mapping]], None],
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""
|
||||
Applies a license to the signoz instance. add_license is a plain function
|
||||
called from the test (function scope), so the function-scoped make_http_mocks
|
||||
fixture is safe to use; base_path prefixes the licensing API call.
|
||||
"""
|
||||
add_license(signoz, make_http_mocks, get_token, base_path=BASE_PATH)
|
||||
|
||||
|
||||
def test_create_auth_domain(
|
||||
signoz: SigNoz,
|
||||
idp: TestContainerIDP, # pylint: disable=unused-argument
|
||||
create_oidc_client: Callable[[str, str], None],
|
||||
get_oidc_settings: Callable[[str], dict],
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""
|
||||
Creates an OIDC auth domain in SigNoz served under a base path. The callback
|
||||
registered with the IdP carries the /signoz prefix.
|
||||
"""
|
||||
client_id = f"oidc.basepath.test.{signoz.self.host_configs['8080'].address}:{signoz.self.host_configs['8080'].port}"
|
||||
# Create an oidc client in the idp with the prefixed callback.
|
||||
create_oidc_client(client_id, OIDC_CALLBACK_PATH)
|
||||
|
||||
# Get the oidc settings from keycloak.
|
||||
settings = get_oidc_settings(client_id)
|
||||
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/signoz/api/v1/domains"),
|
||||
json={
|
||||
"name": "oidc.basepath.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "oidc",
|
||||
"oidcConfig": {
|
||||
"clientId": settings["client_id"],
|
||||
"clientSecret": settings["client_secret"],
|
||||
# Change the hostname of the issuer to the internal resolvable hostname of the idp
|
||||
"issuer": f"{idp.container.container_configs['6060'].get(urlparse(settings['issuer']).path)}",
|
||||
"issuerAlias": settings["issuer"],
|
||||
"getUserInfo": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.CREATED
|
||||
|
||||
|
||||
def test_oidc_authn(
|
||||
signoz: SigNoz,
|
||||
idp: TestContainerIDP,
|
||||
driver: webdriver.Chrome,
|
||||
create_user_idp: Callable[[str, str, bool, str, str], None],
|
||||
idp_login: Callable[[str, str], None],
|
||||
get_token: Callable[[str, str], str],
|
||||
get_session_context: Callable[[str], dict],
|
||||
) -> None:
|
||||
"""
|
||||
Tests the OIDC authn flow when SigNoz is served under a base path. The login
|
||||
URL the backend produces (and thus the IdP callback) carries the /signoz
|
||||
prefix; the e2e browser login must complete and create the user.
|
||||
"""
|
||||
# Create a user in the idp.
|
||||
create_user_idp("viewer@oidc.basepath.test", "password123", True)
|
||||
|
||||
# Get the session context from signoz which will give the OIDC login URL.
|
||||
session_context = get_session_context("viewer@oidc.basepath.test")
|
||||
|
||||
assert len(session_context["orgs"]) == 1
|
||||
assert len(session_context["orgs"][0]["authNSupport"]["callback"]) == 1
|
||||
|
||||
url = session_context["orgs"][0]["authNSupport"]["callback"][0]["url"]
|
||||
|
||||
# change the url to the external resolvable hostname of the idp
|
||||
parsed_url = urlparse(url)
|
||||
actual_url = f"{idp.container.host_configs['6060'].get(parsed_url.path)}?{parsed_url.query}"
|
||||
|
||||
driver.get(actual_url)
|
||||
idp_login("viewer@oidc.basepath.test", "password123")
|
||||
|
||||
# Assert that the user was created in signoz (lookup under the base path).
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
users = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/signoz/api/v2/users"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert users.status_code == HTTPStatus.OK, users.text
|
||||
user = next((u for u in users.json()["data"] if u["email"] == "viewer@oidc.basepath.test"), None)
|
||||
assert user is not None, "User with email 'viewer@oidc.basepath.test' not found"
|
||||
|
||||
user_with_roles = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/signoz/api/v2/users/{user['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert user_with_roles.status_code == HTTPStatus.OK, user_with_roles.text
|
||||
assert_user_has_role(user_with_roles.json()["data"], "signoz-viewer")
|
||||
@@ -1,117 +0,0 @@
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
from selenium import webdriver
|
||||
from wiremock.resources.mappings import Mapping
|
||||
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license, assert_user_has_role
|
||||
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
|
||||
|
||||
# SigNoz is served under /signoz, so the SAML ACS registered with the IdP must
|
||||
# include the prefix to match the backend-generated AssertionConsumerServiceURL.
|
||||
BASE_PATH = "/signoz"
|
||||
SAML_CALLBACK_PATH = f"{BASE_PATH}/api/v1/complete/saml"
|
||||
|
||||
|
||||
def test_apply_license(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
make_http_mocks: Callable[[TestContainerDocker, list[Mapping]], None],
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""
|
||||
Applies a license to the signoz instance. add_license is a plain function
|
||||
called from the test (function scope), so the function-scoped make_http_mocks
|
||||
fixture is safe to use; base_path prefixes the licensing API call.
|
||||
"""
|
||||
add_license(signoz, make_http_mocks, get_token, base_path=BASE_PATH)
|
||||
|
||||
|
||||
def test_create_auth_domain(
|
||||
signoz: SigNoz,
|
||||
idp: TestContainerIDP, # pylint: disable=unused-argument
|
||||
create_saml_client: Callable[[str, str], None],
|
||||
get_saml_settings: Callable[[], dict],
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""
|
||||
Creates a SAML auth domain in SigNoz served under a base path. The ACS
|
||||
registered with the IdP carries the /signoz prefix.
|
||||
"""
|
||||
# Create a saml client in the idp with the prefixed ACS.
|
||||
create_saml_client("saml.basepath.test", SAML_CALLBACK_PATH)
|
||||
|
||||
# Get the saml settings from keycloak.
|
||||
settings = get_saml_settings()
|
||||
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/signoz/api/v1/domains"),
|
||||
json={
|
||||
"name": "saml.basepath.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"samlEntity": settings["entityID"],
|
||||
"samlIdp": settings["singleSignOnServiceLocation"],
|
||||
"samlCert": settings["certificate"],
|
||||
},
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.CREATED
|
||||
|
||||
|
||||
def test_saml_authn(
|
||||
signoz: SigNoz,
|
||||
idp: TestContainerIDP, # pylint: disable=unused-argument
|
||||
driver: webdriver.Chrome,
|
||||
create_user_idp: Callable[[str, str, bool, str, str], None],
|
||||
idp_login: Callable[[str, str], None],
|
||||
get_token: Callable[[str, str], str],
|
||||
get_session_context: Callable[[str], dict],
|
||||
) -> None:
|
||||
"""
|
||||
Tests the SAML authn flow when SigNoz is served under a base path. The
|
||||
AssertionConsumerServiceURL in the AuthnRequest carries the /signoz prefix;
|
||||
the e2e browser login must complete and create the user.
|
||||
"""
|
||||
# Create a user in the idp.
|
||||
create_user_idp("viewer@saml.basepath.test", "password", True)
|
||||
|
||||
# Get the session context from signoz which will give the SAML login URL.
|
||||
session_context = get_session_context("viewer@saml.basepath.test")
|
||||
|
||||
assert len(session_context["orgs"]) == 1
|
||||
assert len(session_context["orgs"][0]["authNSupport"]["callback"]) == 1
|
||||
|
||||
url = session_context["orgs"][0]["authNSupport"]["callback"][0]["url"]
|
||||
|
||||
driver.get(url)
|
||||
idp_login("viewer@saml.basepath.test", "password")
|
||||
|
||||
# Assert that the user was created in signoz (lookup under the base path).
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
users = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/signoz/api/v2/users"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert users.status_code == HTTPStatus.OK, users.text
|
||||
user = next((u for u in users.json()["data"] if u["email"] == "viewer@saml.basepath.test"), None)
|
||||
assert user is not None, "User with email 'viewer@saml.basepath.test' not found"
|
||||
|
||||
user_with_roles = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/signoz/api/v2/users/{user['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert user_with_roles.status_code == HTTPStatus.OK, user_with_roles.text
|
||||
assert_user_has_role(user_with_roles.json()["data"], "signoz-viewer")
|
||||
@@ -1,57 +0,0 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
from testcontainers.core.container import Network
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import register_admin, session_context_getter, token_getter
|
||||
from fixtures.signoz import create_signoz
|
||||
|
||||
# SigNoz is served under this URL path prefix for the base-path suite. The auth
|
||||
# helpers from fixtures/auth.py are reused via their factories with this prefix,
|
||||
# so these fixtures shadow the same-named root ones without duplicating logic.
|
||||
# Only the path component is read by global.ExternalPath(), which derives the
|
||||
# http.StripPrefix route prefix.
|
||||
BASE_PATH = "/signoz"
|
||||
|
||||
|
||||
@pytest.fixture(name="signoz", scope="package")
|
||||
def signoz_base_path( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
||||
network: Network,
|
||||
zeus: types.TestContainerDocker,
|
||||
gateway: types.TestContainerDocker,
|
||||
sqlstore: types.TestContainerSQL,
|
||||
clickhouse: types.TestContainerClickhouse,
|
||||
request: pytest.FixtureRequest,
|
||||
pytestconfig: pytest.Config,
|
||||
) -> types.SigNoz:
|
||||
"""
|
||||
Package-scoped SigNoz served under BASE_PATH. Sets SIGNOZ_GLOBAL_EXTERNAL__URL
|
||||
with the prefix so the backend derives the http.StripPrefix route prefix.
|
||||
"""
|
||||
return create_signoz(
|
||||
network=network,
|
||||
zeus=zeus,
|
||||
gateway=gateway,
|
||||
sqlstore=sqlstore,
|
||||
clickhouse=clickhouse,
|
||||
request=request,
|
||||
pytestconfig=pytestconfig,
|
||||
cache_key="signoz_base_path",
|
||||
env_overrides={"SIGNOZ_GLOBAL_EXTERNAL__URL": f"http://localhost:8080{BASE_PATH}"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="create_user_admin", scope="package")
|
||||
def create_user_admin_base_path(signoz: types.SigNoz, request: pytest.FixtureRequest, pytestconfig: pytest.Config) -> types.Operation:
|
||||
return register_admin(signoz, request, pytestconfig, cache_key="create_user_admin_base_path", base_path=BASE_PATH)
|
||||
|
||||
|
||||
@pytest.fixture(name="get_token", scope="function")
|
||||
def get_token(signoz: types.SigNoz) -> Callable[[str, str], str]:
|
||||
return token_getter(signoz, BASE_PATH)
|
||||
|
||||
|
||||
@pytest.fixture(name="get_session_context", scope="function")
|
||||
def get_session_context(signoz: types.SigNoz) -> Callable[[str], dict]:
|
||||
return session_context_getter(signoz, BASE_PATH)
|
||||
@@ -483,7 +483,7 @@ def test_enable_metrics_provisions_dashboards(
|
||||
assert isinstance(dashboards_in_service, list) and len(dashboards_in_service) > 0, "assets.dashboards should be non-empty after enabling metrics"
|
||||
provisioned_ids = set()
|
||||
for dash in dashboards_in_service:
|
||||
assert "integrationDashboard" in dash, "Integration dashboard entry missing"
|
||||
assert "integrationDashboard" in dash, f"Integration dashboard entry missing"
|
||||
try:
|
||||
uuid.UUID(dash["integrationDashboard"]["id"])
|
||||
except ValueError as err:
|
||||
|
||||
Reference in New Issue
Block a user