axm 1.0.0 → 1.0.1

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: 0d2f95c8bc2de0f7a2c7ea620d2f301980eb6c5a21243bc8ee878b0ae4ebb351
4
- data.tar.gz: 59c698eebe7dbe7baf4b1378ca5a66b0045334a2d69c3441b0300ca5dc3de6a4
3
+ metadata.gz: 729a2e7795a94f807dc732928f6e704f42d4e93fdb712bde4083a04410fb1aa5
4
+ data.tar.gz: 8256543f1d81885b9ee2f6bae17f74506efaef554796b9ac2e03a415a3d50954
5
5
  SHA512:
6
- metadata.gz: 8e3fc06e2e51475d49a6d5612700046e9003967200d1234ca3aa52d4ead5f08827d8f4eb432a512af127cb01920befab0a3f4dd9f354a6705a4f3ca8d1d5d781
7
- data.tar.gz: dac8b555c797f8b773de5c58cc0de9b35128df4251a5cfbe9c3260160c11f5b2178e010f3fe9d8126a63ad144c0db71e9abd452b020488bb6440bb239e1875fe
6
+ metadata.gz: f3ae6ad5f92c66c84284011b9bb3ac2e5f3297df2662783807bf0893ec46efd337490185b6871a131b8a12e2fbfa2b59d581e213921510fadf16be5532ec563f
7
+ data.tar.gz: 5bcb9240a6aae5f17e9e872c0bf5a5796c8b9b0665e583a9063f6d42dafd59161656c1645ea8fa35deba31e8ae899d13993b031c61bddb5c193f7f5381b09bed
data/.rubocop.yml CHANGED
@@ -11,9 +11,3 @@ Layout/LineLength:
11
11
 
12
12
  Style/Documentation:
13
13
  Enabled: false
