Class: OpenFga::SdkClient

Inherits:
Object
  • Object
show all
Defined in:
lib/openfga/client/openfga_client.rb

Constant Summary collapse

PAGE_SIZE =
50
CREDENTIALS_METHODS =
%i[none api_token client_credentials].freeze

Instance Method Summary collapse

Constructor Details

#initialize(config = {}) ⇒ SdkClient

Returns a new instance of SdkClient.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/openfga/client/openfga_client.rb', line 11

def initialize(config = {})
  raise ConfigurationNilError.new(:api_url) unless config[:api_url]

  @config = config

  @config[:credentials] ||= {
    method: :none
  }

  @logger = config[:logger] || Logger.new($stdout)

  # Later we can support custom token managers.
  @token_manager = case @config[:credentials][:method]
                   when :none
                     TokenManager::NoopTokenManager.new
                   when :api_token
                     TokenManager::StaticTokenManager.new(@config.dig(:credentials, :api_token))
                   when :client_credentials
                     oauth_config = TokenManager::Oauth2TokenManager::Config.new(
                       client_id: @config.dig(:credentials, :client_id),
                       client_secret: @config.dig(:credentials, :client_secret),
                       token_issuer: @config.dig(:credentials, :api_token_issuer),
                       audience: @config.dig(:credentials, :api_audience),
                       logger: @logger
                     )

                     TokenManager::Oauth2TokenManager.new(oauth_config)
                   else
                     valid_methods = CREDENTIALS_METHODS.join(', ')

                     raise ConfigurationError, "Unknown credentials method: #{@config[:credentials]}" \
                       "Supported methods: #{valid_methods}"
  end

  api_client_config = Configuration.new do |c|
    c.server_index = nil
    c.host = @config[:api_url]
    c.scheme = URI(@config[:api_url]).scheme
    c.logger = @logger
  end

  @api_client = OpenFga::OpenFgaApi.new(ApiClient.new(api_client_config))
end

Instance Method Details

#batch_check(body = {}) ⇒ BatchCheckResponse

Performs multiple relationship checks in a single batch request.

Parameters:

  • body (Hash) (defaults to: {})

    The request body containing:

    • :checks [Array<Hash>] Each check item must include:

      - :tuple_key [Hash] The tuple key for the relationship to check, with:
          - :user [String] The user involved in the relationship.
          - :relation [String, Symbol] The relation to check (e.g. "reader" or :owner).
          - :object [String] The object involved in the relationship.
      - :correlation_id [String] Unique identifier for correlating the request with the response (max 36 chars, alphanumeric + hyphens).
      - :contextual_tuples [Hash] (optional) Additional contextual tuples to include in the check.
      - :context [Hash] (optional) Additional context for the check.
      
    • :opts [Hash] Optional parameters for the batch check:

      - :authorization_model_id [String] The ID of the authorization model.
      - :consistency [String] The consistency level for the checks.
      - :max_parallel_requests [Integer] Maximum concurrent requests \(default: 10\).
      - :max_batch_size [Integer] Maximum checks per batch \(default: 50\).
      

Returns:

  • (BatchCheckResponse)

    The result of the batch check operation with results keyed by correlation_id.

Raises:

  • (ArgumentError)

    If the checks array is empty, or if any check item is missing required parameters.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/openfga/client/openfga_client.rb', line 74

def batch_check(body = {})
  checks = body[:checks]
  fail ArgumentError, "Missing the required parameter 'checks'" if checks.nil? || checks.empty?

  opts = body[:opts] || {}
  # Configuration with defaults matching Python SDK
  max_parallel_requests = opts[:max_parallel_requests] || 10
  max_batch_size = opts[:max_batch_size] || 50

  # Validate all checks first and check for duplicates
  correlation_ids = Set.new

  checks.each_with_index do |check, index|
    fail ArgumentError, "Missing 'tuple_key' in check item at index #{index}" if check[:tuple_key].nil?
    fail ArgumentError, "Missing 'correlation_id' in check item at index #{index}" if check[:correlation_id].nil?

    # Validate correlation_id format
    correlation_id = check[:correlation_id].to_s
    unless correlation_id.match?(/^[\w\d-]{1,36}$/)
      fail ArgumentError, "correlation_id must be alphanumeric with hyphens only, max 36 characters: #{correlation_id}"
    end

    # Check for duplicates
    if correlation_ids.include?(correlation_id)
      fail ArgumentError, "Duplicate correlation_id found: #{correlation_id}"
    end
    correlation_ids.add(correlation_id)
  end

  # If we have fewer checks than the batch limit, use simple approach
  if checks.length <= max_batch_size
    return execute_single_batch_check(checks, opts)
  end

  # Split checks into batches and process concurrently
  check_batches = checks.each_slice(max_batch_size).to_a
  all_results = process_batches_concurrently(check_batches, max_parallel_requests, opts)

  # Merge all batch results
  merged_results = {}
  all_results.each do |batch_response|
    if batch_response&.result
      merged_results.merge!(batch_response.result)
    end
  end

  # Return a BatchCheckResponse with merged results
  BatchCheckResponse.new(result: merged_results)
