fripa 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5034dad8f9b68de329b79f4a447167c68f60edbf9310854f587a14b780f1c3f7
4
- data.tar.gz: 9dc1b84130c58a48f67aeaa103c39893235c28123b38214acde4eefd40509762
3
+ metadata.gz: 0e46b8ee8ebb691b57a6f5f72f9bcb5b577bca894aebaefc694f28e621038a2e
4
+ data.tar.gz: 991e2a655e038c28a19be01f2acf6c1e780504b4adb6279788bfff243e5ee0e1
5
5
  SHA512:
6
- metadata.gz: 81e82e52a5d09927d3a69ecf550db4482a282faa7fee27b41fcea21d45d27c6764bd7aa45550a21f584ba11c6887b9a3e63829d7c199ca541be1757a7423352a
7
- data.tar.gz: d95654a1a20db0c69f64ba9f007dd3074011b3eb685b561bf2c2cc6d1ebe1fd8056d0e655073bf4ed2de4e772193238081238049a9f7fc77c5439a74293c6dfa
6
+ metadata.gz: 9b5bb37ed15a9909fc9e035455dc96dd0204e28b22db211d1a930f16e3f0e93168661a6736d2cbca77cfcab990409684ad7b4e3b4cd9abb2d17e0793bc3cdf64
7
+ data.tar.gz: 6a0f3c1cf130a5b05e621dc07b6199684dcc4ffd22bcdfa6e0a531bd3c757709d40f1530166157c29360bd33dfbcc576211d0b2916148e344e2130988d4848b8
data/CHANGELOG.md CHANGED
@@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.0] - 2026-10-16
10
+
11
+ ### Added
12
+ - `Fripa::Client#batch!`: like `batch`, but raises `Fripa::BatchError` (a `Fripa::ResponseError`) when any command failed. `#errors` lists each failed command (index, method, code, name, message); `#code` and `#message` are those of the first one.
13
+ - Vault batch commands: `vault.add_command`, `archive_command`, `add_member_command` and `delete_command` return `[method, args, options]` for `Client#batch`, so many vault operations go in one request. `archive_command` wraps the data with the transport certificate, like `archive`. When a transport command fails in `batch!`, the certificate is forgotten and fetched again on the next call, as with `archive`.
14
+
15
+ ## [0.3.0] - 2026-10-15
16
+
17
+ ### Changed
18
+ - **`Fripa::Client.new` no longer connects.** The client logs in on the first call. Wrong credentials now raise `Fripa::AuthenticationError` on the first call, not in `Client.new`. Call `client.login!` to log in at once. Blank credentials still raise `ArgumentError` in `Client.new`.
19
+ - The vault transport certificate is cached per process and server URL, so all clients share it (before: one fetch per client). It is fetched again after a vault call fails.
20
+
21
+ ### Added
22
+ - `Fripa.client`: one client for the account in `config.username` / `config.password`, shared by the process. It is built on first use and again when the configuration changes. `Fripa.reset_client!` forgets the shared client (e.g. between tests).
23
+ - `Fripa::Client#login!` (returns the session cookie) and `#logged_in?`.
24
+ - `Fripa::Client.new(username:, session_cookie:)` builds a client from an earlier session, without a login request. `password` is now optional. Without a password, an expired session raises `Fripa::AuthenticationError`.
25
+ - `Fripa::Client.new` without `username` uses the credentials from the configuration. With a `username`, it never uses the configured password.
26
+ - `Configuration#inspect` does not show the password.
27
+
9
28
  ## [0.2.0] - 2026-10-12
10
29
 
11
30
  ### Changed
@@ -59,7 +78,8 @@ Initial release of Fripa - A Ruby client for the FreeIPA JSON-RPC API.
59
78
  - Full test suite with VCR cassettes
60
79
  - Complete API documentation and usage examples
61
80
 
62
- [Unreleased]: https://github.com/renuo/fripa/compare/v0.2.0...HEAD
81
+ [Unreleased]: https://github.com/renuo/fripa/compare/v0.3.0...HEAD
82
+ [0.3.0]: https://github.com/renuo/fripa/compare/v0.2.0...v0.3.0
63
83
  [0.2.0]: https://github.com/renuo/fripa/compare/v0.1.4...v0.2.0
64
84
  [0.1.4]: https://github.com/renuo/fripa/compare/v0.1.3...v0.1.4