14
-
15
- Style/StringLiterals:
16
- EnforcedStyle: double_quotes
17
-
18
- Style/StringLiteralsInInterpolation:
19
- EnforcedStyle: double_quotes
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 1.0.1 - 2025-11-13
2
+
3
+ - Add OpenSSL as a dev dependency (<https://github.com/nick-f/axm/pull/26>)
4
+ - Store the access token in memory (<https://github.com/nick-f/axm/pull/25>)
5
+
1
6
  ## 1.0.0 - 2025-11-08
2
7
 
3
8
  ### Added
data/README.md CHANGED
@@ -58,7 +58,13 @@ key_id = Secret.read('key_id')
58
58
  client = Axm::Client.new(private_key:, client_id:, key_id:)
59
59
  ```
60
60
 
61
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
61
+ To install this gem onto your local machine, run `bundle exec rake install`.
62
+
63
+ ## Releasing
64
+
65
+ 1. Update the version number in [lib/axm/version.rb](./lib/axm/version.rb)
66
+ 2. Add release notes to [CHANGELOG.md](CHANGELOG.md)
67
+ 3. Run the "Release" Action
62
68
 
63
69
  ## Contributing
64
70
 
@@ -70,4 +76,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
70
76
 
71
77
  ## Code of Conduct
72
78
 
73
- Everyone interacting in the Axm project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/nick-f/axm/blob/main/CODE_OF_CONDUCT.md).
79
+ Everyone interacting with the AXM codebase and issue tracker is expected to follow the [code of conduct](https://github.com/nick-f/axm/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- require "rubocop/rake_task"
8
+ require 'rubocop/rake_task'
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Axm
2
4
  class Client
3
5
  module MdmServers
@@ -11,7 +13,7 @@ module Axm
11
13
  # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-mdm-servers
12
14
  # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-mdm-servers
13
15
  def list_mdm_servers(options = {})
14
- get("v1/mdmServers", options)
16
+ get('v1/mdmServers', options)
15
17
  end
16
18
 
17
19
  # Retrieves a list of IDs for the devices assigned to a specific MDM server.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Axm
2
4
  class Client
3
5
  module OrganizationDeviceActivities
@@ -19,7 +21,7 @@ module Axm
19
21
  # @param mdm_server_id [String] The unique identifier of the MDM server to which the device will be assigned.
20
22
  # @return [Hash, Integer] The response from the POST method, containing details of the assignment and status code.
21
23
  def assign(device_id, mdm_server_id)
22
- assignment_change(device_id, mdm_server_id, "ASSIGN_DEVICES")
24
+ assignment_change(device_id, mdm_server_id, 'ASSIGN_DEVICES')
23
25
  end
24
26
 
25
27
  # Unassigns a device from an MDM server.
@@ -28,7 +30,7 @@ module Axm
28
30
  # @param mdm_server_id [String] The unique identifier of the MDM server to which the device will be assigned.
29
31
  # @return [Hash, Integer] The response from the POST method, containing details of the assignment and status code.
30
32
  def unassign(device_id, mdm_server_id)
31
- assignment_change(device_id, mdm_server_id, "UNASSIGN_DEVICES")
33
+ assignment_change(device_id, mdm_server_id, 'UNASSIGN_DEVICES')
32
34
  end
33
35
 
34
36
  private
@@ -39,24 +41,25 @@ module Axm
39
41
  # @param mdm_server_id [String] The unique identifier of the MDM server.
40
42
  # @param activity_type [String] The type of activity being performed ("ASSIGN_DEVICES" or "UNASSIGN_DEVICES").
41
43
  # @return [Hash, Integer] The response from the POST request, containing details of the operation and status code.
44
+ # rubocop:disable Metrics/MethodLength
42
45
  def assignment_change(device_ids, mdm_server_id, activity_type)
43
46
  devices = device_ids.map do |device_id|
44
47
  {
45
- type: "orgDevices",
48
+ type: 'orgDevices',
46
49
  id: device_id
47
50
  }
48
51
  end
49
52
 
50
53
  request_body = {
51
54
  data: {
52
- type: "orgDeviceActivities",
55
+ type: 'orgDeviceActivities',
53
56
  attributes: {
54
57
  activityType: activity_type
55
58
  },
56
59
  relationships: {
57
60
  mdmServer: {
58
61
  data: {
59
- type: "mdmServers",
62
+ type: 'mdmServers',
60
63
  id: mdm_server_id
61
64
  }
62
65
  },
@@ -67,8 +70,9 @@ module Axm
67
70
  }
68
71
  }
69
72
 
70
- post("v1/orgDeviceActivities", request_body)
73
+ post('v1/orgDeviceActivities', request_body)
71
74
  end
75
+ # rubocop:enable Metrics/MethodLength
72
76
  end
73
77
  end
74
78
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Axm
2
4
  class Client
3
5
  module OrganizationDevices
@@ -11,7 +13,7 @@ module Axm
11
13
  # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-org-devices
12
14
  # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-org-devices
13
15
  def list_org_devices(options = {})
14
- get("v1/orgDevices", options)
16
+ get('v1/orgDevices', options)
15
17
  end
16
18
 
17
19
  # Retrieves information about a specific device in the organization.
@@ -49,7 +51,7 @@ module Axm
49
51
  # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-the-assigned-server-information-for-an-orgdevice
50
52
  # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-the-assigned-server-information-for-an-orgdevice
51
53
  def assigned_mdm_server(device_id, options = {})
52
- options[:fields_key] = "mdmServers"
54
+ options[:fields_key] = 'mdmServers'
53
55
 
54
56
  get("v1/orgDevices/#{device_id}/assignedServer", options)
55
57
  end
@@ -65,7 +67,7 @@ module Axm
65
67
  # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-all-apple-care-coverage-for-an-orgdevice
66
68
  # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-all-apple-care-coverage-for-an-orgdevice
67
69
  def applecare_coverage(device_id, options = {})
68
- options[:fields_key] = "appleCareCoverage"
70
+ options[:fields_key] = 'appleCareCoverage'
69
71
 
70
72
  get("v1/orgDevices/#{device_id}/appleCareCoverage", options)
71
73
  end
data/lib/axm/client.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'jwt'
3
5
  require 'net/http'
@@ -9,6 +11,7 @@ require 'axm/client/organization_device_activities'
9
11
  require 'axm/client/organization_devices'
10
12
 
11
13
  module Axm
14
+ # rubocop: disable Metrics/ClassLength
12
15
  class Client
13
16
  include MdmServers
14
17
  include OrganizationDeviceActivities
@@ -58,13 +61,14 @@ module Axm
58
61
  end
59
62
  end
60
63
 
64
+ # rubocop: disable Metrics/MethodLength
61
65
  def client_assertion
62
66
  @client_assertion ||= begin
63
67
  audience = 'https://account.apple.com/auth/oauth2/v2/token'
64
68
  algo = 'ES256'
65
69
 
66
70
  issued_at_timestamp = Time.now.utc.to_i
67
- expiration_timestamp = issued_at_timestamp + 86_400 * 180 # 180 days
71
+ expiration_timestamp = issued_at_timestamp + (86_400 * 180) # 180 days
68
72
 
69
73
  payload = {
70
74
  sub: @client_id,
@@ -78,26 +82,23 @@ module Axm
78
82
  JWT.encode(payload, @private_key, algo, kid: @key_id)
79
83
  end
80
84
  end
85
+ # rubocop: enable Metrics/MethodLength
81
86
 
82
87
  def access_token
83
- cached_access_token = JSON.parse(Secret.read('stub_access_token')) if File.exist?('secrets/stub_access_token')
84
-
85
- if cached_access_token
86
- token_expiration = Time.parse(cached_access_token['expires_at'])
88
+ if @cached_access_token
89
+ token_expiration = Time.parse(@cached_access_token['expires_at'])
87
90
  token_expired = Time.now.utc >= token_expiration
88
91
 
89
- return cached_access_token unless token_expired
92
+ return @cached_access_token unless token_expired
90
93
  end
91
94
 
92
95
  response = exchange_access_token_request
93
96
 
94
97
  response_body = response.first if response.last.to_i == 200
95
98
 
96
- token = response_body.merge!({ 'expires_at' => Time.now.utc + response_body['expires_in'] })
97
-
98
- Secret.write('stub_access_token', token.to_json)
99
+ @cached_access_token = response_body.merge({ 'expires_at' => Time.now.utc + response_body['expires_in'] })
99
100
 
100
- response_body
101
+ @cached_access_token
101
102
  end
102
103
 
103
104
  # Sends a GET request to the specified API endpoint.
@@ -107,6 +108,7 @@ module Axm
107
108
  # - :paginate [Boolean] Whether to paginate through all results (unused).
108
109
  # - :fields [Array<String>] Optional fields to include as fields[orgDevices].
109
110
  # @return [Hash] The parsed JSON response.
111
+ # rubocop: disable Metrics/MethodLength, Metrics/AbcSize
110
112
  def get(path, options = {})
111
113
  options = options.dup
112
114
 
@@ -132,10 +134,12 @@ module Axm
132
134
 
133
135
  JSON.parse(res.body)
134
136
  end
137
+ # rubocop: enable Metrics/MethodLength, Metrics/AbcSize
135
138
 
136
139
  # Sends a POST request to exchange the credentials for an access token.
137
140
  #
138
141
  # @return [Net::HTTPResponse, integer] The HTTP response object and status code.
142
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
139
143
  def exchange_access_token_request
140
144
  uri = URI('https://account.apple.com/auth/oauth2/v2/token')
141
145
 
@@ -166,6 +170,7 @@ module Axm
166
170
 
167
171
  [response_json, response.code]
168
172
  end
173
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
169
174
 
170
175
  # Sends a POST request to the specified URI with given parameters.
171
176
  #
@@ -175,13 +180,9 @@ module Axm
175
180
  def post(path, request_body = {})
176
181
  uri = URI("https://#{api_domain}/#{path}")
177
182
 
178
- http = Net::HTTP.new(uri.host, uri.port)
179
- http.use_ssl = uri.scheme == 'https'
183
+ http = Net::HTTP.new(uri.host, uri.port, use_ssl: true)
180
184
 
181
- request = Net::HTTP::Post.new(uri)
182
- request['Host'] = uri.host
183
- request['Content-Type'] = 'application/json'
184
- request['Authorization'] = "Bearer #{access_token['access_token']}"
185
+ request = add_post_headers(Net::HTTP::Post.new(uri))
185
186
 
186
187
  request.body = request_body.to_json unless request_body.empty?
187
188
 
@@ -191,5 +192,19 @@ module Axm
191
192
 
192
193
  [response_body, response.code]
193
194
  end
195
+
196
+ private
197
+
198
+ # Adds necessary headers to a POST request.
199
+ #
200
+ # @return [Net::HTTP::Post] The modified request with added headers.
201
+ def add_post_headers(request)
202
+ request['Host'] = uri.host
203
+ request['Content-Type'] = 'application/json'
204
+ request['Authorization'] = "Bearer #{access_token['access_token']}"
205
+
206
+ request
207
+ end
194
208
  end
209
+ # rubocop: enable Metrics/ClassLength
195
210
  end
data/lib/axm/secret.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Secret
2
4
  def self.read(filename)
3
5
  File.read("secrets/#{filename}").strip
data/lib/axm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Axm
4
- VERSION = "1.0.0"
4
+ VERSION = '1.0.1'
5
5
  end
data/lib/axm.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "axm/client"
4
- require_relative "axm/secret"
5
- require_relative "axm/version"
3
+ require_relative 'axm/client'
4
+ require_relative 'axm/secret'
5
+ require_relative 'axm/version'
6
6
 
7
7
  module Axm
8
8
  class Error < StandardError; end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: axm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - nick-f
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-11-08 00:00:00.000000000 Z
10
+ date: 2025-11-13 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: jwt
@@ -24,7 +23,6 @@ dependencies:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
25
  version: '3.1'
27
- description:
28
26
  email:
29
27
  - nick@nickf.dev
30
28
  executables: []
@@ -53,7 +51,7 @@ licenses:
53
51
  metadata:
54
52
  homepage_uri: https://github.com/nick-f/axm
55
53
  source_code_uri: https://github.com/nick-f/axm
56
- post_install_message:
54
+ rubygems_mfa_required: 'true'
57
55
  rdoc_options: []
58
56
  require_paths:
59
57
  - lib
@@ -68,8 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
66
  - !ruby/object:Gem::Version
69
67
  version: '0'
70
68
  requirements: []
71
- rubygems_version: 3.5.22
72
- signing_key:
69
+ rubygems_version: 3.6.5
73
70
  specification_version: 4
74
71
  summary: Gem to interact with the Apple Business Manager/Apple School Manager API
75
72
  test_files: []