end

#check(body = {}) ⇒ CheckResponse

Checks whether a specific relationship exists in the store.

Parameters:

  • body (Hash) (defaults to: {})

    The request body containing check details

    • :user [String] The user involved in the relationship

    • :relation [String, Symbol] The relation to check (e.g. “reader”, “owner”)

    • :object [String] The object involved in the relationship

    • :contextual_tuples [Hash] Additional contextual tuples to include in the check:

      - :tuple_keys [Array<Hash>] Array of tuple key hashes
      
    • :opts [Hash] Optional parameters for the check request:

      - :authorization_model_id [String] The ID of the authorization model to use for the check
      - :context [Hash] Additional context for the check
      - :store_id [String] The ID of the store (required if not set in client config)
      

Returns:

Raises:

  • (ArgumentError)

    If any of the required parameters (user, relation, or object) are missing



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/openfga/client/openfga_client.rb', line 137

def check(body = {})
  opts = body[:opts] || {}
  tuple_key = CheckRequestTupleKey.new({
                                         user: body[:user], relation: body[:relation].to_s, object: body[:object] })

  request_body = CheckRequest.new({ tuple_key: })

  unless body[:contextual_tuples].nil?
    tuple_keys = body[:contextual_tuples][:tuple_keys].map { |tuple_key| TupleKey.new(tuple_key) }
    request_body.contextual_tuples = ContextualTupleKeys.new(tuple_keys:)
  end

  request_body.context = opts[:context] unless opts[:context].nil?
  request_body.authorization_model_id = authorization_model_id(opts)

  @api_client.check(store_id(opts), request_body, wrap_options(opts))
end

#create_store(body = {}) ⇒ CreateStoreResponse

Creates a new OpenFGA store for storing authorization models and relationship tuples.

Parameters:

  • body (Hash) (defaults to: {})

    The request body with store details.

    • :name [String] The name of the store to create.

    • :opts [Hash] Optional parameters for the request.

Returns:



160
161
162
163
164
165
# File 'lib/openfga/client/openfga_client.rb', line 160

def create_store(body = {})
  request_body = OpenFga::CreateStoreRequest.new(body)
  opts = body[:opts] || {}

  @api_client.create_store(request_body, wrap_options(opts))
end

#delete_store(opts = {}) ⇒ nil

Delete a store Delete an OpenFGA store.

Parameters:

  • opts (Hash) (defaults to: {})

    Optional parameters for the request

Options Hash (opts):

  • :store_id (String)

    The ID of the store to delete (required if not set in client config)

Returns:

  • (nil)


172
173
174
# File 'lib/openfga/client/openfga_client.rb', line 172

def delete_store(opts = {})
  @api_client.delete_store(store_id(opts), wrap_options(opts))
end

#expand(body = {}) ⇒ ExpandResponse

Expands a relationship tuple to retrieve all users and groups that have the specified relation with the given object.

Parameters:

  • body (Hash) (defaults to: {})

    Expansion request details:

    • :relation [String, Symbol] The relation to expand (e.g. “reader”, :writer)

    • :object [String] The object involved in the relationship

    • :contextual_tuples [Hash] Additional contextual tuples for the expansion

    • :opts [Hash] Optional parameters:

      - :store_id [String] Store ID (required if not set in client config)
      - :authorization_model_id [String] Authorization model ID for the expansion
      - :consistency [String] Consistency level ("UNSPECIFIED", "MINIMIZE_LATENCY", "HIGHER_CONSISTENCY")
      

Returns:

Raises:

  • (ArgumentError)

    If :relation or :object is missing



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/openfga/client/openfga_client.rb', line 187