65
85
  [0.1.3]: https://github.com/renuo/fripa/compare/v0.1.2...v0.1.3
data/README.md CHANGED
@@ -114,26 +114,54 @@ end
114
114
 
115
115
  ### Creating a Client
116
116
 
117
- Create a client instance with user credentials. The client authenticates immediately upon creation:
117
+ #### Service account (shared client)
118
+
119
+ Put the credentials in the configuration and use `Fripa.client`. It is one
120
+ client for the whole process: it logs in on the first call, keeps the session,
121
+ and logs in again when the session expires. It is built again when the
122
+ configuration changes.
118
123
 
119
124
  ```ruby
120
- client = Fripa::Client.new(
121
- username: 'your-username',
122
- password: 'your-password'
123
- )
124
- # Client is now authenticated and ready to use
125
+ Fripa.configure do |config|
126
+ config.host = 'ipa.example.com'
127
+ config.username = 'admin'
128
+ config.password = 'secret'
129
+ end
130
+
131
+ Fripa.client.users.show('alice')
125
132
  ```
126
133
 
127
- If authentication fails, a `Fripa::AuthenticationError` will be raised immediately:
134
+ In tests, call `Fripa.reset_client!` before each test, so no session or test
135
+ double stays from one test to the next.
136
+
137
+ #### Other accounts
138
+
139
+ `Fripa::Client.new` creates a client for one account. It does not connect: it
140
+ logs in on the first call. Call `login!` to log in now, for example to check
141
+ credentials. It returns the session cookie.
128
142
 
129
143
  ```ruby
144
+ client = Fripa::Client.new(username: 'alice', password: 'her-password')
145
+
130
146
  begin
131
- client = Fripa::Client.new(username: 'admin', password: 'wrong-password')
147
+ cookie = client.login!
132
148
  rescue Fripa::AuthenticationError => e
133
149
  puts "Authentication failed: #{e.message}"
134
150
  end
135
151
  ```
136
152
 
153
+ To skip the login in later requests, build the client from the session cookie
154
+ (e.g. kept in an encrypted session):
155
+
156
+ ```ruby
157
+ client = Fripa::Client.new(username: 'alice', session_cookie: cookie)
158
+ ```
159
+
160
+ A client without a password cannot log in again: when its session expires, it
161
+ raises `Fripa::AuthenticationError`. With a `username`, `Fripa::Client.new`
162
+ never uses the password from the configuration. A client can be shared between
163
+ threads.
164
+
137
165
  ### Making API Calls
138
166
 
139
167
  #### Using Resource Methods (Recommended)
@@ -228,7 +256,9 @@ client.vault.archive("my-vault", data: "secret", ipa_format: true, shared: true)
228
256
  client.vault.retrieve("my-vault", ipa_format: true, shared: true) # => "secret"
229
257
  ```
230
258
 
231
- The client fetches the KRA transport certificate once and keeps it.
259
+ The KRA transport certificate is fetched once per process and server, and all
260
+ clients share it (it is public and the same for every account). If the server
261
+ rejects a vault call, the certificate is fetched again on the next call.
232
262
 
233
263
  #### Batch Calls
234
264
 
@@ -243,6 +273,27 @@ result.dig("result", "results").each { |r| puts r["error"] || r["result"] }
243
273
  ```
244
274
 
245
275
  `batch` does not raise for errors in a single command. Check `error` in each result.
276
+ `batch!` raises `Fripa::BatchError` when any command failed. `#errors` lists them, with
277
+ the index, method, code, name and message of each:
278
+
279
+ ```ruby
280
+ client.batch!(["vault_del", ["v1"]], ["vault_del", ["v2"]])
281
+ rescue Fripa::BatchError => e
282
+ e.errors # => [{ index: 1, method: "vault_del", code: 4001, name: "NotFound", message: "v2: vault not found" }]
283
+ ```
284
+
285
+ The vault resource builds batch commands with `add_command`, `archive_command`,
286
+ `add_member_command` and `delete_command`. They take the same arguments as `add`,
287
+ `archive`, `add_member` and `delete`, so you can set up many vaults in one request:
288
+
289
+ ```ruby
290
+ vault = client.vault
291
+ client.batch!(*%w[v1 v2].flat_map do |cn|
292
+ [vault.add_command(cn, shared: true),
293
+ vault.archive_command(cn, data: "secret", shared: true),
294
+ vault.add_member_command(cn, user: ["alice"], shared: true)]
295
+ end)
296
+ ```
246
297
 
