kinetic_sdk 7.0.0.rc2 → 7.0.0.rc4
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 +4 -4
- data/CHANGELOG.md +64 -0
- data/Rakefile +13 -1
- data/kinetic_sdk.gemspec +1 -0
- data/lib/kinetic_sdk/core/core-sdk.rb +9 -1
- data/lib/kinetic_sdk/core/lib/jwt.rb +59 -35
- data/lib/kinetic_sdk/task/lib/trees.rb +12 -43
- data/lib/kinetic_sdk/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ab81a960e1aebbf2e7cd839fdf3fd60c33ca71e985e0c6530c7e84ca8e8ec6f
|
|
4
|
+
data.tar.gz: d485a88f2085aa142c0015c2899999d46c403f66b8b460efffb0918730ca51aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dbd1dbf0607dc20f54e2cbea981496d1fbc8088c8b00fa8ce6483317577fe15bff2b8ae3328d7e797506dea4db66a9cc5c6d3b3ca491bdc99a0ff6fd144e9725
|
|
7
|
+
data.tar.gz: 86092ddc85c919ace2d1bb3133c8feb9aa320969162e471cc100617b81fef05fa0dc5b644c770dbb1a41ebc8c4b6e8aa53586bcc31463b966caef2fb44aa74fe
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [7.0.0.rc4](https://github.com/kineticdata/kinetic-sdk-rb/tree/7.0.0.rc4) (2026-09-01)
|
|
5
|
+
|
|
6
|
+
**Breaking changes:**
|
|
7
|
+
|
|
8
|
+
- `jwt_token` now uses the OAuth 2.0 `client_credentials` grant, a single POST to
|
|
9
|
+
`/{space_slug}/app/oauth2/token`. Kinetic Platform 7 runs a standards compliant
|
|
10
|
+
authorization server in which `/app/oauth2/authorize` is an interactive endpoint
|
|
11
|
+
requiring a browser session, consent, a registered redirect_uri and PKCE, so the
|
|
12
|
+
previous authorization-code flow returned `400`. The response shape is unchanged:
|
|
13
|
+
callers reading `jwt_response.content["access_token"]` are unaffected.
|
|
14
|
+
- `jwt_code` has been removed. It drove the authorization-code flow that no longer
|
|
15
|
+
applies to machine to machine clients.
|
|
16
|
+
- The OAuth client used by the SDK must be registered in the space as a **confidential**
|
|
17
|
+
client (or with no `clientType`, which defaults to confidential). Public clients and
|
|
18
|
+
the built in system client register only the `authorization_code` grant and are
|
|
19
|
+
rejected by the token endpoint.
|
|
20
|
+
|
|
21
|
+
**Fixed bugs:**
|
|
22
|
+
|
|
23
|
+
- The OAuth routes are now space scoped (`/{space_slug}/app/oauth2/...`) when the SDK is
|
|
24
|
+
built from an `:app_server_url` plus a `:space_slug`. The authorization server resolves
|
|
25
|
+
the space from the request path and rejects requests made outside a space context.
|
|
26
|
+
- OAuth failures now report the `error` and `error_description` from a JSON error, or the
|
|
27
|
+
`X-Kinetic-CID` correlation id when Core answers with its generic HTML error page,
|
|
28
|
+
instead of dumping the inspected response object.
|
|
29
|
+
|
|
30
|
+
**Implemented enhancements:**
|
|
31
|
+
|
|
32
|
+
- Added the `:oauth_scope` option, defaulting to `full`. Valid scopes are `full`, `read`,
|
|
33
|
+
`write`, `admin`, `submissions` and `submissions:read`.
|
|
34
|
+
- Added specs covering the token exchange, scope configuration, and the failure modes for
|
|
35
|
+
both the Integrator and Discussions SDKs.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## [7.0.0.rc3](https://github.com/kineticdata/kinetic-sdk-rb/tree/7.0.0.rc3) (2026-09-01)
|
|
39
|
+
|
|
40
|
+
**Fixed bugs:**
|
|
41
|
+
|
|
42
|
+
- Fixed a crash in `import_trees` and `import_trees_threaded` that aborted template installs
|
|
43
|
+
with `NoMethodError: undefined method 'document' for an instance of File`. The tree
|
|
44
|
+
comparison introduced in 7.x passed a raw `File` handle to `REXML::XPath.first`, which
|
|
45
|
+
requires a parsed document.
|
|
46
|
+
- Removed the tree comparison entirely, restoring the unconditional import behavior of 5.0.29.
|
|
47
|
+
The comparison identified trees by a `//definitionId` element that exists only in routine
|
|
48
|
+
exports, never in the tree exports `import_trees` reads, so it could not succeed. See the
|
|
49
|
+
specs in `spec/kinetic_sdk/task/trees_spec.rb`.
|
|
50
|
+
- The per-tree and per-routine rescues in the threaded importers no longer discard exceptions
|
|
51
|
+
silently; failures are logged and propagated.
|
|
52
|
+
- `trees.rb` now requires `rexml/document` explicitly rather than relying on load order.
|
|
53
|
+
|
|
54
|
+
**Implemented enhancements:**
|
|
55
|
+
|
|
56
|
+
- Added rspec and the first unit test coverage, run with `bundle exec rspec` or `rake spec`.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## [7.0.0.rc2](https://github.com/kineticdata/kinetic-sdk-rb/tree/7.0.0.rc2) (2026-09-01)
|
|
60
|
+
|
|
61
|
+
**Fixed bugs:**
|
|
62
|
+
|
|
63
|
+
- Declared `concurrent-ruby` and `rexml` as runtime dependencies. Both were required by the
|
|
64
|
+
library but missing from the gemspec, so the published gem could fail to load.
|
|
65
|
+
- Pinned `BUNDLED WITH` to a concrete Bundler version; the range value broke `bundle install`.
|
|
66
|
+
|
|
67
|
+
|
|
4
68
|
## [7.0.0-rc1](https://github.com/kineticdata/kinetic-sdk-rb/tree/7.0.0-rc1) (2026-08-31)
|
|
5
69
|
|
|
6
70
|
**Breaking changes:**
|
data/Rakefile
CHANGED
|
@@ -7,7 +7,19 @@ YARD::Rake::YardocTask.new do |t|
|
|
|
7
7
|
t.stats_options = %w( --list-undoc )
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# Run the specs. Guarded so the Rakefile still loads (and `rake build` still
|
|
11
|
+
# works) in an environment where the development dependencies are absent.
|
|
12
|
+
begin
|
|
13
|
+
require "rspec/core/rake_task"
|
|
14
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
15
|
+
rescue LoadError
|
|
16
|
+
desc "Run the specs (rspec is not installed)"
|
|
17
|
+
task :spec do
|
|
18
|
+
abort "rspec is not available. Run `bundle install` first."
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
10
22
|
# Generate Yard documentation
|
|
11
23
|
task :doc => [:yard]
|
|
12
24
|
task :rdoc => [:yard]
|
|
13
|
-
task :default => [:doc]
|
|
25
|
+
task :default => [:spec, :doc]
|
data/kinetic_sdk.gemspec
CHANGED
|
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
spec.add_development_dependency "bundler", ">= 1.17"
|
|
27
27
|
spec.add_development_dependency "rake", ">= 13.0.1"
|
|
28
28
|
spec.add_development_dependency "yard", "~> 0.9.45"
|
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.13"
|
|
29
30
|
|
|
30
31
|
spec.metadata["yard.run"] = "yri"
|
|
31
32
|
end
|
|
@@ -14,7 +14,8 @@ module KineticSdk
|
|
|
14
14
|
include KineticSdk::Utils::KineticExportUtils
|
|
15
15
|
|
|
16
16
|
attr_reader :api_url, :username, :options, :password, :proxy_url,
|
|
17
|
-
:space_slug, :server, :version, :logger
|
|
17
|
+
:space_slug, :server, :version, :logger, :oauth_url,
|
|
18
|
+
:oauth_scope
|
|
18
19
|
|
|
19
20
|
# Initalize the Core SDK with the web server URL, the space user
|
|
20
21
|
# username and password, along with any custom option values.
|
|
@@ -55,6 +56,8 @@ module KineticSdk
|
|
|
55
56
|
# * :max_redirects (Fixnum) (_defaults to: 5_) maximum number of redirects to follow
|
|
56
57
|
# * :ssl_ca_file (String) full path to PEM certificate used to verify the server
|
|
57
58
|
# * :ssl_verify_mode (String) (_defaults to: none_) - none | peer
|
|
59
|
+
# * :oauth_scope (String) (_defaults to: full_) scope requested when retrieving a
|
|
60
|
+
# JWT - full | read | write | admin | submissions | submissions:read
|
|
58
61
|
#
|
|
59
62
|
# Example: using a configuration file
|
|
60
63
|
#
|
|
@@ -154,12 +157,17 @@ module KineticSdk
|
|
|
154
157
|
@server = options[:app_server_url].chomp('/')
|
|
155
158
|
@api_url = @server + (@space_slug.nil? ? "/app/api/v1" : "/#{@space_slug}/app/api/v1")
|
|
156
159
|
@proxy_url = @space_slug.nil? ? nil : "#{@server}/#{@space_slug}/app/components"
|
|
160
|
+
# The authorization server resolves the space from the request path, so the
|
|
161
|
+
# OAuth routes must be space scoped exactly like the API routes.
|
|
162
|
+
@oauth_url = @server + (@space_slug.nil? ? "/app/oauth2" : "/#{@space_slug}/app/oauth2")
|
|
157
163
|
else
|
|
158
164
|
raise StandardError.new "The :space_slug option is required when using the :space_server_url option" if @space_slug.nil?
|
|
159
165
|
@server = options[:space_server_url].chomp('/')
|
|
160
166
|
@api_url = "#{@server}/app/api/v1"
|
|
161
167
|
@proxy_url = "#{@server}/app/components"
|
|
168
|
+
@oauth_url = "#{@server}/app/oauth2"
|
|
162
169
|
end
|
|
170
|
+
@oauth_scope = @options[:oauth_scope] || @options["oauth_scope"] || "full"
|
|
163
171
|
@version = 1
|
|
164
172
|
end
|
|
165
173
|
|
|
@@ -1,55 +1,79 @@
|
|
|
1
1
|
module KineticSdk
|
|
2
2
|
class Core
|
|
3
3
|
|
|
4
|
-
# Gets an authentication token
|
|
4
|
+
# Gets an authentication token using the OAuth 2.0 client credentials grant.
|
|
5
|
+
#
|
|
6
|
+
# Kinetic Platform 7 runs a standards compliant authorization server, in which
|
|
7
|
+
# `/app/oauth2/authorize` is an interactive endpoint: it expects an authenticated
|
|
8
|
+
# browser session, a consent round trip, a registered redirect_uri, and PKCE. A
|
|
9
|
+
# machine to machine client cannot drive it, and should not try - it exchanges its
|
|
10
|
+
# own credentials for a token directly at the token endpoint instead.
|
|
11
|
+
#
|
|
12
|
+
# The OAuth client must be registered in the space as a confidential client (or with
|
|
13
|
+
# no clientType, which defaults to confidential) so that it carries the
|
|
14
|
+
# `client_credentials` grant. Public clients and the built in system client support
|
|
15
|
+
# only `authorization_code`, and will be rejected here.
|
|
5
16
|
#
|
|
6
17
|
# @param client_id [String] the oauth client id
|
|
7
18
|
# @param client_secret [String] the oauth client secret
|
|
8
|
-
# @param headers [Hash]
|
|
19
|
+
# @param headers [Hash] additional headers to send. The Accept, Authorization and
|
|
20
|
+
# Content-Type headers required by the token endpoint always take precedence.
|
|
21
|
+
# @param scope [String] scope to request, defaults to the +:oauth_scope+ option (+full+)
|
|
9
22
|
# @return [KineticSdk::Utils::KineticHttpResponse] object, with +code+, +message+, +content_string+, and +content+ properties
|
|
10
|
-
def jwt_token(client_id, client_secret, headers =
|
|
11
|
-
# retrieve the jwt code
|
|
12
|
-
jwt_code = jwt_code(client_id, headers)
|
|
13
|
-
# retrieve the jwt token
|
|
23
|
+
def jwt_token(client_id, client_secret, headers = {}, scope = oauth_scope)
|
|
14
24
|
@logger.info("Retrieving JWT authorization token")
|
|
15
|
-
url = "#{@
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
url = "#{@oauth_url}/token"
|
|
26
|
+
|
|
27
|
+
# The required headers are merged last so that a caller passing the SDK default
|
|
28
|
+
# headers cannot replace the client's basic authentication with the user's.
|
|
29
|
+
token_headers = headers
|
|
30
|
+
.merge(header_accept_json)
|
|
31
|
+
.merge(header_basic_auth(client_id, client_secret))
|
|
32
|
+
.merge({ "Content-Type" => "application/x-www-form-urlencoded" })
|
|
33
|
+
|
|
34
|
+
payload = { "grant_type" => "client_credentials" }
|
|
35
|
+
payload["scope"] = scope unless scope.nil? || scope.to_s.empty?
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
# Redirects are not followed: the token endpoint has no reason to redirect, and
|
|
38
|
+
# following one would forward the client credentials to another host.
|
|
39
|
+
response = post(url, URI.encode_www_form(payload), token_headers, { :max_redirects => 0 })
|
|
40
|
+
|
|
41
|
+
case response.status
|
|
42
|
+
when 200
|
|
22
43
|
response
|
|
44
|
+
when 401
|
|
45
|
+
raise StandardError.new(
|
|
46
|
+
"Unable to retrieve token: #{oauth_error_message(response)}. The oauth client id " \
|
|
47
|
+
"and secret are invalid, or no such client is registered in this space."
|
|
48
|
+
)
|
|
23
49
|
else
|
|
24
|
-
raise StandardError.new
|
|
50
|
+
raise StandardError.new("Unable to retrieve token: #{oauth_error_message(response)}")
|
|
25
51
|
end
|
|
26
52
|
end
|
|
27
53
|
|
|
28
|
-
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Builds an actionable message from an OAuth error response.
|
|
29
57
|
#
|
|
30
|
-
#
|
|
58
|
+
# The authorization server answers most failures with a JSON OAuth error, but falls
|
|
59
|
+
# back to Core's generic HTML error page for others. That page carries no error code,
|
|
60
|
+
# only a correlation id in the X-Kinetic-CID response header, which is what is needed
|
|
61
|
+
# to find the matching entry in the server log.
|
|
31
62
|
#
|
|
32
|
-
# @param
|
|
33
|
-
# @
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if response.status == 401
|
|
41
|
-
raise StandardError.new "#{response.message}: #{response.content["error"]}"
|
|
42
|
-
elsif response.status == 302 || response.status == 303
|
|
43
|
-
location = response.headers["location"]
|
|
44
|
-
if location.nil?
|
|
45
|
-
raise StandardError.new "Unable to retrieve code: #{response.inspect}"
|
|
46
|
-
elsif !location.include?("?code=")
|
|
47
|
-
raise StandardError.new "Unable to retrieve code, the authorize endpoint redirected to #{location} without an authorization code."
|
|
48
|
-
else
|
|
49
|
-
location.split("?code=").last.split("#/").first
|
|
50
|
-
end
|
|
63
|
+
# @param response [KineticSdk::Utils::KineticHttpResponse] the failed response
|
|
64
|
+
# @return [String] a single line description of the failure
|
|
65
|
+
def oauth_error_message(response)
|
|
66
|
+
content = response.content.is_a?(Hash) ? response.content : {}
|
|
67
|
+
if content["error"]
|
|
68
|
+
message = "#{response.status} #{content["error"]}"
|
|
69
|
+
message += " - #{content["error_description"]}" if content["error_description"]
|
|
70
|
+
message
|
|
51
71
|
else
|
|
52
|
-
|
|
72
|
+
message = "#{response.status} #{response.message}"
|
|
73
|
+
headers = response.headers
|
|
74
|
+
correlation_id = headers.is_a?(Hash) ? headers["x-kinetic-cid"] : nil
|
|
75
|
+
message += " (correlation id #{correlation_id})" if correlation_id
|
|
76
|
+
message
|
|
53
77
|
end
|
|
54
78
|
end
|
|
55
79
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
|
|
1
3
|
module KineticSdk
|
|
2
4
|
class Task
|
|
3
5
|
# Delete a tree.
|
|
@@ -146,12 +148,7 @@ module KineticSdk
|
|
|
146
148
|
@logger.info("Importing all Trees from Export Directory")
|
|
147
149
|
Dir["#{@options[:export_directory]}/sources/**/*.xml"].sort.each do |file|
|
|
148
150
|
tree_file = File.new(file, "rb")
|
|
149
|
-
|
|
150
|
-
if !match
|
|
151
|
-
import_tree(tree_file, force_overwrite, headers)
|
|
152
|
-
else
|
|
153
|
-
#Matching trees, skipping
|
|
154
|
-
end
|
|
151
|
+
import_tree(tree_file, force_overwrite, headers)
|
|
155
152
|
end
|
|
156
153
|
end
|
|
157
154
|
def import_trees_threaded(force_overwrite=false, headers=header_basic_auth)
|
|
@@ -164,48 +161,18 @@ module KineticSdk
|
|
|
164
161
|
Dir["#{@options[:export_directory]}/sources/**/*.xml"].sort.each do |file|
|
|
165
162
|
promises << Concurrent::Promise.execute(executor: pool) do
|
|
166
163
|
begin
|
|
167
|
-
|
|
168
164
|
tree_file = File.new(file, "rb")
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
#Matching trees, skipping
|
|
174
|
-
end
|
|
175
|
-
rescue
|
|
165
|
+
import_tree(tree_file, force_overwrite, headers)
|
|
166
|
+
rescue => e
|
|
167
|
+
@logger.error("Failed to import Tree #{file}: #{e.class}: #{e.message}")
|
|
168
|
+
raise
|
|
176
169
|
end
|
|
177
|
-
|
|
178
170
|
end
|
|
179
171
|
end
|
|
180
172
|
promises.each(&:wait!)
|
|
181
173
|
pool.shutdown
|
|
182
174
|
pool.wait_for_termination
|
|
183
175
|
end
|
|
184
|
-
# Import a tree
|
|
185
|
-
#
|
|
186
|
-
# If the tree already exists on the server, this will fail unless forced
|
|
187
|
-
# to overwrite.
|
|
188
|
-
#
|
|
189
|
-
# The source named in the tree content must also exist on the server, or
|
|
190
|
-
# the import will fail.
|
|
191
|
-
#
|
|
192
|
-
# @param tree [String] content from tree file
|
|
193
|
-
# @param force_overwrite [Boolean] whether to overwrite a tree if it exists, default is false
|
|
194
|
-
# @param headers [Hash] hash of headers to send, default is basic authentication
|
|
195
|
-
# @return [KineticSdk::Utils::KineticHttpResponse] object, with +code+, +message+, +content_string+, and +content+ properties
|
|
196
|
-
def compare_trees(tree, headers=header_basic_auth)
|
|
197
|
-
definitionId = REXML::XPath.first(tree, "//definitionId")&.text
|
|
198
|
-
@logger.info("Querying for Tree #{definitionId}")
|
|
199
|
-
response = find_tree(definitionId, { "include" => "details,export" })
|
|
200
|
-
@logger.info("Comparing new and old tree")
|
|
201
|
-
prev_tree = response.content
|
|
202
|
-
if prev_tree == tree
|
|
203
|
-
return true
|
|
204
|
-
else
|
|
205
|
-
return false
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
176
|
# Import a routine
|
|
210
177
|
#
|
|
211
178
|
# If the routine already exists on the server, this will fail unless
|
|
@@ -248,9 +215,11 @@ module KineticSdk
|
|
|
248
215
|
Dir["#{@options[:export_directory]}/routines/*.xml"].sort.each do |file|
|
|
249
216
|
promises << Concurrent::Promise.execute(executor: pool) do
|
|
250
217
|
begin
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
rescue
|
|
218
|
+
routine_file = File.new(file, "rb")
|
|
219
|
+
import_routine(routine_file, force_overwrite, headers)
|
|
220
|
+
rescue => e
|
|
221
|
+
@logger.error("Failed to import Routine #{file}: #{e.class}: #{e.message}")
|
|
222
|
+
raise
|
|
254
223
|
end
|
|
255
224
|
end
|
|
256
225
|
end
|
data/lib/kinetic_sdk/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kinetic_sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.0.0.
|
|
4
|
+
version: 7.0.0.rc4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kinetic Data
|
|
@@ -122,6 +122,20 @@ dependencies:
|
|
|
122
122
|
- - "~>"
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
124
|
version: 0.9.45
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rspec
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '3.13'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '3.13'
|
|
125
139
|
description:
|
|
126
140
|
email:
|
|
127
141
|
- support@kineticdata.com
|