mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-10 19:00:34 +01:00
Compare commits
5 Commits
feat/trace
...
feat/auth-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a92f33ca5 | ||
|
|
2af4f81349 | ||
|
|
bfc89d79ff | ||
|
|
f9e8ce6f91 | ||
|
|
e0d87e216b |
@@ -470,25 +470,6 @@ components:
|
||||
role:
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesAuthDomainConfig:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
- $ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
- $ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
properties:
|
||||
googleAuthConfig:
|
||||
$ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
oidcConfig:
|
||||
$ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
roleMapping:
|
||||
$ref: '#/components/schemas/AuthtypesRoleMapping'
|
||||
samlConfig:
|
||||
$ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
ssoEnabled:
|
||||
type: boolean
|
||||
ssoType:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
type: object
|
||||
AuthtypesAuthNProvider:
|
||||
enum:
|
||||
- google_auth
|
||||
@@ -515,6 +496,48 @@ components:
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
AuthtypesAuthProviderEnvelope:
|
||||
discriminator:
|
||||
mapping:
|
||||
google_auth: '#/components/schemas/AuthtypesAuthProviderGoogle'
|
||||
oidc: '#/components/schemas/AuthtypesAuthProviderOIDC'
|
||||
saml: '#/components/schemas/AuthtypesAuthProviderSAML'
|
||||
propertyName: type
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AuthtypesAuthProviderSAML'
|
||||
- $ref: '#/components/schemas/AuthtypesAuthProviderOIDC'
|
||||
- $ref: '#/components/schemas/AuthtypesAuthProviderGoogle'
|
||||
type: object
|
||||
AuthtypesAuthProviderGoogle:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
type:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
required:
|
||||
- type
|
||||
- config
|
||||
type: object
|
||||
AuthtypesAuthProviderOIDC:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
type:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
required:
|
||||
- type
|
||||
- config
|
||||
type: object
|
||||
AuthtypesAuthProviderSAML:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesSAMLConfig'
|
||||
type:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
required:
|
||||
- type
|
||||
- config
|
||||
type: object
|
||||
AuthtypesCallbackAuthNSupport:
|
||||
properties:
|
||||
provider:
|
||||
@@ -526,8 +549,6 @@ components:
|
||||
properties:
|
||||
authNProviderInfo:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProviderInfo'
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesAuthDomainConfig'
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
@@ -537,6 +558,12 @@ components:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
provider:
|
||||
$ref: '#/components/schemas/AuthtypesAuthProviderEnvelope'
|
||||
roleMapping:
|
||||
$ref: '#/components/schemas/AuthtypesRoleMapping'
|
||||
ssoEnabled:
|
||||
type: boolean
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
@@ -634,10 +661,14 @@ components:
|
||||
type: object
|
||||
AuthtypesPostableAuthDomain:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesAuthDomainConfig'
|
||||
name:
|
||||
type: string
|
||||
provider:
|
||||
$ref: '#/components/schemas/AuthtypesAuthProviderEnvelope'
|
||||
roleMapping:
|
||||
$ref: '#/components/schemas/AuthtypesRoleMapping'
|
||||
ssoEnabled:
|
||||
type: boolean
|
||||
type: object
|
||||
AuthtypesPostableEmailPasswordSession:
|
||||
properties:
|
||||
@@ -710,7 +741,7 @@ components:
|
||||
useRoleAttribute:
|
||||
type: boolean
|
||||
type: object
|
||||
AuthtypesSamlConfig:
|
||||
AuthtypesSAMLConfig:
|
||||
properties:
|
||||
attributeMapping:
|
||||
$ref: '#/components/schemas/AuthtypesAttributeMapping'
|
||||
@@ -745,8 +776,12 @@ components:
|
||||
type: object
|
||||
AuthtypesUpdatableAuthDomain:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesAuthDomainConfig'
|
||||
provider:
|
||||
$ref: '#/components/schemas/AuthtypesAuthProviderEnvelope'
|
||||
roleMapping:
|
||||
$ref: '#/components/schemas/AuthtypesRoleMapping'
|
||||
ssoEnabled:
|
||||
type: boolean
|
||||
type: object
|
||||
AuthtypesUserRole:
|
||||
properties:
|
||||
|
||||
@@ -53,7 +53,7 @@ func New(store authtypes.AuthNStore, licensing licensing.Licensing, providerSett
|
||||
}
|
||||
|
||||
func (a *AuthN) LoginURL(ctx context.Context, siteURL *url.URL, authDomain *authtypes.AuthDomain) (string, error) {
|
||||
if authDomain.AuthDomainConfig().AuthNProvider != authtypes.AuthNProviderOIDC {
|
||||
if authDomain.AuthDomainConfig().Provider.Type != authtypes.AuthNProviderOIDC {
|
||||
return "", errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthDomainMismatch, "domain type is not oidc")
|
||||
}
|
||||
|
||||
@@ -106,14 +106,14 @@ func (a *AuthN) HandleCallback(ctx context.Context, query url.Values) (*authtype
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims == nil && authDomain.AuthDomainConfig().OIDC.GetUserInfo {
|
||||
if claims == nil && authDomain.AuthDomainConfig().Oidc().GetUserInfo {
|
||||
claims, err = a.claimsFromUserInfo(ctx, oidcProvider, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
emailClaim, ok := claims[authDomain.AuthDomainConfig().OIDC.ClaimMapping.Email].(string)
|
||||
emailClaim, ok := claims[authDomain.AuthDomainConfig().Oidc().ClaimMapping.Email].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "oidc: missing email in claims")
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (a *AuthN) HandleCallback(ctx context.Context, query url.Values) (*authtype
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "oidc: failed to parse email").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
if !authDomain.AuthDomainConfig().OIDC.InsecureSkipEmailVerified {
|
||||
if !authDomain.AuthDomainConfig().Oidc().InsecureSkipEmailVerified {
|
||||
emailVerifiedClaim, ok := claims["email_verified"].(bool)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "oidc: missing email_verified in claims")
|
||||
@@ -135,14 +135,14 @@ func (a *AuthN) HandleCallback(ctx context.Context, query url.Values) (*authtype
|
||||
}
|
||||
|
||||
name := ""
|
||||
if nameClaim := authDomain.AuthDomainConfig().OIDC.ClaimMapping.Name; nameClaim != "" {
|
||||
if nameClaim := authDomain.AuthDomainConfig().Oidc().ClaimMapping.Name; nameClaim != "" {
|
||||
if n, ok := claims[nameClaim].(string); ok {
|
||||
name = n
|
||||
}
|
||||
}
|
||||
|
||||
var groups []string
|
||||
if groupsClaim := authDomain.AuthDomainConfig().OIDC.ClaimMapping.Groups; groupsClaim != "" {
|
||||
if groupsClaim := authDomain.AuthDomainConfig().Oidc().ClaimMapping.Groups; groupsClaim != "" {
|
||||
if claimValue, exists := claims[groupsClaim]; exists {
|
||||
switch g := claimValue.(type) {
|
||||
case []any:
|
||||
@@ -161,7 +161,7 @@ func (a *AuthN) HandleCallback(ctx context.Context, query url.Values) (*authtype
|
||||
}
|
||||
|
||||
role := ""
|
||||
if roleClaim := authDomain.AuthDomainConfig().OIDC.ClaimMapping.Role; roleClaim != "" {
|
||||
if roleClaim := authDomain.AuthDomainConfig().Oidc().ClaimMapping.Role; roleClaim != "" {
|
||||
if r, ok := claims[roleClaim].(string); ok {
|
||||
role = r
|
||||
}
|
||||
@@ -177,11 +177,11 @@ func (a *AuthN) ProviderInfo(ctx context.Context, authDomain *authtypes.AuthDoma
|
||||
}
|
||||
|
||||
func (a *AuthN) oidcProviderAndoauth2Config(ctx context.Context, siteURL *url.URL, authDomain *authtypes.AuthDomain) (*oidc.Provider, *oauth2.Config, error) {
|
||||
if authDomain.AuthDomainConfig().OIDC.IssuerAlias != "" {
|
||||
ctx = oidc.InsecureIssuerURLContext(ctx, authDomain.AuthDomainConfig().OIDC.IssuerAlias)
|
||||
if authDomain.AuthDomainConfig().Oidc().IssuerAlias != "" {
|
||||
ctx = oidc.InsecureIssuerURLContext(ctx, authDomain.AuthDomainConfig().Oidc().IssuerAlias)
|
||||
}
|
||||
|
||||
oidcProvider, err := oidc.NewProvider(ctx, authDomain.AuthDomainConfig().OIDC.Issuer)
|
||||
oidcProvider, err := oidc.NewProvider(ctx, authDomain.AuthDomainConfig().Oidc().Issuer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -194,8 +194,8 @@ func (a *AuthN) oidcProviderAndoauth2Config(ctx context.Context, siteURL *url.UR
|
||||
}
|
||||
|
||||
return oidcProvider, &oauth2.Config{
|
||||
ClientID: authDomain.AuthDomainConfig().OIDC.ClientID,
|
||||
ClientSecret: authDomain.AuthDomainConfig().OIDC.ClientSecret,
|
||||
ClientID: authDomain.AuthDomainConfig().Oidc().ClientID,
|
||||
ClientSecret: authDomain.AuthDomainConfig().Oidc().ClientSecret,
|
||||
Endpoint: oidcProvider.Endpoint(),
|
||||
Scopes: scopes,
|
||||
RedirectURL: (&url.URL{
|
||||
@@ -212,7 +212,7 @@ func (a *AuthN) claimsFromIDToken(ctx context.Context, authDomain *authtypes.Aut
|
||||
return nil, errors.New(errors.TypeNotFound, errors.CodeNotFound, "oidc: no id_token in token response")
|
||||
}
|
||||
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: authDomain.AuthDomainConfig().OIDC.ClientID})
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: authDomain.AuthDomainConfig().Oidc().ClientID})
|
||||
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return nil, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "oidc: failed to verify token").WithAdditional(err.Error())
|
||||
|
||||
@@ -40,7 +40,7 @@ func New(ctx context.Context, store authtypes.AuthNStore, licensing licensing.Li
|
||||
}
|
||||
|
||||
func (a *AuthN) LoginURL(ctx context.Context, siteURL *url.URL, authDomain *authtypes.AuthDomain) (string, error) {
|
||||
if authDomain.AuthDomainConfig().AuthNProvider != authtypes.AuthNProviderSAML {
|
||||
if authDomain.AuthDomainConfig().Provider.Type != authtypes.AuthNProviderSAML {
|
||||
return "", errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthDomainMismatch, "saml: domain type is not saml")
|
||||
}
|
||||
|
||||
@@ -101,19 +101,19 @@ func (a *AuthN) HandleCallback(ctx context.Context, formValues url.Values) (*aut
|
||||
}
|
||||
|
||||
name := ""
|
||||
if nameAttribute := authDomain.AuthDomainConfig().SAML.AttributeMapping.Name; nameAttribute != "" {
|
||||
if nameAttribute := authDomain.AuthDomainConfig().Saml().AttributeMapping.Name; nameAttribute != "" {
|
||||
if val := assertionInfo.Values.Get(nameAttribute); val != "" {
|
||||
name = val
|
||||
}
|
||||
}
|
||||
|
||||
var groups []string
|
||||
if groupAttribute := authDomain.AuthDomainConfig().SAML.AttributeMapping.Groups; groupAttribute != "" {
|
||||
if groupAttribute := authDomain.AuthDomainConfig().Saml().AttributeMapping.Groups; groupAttribute != "" {
|
||||
groups = assertionInfo.Values.GetAll(groupAttribute)
|
||||
}
|
||||
|
||||
role := ""
|
||||
if roleAttribute := authDomain.AuthDomainConfig().SAML.AttributeMapping.Role; roleAttribute != "" {
|
||||
if roleAttribute := authDomain.AuthDomainConfig().Saml().AttributeMapping.Role; roleAttribute != "" {
|
||||
if val := assertionInfo.Values.Get(roleAttribute); val != "" {
|
||||
role = val
|
||||
}
|
||||
@@ -142,11 +142,11 @@ func (a *AuthN) serviceProvider(siteURL *url.URL, authDomain *authtypes.AuthDoma
|
||||
// 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.
|
||||
// For AWSSSO, this is the value of Application SAML audience.
|
||||
return &saml2.SAMLServiceProvider{
|
||||
IdentityProviderSSOURL: authDomain.AuthDomainConfig().SAML.SamlIdp,
|
||||
IdentityProviderIssuer: authDomain.AuthDomainConfig().SAML.SamlEntity,
|
||||
IdentityProviderSSOURL: authDomain.AuthDomainConfig().Saml().SamlIdp,
|
||||
IdentityProviderIssuer: authDomain.AuthDomainConfig().Saml().SamlEntity,
|
||||
ServiceProviderIssuer: siteURL.Host,
|
||||
AssertionConsumerServiceURL: acsURL.String(),
|
||||
SignAuthnRequests: !authDomain.AuthDomainConfig().SAML.InsecureSkipAuthNRequestsSigned,
|
||||
SignAuthnRequests: !authDomain.AuthDomainConfig().Saml().InsecureSkipAuthNRequestsSigned,
|
||||
AllowMissingAttributes: true,
|
||||
IDPCertificateStore: certStore,
|
||||
SPKeyStore: dsig.RandomKeyStoreForTest(),
|
||||
@@ -159,15 +159,15 @@ func (a *AuthN) getCertificateStore(authDomain *authtypes.AuthDomain) (dsig.X509
|
||||
}
|
||||
|
||||
var certBytes []byte
|
||||
if strings.Contains(authDomain.AuthDomainConfig().SAML.SamlCert, "-----BEGIN CERTIFICATE-----") {
|
||||
block, _ := pem.Decode([]byte(authDomain.AuthDomainConfig().SAML.SamlCert))
|
||||
if strings.Contains(authDomain.AuthDomainConfig().Saml().SamlCert, "-----BEGIN CERTIFICATE-----") {
|
||||
block, _ := pem.Decode([]byte(authDomain.AuthDomainConfig().Saml().SamlCert))
|
||||
if block == nil {
|
||||
return certStore, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "no valid pem cert found")
|
||||
}
|
||||
|
||||
certBytes = block.Bytes
|
||||
} else {
|
||||
certData, err := base64.StdEncoding.DecodeString(authDomain.AuthDomainConfig().SAML.SamlCert)
|
||||
certData, err := base64.StdEncoding.DecodeString(authDomain.AuthDomainConfig().Saml().SamlCert)
|
||||
if err != nil {
|
||||
return certStore, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to read certificate: %s", err.Error())
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (a *AuthN) LoginURL(ctx context.Context, siteURL *url.URL, authDomain *auth
|
||||
return "", err
|
||||
}
|
||||
|
||||
if authDomain.AuthDomainConfig().AuthNProvider != authtypes.AuthNProviderGoogleAuth {
|
||||
if authDomain.AuthDomainConfig().Provider.Type != authtypes.AuthNProviderGoogleAuth {
|
||||
return "", errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthDomainMismatch, "domain type is not google")
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func (a *AuthN) HandleCallback(ctx context.Context, query url.Values) (*authtype
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "google: no id_token in token response")
|
||||
}
|
||||
|
||||
verifier := oidcProvider.Verifier(&oidc.Config{ClientID: authDomain.AuthDomainConfig().Google.ClientID})
|
||||
verifier := oidcProvider.Verifier(&oidc.Config{ClientID: authDomain.AuthDomainConfig().Google().ClientID})
|
||||
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
a.settings.Logger().ErrorContext(ctx, "google: failed to verify token", errors.Attr(err))
|
||||
@@ -135,7 +135,7 @@ func (a *AuthN) HandleCallback(ctx context.Context, query url.Values) (*authtype
|
||||
return nil, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "google: unexpected hd claim")
|
||||
}
|
||||
|
||||
if !authDomain.AuthDomainConfig().Google.InsecureSkipEmailVerified {
|
||||
if !authDomain.AuthDomainConfig().Google().InsecureSkipEmailVerified {
|
||||
if !claims.EmailVerified {
|
||||
a.settings.Logger().ErrorContext(ctx, "google: email is not verified", slog.String("email", claims.Email))
|
||||
return nil, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "google: email is not verified")
|
||||
@@ -148,14 +148,14 @@ func (a *AuthN) HandleCallback(ctx context.Context, query url.Values) (*authtype
|
||||
}
|
||||
|
||||
var groups []string
|
||||
if authDomain.AuthDomainConfig().Google.FetchGroups {
|
||||
groups, err = a.fetchGoogleWorkspaceGroups(ctx, claims.Email, authDomain.AuthDomainConfig().Google)
|
||||
if authDomain.AuthDomainConfig().Google().FetchGroups {
|
||||
groups, err = a.fetchGoogleWorkspaceGroups(ctx, claims.Email, authDomain.AuthDomainConfig().Google())
|
||||
if err != nil {
|
||||
a.settings.Logger().ErrorContext(ctx, "google: could not fetch groups", errors.Attr(err))
|
||||
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "google: could not fetch groups").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
allowedGroups := authDomain.AuthDomainConfig().Google.AllowedGroups
|
||||
allowedGroups := authDomain.AuthDomainConfig().Google().AllowedGroups
|
||||
if len(allowedGroups) > 0 {
|
||||
groups = filterGroups(groups, allowedGroups)
|
||||
if len(groups) == 0 {
|
||||
@@ -175,8 +175,8 @@ func (a *AuthN) ProviderInfo(ctx context.Context, authDomain *authtypes.AuthDoma
|
||||
|
||||
func (a *AuthN) oauth2Config(siteURL *url.URL, authDomain *authtypes.AuthDomain, provider *oidc.Provider) *oauth2.Config {
|
||||
return &oauth2.Config{
|
||||
ClientID: authDomain.AuthDomainConfig().Google.ClientID,
|
||||
ClientSecret: authDomain.AuthDomainConfig().Google.ClientSecret,
|
||||
ClientID: authDomain.AuthDomainConfig().Google().ClientID,
|
||||
ClientSecret: authDomain.AuthDomainConfig().Google().ClientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Scopes: scopes,
|
||||
RedirectURL: (&url.URL{
|
||||
|
||||
@@ -38,7 +38,7 @@ func (handler *handler) Create(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
authDomain, err := authtypes.NewAuthDomainFromConfig(body.Name, &body.Config, valuer.MustNewUUID(claims.OrgID))
|
||||
authDomain, err := authtypes.NewAuthDomainFromConfig(body.Name, &body.AuthDomainConfig, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -154,7 +154,7 @@ func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = authDomain.Update(&body.Config)
|
||||
err = authDomain.Update(&body.AuthDomainConfig)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
@@ -27,7 +27,7 @@ func (module *module) Get(ctx context.Context, id valuer.UUID) (*authtypes.AuthD
|
||||
}
|
||||
|
||||
func (module *module) GetAuthNProviderInfo(ctx context.Context, domain *authtypes.AuthDomain) *authtypes.AuthNProviderInfo {
|
||||
if callbackAuthN, ok := module.authNs[domain.AuthDomainConfig().AuthNProvider].(authn.CallbackAuthN); ok {
|
||||
if callbackAuthN, ok := module.authNs[domain.AuthDomainConfig().Provider.Type].(authn.CallbackAuthN); ok {
|
||||
return callbackAuthN.ProviderInfo(ctx, domain)
|
||||
}
|
||||
return &authtypes.AuthNProviderInfo{}
|
||||
@@ -62,7 +62,7 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
stats := make(map[string]any)
|
||||
|
||||
for _, domain := range domains {
|
||||
key := "authdomain." + domain.AuthDomainConfig().AuthNProvider.StringValue() + ".count"
|
||||
key := "authdomain." + domain.AuthDomainConfig().Provider.Type.StringValue() + ".count"
|
||||
if value, ok := stats[key]; ok {
|
||||
stats[key] = value.(int64) + 1
|
||||
} else {
|
||||
|
||||
@@ -201,7 +201,7 @@ func (module *module) getOrgSessionContext(ctx context.Context, org *types.Organ
|
||||
return authtypes.NewOrgSessionContext(org.ID, org.Name).AddPasswordAuthNSupport(authtypes.AuthNProviderEmailPassword), nil
|
||||
}
|
||||
|
||||
provider, err := getProvider[authn.CallbackAuthN](authDomain.AuthDomainConfig().AuthNProvider, module.authNs)
|
||||
provider, err := getProvider[authn.CallbackAuthN](authDomain.AuthDomainConfig().Provider.Type, module.authNs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -211,7 +211,7 @@ func (module *module) getOrgSessionContext(ctx context.Context, org *types.Organ
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewOrgSessionContext(org.ID, org.Name).AddCallbackAuthNSupport(authDomain.AuthDomainConfig().AuthNProvider, loginURL), nil
|
||||
return authtypes.NewOrgSessionContext(org.ID, org.Name).AddCallbackAuthNSupport(authDomain.AuthDomainConfig().Provider.Type, loginURL), nil
|
||||
}
|
||||
|
||||
func getProvider[T authn.AuthN](authNProvider authtypes.AuthNProvider, authNs map[authtypes.AuthNProvider]authn.AuthN) (T, error) {
|
||||
|
||||
@@ -211,6 +211,7 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewAddDashboardNameFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewFixChangelogOperationTypeFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewCloudIntegrationRemoveCascadeDeleteFactory(sqlschema),
|
||||
sqlmigration.NewMigrateAuthDomainPayloadFactory(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
118
pkg/sqlmigration/092_migrate_auth_domain_payload.go
Normal file
118
pkg/sqlmigration/092_migrate_auth_domain_payload.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type migrateAuthDomainPayload struct{}
|
||||
|
||||
type authDomainPayloadRaw struct {
|
||||
bun.BaseModel `bun:"table:auth_domain"`
|
||||
|
||||
ID string `bun:"id"`
|
||||
Data string `bun:"data"`
|
||||
}
|
||||
|
||||
// auth config type -> old sso type.
|
||||
var legacyConfigKeyByType = map[string]string{
|
||||
"saml": "samlConfig",
|
||||
"oidc": "oidcConfig",
|
||||
"google_auth": "googleAuthConfig",
|
||||
}
|
||||
|
||||
func NewMigrateAuthDomainPayloadFactory() factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(
|
||||
factory.MustNewName("migrate_auth_domain_payload"),
|
||||
func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &migrateAuthDomainPayload{}, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (migration *migrateAuthDomainPayload) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
func (migration *migrateAuthDomainPayload) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
var rows []*authDomainPayloadRaw
|
||||
if err := tx.NewSelect().Model(&rows).Scan(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
var oldData map[string]json.RawMessage
|
||||
if err := json.Unmarshal([]byte(row.Data), &oldData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// idempotency - we skip the ones which already migrated.
|
||||
if _, hasProvider := oldData["provider"]; hasProvider {
|
||||
continue
|
||||
}
|
||||
if _, hasSSOType := oldData["ssoType"]; !hasSSOType {
|
||||
continue
|
||||
}
|
||||
|
||||
var ssoType string
|
||||
if err := json.Unmarshal(oldData["ssoType"], &ssoType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
provider := map[string]json.RawMessage{
|
||||
"type": oldData["ssoType"],
|
||||
}
|
||||
|
||||
// get from old data and set config in provider.
|
||||
if configKey, ok := legacyConfigKeyByType[ssoType]; ok {
|
||||
if cfg, ok := oldData[configKey]; ok {
|
||||
provider["config"] = cfg
|
||||
}
|
||||
}
|
||||
|
||||
providerRaw, err := json.Marshal(provider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedData := map[string]json.RawMessage{
|
||||
"provider": providerRaw,
|
||||
}
|
||||
if v, ok := oldData["ssoEnabled"]; ok {
|
||||
updatedData["ssoEnabled"] = v
|
||||
}
|
||||
if v, ok := oldData["roleMapping"]; ok {
|
||||
updatedData["roleMapping"] = v
|
||||
}
|
||||
|
||||
updatedDataRaw, err := json.Marshal(updatedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
row.Data = string(updatedDataRaw)
|
||||
|
||||
if _, err := tx.NewUpdate().Model(row).Column("data").Where("id = ?", row.ID).Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *migrateAuthDomainPayload) Down(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ var (
|
||||
|
||||
type GettableAuthDomain struct {
|
||||
StorableAuthDomain
|
||||
Config AuthDomainConfig `json:"config"`
|
||||
AuthDomainConfig
|
||||
AuthNProviderInfo *AuthNProviderInfo `json:"authNProviderInfo"`
|
||||
}
|
||||
|
||||
@@ -39,12 +40,12 @@ type AuthNProviderInfo struct {
|
||||
}
|
||||
|
||||
type PostableAuthDomain struct {
|
||||
Config AuthDomainConfig `json:"config"`
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name"`
|
||||
AuthDomainConfig
|
||||
}
|
||||
|
||||
type UpdatableAuthDomain struct {
|
||||
Config AuthDomainConfig `json:"config"`
|
||||
AuthDomainConfig
|
||||
}
|
||||
|
||||
type StorableAuthDomain struct {
|
||||
@@ -57,22 +58,114 @@ type StorableAuthDomain struct {
|
||||
types.TimeAuditable
|
||||
}
|
||||
|
||||
// TODO: the oneOf emitted by JSONSchemaOneOf is not the shape OpenAPI wants
|
||||
// for a discriminated union. OpenAPI's discriminator requires every oneOf
|
||||
// branch to be a $ref to a named component and a sibling property whose value
|
||||
// selects the variant. ssoType is already discriminator-shaped, but the
|
||||
// variant payload lives in a sibling field (samlConfig / googleAuthConfig /
|
||||
// oidcConfig) instead of being the payload itself, so no discriminator can
|
||||
// be attached. Refactor AuthDomainConfig into an envelope (see
|
||||
// ruletypes.RuleThresholdData for the pattern) where the chosen config is
|
||||
// the payload and ssoType is the discriminator.
|
||||
type AuthDomainConfig struct {
|
||||
SSOEnabled bool `json:"ssoEnabled"`
|
||||
AuthNProvider AuthNProvider `json:"ssoType"`
|
||||
SAML *SamlConfig `json:"samlConfig"`
|
||||
Google *GoogleConfig `json:"googleAuthConfig"`
|
||||
OIDC *OIDCConfig `json:"oidcConfig"`
|
||||
RoleMapping *RoleMapping `json:"roleMapping"`
|
||||
SSOEnabled bool `json:"ssoEnabled"`
|
||||
RoleMapping *RoleMapping `json:"roleMapping,omitempty"`
|
||||
Provider AuthProviderEnvelope `json:"provider"`
|
||||
}
|
||||
|
||||
func (config AuthDomainConfig) Saml() *SAMLConfig {
|
||||
cfg, _ := config.Provider.Config.(*SAMLConfig)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (config AuthDomainConfig) Google() *GoogleConfig {
|
||||
cfg, _ := config.Provider.Config.(*GoogleConfig)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (config AuthDomainConfig) Oidc() *OIDCConfig {
|
||||
cfg, _ := config.Provider.Config.(*OIDCConfig)
|
||||
return cfg
|
||||
}
|
||||
|
||||
type AuthProviderEnvelope struct {
|
||||
Type AuthNProvider `json:"type" required:"true"`
|
||||
Config any `json:"config" required:"true"` // this can be either of SamlConfig, OIDCConfig and GoogleConfig
|
||||
}
|
||||
|
||||
// internal - drives the oneOf thing in open api spec.
|
||||
type authProviderSAML struct {
|
||||
Type AuthNProvider `json:"type" required:"true"`
|
||||
Config SAMLConfig `json:"config" required:"true"`
|
||||
}
|
||||
|
||||
type authProviderOIDC struct {
|
||||
Type AuthNProvider `json:"type" required:"true"`
|
||||
Config OIDCConfig `json:"config" required:"true"`
|
||||
}
|
||||
|
||||
type authProviderGoogle struct {
|
||||
Type AuthNProvider `json:"type" required:"true"`
|
||||
Config GoogleConfig `json:"config" required:"true"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ jsonschema.OneOfExposer = AuthProviderEnvelope{}
|
||||
_ jsonschema.Preparer = AuthProviderEnvelope{}
|
||||
)
|
||||
|
||||
func (AuthProviderEnvelope) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
authProviderSAML{},
|
||||
authProviderOIDC{},
|
||||
authProviderGoogle{},
|
||||
}
|
||||
}
|
||||
|
||||
func (AuthProviderEnvelope) PrepareJSONSchema(schema *jsonschema.Schema) error {
|
||||
if schema.ExtraProperties == nil {
|
||||
schema.ExtraProperties = map[string]any{}
|
||||
}
|
||||
|
||||
schema.ExtraProperties["x-signoz-discriminator"] = map[string]any{
|
||||
"propertyName": "type",
|
||||
"mapping": map[string]string{
|
||||
"saml": "#/components/schemas/AuthtypesAuthProviderSAML",
|
||||
"oidc": "#/components/schemas/AuthtypesAuthProviderOIDC",
|
||||
"google_auth": "#/components/schemas/AuthtypesAuthProviderGoogle",
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (envelop *AuthProviderEnvelope) UnmarshalJSON(data []byte) error {
|
||||
var raw struct {
|
||||
Type AuthNProvider `json:"type"`
|
||||
Config json.RawMessage `json:"config"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to unmarshal auth provider: %v", err)
|
||||
}
|
||||
|
||||
envelop.Type = raw.Type
|
||||
|
||||
switch raw.Type {
|
||||
case AuthNProviderSAML:
|
||||
cfg := new(SAMLConfig)
|
||||
if err := json.Unmarshal(raw.Config, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
envelop.Config = cfg
|
||||
case AuthNProviderOIDC:
|
||||
cfg := new(OIDCConfig)
|
||||
if err := json.Unmarshal(raw.Config, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
envelop.Config = cfg
|
||||
case AuthNProviderGoogleAuth:
|
||||
cfg := new(GoogleConfig)
|
||||
if err := json.Unmarshal(raw.Config, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
envelop.Config = cfg
|
||||
default:
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "unknown auth provider type: %s", raw.Type.StringValue())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuthDomain struct {
|
||||
@@ -121,7 +214,7 @@ func NewAuthDomainFromStorableAuthDomain(storableAuthDomain *StorableAuthDomain)
|
||||
func NewGettableAuthDomainFromAuthDomain(authDomain *AuthDomain, authNProviderInfo *AuthNProviderInfo) *GettableAuthDomain {
|
||||
return &GettableAuthDomain{
|
||||
StorableAuthDomain: *authDomain.StorableAuthDomain(),
|
||||
Config: *authDomain.AuthDomainConfig(),
|
||||
AuthDomainConfig: *authDomain.AuthDomainConfig(),
|
||||
AuthNProviderInfo: authNProviderInfo,
|
||||
}
|
||||
}
|
||||
@@ -158,51 +251,14 @@ func (typ *PostableAuthDomain) UnmarshalJSON(data []byte) error {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthDomainInvalidName, "invalid domain name %s", temp.Name)
|
||||
}
|
||||
|
||||
if temp.Provider.Config == nil {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthDomainInvalidConfig, "provider config is required")
|
||||
}
|
||||
|
||||
*typ = PostableAuthDomain(temp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (typ *AuthDomainConfig) UnmarshalJSON(data []byte) error {
|
||||
type Alias AuthDomainConfig
|
||||
|
||||
var temp Alias
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch temp.AuthNProvider {
|
||||
case AuthNProviderGoogleAuth:
|
||||
if temp.Google == nil {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthDomainInvalidConfig, "google auth config is required")
|
||||
}
|
||||
|
||||
case AuthNProviderSAML:
|
||||
if temp.SAML == nil {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthDomainInvalidConfig, "saml config is required")
|
||||
}
|
||||
|
||||
case AuthNProviderOIDC:
|
||||
if temp.OIDC == nil {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthDomainInvalidConfig, "oidc config is required")
|
||||
}
|
||||
|
||||
default:
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthDomainInvalidConfig, "invalid authn provider %q", temp.AuthNProvider.StringValue())
|
||||
}
|
||||
|
||||
*typ = AuthDomainConfig(temp)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (AuthDomainConfig) JSONSchemaOneOf() []any {
|
||||
return []any{
|
||||
SamlConfig{},
|
||||
GoogleConfig{},
|
||||
OIDCConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
type AuthDomainStore interface {
|
||||
// Get by id.
|
||||
Get(context.Context, valuer.UUID) (*AuthDomain, error)
|
||||
|
||||
154
pkg/types/authtypes/domain_test.go
Normal file
154
pkg/types/authtypes/domain_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Verifies the new flat wire shape: ssoType/provider configs collapse into a
|
||||
// single discriminated `provider:{type,config}`, and the typed payload survives
|
||||
// a marshal -> unmarshal round-trip. This fails if UnmarshalJSON forgets to
|
||||
// assign envelop.Config (the decoded config would be lost).
|
||||
func TestAuthDomainConfigWireRoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
provider AuthNProvider
|
||||
config any
|
||||
wantType string
|
||||
assertConfig func(t *testing.T, c AuthDomainConfig)
|
||||
}{
|
||||
{
|
||||
name: "saml",
|
||||
provider: AuthNProviderSAML,
|
||||
config: &SAMLConfig{
|
||||
SamlEntity: "https://idp.example.com",
|
||||
SamlIdp: "https://idp.example.com/sso",
|
||||
SamlCert: "cert-bytes",
|
||||
},
|
||||
wantType: "saml",
|
||||
assertConfig: func(t *testing.T, c AuthDomainConfig) {
|
||||
require.NotNil(t, c.Saml())
|
||||
require.Equal(t, "https://idp.example.com", c.Saml().SamlEntity)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "google",
|
||||
provider: AuthNProviderGoogleAuth,
|
||||
config: &GoogleConfig{ClientID: "cid", ClientSecret: "secret"},
|
||||
wantType: "google_auth",
|
||||
assertConfig: func(t *testing.T, c AuthDomainConfig) {
|
||||
require.NotNil(t, c.Google())
|
||||
require.Equal(t, "cid", c.Google().ClientID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "oidc",
|
||||
provider: AuthNProviderOIDC,
|
||||
config: &OIDCConfig{Issuer: "https://issuer", ClientID: "cid", ClientSecret: "secret"},
|
||||
wantType: "oidc",
|
||||
assertConfig: func(t *testing.T, c AuthDomainConfig) {
|
||||
require.NotNil(t, c.Oidc())
|
||||
require.Equal(t, "https://issuer", c.Oidc().Issuer)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
in := AuthDomainConfig{
|
||||
SSOEnabled: true,
|
||||
Provider: AuthProviderEnvelope{Type: tt.provider, Config: tt.config},
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(in)
|
||||
require.NoError(t, err)
|
||||
|
||||
js := string(raw)
|
||||
require.Contains(t, js, `"provider"`)
|
||||
require.Contains(t, js, `"type":"`+tt.wantType+`"`)
|
||||
// legacy keys must be gone
|
||||
require.NotContains(t, js, "ssoType")
|
||||
require.NotContains(t, js, "samlConfig")
|
||||
require.NotContains(t, js, "googleAuthConfig")
|
||||
require.NotContains(t, js, "oidcConfig")
|
||||
|
||||
var out AuthDomainConfig
|
||||
require.NoError(t, json.Unmarshal(raw, &out))
|
||||
require.True(t, out.SSOEnabled)
|
||||
require.Equal(t, tt.provider, out.Provider.Type)
|
||||
tt.assertConfig(t, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown discriminator values are rejected, and the nested provider config's
|
||||
// own validators still run through the envelope.
|
||||
func TestAuthDomainConfigUnmarshalRejects(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
}{
|
||||
{
|
||||
name: "unknown provider type",
|
||||
json: `{"ssoEnabled":true,"provider":{"type":"ldap","config":{}}}`,
|
||||
},
|
||||
{
|
||||
name: "oidc config missing clientId",
|
||||
json: `{"ssoEnabled":true,"provider":{"type":"oidc","config":{"issuer":"https://issuer","clientSecret":"secret"}}}`,
|
||||
},
|
||||
{
|
||||
name: "saml config missing samlEntity",
|
||||
json: `{"ssoEnabled":true,"provider":{"type":"saml","config":{"samlIdp":"https://idp/sso","samlCert":"abc"}}}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var c AuthDomainConfig
|
||||
require.Error(t, json.Unmarshal([]byte(tt.json), &c))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The config is marshaled into the `data` column and unmarshaled back when the
|
||||
// domain is loaded, so the typed provider config must survive that round-trip.
|
||||
func TestAuthDomainStorageRoundTrip(t *testing.T) {
|
||||
cfg := AuthDomainConfig{
|
||||
SSOEnabled: true,
|
||||
Provider: AuthProviderEnvelope{
|
||||
Type: AuthNProviderSAML,
|
||||
Config: &SAMLConfig{SamlEntity: "https://idp", SamlIdp: "https://idp/sso", SamlCert: "abc"},
|
||||
},
|
||||
}
|
||||
|
||||
domain, err := NewAuthDomainFromConfig("example.com", &cfg, valuer.GenerateUUID())
|
||||
require.NoError(t, err)
|
||||
|
||||
got := domain.AuthDomainConfig()
|
||||
require.Equal(t, AuthNProviderSAML, got.Provider.Type)
|
||||
require.NotNil(t, got.Saml())
|
||||
require.Equal(t, "https://idp", got.Saml().SamlEntity)
|
||||
}
|
||||
|
||||
// Postable keeps name-regex validation and still decodes the embedded provider.
|
||||
func TestPostableAuthDomainUnmarshal(t *testing.T) {
|
||||
valid := `{
|
||||
"name":"example.com",
|
||||
"ssoEnabled":true,
|
||||
"provider":{"type":"saml","config":{"samlEntity":"https://idp","samlIdp":"https://idp/sso","samlCert":"abc"}}
|
||||
}`
|
||||
|
||||
var p PostableAuthDomain
|
||||
require.NoError(t, json.Unmarshal([]byte(valid), &p))
|
||||
require.Equal(t, "example.com", p.Name)
|
||||
require.True(t, p.SSOEnabled)
|
||||
require.NotNil(t, p.Saml())
|
||||
require.Equal(t, "https://idp", p.Saml().SamlEntity)
|
||||
|
||||
invalid := `{"name":"not a domain!","provider":{"type":"saml","config":{"samlEntity":"https://idp","samlIdp":"https://idp/sso","samlCert":"abc"}}}`
|
||||
var bad PostableAuthDomain
|
||||
require.Error(t, json.Unmarshal([]byte(invalid), &bad))
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
)
|
||||
|
||||
type SamlConfig struct {
|
||||
type SAMLConfig struct {
|
||||
// The entityID of the SAML identity provider. It can typically be found in the EntityID attribute of the EntityDescriptor element in the SAML metadata of the identity provider. Example: <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="{samlEntity}">
|
||||
SamlEntity string `json:"samlEntity"`
|
||||
|
||||
@@ -25,8 +25,8 @@ type SamlConfig struct {
|
||||
AttributeMapping AttributeMapping `json:"attributeMapping"`
|
||||
}
|
||||
|
||||
func (config *SamlConfig) UnmarshalJSON(data []byte) error {
|
||||
type Alias SamlConfig
|
||||
func (config *SAMLConfig) UnmarshalJSON(data []byte) error {
|
||||
type Alias SAMLConfig
|
||||
|
||||
var temp Alias
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
@@ -51,6 +51,6 @@ func (config *SamlConfig) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
*config = SamlConfig(temp)
|
||||
*config = SAMLConfig(temp)
|
||||
return nil
|
||||
}
|
||||
|
||||
5
tests/fixtures/idp.py
vendored
5
tests/fixtures/idp.py
vendored
@@ -371,10 +371,11 @@ def idp_login(driver: webdriver.Chrome) -> Callable[[str, str], None]:
|
||||
|
||||
# Click the login button
|
||||
login_button = wait.until(EC.element_to_be_clickable((By.ID, "kc-login")))
|
||||
current_url = driver.current_url
|
||||
login_button.click()
|
||||
|
||||
# Wait till kc-login element has vanished from the page, which means that a redirection is taking place.
|
||||
wait.until(EC.invisibility_of_element((By.ID, "kc-login")))
|
||||
# Wait till the page redirects away from the login form. We poll the URL.
|
||||
wait.until(EC.url_changes(current_url))
|
||||
|
||||
return _idp_login
|
||||
|
||||
|
||||
@@ -53,10 +53,10 @@ def test_create_auth_domain(
|
||||
signoz.self.host_configs["8080"].get("/signoz/api/v1/domains"),
|
||||
json={
|
||||
"name": "oidc.basepath.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "oidc",
|
||||
"oidcConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "oidc",
|
||||
"config": {
|
||||
"clientId": settings["client_id"],
|
||||
"clientSecret": settings["client_secret"],
|
||||
# Change the hostname of the issuer to the internal resolvable hostname of the idp
|
||||
|
||||
@@ -51,10 +51,10 @@ def test_create_auth_domain(
|
||||
signoz.self.host_configs["8080"].get("/signoz/api/v1/domains"),
|
||||
json={
|
||||
"name": "saml.basepath.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": settings["entityID"],
|
||||
"samlIdp": settings["singleSignOnServiceLocation"],
|
||||
"samlCert": settings["certificate"],
|
||||
|
||||
@@ -31,10 +31,10 @@ def test_create_and_get_domain(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "domain-google.integration.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "google_auth",
|
||||
"googleAuthConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "google_auth",
|
||||
"config": {
|
||||
"clientId": "client-id",
|
||||
"clientSecret": "client-secret",
|
||||
"redirectURI": "redirect-uri",
|
||||
@@ -52,10 +52,10 @@ def test_create_and_get_domain(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "domain-saml.integration.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": "saml-entity",
|
||||
"samlIdp": "saml-idp",
|
||||
"samlCert": "saml-cert",
|
||||
@@ -86,7 +86,7 @@ def test_create_and_get_domain(
|
||||
"domain-google.integration.test",
|
||||
"domain-saml.integration.test",
|
||||
]
|
||||
assert domain["config"]["ssoType"] in ["google_auth", "saml"]
|
||||
assert domain["provider"]["type"] in ["google_auth", "saml"]
|
||||
|
||||
|
||||
def test_create_invalid(
|
||||
@@ -96,15 +96,16 @@ def test_create_invalid(
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a domain with type saml and body for oidc, this should fail because oidcConfig is not allowed for saml
|
||||
# Create a domain with type saml but an oidc-shaped config; this should fail
|
||||
# because the config is decoded as SAML and fails SAML validation.
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "domain.integration.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"oidcConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"clientId": "client-id",
|
||||
"clientSecret": "client-secret",
|
||||
"issuer": "issuer",
|
||||
@@ -122,10 +123,10 @@ def test_create_invalid(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "$%^invalid",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": "saml-entity",
|
||||
"samlIdp": "saml-idp",
|
||||
"samlCert": "saml-cert",
|
||||
@@ -142,15 +143,15 @@ def test_create_invalid(
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": "saml-entity",
|
||||
"samlIdp": "saml-idp",
|
||||
"samlCert": "saml-cert",
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
@@ -158,7 +159,7 @@ def test_create_invalid(
|
||||
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST
|
||||
|
||||
# Create a domain with no config
|
||||
# Create a domain with no provider
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
@@ -184,17 +185,17 @@ def test_create_invalid_role_mapping(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "invalid-role-test.integration.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": "saml-entity",
|
||||
"samlIdp": "saml-idp",
|
||||
"samlCert": "saml-cert",
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "SUPERADMIN", # Invalid role
|
||||
},
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "SUPERADMIN", # Invalid role
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
@@ -208,19 +209,19 @@ def test_create_invalid_role_mapping(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "invalid-group-role.integration.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": "saml-entity",
|
||||
"samlIdp": "saml-idp",
|
||||
"samlCert": "saml-cert",
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"admins": "SUPERUSER", # Invalid role
|
||||
},
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"admins": "SUPERUSER", # Invalid role
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -235,20 +236,20 @@ def test_create_invalid_role_mapping(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "valid-role-mapping.integration.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": "saml-entity",
|
||||
"samlIdp": "saml-idp",
|
||||
"samlCert": "saml-cert",
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
},
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -53,10 +53,10 @@ def test_create_auth_domain(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "saml.integration.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": settings["entityID"],
|
||||
"samlIdp": settings["singleSignOnServiceLocation"],
|
||||
"samlCert": settings["certificate"],
|
||||
@@ -176,10 +176,10 @@ def test_saml_update_domain_with_group_mappings(
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/domains/{domain['id']}"),
|
||||
json={
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": settings["entityID"],
|
||||
"samlIdp": settings["singleSignOnServiceLocation"],
|
||||
"samlCert": settings["certificate"],
|
||||
@@ -189,15 +189,15 @@ def test_saml_update_domain_with_group_mappings(
|
||||
"role": "signoz_role",
|
||||
},
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
"signoz-viewers": "VIEWER",
|
||||
},
|
||||
"useRoleAttribute": False,
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
"signoz-viewers": "VIEWER",
|
||||
},
|
||||
"useRoleAttribute": False,
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
@@ -340,10 +340,10 @@ def test_saml_update_domain_with_use_role_claim(
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/domains/{domain['id']}"),
|
||||
json={
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "saml",
|
||||
"samlConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "saml",
|
||||
"config": {
|
||||
"samlEntity": settings["entityID"],
|
||||
"samlIdp": settings["singleSignOnServiceLocation"],
|
||||
"samlCert": settings["certificate"],
|
||||
@@ -353,14 +353,14 @@ def test_saml_update_domain_with_use_role_claim(
|
||||
"role": "signoz_role",
|
||||
},
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
},
|
||||
"useRoleAttribute": True,
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
},
|
||||
"useRoleAttribute": True,
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
|
||||
@@ -57,10 +57,10 @@ def test_create_auth_domain(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/domains"),
|
||||
json={
|
||||
"name": "oidc.integration.test",
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "oidc",
|
||||
"oidcConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "oidc",
|
||||
"config": {
|
||||
"clientId": settings["client_id"],
|
||||
"clientSecret": settings["client_secret"],
|
||||
# Change the hostname of the issuer to the internal resolvable hostname of the idp
|
||||
@@ -132,10 +132,10 @@ def test_oidc_update_domain_with_group_mappings(
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/domains/{domain['id']}"),
|
||||
json={
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "oidc",
|
||||
"oidcConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "oidc",
|
||||
"config": {
|
||||
"clientId": settings["client_id"],
|
||||
"clientSecret": settings["client_secret"],
|
||||
"issuer": f"{idp.container.container_configs['6060'].get(urlparse(settings['issuer']).path)}",
|
||||
@@ -148,15 +148,15 @@ def test_oidc_update_domain_with_group_mappings(
|
||||
"role": "signoz_role",
|
||||
},
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
"signoz-viewers": "VIEWER",
|
||||
},
|
||||
"useRoleAttribute": False,
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
"signoz-viewers": "VIEWER",
|
||||
},
|
||||
"useRoleAttribute": False,
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
@@ -301,10 +301,10 @@ def test_oidc_update_domain_with_use_role_claim(
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/domains/{domain['id']}"),
|
||||
json={
|
||||
"config": {
|
||||
"ssoEnabled": True,
|
||||
"ssoType": "oidc",
|
||||
"oidcConfig": {
|
||||
"ssoEnabled": True,
|
||||
"provider": {
|
||||
"type": "oidc",
|
||||
"config": {
|
||||
"clientId": settings["client_id"],
|
||||
"clientSecret": settings["client_secret"],
|
||||
"issuer": f"{idp.container.container_configs['6060'].get(urlparse(settings['issuer']).path)}",
|
||||
@@ -317,14 +317,14 @@ def test_oidc_update_domain_with_use_role_claim(
|
||||
"role": "signoz_role",
|
||||
},
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
},
|
||||
"useRoleAttribute": True,
|
||||
},
|
||||
"roleMapping": {
|
||||
"defaultRole": "VIEWER",
|
||||
"groupMappings": {
|
||||
"signoz-admins": "ADMIN",
|
||||
"signoz-editors": "EDITOR",
|
||||
},
|
||||
"useRoleAttribute": True,
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
|
||||
Reference in New Issue
Block a user