Files
fleet/server/fleet/scim.go
T
Lucas Manuel Rodriguez 608f768dd7 Add support for IdP department to SCIM and add FLEET_VAR_HOST_END_USER_IDP_DEPARTMENT fleet variable (#30375)
#29609

Verified the changes with [Entra ID's
validator](https://scimvalidator.microsoft.com/) and adding the
department attribute to the tester:
<img width="1312" alt="Screenshot 2025-06-27 at 8 54 32 AM"
src="https://github.com/user-attachments/assets/45a5deb8-7c65-49df-b3e8-eb05bea11f6b"
/>
<img width="1312" alt="Screenshot 2025-06-27 at 8 54 21 AM"
src="https://github.com/user-attachments/assets/91b554b5-b0b9-4bb6-a0cf-4e3b40e6ce21"
/>

- Tested with Okta
- TODO: Test with Entra ID and Google Workspace.
- I decided to not fail profile deployment if a user has no department
because it's not a required attribute, instead the
`FLEET_VAR_HOST_END_USER_IDP_DEPARTMENT` will be replaced with the empty
string.

---

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [X] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [X] If database migrations are included, checked table schema to
confirm autoupdate

(https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485)
- For database migrations:
- [X] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [X] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [X] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).
- [X] Added/updated automated tests
- [X] Manual QA for all new/changed functionality
2025-06-29 15:23:03 -03:00

121 lines
3.2 KiB
Go

package fleet
import "time"
// SCIMMaxFieldLength is the default maximum length for SCIM fields
const SCIMMaxFieldLength = 255
// ScimUser represents a SCIM user in the database
type ScimUser struct {
ID uint `db:"id"`
ExternalID *string `db:"external_id"`
UserName string `db:"user_name"`
GivenName *string `db:"given_name"`
FamilyName *string `db:"family_name"`
Department *string `db:"department"`
Active *bool `db:"active"`
UpdatedAt time.Time `db:"updated_at"`
Emails []ScimUserEmail
Groups []ScimUserGroup
}
type ScimUserGroup struct {
ID uint `db:"id"`
DisplayName string `db:"display_name"`
}
func (su *ScimUser) AuthzType() string {
return "scim_user"
}
func (su *ScimUser) DisplayName() string {
switch {
case su.GivenName != nil && len(*su.GivenName) > 0 && su.FamilyName != nil && len(*su.FamilyName) > 0:
return *su.GivenName + " " + *su.FamilyName
case su.GivenName != nil && len(*su.GivenName) > 0:
return *su.GivenName
case su.FamilyName != nil && len(*su.FamilyName) > 0:
return *su.FamilyName
default:
return ""
}
}
// ScimUserEmail represents an email address associated with a SCIM user
type ScimUserEmail struct {
ScimUserID uint `db:"scim_user_id"`
Email string `db:"email"`
Primary *bool `db:"primary"`
Type *string `db:"type"`
}
// GenerateComparisonKey generates a unique string representation of the email
// that can be used for comparison, properly handling nil values.
func (e ScimUserEmail) GenerateComparisonKey() string {
// Handle Type field which can be nil
typeValue := "nil"
if e.Type != nil {
typeValue = *e.Type
}
// Handle Primary field which can be nil
primaryValue := "nil"
if e.Primary != nil {
if *e.Primary {
primaryValue = "true"
} else {
primaryValue = "false"
}
}
return e.Email + ":" + typeValue + ":" + primaryValue
}
type ScimListOptions struct {
// 1-based index of the first result to return (must be positive integer)
StartIndex uint
// How many results per page (must be positive integer)
PerPage uint
}
type ScimUsersListOptions struct {
ScimListOptions
// UserNameFilter filters by userName -- max of 1 response is expected
// Cannot be used with other filters.
UserNameFilter *string
// EmailTypeFilter and EmailValueFilter are needed to support Entra ID filter: emails[type eq "work"].value eq "user@contoso.com"
// https://learn.microsoft.com/en-us/entra/identity/app-provisioning/use-scim-to-provision-users-and-groups#users
// Cannot be used with other filters.
EmailTypeFilter *string
EmailValueFilter *string
}
type ScimGroupsListOptions struct {
ScimListOptions
// DisplayNameFilter filters by displayName
DisplayNameFilter *string
// ExcludeUsers if true, the group's users will not be fetched
ExcludeUsers bool
}
type ScimGroup struct {
ID uint `db:"id"`
ExternalID *string `db:"external_id"`
DisplayName string `db:"display_name"`
ScimUsers []uint
}
type ScimLastRequest struct {
Status string `db:"status" json:"status"`
Details string `db:"details" json:"details"`
RequestedAt time.Time `db:"updated_at" json:"requested_at"`
}
type ScimDetails struct {
LastRequest *ScimLastRequest `json:"last_request"`
}