247
298
  #### Using Raw API Calls
248
299
 
@@ -30,13 +30,13 @@ module Fripa
30
30
  "#<#{self.class.name} username=#{client.username.inspect}>"
31
31
  end
32
32
 
33
- private
34
-
35
33
  def validate_credentials!
36
34
  raise ArgumentError, "Username cannot be blank" if client.username.to_s.strip.empty?
37
35
  raise ArgumentError, "Password cannot be blank" if client.password.to_s.strip.empty?
38
36
  end
39
37
 
38
+ private
39
+
40
40
  def perform_login
41
41
  client.connection.post(LOGIN_PATH) do |request|
42
42
  request.headers["Content-Type"] = CONTENT_TYPE
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fripa
4
+ # Batch calls for Fripa::Client: many commands in one HTTP request.
5
+ module Batch
6
+ # Sends several commands in one HTTP request. Each command is
7
+ # [method, args = [], options = {}]. Per-command errors are not raised:
8
+ # check result["result"]["results"][i]["error"].
9
+ def batch(*commands)
10
+ requests = commands.map do |method, args = [], options = {}|
11
+ { method: method, params: [args, options.merge(version: config.api_version)] }
12
+ end
13
+ call("batch", requests)
14
+ end
15
+
16
+ # Commands wrapped with the vault transport certificate (see Resources::Vault#archive_command).
17
+ TRANSPORT_COMMANDS = %w[vault_archive_internal vault_retrieve_internal].freeze
18
+
19
+ # Like batch, but raises Fripa::BatchError when any command failed.
20
+ # Like Resources::Vault#archive, a failed transport command forgets the certificate, so the next
21
+ # call fetches it again (e.g. after the server renewed it).
22
+ def batch!(*commands)
23
+ batch(*commands).tap { |response| BatchError.check!(commands, response) }
24
+ rescue BatchError => e
25
+ forget_transport_cert_after(e)
26
+ raise
27
+ end
28
+
29
+ private
30
+
31
+ def forget_transport_cert_after(error)
32
+ return unless error.errors.any? do |failed|
33
+ TRANSPORT_COMMANDS.include?(failed[:method]) && failed[:code] != Resources::Vault::NOT_FOUND
34
+ end
35
+
36
+ Resources::Vault.forget_transport_cert(config.base_url.to_s)
37
+ end
38
+ end
39
+ end
data/lib/fripa/client.rb CHANGED
@@ -5,6 +5,8 @@ require "json"
5
5
 
6
6
  module Fripa
7
7
  class Client
8
+ include Batch
9
+
8
10
  API_PATH = "/ipa/session/json"
9
11
  API_VERSION = Configuration::DEFAULT_API_VERSION
10
12
  UNAUTHORIZED = 401
@@ -12,11 +14,18 @@ module Fripa
12
14
  attr_reader :config, :username, :password
13
15
  attr_accessor :session_cookie
14
16
 
15
- def initialize(username:, password:, config: nil)
17
+ # Does not connect: the client logs in on the first call, or when you call login!.
18
+ # Without username, it uses config.username and config.password. With a
19
+ # username, it never uses the password from the configuration.
20
+ # Give a session_cookie from an earlier login to skip the login request.
21
+ # Without a password, an expired session raises AuthenticationError.
22
+ def initialize(username: nil, password: nil, config: nil, session_cookie: nil)
16
23
  @config = config || Fripa.config
17
- @username = username
18
- @password = password
19
- @session_cookie = authenticator.login!
24
+ @username = username || @config.username
25
+ @password = username ? password : password || @config.password
26
+ @session_cookie = session_cookie unless session_cookie.to_s.empty?
27
+ @logged_in = !@session_cookie.nil?
28
+ authenticator.validate_credentials! unless @logged_in
20
29
  end
21
30
 
22
31
  def users
@@ -35,7 +44,20 @@ module Fripa
35
44
  @authenticator ||= Authenticator.new(self)
36
45
  end
37
46
 
47
+ # Logs in now and returns the session cookie. Raises AuthenticationError
48
+ # if the credentials are wrong.
49
+ def login!
50
+ self.session_cookie = authenticator.login!
51
+ @logged_in = true
52
+ session_cookie
53
+ end
54
+
55
+ def logged_in?
56
+ @logged_in
57
+ end
58
+
38
59
  def call(method, args = [], options = {})
