diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index bbbbc64c..12d6833c 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -92,7 +92,7 @@ type userFilterConfig struct { type Config struct { Domain string - ApiToken string //nolint:gosec // Not a credential + ApiToken string OktaClientId string OktaPrivateKey string OktaPrivateKeyId string @@ -325,6 +325,106 @@ func (c *Okta) Metadata(ctx context.Context) (*v2.ConnectorMetadata, error) { Placeholder: "True/False", Order: 5, }, + "create_in_staged_status": { + DisplayName: "Create in Staged Status", + Required: false, + Description: "When 'true', the user is created in STAGED status instead of being activated. Staged users can be activated later via Okta workflows.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "True/False", + Order: 6, + }, + "department": { + DisplayName: "Department", + Required: false, + Description: "The department of the user.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "Department", + Order: 7, + }, + "title": { + DisplayName: "Title", + Required: false, + Description: "The job title of the user.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "Title", + Order: 8, + }, + "display_name": { + DisplayName: "Display Name", + Required: false, + Description: "The display name of the user. If not set, Okta will derive it from first and last name.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "Display Name", + Order: 9, + }, + "user_type": { + DisplayName: "User Type", + Required: false, + Description: "The user type (e.g., 'Employee', 'Contractor').", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "User Type", + Order: 10, + }, + "organization": { + DisplayName: "Organization", + Required: false, + Description: "The organization of the user.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "Organization", + Order: 11, + }, + "manager_id": { + DisplayName: "Manager ID", + Required: false, + Description: "The Okta user ID of the user's manager.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "Manager ID", + Order: 12, + }, + "cost_center": { + DisplayName: "Cost Center", + Required: false, + Description: "The cost center of the user.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "Cost Center", + Order: 13, + }, + "division": { + DisplayName: "Division", + Required: false, + Description: "The division of the user.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "Division", + Order: 14, + }, + "employee_number": { + DisplayName: "Employee Number", + Required: false, + Description: "The employee number of the user.", + Field: &v2.ConnectorAccountCreationSchema_Field_StringField{ + StringField: &v2.ConnectorAccountCreationSchema_StringField{}, + }, + Placeholder: "Employee Number", + Order: 15, + }, }, }, }, nil diff --git a/pkg/connector/user.go b/pkg/connector/user.go index 5297da5c..1d9d8187 100644 --- a/pkg/connector/user.go +++ b/pkg/connector/user.go @@ -541,6 +541,19 @@ func getCredentialOption(credentialOptions *v2.LocalCredentialOptions) (*okta.Us }, nil } +// optionalProfileFields maps account creation schema field names to Okta profile attribute names. +var optionalProfileFields = map[string]string{ + "department": "department", + "title": "title", + "display_name": "displayName", + "user_type": "userType", + "organization": "organization", + "manager_id": "managerId", + "cost_center": "costCenter", + "division": "division", + "employee_number": "employeeNumber", +} + func getUserProfile(accountInfo *v2.AccountInfo) (*okta.UserProfile, error) { pMap := accountInfo.Profile.AsMap() firstName, ok := pMap["first_name"] @@ -563,33 +576,65 @@ func getUserProfile(accountInfo *v2.AccountInfo) (*okta.UserProfile, error) { login = email } - return &okta.UserProfile{ + profile := &okta.UserProfile{ "firstName": firstName, "lastName": lastName, "email": email, "login": login, - }, nil -} + } -func getAccountCreationQueryParams(accountInfo *v2.AccountInfo, credentialOptions *v2.LocalCredentialOptions) (*query.Params, error) { - if credentialOptions.GetNoPassword() != nil { - return nil, nil + for schemaField, oktaField := range optionalProfileFields { + if val, ok := pMap[schemaField]; ok { + if strVal, isStr := val.(string); isStr && strVal != "" { + (*profile)[oktaField] = strVal + } + } } - pMap := accountInfo.Profile.AsMap() - requirePass := pMap["password_change_on_login_required"] - requirePasswordChanged := false - switch v := requirePass.(type) { + return profile, nil +} + +func parseBoolField(pMap map[string]interface{}, fieldName string) (bool, error) { + val := pMap[fieldName] + switch v := val.(type) { case bool: - requirePasswordChanged = v + return v, nil case string: parsed, err := strconv.ParseBool(v) if err != nil { - return nil, err + return false, err } - requirePasswordChanged = parsed + return parsed, nil case nil: - // Do nothing + return false, nil + default: + return false, fmt.Errorf("okta-connectorv2: unsupported type for %s: %T", fieldName, val) + } +} + +func getAccountCreationQueryParams(accountInfo *v2.AccountInfo, credentialOptions *v2.LocalCredentialOptions) (*query.Params, error) { + pMap := accountInfo.Profile.AsMap() + + createStaged, err := parseBoolField(pMap, "create_in_staged_status") + if err != nil { + return nil, err + } + + // If creating in staged status, set Activate to false and return early. + // Staged users are not activated on creation regardless of credential options. + if createStaged { + return &query.Params{ + Activate: ToPtr(false), + }, nil + } + + if credentialOptions.GetNoPassword() != nil { + return nil, nil + } + + requirePasswordChanged, err := parseBoolField(pMap, "password_change_on_login_required") + if err != nil { + return nil, err } params := &query.Params{}