strongmind-platform-sdk 3.34.1 → 3.34.3

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: fd51875610b6504f5d6cdc31e8384443b384e90ed25a5293d34a1592eba93180
4
- data.tar.gz: fbd570234a1128e37a2b8e8dfc408fce6a98cc995486855e6bf600df736a795d
3
+ metadata.gz: 9b82e33dd88733dca1da0a516c00342899c1227c4301dd4dfd8394aeca5e292a
4
+ data.tar.gz: 53de61789c363e674b4df379ce42aa33b77b2f0f49fffd49173b82f146b8e382
5
5
  SHA512:
6
- metadata.gz: 30f9636c5de88c33b9e005d7c68cb351c2ebf44e69fab99c10e3c80f9ef9ed62c69e6e2b1c701d6fb96de6c958ee7814ad69fb2819c57a17595c95cdbccce328
7
- data.tar.gz: 68fe58679f511b353f348d5fd5c4624b856858127026293b1a8ef175be3ceb9b9d8d4d9999a5dfa3ad3ee7dc206a65fe2ca3fefc1b9640d59a34595ac13ce441
6
+ metadata.gz: b12297b6fbe2837bdb9bad307b2035e2f9c3942a1b4d61f27ee81e4638fdb1dfd30d8f92fe64c31ad5f893d4daa7276bf018d72c06d8eb0b2898752d0cad6596
7
+ data.tar.gz: a63f3b1704854273bd724bcbf69058b1436008f483505481f8f4f1a7d018dac8464d12ee341074fbfe0878f30f3bf5abae542070705a5e035069264fedf8cbde
data/CHANGELOG.md CHANGED
@@ -1,5 +1,6 @@
1
1
  ## [Unreleased]
2
2
 
3
+ - Stop logging Identity error response bodies. IS4 answers a create that collides on the username unique index with SQL Server's raw message, which includes the duplicate value (a student's name and SIS id), and the error log line carried that body verbatim. The log line now has the exception class, method, path and status only. The raised error still carries the response, with its request scrubbed, for a consumer that decides it is safe to inspect.
3
4
  - Raise typed errors from every Canvas error branch. The read/list/PUT-write methods raised bare `RuntimeError` strings, which consumers could not distinguish from a bug — Central's grade-passback retry wrapper, for example, only retries `CanvasClientError` and so treated every throttled read as fatal. All error branches now raise through one handler: 404 → `MissingModuleItemError`, 401 → `UnauthorizedError`, rate-limited → `RateLimitedError` (new), anything else → `CanvasClientError`. Every class subclasses `CanvasClientError` (itself a `StandardError`), so rescues written against the old classes keep working; only code matching on `RuntimeError` specifically (none known) would notice. Message text changed (see below); no known consumer parses it.
