#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
121 lines
3.2 KiB
Go
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"`
|
|
}
|