def expand(body = {})
  relation, object = body[:relation], body[:object]

  fail ArgumentError, "Missing the required parameter 'relation'" if relation.nil?
  fail ArgumentError, "Missing the required parameter 'object'" if object.nil?

  opts = body[:opts] || {}

  request_body = ExpandRequest.new(
    tuple_key: ExpandRequestTupleKey.new(relation:, object:),
    authorization_model_id: authorization_model_id(opts),
    consistency: opts[:consistency] || 'UNSPECIFIED'
  )

  if body.include?(:contextual_tuples)
    contextual_tuples = body[:contextual_tuples]
    tuple_keys = contextual_tuples[:tuple_keys].map { |tuple_key| TupleKey.new(tuple_key) }
    request_body.contextual_tuples = ContextualTupleKeys.new(tuple_keys:)
  end

  # Call the API client to perform the expansion
  @api_client.expand(store_id(opts), request_body, wrap_options(opts))
end

#get_store(opts = {}) ⇒ GetStoreResponse

Get a store Returns an OpenFGA store by its identifier

Parameters:

  • opts (Hash) (defaults to: {})

    Optional parameters for the request

Options Hash (opts):

  • :store_id (String)

    The ID of the store to retrieve (required if not set in client config)

Returns:



216
217
218
# File 'lib/openfga/client/openfga_client.rb', line 216

def get_store(opts = {})
  @api_client.get_store(store_id(opts), wrap_options(opts))
end

#list_objects(body = {}) ⇒ ListObjectsResponse

List objects for a given user and relation. Returns objects of a specified type for a user and relation.

Parameters:

  • body (Hash) (defaults to: {})

    Request body:

    • :user [String] User to list objects for.

    • :relation [String, Symbol] Relation to list objects for (e.g., “reader”, :writer).

    • :type [String] Type of objects to list.

    • :contextual_tuples [Hash] (optional) Additional contextual tuples.

    • :context [Hash] (optional) Additional context.

    • :opts [Hash] (optional) Request options:

      - :store_id [String] Store ID (required if not set in client config).
      - :authorization_model_id [String] Authorization model ID.
      - :consistency [String] Consistency level ("UNSPECIFIED", "MINIMIZE_LATENCY", "HIGHER_CONSISTENCY").
      

Returns:

Raises:

  • (ArgumentError)

    If :user, :relation, or :type is missing.



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/openfga/client/openfga_client.rb', line 243

def list_objects(body = {})
  user = body[:user]
  relation = body[:relation]
  type = body[:type]
  contextual_tuples = body[:contextual_tuples]
  context = body[:context]
  opts = body[:opts] || {}

  fail ArgumentError, "Missing the required parameter 'user'" if user.nil?
  fail ArgumentError, "Missing the required parameter 'relation'" if relation.nil?
  fail ArgumentError, "Missing the required parameter 'type'" if type.nil?

  request_body = ListObjectsRequest.new(
    type:,
    relation:,
    user:,
    authorization_model_id: authorization_model_id(opts),
    consistency: opts[:consistency] || 'UNSPECIFIED'
  )

  unless contextual_tuples.nil?
    tuple_keys = contextual_tuples[:tuple_keys].map { |tuple_key| TupleKey.new(tuple_key) }
    request_body.contextual_tuples = ContextualTupleKeys.new(tuple_keys:)
  end

  request_body.context = context unless context.nil?

  # Call the API client to perform the list objects request
  @api_client.list_objects(store_id(opts), request_body, wrap_options(opts))
end

#list_stores(opts = {}) ⇒ ListStoresResponse

List all stores Returns a paginated list of OpenFGA stores and a continuation token to get additional stores. The continuation token will be empty if there are no more stores.

Parameters:

  • opts (Hash) (defaults to: {})

    Optional parameters for the request

Options Hash (opts):

  • :page_size (Integer)

    The number of stores to return per page

  • :continuation_token (String)

    The continuation token for pagination

Returns:



280
281
282
# File 'lib/openfga/client/openfga_client.rb', line 280

def list_stores(opts = {})
  @api_client.list_stores(wrap_options(opts))
end

#list_users(body = {}) ⇒ ListUsersResponse

List users that have a specific relation with an object Returns a list of all users that have the specified relation with the given object.

Parameters:

  • body (Hash) (defaults to: {})

    The request body containing list details