4
5
  - Stop echoing raw Canvas response bodies and page query strings into error messages. Consumers persist `error.message` (Central's `OutcomeLog.exception_message`) and forward it to Sentry, and a raw body from a submission, quiz or roster endpoint can carry student records (FERPA). Every error path now shares one message policy: `"<descriptor>: <Canvas's own error messages> (status: N, host: H)"`, with a fixed `unparseable Canvas error body` / `empty Canvas error body` / `Canvas rate limit exceeded` descriptor in place of any body that isn't a JSON error object. The write-path tail (`handle_post_response_errors`) previously appended the whole body and skipped the `error_messages_from` hardening; it now goes through the same builder. Pagination failures name the page path only, dropping the query that carries `as_user_id`/`student_id`. The raw body and status are still available as `CanvasClientError#response_body` and `#status` for a consumer that decides it is safe to inspect.
5
6
  - `error_messages_from` also reads a hash-shaped `errors` (field => messages, as Canvas validation errors return) and a top-level `message` key, instead of producing an inspect blob or nothing.
@@ -4,12 +4,7 @@ module PlatformSdk
4
4
  module Identity
5
5
  # Error handling for Identity SDK
6
6
  module ErrorHandleable
7
- # The logged payload is the raw request body, which for token endpoints
8
- # carries client credentials; redact those fields (form-encoded and
9
- # JSON shapes) so an auth outage can't write secrets to consumer logs.
10
- SENSITIVE_KEYS = 'client_secret|client_assertion|refresh_token|password'
11
- FORM_SECRET_PATTERN = /(#{SENSITIVE_KEYS})=[^&]*/i
12
- JSON_SECRET_PATTERN = /("(?:#{SENSITIVE_KEYS})"\s*:\s*")[^"]*(")/i
7
+ REDACTED = '[REDACTED]'
13
8
 
14
9
  def with_rescue
15
10
  yield
@@ -22,12 +17,9 @@ module PlatformSdk
22
17
  end
23
18
 
24
19
  def raise_error_with_payload(exception_class, error)
25
- Rails.logger.info(error_log_payload(exception_class, error).to_json) if defined?(Rails)
26
- # The exception itself carries error.response on to consumer
27
- # instrumentation (Sentry, custom error handlers), so the raw
28
- # request body/headers - which can hold a client_secret or bearer
29
- # token - must be scrubbed here too, not just in the log line above.
30
- raise exception_class, scrub_response(error.response)
20
+ payload = error_log_payload(exception_class, error)
21
+ Rails.logger.info(payload.to_json) if defined?(Rails)
22
+ raise exception_class.new(error_message(payload), scrub_response(error.response)), cause: nil
31
23
  end
32
24
 
33
25
  # No ActiveSupport here (String#demodulize): this must work for
@@ -36,25 +28,19 @@ module PlatformSdk
36
28
  def error_log_payload(exception_class, error)
37
29
  {
38
30
  exception: exception_class.name.split('::').last,
39
- payload: scrub_secrets(error.response.dig(:request, :body)),
40
- response_body: error.response[:body],
31
+ method: error.response.dig(:request, :method),
32
+ path: error.response.dig(:request, :url_path),
41
33
  status: error.response[:status]
42
34
  }.compact
43
35
  end
44
36
 
45
- def scrub_secrets(body)
46
- return body unless body.is_a?(String)
37
+ def error_message(payload)
38
+ message = "the server responded with status #{payload[:status]}"
39
+ return message unless payload[:method] && payload[:path]
47
40
 
48
- body.gsub(FORM_SECRET_PATTERN) { "#{Regexp.last_match(1)}=[REDACTED]" }
49
- .gsub(JSON_SECRET_PATTERN, '\1[REDACTED]\2')
41
+ "#{message} for #{payload[:method].to_s.upcase} #{payload[:path]}"
50
42
  end
51
43
 
52
- # Returns a scrubbed copy of the Faraday error response hash; the
53
- # original (and its nested :request hash) are left untouched. Only
54
- # [:request][:body] and [:request][:headers] are touched -
55
- # [:status] and [:body] (the response body) are read elsewhere
56
- # (e.g. e.response&.dig(:status), e.response&.dig(:body, 'paramName'))
57
- # and must reach consumers unchanged.
58
44
  def scrub_response(response)
59
45
  return response unless response.is_a?(Hash)
60
46
 
@@ -65,7 +51,9 @@ module PlatformSdk
65
51
 
66
52
  def scrub_request(request)
67
53
  scrubbed_request = request.dup
68
- scrubbed_request[:body] = scrub_secrets(request[:body]) if request[:body].is_a?(String)
54
+ scrubbed_request[:body] = REDACTED if request.key?(:body)
55
+ scrubbed_request[:params] = REDACTED if request.key?(:params)
56
+ scrubbed_request[:url] = request[:url_path] if request.key?(:url)
69
57
  scrubbed_request[:headers] = scrub_headers(request[:headers]) if request[:headers].is_a?(Hash)
70
58
  scrubbed_request
71
59
  end
@@ -75,7 +63,7 @@ module PlatformSdk
75
63
  def scrub_headers(headers)
76
64
  scrubbed = headers.dup
77
65
  scrubbed.each_key do |key|
78
- scrubbed[key] = '[REDACTED]' if key.to_s.casecmp('authorization').zero?
66
+ scrubbed[key] = REDACTED if key.to_s.casecmp('authorization').zero?
79
67
  end
80
68
  scrubbed
81
69
  end
@@ -3,7 +3,7 @@
3
3
  module PlatformSdk
4
4
  MAJOR = 3
5
5
  MINOR = 34
6
- PATCH = 1
6
+ PATCH = 3
7
7
 
8
8
  VERSION = "#{PlatformSdk::MAJOR}.#{PlatformSdk::MINOR}.#{PlatformSdk::PATCH}".freeze
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strongmind-platform-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.34.1
4
+ version: 3.34.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Platform Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-12 00:00:00.000000000 Z
11
+ date: 2026-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -392,7 +392,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
392
392
  - !ruby/object:Gem::Version
393
393
  version: '0'
394
394
  requirements: []
395
- rubygems_version: 3.4.10
395
+ rubygems_version: 3.4.19
396
396
  signing_key:
397
397
  specification_version: 4
398
398
  summary: This gem is intended to provide clients and models for accessing the Platform