60
+ login! unless logged_in?
39
61
  response = perform_request(method, args, options)
40
62
  response = reauthenticate_and_retry(method, args, options) if response.status == UNAUTHORIZED
41
63
 
@@ -44,16 +66,6 @@ module Fripa
44
66
  parse_response(response)
45
67
  end
46
68
 
47
- # Sends several commands in one HTTP request. Each command is
48
- # [method, args = [], options = {}]. Per-command errors are not raised:
49
- # check result["result"]["results"][i]["error"].
50
- def batch(*commands)
51
- requests = commands.map do |method, args = [], options = {}|
52
- { method: method, params: [args, options.merge(version: config.api_version)] }
53
- end
54
- call("batch", requests)
55
- end
56
-
57
69
  # Shared by Authenticator, so login and API calls reuse one connection.
58
70
  def connection
59
71
  @connection ||= Faraday.new(url: config.base_url.to_s, request: request_options) do |f|
@@ -73,7 +85,9 @@ module Fripa
73
85
  private
74
86
 
75
87
  def reauthenticate_and_retry(method, args, options)
76
- self.session_cookie = authenticator.login!
88
+ raise AuthenticationError, "Session expired and no password to log in again" if password.to_s.empty?
89
+
90
+ login!
77
91
  perform_request(method, args, options)
78
92
  end
79
93
 
@@ -10,10 +10,13 @@ module Fripa
10
10
  SCHEMES = %w[http https].freeze
11
11
 
12
12
  attr_accessor :host, :port, :scheme, :verify_ssl, :api_version, :timeout, :open_timeout, :adapter,
13
- :raise_on_partial_failure
13
+ :raise_on_partial_failure, :username, :password
14
14
 
15
+ # username and password are only used by Fripa.client and by
16
+ # Fripa::Client.new without a username.
15
17
  def initialize(host: nil, port: nil, scheme: "https", verify_ssl: true, api_version: DEFAULT_API_VERSION,
16
- timeout: nil, open_timeout: nil, adapter: nil, raise_on_partial_failure: false)
18
+ timeout: nil, open_timeout: nil, adapter: nil, raise_on_partial_failure: false,
19
+ username: nil, password: nil)
17
20
  @host = host
18
21
  @port = port
19
22
  @scheme = scheme
@@ -23,6 +26,19 @@ module Fripa
23
26
  @open_timeout = open_timeout
24
27
  @adapter = adapter
25
28
  @raise_on_partial_failure = raise_on_partial_failure
29
+ @username = username
30
+ @password = password
31
+ end
32
+
33
+ # Values that change how a client connects or logs in. Fripa.client builds
34
+ # a new client when they change.
35
+ def options
36
+ { host:, port:, scheme:, verify_ssl:, api_version:, timeout:, open_timeout:, adapter:,
37
+ raise_on_partial_failure:, username:, password: }
38
+ end
39
+
40
+ def inspect
41
+ "#<#{self.class.name} host=#{host.inspect} scheme=#{scheme.inspect} username=#{username.inspect}>"
26
42
  end
27
43
 
28
44
  def base_url
@@ -50,11 +66,31 @@ module Fripa
50
66
  end
51
67
  end
52
68
 
69
+ @client_lock = Mutex.new
70
+
53
71
  class << self
54
72
  def config
55
73
  @config ||= Configuration.new
56
74
  end
57
75
 
76
+ # A client for the account in Fripa.config (username and password), shared
77
+ # by the whole process. It is built on first use, and again when the
78
+ # configuration changes. Use Fripa::Client.new for other accounts.
79
+ def client
80
+ @client_lock.synchronize do
81
+ unless @client&.config.equal?(config) && @client_options == config.options
82
+ @client = Client.new(config: config)
83
+ @client_options = config.options
84
+ end
85
+ @client
86
+ end
87
+ end
88
+
89
+ # Forgets the shared client (e.g. between tests). The next Fripa.client builds a new one.
90
+ def reset_client!
91
+ @client_lock.synchronize { @client = @client_options = nil }
92
+ end
93
+
58
94
  def config=(value)
59
95
  @config = case value
60
96
  when Hash
data/lib/fripa/errors.rb CHANGED
@@ -15,6 +15,31 @@ module Fripa
15
15
  end