Options Hash (body):

  • :relation (String, Symbol)

    The relation to check for (e.g., “reader”, “writer”)

  • :object (String)

    The object to check relations against

  • :user_filters (Array<Hash>)

    Filter criteria for the users to return

  • :contextual_tuples (Array<Hash>)

    Additional contextual tuples to include in the query

  • :context (Hash)

    Additional context for the query

  • :opts (Hash)

    Optional parameters for the request, including:

    • :authorization_model_id [String] The ID of the authorization model to use for the query

    • :consistency [String] The consistency level for the query (defaults to ‘UNSPECIFIED’)

    • :store_id [String] The store ID to query users from (required if not set in client config)

Returns:

Raises:

  • (ArgumentError)

    If the required parameter ‘relation’ is missing

  • (ArgumentError)

    If the required parameter ‘object’ is missing



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/openfga/client/openfga_client.rb', line 299

def list_users(body = {})
  relation = body[:relation]
  object = body[:object]
  user_filters = body[:user_filters]
  contextual_tuples = body[:contextual_tuples]
  context = body[:context]
  opts = body[:opts] || {}

  fail ArgumentError, "Missing the required parameter 'relation'" if relation.nil?
  fail ArgumentError, "Missing the required parameter 'object'" if object.nil?

  request_body = ListUsersRequest.new(
    relation: relation.to_s,
    object:,
    context:,
    user_filters: (user_filters || []).map { |filter| UserTypeFilter.new(filter) },
    authorization_model_id: authorization_model_id(opts),
  )

  # Build the request body
  if contextual_tuples
    tuples = contextual_tuples.map { |tuple| TupleKey.new(tuple) }
    request_body.contextual_tuples = tuples
  end

  @api_client.list_users(store_id(opts), request_body, wrap_options(opts))
end

#read(body = {}) ⇒ ReadResponse

Read tuples from the store Reads tuples from the store.

Parameters:

  • body (Hash) (defaults to: {})

    The request body containing read query details

  • opts (Hash)

    a customizable set of options

Options Hash (body):

  • :user (String)

    The user to read tuples for

  • :relation (String, Symbol)

    The relation to read tuples for

  • :object (String)

    The object to read tuples for

Returns:



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/openfga/client/openfga_client.rb', line 338

def read(body = {})
  user = body[:user]
  relation = body[:relation]
  object = body[:object]
  opts = body[:opts] || {}

  request_body = ReadRequest.new(
    continuation_token: opts[:continuation_token],
    page_size: opts[:page_size] || PAGE_SIZE,
    consistency: opts[:consistency])

  if user || relation || object
    tuple_key = {}
    tuple_key[:user] = user if user
    tuple_key[:relation] = relation.to_s if relation
    tuple_key[:object] = object if object
    request_body.tuple_key = ReadRequestTupleKey.new(tuple_key)
  end

  @api_client.read(store_id(opts), request_body, wrap_options(opts))
end

#read_assertions(opts = {}) ⇒ ReadAssertionsResponse

Read assertions Retrieve assertions for a specific store and authorization model

Parameters:

  • opts (Hash) (defaults to: {})

    Optional parameters for the request

Options Hash (opts):

  • :store_id (String)

    The ID of the store (required if not set in client config)

  • :authorization_model_id (String)

    The ID of the authorization model (required if not set in client config)

Returns:

Raises:



368
369
370
371
# File 'lib/openfga/client/openfga_client.rb', line 368

def read_assertions(opts = {})
  fail MissingAuthorizationModelIdError unless authorization_model_id(opts)
  @api_client.read_assertions(store_id(opts), authorization_model_id(opts), wrap_options(opts))
end

#read_authorization_model(opts = {}) ⇒ ReadAuthorizationModelResponse

Read an authorization model Retrieves a specific authorization model by its ID from a given store.

Parameters:

  • opts (Hash) (defaults to: {})

    Optional parameters for the request

Options Hash (opts):

  • :store_id (String)

    The ID of the store (required if not set in client config)

  • :authorization_model_id (String)

    The ID of the authorization model to read (required if not set in client config)

Returns:

Raises:



381
382
383
384
# File 'lib/openfga/client/openfga_client.rb', line 381

def read_authorization_model(opts = {})
  fail MissingAuthorizationModelIdError unless authorization_model_id(opts)
  @api_client.read_authorization_model(store_id(opts), authorization_model_id(opts), wrap_options(opts))
