Rewrite identity and access management

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-12-03 19:23:15 +01:00
parent 4ed3f5a067
commit 74fc3b8cd1
201 changed files with 32895 additions and 23649 deletions

View File

@@ -16,6 +16,8 @@ package coredata
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"maps"
"time"
@@ -23,28 +25,55 @@ import (
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type SAMLConfiguration struct {
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
EmailDomain string `db:"email_domain"`
Enabled bool `db:"enabled"`
EnforcementPolicy SAMLEnforcementPolicy `db:"enforcement_policy"`
IdPEntityID string `db:"idp_entity_id"`
IdPSsoURL string `db:"idp_sso_url"`
IdPCertificate string `db:"idp_certificate"`
IdPMetadataURL *string `db:"idp_metadata_url"`
AttributeEmail string `db:"attribute_email"`
AttributeFirstname string `db:"attribute_firstname"`
AttributeLastname string `db:"attribute_lastname"`
AttributeRole string `db:"attribute_role"`
AutoSignupEnabled bool `db:"auto_signup_enabled"`
DomainVerified bool `db:"domain_verified"`
DomainVerificationToken *string `db:"domain_verification_token"`
DomainVerifiedAt *time.Time `db:"domain_verified_at"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
type (
SAMLConfiguration struct {
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
EmailDomain string `db:"email_domain"`
EnforcementPolicy SAMLEnforcementPolicy `db:"enforcement_policy"`
IdPEntityID string `db:"idp_entity_id"`
IdPSsoURL string `db:"idp_sso_url"`
IdPCertificate string `db:"idp_certificate"`
IdPMetadataURL *string `db:"idp_metadata_url"`
AttributeEmail string `db:"attribute_email"`
AttributeFirstname string `db:"attribute_firstname"`
AttributeLastname string `db:"attribute_lastname"`
AttributeRole string `db:"attribute_role"`
AutoSignupEnabled bool `db:"auto_signup_enabled"`
DomainVerified bool `db:"domain_verified"`
DomainVerificationToken *string `db:"domain_verification_token"`
DomainVerifiedAt *time.Time `db:"domain_verified_at"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
SAMLConfigurations []*SAMLConfiguration
)
func (s *SAMLConfiguration) CursorKey(orderBy SAMLConfigurationOrderField) page.CursorKey {
switch orderBy {
case SAMLConfigurationOrderFieldCreatedAt:
return page.NewCursorKey(s.ID, s.CreatedAt)
}
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
}
func (s *SAMLConfiguration) GetIdPCertificate() (*x509.Certificate, error) {
block, _ := pem.Decode([]byte(s.IdPCertificate))
if block == nil {
return nil, fmt.Errorf("cannot decode PEM block from IdP certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("cannot parse X.509 certificate: %w", err)
}
return cert, nil
}
func (s *SAMLConfiguration) LoadByOrganizationIDAndEmailDomain(
@@ -154,6 +183,10 @@ LIMIT 1;
config, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[SAMLConfiguration])
if err != nil {
if err == pgx.ErrNoRows {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect saml_configuration: %w", err)
}
@@ -173,7 +206,6 @@ INSERT INTO auth_saml_configurations (
tenant_id,
organization_id,
email_domain,
enabled,
enforcement_policy,
idp_entity_id,
idp_sso_url,
@@ -194,7 +226,6 @@ INSERT INTO auth_saml_configurations (
@tenant_id,
@organization_id,
@email_domain,
@enabled,
@enforcement_policy,
@idp_entity_id,
@idp_sso_url,
@@ -218,7 +249,6 @@ INSERT INTO auth_saml_configurations (
"tenant_id": scope.GetTenantID(),
"organization_id": s.OrganizationID,
"email_domain": s.EmailDomain,
"enabled": s.Enabled,
"enforcement_policy": s.EnforcementPolicy,
"idp_entity_id": s.IdPEntityID,
"idp_sso_url": s.IdPSsoURL,
@@ -252,7 +282,6 @@ func (s *SAMLConfiguration) Update(
q := `
UPDATE auth_saml_configurations
SET
enabled = @enabled,
enforcement_policy = @enforcement_policy,
idp_entity_id = @idp_entity_id,
idp_sso_url = @idp_sso_url,
@@ -276,7 +305,6 @@ WHERE
args := pgx.StrictNamedArgs{
"id": s.ID,
"enabled": s.Enabled,
"enforcement_policy": s.EnforcementPolicy,
"idp_entity_id": s.IdPEntityID,
"idp_sso_url": s.IdPSsoURL,
@@ -328,18 +356,17 @@ WHERE
return nil
}
func LoadSAMLConfigurationsByOrganizationID(
func (s *SAMLConfigurations) LoadByOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
) ([]*SAMLConfiguration, error) {
) error {
q := `
SELECT
id,
organization_id,
email_domain,
enabled,
enforcement_policy,
idp_entity_id,
idp_sso_url,
@@ -370,20 +397,17 @@ ORDER BY email_domain ASC;
rows, err := conn.Query(ctx, q, args)
if err != nil {
return nil, fmt.Errorf("cannot query auth_saml_configurations: %w", err)
return fmt.Errorf("cannot query auth_saml_configurations: %w", err)
}
configs, err := pgx.CollectRows(rows, pgx.RowToStructByName[SAMLConfiguration])
samlConfigurations, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[SAMLConfiguration])
if err != nil {
return nil, fmt.Errorf("cannot collect saml_configurations: %w", err)
return fmt.Errorf("cannot collect saml_configurations: %w", err)
}
result := make([]*SAMLConfiguration, len(configs))
for i := range configs {
result[i] = &configs[i]
}
*s = samlConfigurations
return result, nil
return nil
}
// LoadAllEnabledSAMLConfigurationsByEmailDomain loads all enabled SAML configurations for a given email domain
@@ -398,7 +422,6 @@ SELECT
id,
organization_id,
email_domain,
enabled,
enforcement_policy,
idp_entity_id,
idp_sso_url,
@@ -458,7 +481,6 @@ SELECT
id,
organization_id,
email_domain,
enabled,
enforcement_policy,
idp_entity_id,
idp_sso_url,
@@ -503,3 +525,38 @@ WHERE
return result, nil
}
func (s *SAMLConfigurations) CountByOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
) (int, error) {
q := `
SELECT
COUNT(*)
FROM
auth_saml_configurations
WHERE
%s
AND organization_id = @organization_id
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return 0, fmt.Errorf("cannot query auth_saml_configurations: %w", err)
}
var count int
err = rows.Scan(&count)
if err != nil {
return 0, fmt.Errorf("cannot collect count: %w", err)
}
return count, nil
}