16
16
  end
17
17
 
18
+ # Raised by Client#batch! when one or more commands of the batch failed. #errors lists them:
19
+ # { index:, method:, code:, name:, message: }. #code and #message are those of the first one.
20
+ # #data holds every result of the batch.
21
+ class BatchError < ResponseError
22
+ attr_reader :errors
23
+
24
+ def initialize(errors, results)
25
+ @errors = errors
26
+ first = errors.first
27
+ super(code: first[:code], message: "#{first[:method]}: #{first[:message]}", data: results)
28
+ end
29
+
30
+ # Raises a BatchError if any result of the batch response holds an error.
31
+ def self.check!(commands, response)
32
+ results = response.dig("result", "results") || []
33
+ errors = results.each_with_index.filter_map do |result, index|
34
+ next unless result["error"]
35
+
36
+ { index: index, method: commands[index].first, code: result["error_code"],
37
+ name: result["error_name"], message: result["error"] }
38
+ end
39
+ raise new(errors, results) if errors.any?
40
+ end
41
+ end
42
+
18
43
  # Raised when FreeIPA answers without an error but reports per-entry failures,
19
44
  # e.g. group_add_member with a user that does not exist or is already a member.
20
45
  # Only raised with config.raise_on_partial_failure = true.
@@ -5,24 +5,42 @@ require "base64"
5
5
  module Fripa
6
6
  module Resources
7
7
  class Vault < Base
8
+ NOT_FOUND = 4001
9
+
10
+ # The KRA transport certificate belongs to the server, not to an account,
11
+ # and it is public. So all clients of one process share it, per server URL:
12
+ # a service account and per-user clients fetch it only once.
13
+ @transport_certs = {}
14
+ @transport_certs_lock = Mutex.new
15
+
16
+ class << self
17
+ def transport_cert(key)
18
+ @transport_certs_lock.synchronize { @transport_certs[key] ||= yield }
19
+ end
20
+
21
+ def forget_transport_cert(key)
22
+ @transport_certs_lock.synchronize { @transport_certs.delete(key) }
23
+ end
24
+
25
+ def clear_transport_certs!
26
+ @transport_certs_lock.synchronize { @transport_certs.clear }
27
+ end
28
+ end
29
+
8
30
  def show(cn, **scope)
9
31
  validate_identifier!(cn, "cn")
10
32
  call("vault_show", [cn], scope)
11
33
  end
12
34
 
13
35
  def add(cn, **scope)
14
- validate_identifier!(cn, "cn")
15
- call("vault_add_internal", [cn], { ipavaulttype: "standard" }.merge(scope))
36
+ call(*add_command(cn, **scope))
16
37
  end
17
38
 
18
39
  # ipa_format: true stores the data like `ipa vault-archive`, so the ipa CLI can read it.
19
40
  # The default (false) stores the raw bytes, as in previous versions.
20
41
  def archive(cn, data:, ipa_format: false, **scope)
21
- validate_identifier!(cn, "cn")
22
- data = VaultTransport.to_ipa_format(data) if ipa_format
23
- payload = VaultTransport.wrap(data, transport_cert_der)
24
- call("vault_archive_internal", [cn],
25
- binary_encode(payload).merge(wrapping_algo: VaultTransport::WRAPPING_ALGO).merge(scope))
42
+ command = archive_command(cn, data:, ipa_format:, **scope)
43
+ with_transport_cert_refresh { call(*command) }
26
44
  end
27
45
 
28
46
  # ipa_format: true reads data stored by `ipa vault-archive` (or archive with ipa_format: true).
@@ -42,21 +60,51 @@ module Fripa
42
60
  end
43
61
 
44
62
  def add_member(cn, **scope)
45
- validate_identifier!(cn, "cn")
46
- call("vault_add_member", [cn], scope)
63
+ call(*add_member_command(cn, **scope))
47
64
  end
48
65
 
49
66
  def delete(cn, **scope)
67
+ call(*delete_command(cn, **scope))
68
+ end
69
+
70
+ # The *_command methods build [method, args, options] for Client#batch, so many vault
71
+ # operations go in one request. archive_command wraps the data like archive does.
72
+ def add_command(cn, **scope)
50
73
  validate_identifier!(cn, "cn")