end

#read_authorization_models(opts = {}) ⇒ ReadAuthorizationModelsResponse

Read all authorization models Retrieves all authorization models for a specific store.

Parameters:

  • opts (Hash) (defaults to: {})

    Optional parameters for the request

Options Hash (opts):

  • :store_id (String)

    The ID of the store (required if not set in client config)

Returns:

Raises:



392
393
394
# File 'lib/openfga/client/openfga_client.rb', line 392

def read_authorization_models(opts = {})
  @api_client.read_authorization_models(store_id(opts), wrap_options(opts))
end

#read_changes(opts = {}) ⇒ ReadChangesResponse

Read changes Reads the list of historical relationship tuple writes and deletes.

Parameters:

  • body (Hash)

    The request body containing change query details

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :page_size (Integer)

    The number of pages to return in the request

  • :continuation_token (String)

    The continuation token to use to get the next page of results. This will be empty if there are no more results

  • :store_id (String)

    The store ID to read changes from (required if not set in client config)

Returns:



405
406
407
# File 'lib/openfga/client/openfga_client.rb', line 405

def read_changes(opts = {})
  @api_client.read_changes(store_id(opts), wrap_options(opts))
end

#write(body = {}) ⇒ WriteResponse

Write tuples to the store. Atomically creates, updates, or deletes relationship tuples for a given store.

Parameters:

  • body (Hash) (defaults to: {})

    The request body containing write details:

    • :writes [Array<Hash>] Tuples to add or update in the store.

    • :deletes [Array<Hash>] Tuples to remove from the store.

    • :opts [Hash] Optional parameters for the request:

      - :store_id [String] Store ID to write to (required if not set in client config).
      - :authorization_model_id [String] Authorization model ID to use for writing.
      

Returns:

  • (WriteResponse)

    The response from the write operation.



418
419
420
421
422
423
424
# File 'lib/openfga/client/openfga_client.rb', line 418

def write(body = {})
  opts = body[:opts] || {}
  request_body = WriteRequest.new(writes: body[:writes], deletes: body[:deletes])

  request_body.authorization_model_id = opts[:authorization_model_id] if opts[:authorization_model_id]
  @api_client.write(store_id(opts), request_body, wrap_options(opts))
end

#write_assertions(body = {}) ⇒ nil

Writes assertions for a specific store and authorization model. Updates or creates assertions used for testing authorization logic.

Parameters:

  • body (Hash) (defaults to: {})

    The request body containing assertions:

    • :assertions [Array<Hash>] The assertions to write.

    • :opts [Hash] Optional parameters:

      - :store_id [String] Store ID (required if not set in client config).
      - :authorization_model_id [String] Authorization model ID (required if not set in client config).
      

Returns:

  • (nil)

Raises:



436
437
438
439
440
441
442
443
444
445
# File 'lib/openfga/client/openfga_client.rb', line 436

def write_assertions(body = {})
  opts = body[:opts] || {}
  store_id = store_id(opts)
  model_id = authorization_model_id(opts)
  fail MissingAuthorizationModelIdError unless model_id

  request_body = WriteAssertionsRequest.new(assertions: body[:assertions])

  @api_client.write_assertions(store_id, model_id, request_body, wrap_options(opts))
end

#write_authorization_model(body = {}) ⇒ WriteAuthorizationModelResponse

Writes an authorization model for a specific store. Creates or updates the authorization model with provided type definitions, schema version, and conditions.

Parameters:

  • body (Hash) (defaults to: {})

    The request body containing:

    • :type_definitions [Array<TypeDefinition>] Type definitions for the authorization model.

    • :schema_version [String] Schema version for the authorization model.

    • :conditions [Hash] Conditions for the authorization model.

    • :opts [Hash] Optional parameters:

      - :store_id [String] Store ID (required if not set in client config).
      

Returns:



456
457
458
459
460
461
462
463
464
465
# File 'lib/openfga/client/openfga_client.rb', line 456

def write_authorization_model(body = {})
  opts = body[:opts] || {}
  request_body = WriteAuthorizationModelRequest.new(
    type_definitions: body[:type_definitions],
    schema_version: body[:schema_version],
    conditions: body[:conditions]
  )

  @api_client.write_authorization_model(store_id(opts), request_body, wrap_options(opts))
end