Class: TwoPercent::ScimGroup

Inherits:
ApplicationRecord show all
Defined in:
app/models/two_percent/scim_group.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.destroy_by_scim_id(scim_id) ⇒ Object



54
55
56
# File 'app/models/two_percent/scim_group.rb', line 54

def self.destroy_by_scim_id(scim_id)
  find_by_scim_id(scim_id)&.destroy
end

.exists_by_scim_id?(scim_id) ⇒ Boolean

Returns:

  • (Boolean)


50
51
52
# File 'app/models/two_percent/scim_group.rb', line 50

def self.exists_by_scim_id?(scim_id)
  exists?(scim_id: scim_id)
end

.find_by_scim_id(scim_id) ⇒ Object



46
47
48
# File 'app/models/two_percent/scim_group.rb', line 46

def self.find_by_scim_id(scim_id)
  find_by(scim_id: scim_id)
end

.upsert_from_scim(resource_type, scim_hash, correlation_id: nil) ⇒ TwoPercent::ScimGroup

Creates or updates a group from SCIM data

Generates a UUID for the id field if not present (for POST/create operations). Validates the SCIM data against the Group schema before persisting.

Parameters:

  • resource_type (String)

    The resource type (e.g., “Groups”, “Departments”, “Territories”)

  • scim_hash (Hash)

    SCIM Group resource hash conforming to RFC 7643

  • correlation_id (String, nil) (defaults to: nil)

    Optional correlation ID for tracking changes across network hops (e.g., App A -> App B -> App C)

Returns:

Raises:

  • (TwoPercent::Scim::ValidationError)

    If SCIM data fails schema validation



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'app/models/two_percent/scim_group.rb', line 32

def self.upsert_from_scim(resource_type, scim_hash, correlation_id: nil)
  # Generate ID if not present (for POST/create operations)
  scim_hash = scim_hash.dup
  scim_hash["id"] ||= SecureRandom.uuid

  validated_data = TwoPercent::Scim::Schema.validate_group(scim_hash, require_id: true)
  scim_group = find_or_initialize_by(scim_id: scim_hash["id"])
  scim_group.update_from_scim!(resource_type, validated_data, correlation_id: correlation_id)

  scim_group.replace_members(scim_hash["members"], correlation_id) if scim_hash.key?("members")

  scim_group
end

Instance Method Details

#replace_members(members_array, correlation_id) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
# File 'app/models/two_percent/scim_group.rb', line 104

def replace_members(members_array, correlation_id)
  member_scim_ids = members_array.filter_map { |m| m["value"] }
  existing_users = validate_users_exist!(member_scim_ids)
  existing_user_ids = scim_group_memberships.pluck(:scim_user_id)

  users_to_add = existing_users.where.not(id: existing_user_ids)
  bulk_insert_memberships(users_to_add, correlation_id) if users_to_add.any?

  # Bulk delete removed memberships
  users_to_remove_ids = scim_users.where.not(scim_id: member_scim_ids).pluck(:id)
  scim_group_memberships.where(scim_user_id: users_to_remove_ids).delete_all
end

#scim_attribute(path) ⇒ Object?

Extracts a nested attribute from the scim_data JSON

Examples:

group.scim_attribute("members.0.value") # => "user-id-123"

Parameters:

  • path (String)

    Dot-separated path to the attribute (e.g., “displayName”)

Returns:

  • (Object, nil)

    The attribute value or nil if not found



123
124
125
126
# File 'app/models/two_percent/scim_group.rb', line 123

def scim_attribute(path)
  keys = path.split(".")
  scim_data.dig(*keys)
end

#to_domain_attributesHash

Extracts domain attributes for publishing in domain events

Returns key attributes for event payloads.

Returns:

  • (Hash)

    Domain attributes



62
63
64
65
66
67
68
69
70
# File 'app/models/two_percent/scim_group.rb', line 62

def to_domain_attributes
  {
    scim_id: scim_id,
    external_id: external_id,
    display_name: display_name,
    resource_type: resource_type,
    active: active,
  }.compact
end

#to_scim_representationHash

Returns full SCIM representation for HTTP responses

Returns:

  • (Hash)

    RFC 7644 compliant SCIM Group resource



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/models/two_percent/scim_group.rb', line 75

def to_scim_representation
  representation = scim_data.merge(
    "id" => scim_id,
    "meta" => {
      "resourceType" => resource_type,
      "created" => created_at.iso8601,
      "lastModified" => updated_at.iso8601,
    }
  )

  representation["members"] = members_representation if scim_users.loaded?
  representation
end

#update_from_scim!(resource_type, validated_data, correlation_id: nil) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'app/models/two_percent/scim_group.rb', line 89

def update_from_scim!(resource_type, validated_data, correlation_id: nil)
  core_data = validated_data[:core]
  self.scim_data = core_data.merge(validated_data[:extensions])
  self.scim_id = core_data["id"]
  self.external_id = core_data["externalId"]
  self.display_name = core_data["displayName"]
  self.resource_type = resource_type

  extension_data = validated_data[:extensions]
  self.active = extension_data.dig("urn:ietf:params:scim:schemas:extension:authservice:2.0:Group",
                                   "active") != false
  self.correlation_id = correlation_id
  save!
end