51
- call("vault_del", [cn], scope)
74
+ ["vault_add_internal", [cn], { ipavaulttype: "standard" }.merge(scope)]
75
+ end
76
+
77
+ def archive_command(cn, data:, ipa_format: false, **scope)
78
+ validate_identifier!(cn, "cn")
79
+ data = VaultTransport.to_ipa_format(data) if ipa_format
80
+ payload = VaultTransport.wrap(data, transport_cert_der)
81
+ ["vault_archive_internal", [cn],
82
+ binary_encode(payload).merge(wrapping_algo: VaultTransport::WRAPPING_ALGO).merge(scope)]
83
+ end
84
+
85
+ def add_member_command(cn, **scope)
86
+ validate_identifier!(cn, "cn")
87
+ ["vault_add_member", [cn], scope]
88
+ end
89
+
90
+ def delete_command(cn, **scope)
91
+ validate_identifier!(cn, "cn")
92
+ ["vault_del", [cn], scope]
52
93
  end
53
94
 
54
95
  private
55
96
 
56
- # The KRA transport certificate does not change between calls, so we
57
- # fetch it once per client (the ipa CLI caches it too).
58
97
  def transport_cert_der
59
- @transport_cert_der ||= fetch_transport_cert
98
+ self.class.transport_cert(client.config.base_url.to_s) { fetch_transport_cert }
99
+ end
100
+
101
+ # If the server rejects a payload, its certificate can have changed
102
+ # (e.g. renewed). Forget it, so that the next call fetches it again.
103
+ def with_transport_cert_refresh
104
+ yield
105
+ rescue ResponseError => e
106
+ self.class.forget_transport_cert(client.config.base_url.to_s) unless e.code == NOT_FOUND
107
+ raise
60
108
  end
61
109
 
62
110
  def fetch_transport_cert
@@ -67,10 +115,12 @@ module Fripa
67
115
  def call_retrieve(cn, session_key, **scope)
68
116
  encrypted_key = VaultTransport.encrypt_session_key(session_key, transport_cert_der)
69
117
 
70
- call("vault_retrieve_internal", [cn], {
71
- session_key: { "__base64__" => Base64.strict_encode64(encrypted_key) },
72
- wrapping_algo: VaultTransport::WRAPPING_ALGO
73
- }.merge(scope))
118
+ with_transport_cert_refresh do
119
+ call("vault_retrieve_internal", [cn], {
120
+ session_key: { "__base64__" => Base64.strict_encode64(encrypted_key) },
121
+ wrapping_algo: VaultTransport::WRAPPING_ALGO
122
+ }.merge(scope))
123
+ end
74
124
  end
75
125
 
76
126
  def binary_encode(hash)
data/lib/fripa/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fripa
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/fripa.rb CHANGED
@@ -9,6 +9,7 @@ require_relative "fripa/resources/user"
9
9
  require_relative "fripa/resources/group"
10
10
  require_relative "fripa/vault_transport"
11
11
  require_relative "fripa/resources/vault"
12
+ require_relative "fripa/batch"
12
13
  require_relative "fripa/client"
13
14
 
14
15
  module Fripa
data/sig/fripa.rbs CHANGED
@@ -6,6 +6,8 @@ module Fripa
6
6
  def self.config: () -> Configuration
7
7
  def self.config=: (Configuration | Hash[Symbol, untyped] value) -> Configuration
8
8
  def self.configure: () { (Configuration) -> void } -> void
9
+ def self.client: () -> Client
10
+ def self.reset_client!: () -> void
9
11
 
10
12
  class Configuration
11
13
  DEFAULT_API_VERSION: String
@@ -20,10 +22,13 @@ module Fripa
20
22
  attr_accessor open_timeout: Numeric?
21
23
  attr_accessor adapter: untyped
22
24
  attr_accessor raise_on_partial_failure: bool
25
+ attr_accessor username: String?
26
+ attr_accessor password: String?
23
27
 
24
28
  def initialize: (?host: String?, ?port: Integer?, ?scheme: String, ?verify_ssl: bool, ?api_version: String,
25
29
  ?timeout: Numeric?, ?open_timeout: Numeric?, ?adapter: untyped,
26
- ?raise_on_partial_failure: bool) -> void
30
+ ?raise_on_partial_failure: bool, ?username: String?, ?password: String?) -> void
31
+ def options: () -> Hash[Symbol, untyped]
27
32
  def base_url: () -> URI::Generic
28
33
  def login_url: () -> URI::Generic
