package model import ( "encoding/json" "fmt" "net/url" "strings" "github.com/google/uuid" "github.com/pkg/errors" saml2 "github.com/russellhaering/gosaml2" "go.signoz.io/signoz/ee/query-service/saml" basemodel "go.signoz.io/signoz/pkg/query-service/model" ) type SSOType string const ( SAML SSOType = "SAML" GoogleAuth SSOType = "GOOGLE_AUTH" ) type SamlConfig struct { SamlEntity string `json:"samlEntity"` SamlIdp string `json:"samlIdp"` SamlCert string `json:"samlCert"` } // OrgDomain identify org owned web domains for auth and other purposes type OrgDomain struct { Id uuid.UUID `json:"id"` Name string `json:"name"` OrgId string `json:"orgId"` SsoEnabled bool `json:"ssoEnabled"` SsoType SSOType `json:"ssoType"` SamlConfig *SamlConfig `json:"samlConfig"` Org *basemodel.Organization } // Valid is used a pipeline function to check if org domain // loaded from db is valid func (od *OrgDomain) Valid(err error) error { if err != nil { return err } if od.Id == uuid.Nil || od.OrgId == "" { return fmt.Errorf("both id and orgId are required") } return nil } // ValidNew cheks if the org domain is valid for insertion in db func (od *OrgDomain) ValidNew() error { if od.OrgId == "" { return fmt.Errorf("orgId is required") } if od.Name == "" { return fmt.Errorf("name is required") } return nil } // LoadConfig loads config params from json text func (od *OrgDomain) LoadConfig(jsondata string) error { d := *od err := json.Unmarshal([]byte(jsondata), &d) if err != nil { return errors.Wrap(err, "failed to marshal json to OrgDomain{}") } *od = d return nil } func (od *OrgDomain) GetSAMLEntityID() string { if od.SamlConfig != nil { return od.SamlConfig.SamlEntity } return "" } func (od *OrgDomain) GetSAMLIdpURL() string { if od.SamlConfig != nil { return od.SamlConfig.SamlIdp } return "" } func (od *OrgDomain) GetSAMLCert() string { if od.SamlConfig != nil { return od.SamlConfig.SamlCert } return "" } // PrepareSamlRequest creates a request accordingly gosaml2 func (od *OrgDomain) PrepareSamlRequest(siteUrl *url.URL) (*saml2.SAMLServiceProvider, error) { // this is the url Idp will call after login completion acs := fmt.Sprintf("%s://%s/%s", siteUrl.Scheme, siteUrl.Host, "api/v1/complete/saml") // this is the address of the calling url, useful to redirect user sourceUrl := fmt.Sprintf("%s://%s%s", siteUrl.Scheme, siteUrl.Host, siteUrl.Path) // ideally this should be some unique ID for each installation // but since we dont have UI to support it, we default it to // host. this issuer is an identifier of service provider (signoz) // on id provider (e.g. azure, okta). Azure requires this id to be configured // in their system, while others seem to not care about it. // currently we default it to host from window.location (received from browser) issuer := siteUrl.Host return saml.PrepareRequest(issuer, acs, sourceUrl, od.GetSAMLEntityID(), od.GetSAMLIdpURL(), od.GetSAMLCert()) } func (od *OrgDomain) BuildSsoUrl(siteUrl *url.URL) (ssoUrl string, err error) { sp, err := od.PrepareSamlRequest(siteUrl) if err != nil { return "", err } fmtDomainId := strings.Replace(od.Id.String(), "-", ":", -1) relayState := fmt.Sprintf("%s://%s%s?domainId=%s", siteUrl.Scheme, siteUrl.Host, siteUrl.Path, fmtDomainId) return sp.BuildAuthURL(relayState) }