29
34
  def api_url: () -> URI::Generic
@@ -49,6 +54,13 @@ module Fripa
49
54
  def initialize: (code: Integer?, message: String?, ?data: untyped) -> void
50
55
  end
51
56
 
57
+ class BatchError < ResponseError
58
+ attr_reader errors: Array[Hash[Symbol, untyped]]
59
+
60
+ def initialize: (Array[Hash[Symbol, untyped]] errors, Array[json_hash] results) -> void
61
+ def self.check!: (Array[Array[untyped]] commands, json_hash response) -> void
62
+ end
63
+
52
64
  class PartialFailureError < ResponseError
53
65
  def initialize: (json_hash failed) -> void
54
66
  def self.failures: (untyped failed) -> Array[untyped]
@@ -61,17 +73,20 @@ module Fripa
61
73
  UNAUTHORIZED: Integer
62
74
 
63
75
  attr_reader config: Configuration
64
- attr_reader username: String
65
- attr_reader password: String
76
+ attr_reader username: String?
77
+ attr_reader password: String?
66
78
  attr_accessor session_cookie: String?
67
79
 
68
- def initialize: (username: String, password: String, ?config: Configuration?) -> void
80
+ def initialize: (?username: String?, ?password: String?, ?config: Configuration?, ?session_cookie: String?) -> void
81
+ def login!: () -> String?
82
+ def logged_in?: () -> bool
69
83
  def users: () -> Resources::User
70
84
  def groups: () -> Resources::Group
71
85
  def vault: () -> Resources::Vault
72
86
  def authenticator: () -> Authenticator
73
87
  def call: (String method, ?Array[untyped] args, ?Hash[Symbol, untyped] options) -> json_hash
74
88
  def batch: (*Array[untyped] commands) -> json_hash
89
+ def batch!: (*Array[untyped] commands) -> json_hash
75
90
  def connection: () -> untyped
76
91
  def referer: () -> String
77
92
  end
@@ -85,6 +100,7 @@ module Fripa
85
100
 
86
101
  def initialize: (Client client) -> void
87
102
  def login!: () -> String?
103
+ def validate_credentials!: () -> void
88
104
  end
89
105
 
90
106
  module VaultTransport
@@ -137,12 +153,22 @@ module Fripa
137
153
  end
138
154
 
139
155
  class Vault < Base
156
+ NOT_FOUND: Integer
157
+
158
+ def self.transport_cert: (String key) { () -> String } -> String
159
+ def self.forget_transport_cert: (String key) -> String?
160
+ def self.clear_transport_certs!: () -> void
161
+
140
162
  def show: (String cn, **untyped scope) -> json_hash
141
163
  def add: (String cn, **untyped scope) -> json_hash
142
164
  def archive: (String cn, data: String, ?ipa_format: bool, **untyped scope) -> json_hash
143
165
  def retrieve: (String cn, ?ipa_format: bool, **untyped scope) -> String?
144
166
  def add_member: (String cn, **untyped scope) -> json_hash
145
167
  def delete: (String cn, **untyped scope) -> json_hash
168
+ def add_command: (String cn, **untyped scope) -> [String, Array[String], Hash[Symbol, untyped]]
169
+ def archive_command: (String cn, data: String, ?ipa_format: bool, **untyped scope) -> [String, Array[String], Hash[Symbol, untyped]]
170
+ def add_member_command: (String cn, **untyped scope) -> [String, Array[String], Hash[Symbol, untyped]]
171
+ def delete_command: (String cn, **untyped scope) -> [String, Array[String], Hash[Symbol, untyped]]
146
172
  end
147
173
  end
148
174
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fripa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Costanzo
@@ -50,6 +50,7 @@ files:
50
50
  - README.md
51
51
  - lib/fripa.rb
52
52
  - lib/fripa/authenticator.rb
53
+ - lib/fripa/batch.rb
53
54
  - lib/fripa/client.rb
54
55
  - lib/fripa/configuration.rb
55
56
  - lib/fripa/errors.rb
@@ -83,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
84
  - !ruby/object:Gem::Version
84
85
  version: '0'
85
86
  requirements: []
86
- rubygems_version: 4.0.10
87
+ rubygems_version: 4.0.20
87
88
  specification_version: 4
88
89
  summary: Ruby client for FreeIPA JSON-RPC API
89
90
  test_files: []