axm 0.1.1 → 1.0.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: 02ec17dcd1e6762bebf731dd97394f9fe87c75be604baed827f34106482d0caf
4
- data.tar.gz: 4b480339114ab452ed36ce63ae9b843a95e548f5ccc38de034c4d85eb331e674
3
+ metadata.gz: 0d2f95c8bc2de0f7a2c7ea620d2f301980eb6c5a21243bc8ee878b0ae4ebb351
4
+ data.tar.gz: 59c698eebe7dbe7baf4b1378ca5a66b0045334a2d69c3441b0300ca5dc3de6a4
5
5
  SHA512:
6
- metadata.gz: 28c69e287d78e9b120a75a3092294b1a7526d9b41301869c04c96921066dc34440423e28f06ccca208396dd24f98a5d460c5ab0c1e32e89ccbebb4f4ded09317
7
- data.tar.gz: 0bfd98852076b669c53f02c3ddbcbdc3e60a1958125e2ef9d66af2ab8ec11207451245fd9fabe123583c24996703bb977990cba1e001950b5323696db38820d9
6
+ metadata.gz: 8e3fc06e2e51475d49a6d5612700046e9003967200d1234ca3aa52d4ead5f08827d8f4eb432a512af127cb01920befab0a3f4dd9f354a6705a4f3ca8d1d5d781
7
+ data.tar.gz: dac8b555c797f8b773de5c58cc0de9b35128df4251a5cfbe9c3260160c11f5b2178e010f3fe9d8126a63ad144c0db71e9abd452b020488bb6440bb239e1875fe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
- ## [Unreleased]
1
+ ## 1.0.0 - 2025-11-08
2
2
 
3
- ## [0.1.0] - 2025-06-16
3
+ ### Added
4
+
5
+ - List all MDM servers in an organisation
6
+ - Show all device IDs assigned to a specific MDM server
7
+ - Show which MDM server a device is assigned to
8
+ - Show the ID of the MDM server a device is assigned to
9
+ - Show information about the MDM server a device is assigned to
10
+ - Assign and unassign devices to MDM servers
11
+ - Show AppleCare coverage information for devices
12
+
13
+ ## 0.1.2 - 2025-07-14
14
+
15
+ ### Added
16
+
17
+ - Get information about a specific device
18
+
19
+ ### Fixed
20
+
21
+ - Fixed issue with incorrect endpoints being included
22
+
23
+ ## [0.1.1] - 2025-07-14
24
+
25
+ - Updated README with more detailed usage instructions
26
+
27
+ ## [0.1.0] - 2025-07-14
4
28
 
5
29
  - Initial release
30
+
31
+ ### Added
32
+
33
+ - Exchange credentials for an access token
34
+ - List all devices in an organisation
data/README.md CHANGED
@@ -26,10 +26,10 @@ With the credentials handy, you can create an instance of the client:
26
26
 
27
27
  ```ruby
28
28
  private_key = File.read('path/to/your/private_key.pem')
29
- issuer_id = 'your_issuer_id'
29
+ client_id = 'your_client_id'
30
30
  key_id = 'your_key_id'
31
31
 
32
- client = Axm::Client.new(private_key:, issuer_id:, key_id:)
32
+ client = Axm::Client.new(private_key:, client_id:, key_id:)
33
33
  ```
34
34
 
35
35
  You're now ready to make API requests.
@@ -48,6 +48,16 @@ client.get('/v1/some/endpoint', { limit: 10 })
48
48
 
49
49
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
50
50
 
51
+ If the credentials are stored in the `secrets/` directory, you can use the `Secret.read` method to load them:
52
+
53
+ ```ruby
54
+ private_key = Secret.read('private_key.pem')
55
+ client_id = Secret.read('client_id')
56
+ key_id = Secret.read('key_id')
57
+
58
+ client = Axm::Client.new(private_key:, client_id:, key_id:)
59
+ ```
60
+
51
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).
52
62
 
53
63
  ## Contributing
@@ -0,0 +1,31 @@
1
+ module Axm
2
+ class Client
3
+ module MdmServers
4
+ # Retrieves a list of MDM servers associated with the organization.
5
+ #
6
+ # @param options [Hash] Optional query parameters to filter fields or paginate results.
7
+ # - fields: (Array) Array of fields to include in the response.
8
+ # - limit: (Integer) Maximum number of devices to return per page (default: 100, maximum: 1000).
9
+ # @return [Array<Hash>] An array of MDM servers.
10
+ #
11
+ # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-mdm-servers
12
+ # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-mdm-servers
13
+ def list_mdm_servers(options = {})
14
+ get("v1/mdmServers", options)
15
+ end
16
+
17
+ # Retrieves a list of IDs for the devices assigned to a specific MDM server.
18
+ #
19
+ # @param mdm_server_id [String] The unique identifier of the MDM server.
20
+ # @param options [Hash] Optional query parameters to paginate results or increase number of returned results.
21
+ # - limit: (Integer) Maximum number of devices to return per page (default: 100, maximum: 1000).
22
+ # @return [Array<Hash>] An array of device IDs assigned to the specified MDM server.
23
+ #
24
+ # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-all-device-ids-for-a-mdmserver
25
+ # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-all-device-ids-for-a-mdmserver
26
+ def devices_assigned_to_mdm_server(mdm_server_id, options = {})
27
+ get("v1/mdmServers/#{mdm_server_id}/relationships/devices", options)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,74 @@
1
+ module Axm
2
+ class Client
3
+ module OrganizationDeviceActivities
4
+ # Get information for an organization device activity that a device management action, such as assign or unassign, creates.
5
+ #
6
+ # @param options [Hash] Optional query parameters to filter returned attributes.
7
+ # - fields: (Array) Array of fields to include in the response.
8
+ # @return [<Hash>] A single organization device activity resource.
9
+ #
10
+ # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-orgdeviceactivity-information
11
+ # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-orgdeviceactivity-information
12
+ def org_device_activity(activity_id, options = {})
13
+ get("v1/orgDeviceActivities/#{activity_id}", options)
14
+ end
15
+
16
+ # Assigns a device to an MDM server.
17
+ #
18
+ # @param device_ids [Array<String>] Array of device IDs to be assigned.
19
+ # @param mdm_server_id [String] The unique identifier of the MDM server to which the device will be assigned.
20
+ # @return [Hash, Integer] The response from the POST method, containing details of the assignment and status code.
21
+ def assign(device_id, mdm_server_id)
22
+ assignment_change(device_id, mdm_server_id, "ASSIGN_DEVICES")
23
+ end
24
+
25
+ # Unassigns a device from an MDM server.
26
+ #
27
+ # @param device_ids [Array<String>] Array of device IDs to be unassigned.
28
+ # @param mdm_server_id [String] The unique identifier of the MDM server to which the device will be assigned.
29
+ # @return [Hash, Integer] The response from the POST method, containing details of the assignment and status code.
30
+ def unassign(device_id, mdm_server_id)
31
+ assignment_change(device_id, mdm_server_id, "UNASSIGN_DEVICES")
32
+ end
33
+
34
+ private
35
+
36
+ # Sends a request to change the assignment of a device to an MDM server.
37
+ #
38
+ # @param device_ids [Array<String>] Array of IDs of devices to be assigned or unassigned.
39
+ # @param mdm_server_id [String] The unique identifier of the MDM server.
40
+ # @param activity_type [String] The type of activity being performed ("ASSIGN_DEVICES" or "UNASSIGN_DEVICES").
41
+ # @return [Hash, Integer] The response from the POST request, containing details of the operation and status code.
42
+ def assignment_change(device_ids, mdm_server_id, activity_type)
43
+ devices = device_ids.map do |device_id|
44
+ {
45
+ type: "orgDevices",
46
+ id: device_id
47
+ }
48
+ end
49
+
50
+ request_body = {
51
+ data: {
52
+ type: "orgDeviceActivities",
53
+ attributes: {
54
+ activityType: activity_type
55
+ },
56
+ relationships: {
57
+ mdmServer: {
58
+ data: {
59
+ type: "mdmServers",
60
+ id: mdm_server_id
61
+ }
62
+ },
63
+ devices: {
64
+ data: devices
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ post("v1/orgDeviceActivities", request_body)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -11,7 +11,63 @@ module Axm
11
11
  # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-org-devices
12
12
  # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-org-devices
13
13
  def list_org_devices(options = {})
14
- get('/v1/organization/devices', options)
14
+ get("v1/orgDevices", options)
15
+ end
16
+
17
+ # Retrieves information about a specific device in the organization.
18
+ #
19
+ # @param options [Hash] Optional query parameters to filter or paginate results.
20
+ # - fields: (Array) Array of fields to include in the response.
21
+ # @return [<Hash>] A device and its selected attributes.
22
+ #
23
+ # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-orgdevice-information
24
+ # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-orgdevice-information
25
+ def device(id, options = {})
26
+ get("/v1/orgDevices/#{id}", options)
27
+ end
28
+
29
+ # Fetch the assigned device management service ID for a device.
30
+ #
31
+ # @param device_id [String] The unique identifier of the device.
32
+ # @param options [Hash] Optional query parameters to filter or paginate results.
33
+ # @return [<Hash>] Identifier of the device's assigned MDM server.
34
+ #
35
+ # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-the-assigned-server-id-for-an-orgdevice
36
+ # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-the-assigned-server-id-for-an-orgdevice
37
+ def assigned_mdm_server_id(device_id, options = {})
38
+ get("v1/orgDevices/#{device_id}/relationships/assignedServer", options)
39
+ end
40
+
41
+ # Fetch the assigned device management service information for a device.
42
+ #
43
+ # @param device_id [String] The unique identifier of the device.
44
+ # @param options [Hash] Optional query parameters to filter or paginate results.
45
+ # - id: (String) The unique identifier of the device.
46
+ # - fields: (Array) Array of fields to include in the response.
47
+ # @return [<Hash>] A hash containing the identifier of the device's assigned MDM server.
48
+ #
49
+ # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-the-assigned-server-information-for-an-orgdevice
50
+ # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-the-assigned-server-information-for-an-orgdevice
51
+ def assigned_mdm_server(device_id, options = {})
52
+ options[:fields_key] = "mdmServers"
53
+
54
+ get("v1/orgDevices/#{device_id}/assignedServer", options)
55
+ end
56
+
57
+ # Fetch the AppleCare coverage information for a device.
58
+ #
59
+ # @param device_id [String] The unique identifier of the device.
60
+ # @param options [Hash] Optional query parameters to filter or paginate results.
61
+ # - fields: (Array) Array of fields to include in the response.
62
+ # - limit: (Integer) The number of included related resources to return (default: 100, maximum: 1000).
63
+ # @return [<Hash>] A hash containing the identifier of the device's assigned MDM server.
64
+ #
65
+ # See: https://developer.apple.com/documentation/applebusinessmanagerapi/get-all-apple-care-coverage-for-an-orgdevice
66
+ # See: https://developer.apple.com/documentation/appleschoolmanagerapi/get-all-apple-care-coverage-for-an-orgdevice
67
+ def applecare_coverage(device_id, options = {})
68
+ options[:fields_key] = "appleCareCoverage"
69
+
70
+ get("v1/orgDevices/#{device_id}/appleCareCoverage", options)
15
71
  end
16
72
  end
17
73
  end
data/lib/axm/client.rb CHANGED
@@ -4,10 +4,16 @@ require 'net/http'
4
4
  require 'securerandom'
5
5
  require 'time'
6
6
 
7
+ require 'axm/client/mdm_servers'
8
+ require 'axm/client/organization_device_activities'
7
9
  require 'axm/client/organization_devices'
8
10
 
9
11
  module Axm
10
12
  class Client
13
+ include MdmServers
14
+ include OrganizationDeviceActivities
15
+ include OrganizationDevices
16
+
11
17
  # Initializes a new instance of the AXM client.
12
18
  #
13
19
  # @param private_key [String] The private key used for authentication.
@@ -83,15 +89,7 @@ module Axm
83
89
  return cached_access_token unless token_expired
84
90
  end
85
91
 
86
- params = {
87
- grant_type: 'client_credentials',
88
- client_id: @client_id,
89
- client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
90
- client_assertion: client_assertion,
91
- scope: "#{scope}.api"
92
- }
93
-
94
- response = post('https://account.apple.com/auth/oauth2/v2/token', params)
92
+ response = exchange_access_token_request
95
93
 
96
94
  response_body = response.first if response.last.to_i == 200
97
95
 
@@ -112,10 +110,14 @@ module Axm
112
110
  def get(path, options = {})
113
111
  options = options.dup
114
112
 
115
- endpoint = path.split('/').last
113
+ # API endpoints are prefixed with 'v1', so we can extract the endpoint from the path.
114
+ endpoint = path.split('/')[1]
115
+
116
+ # For the cases where the fields_key is different to the path component, it can be overriden as an option.
117
+ fields_key = options.delete(:fields_key) || endpoint
116
118
 
117
119
  fields = options.delete(:fields)
118
- options["fields[#{endpoint}]"] = fields.join(',') if fields
120
+ options["fields[#{fields_key}]"] = fields.join(',') if fields
119
121
 
120
122
  uri = URI("https://#{api_domain}/#{path}")
121
123
  uri.query = URI.encode_www_form(options) unless options.empty?
@@ -131,20 +133,27 @@ module Axm
131
133
  JSON.parse(res.body)
132
134
  end
133
135
 
134
- # Sends a POST request to the specified URI with given parameters.
136
+ # Sends a POST request to exchange the credentials for an access token.
135
137
  #
136
- # @param uri [String, URI] The endpoint URI.
137
- # @param params [Hash] Parameters to include in the request body.
138
- # @return [Net::HTTPResponse] The HTTP response object.
139
- def post(uri, params = {})
140
- uri = URI(uri) if uri.is_a?(String)
138
+ # @return [Net::HTTPResponse, integer] The HTTP response object and status code.
139
+ def exchange_access_token_request
140
+ uri = URI('https://account.apple.com/auth/oauth2/v2/token')
141
+
142
+ request_body = {
143
+ grant_type: 'client_credentials',
144
+ client_id: @client_id,
145
+ client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
146
+ client_assertion: client_assertion,
147
+ scope: "#{scope}.api"
148
+ }
149
+
141
150
  http = Net::HTTP.new(uri.host, uri.port)
142
151
  http.use_ssl = uri.scheme == 'https'
143
152
 
144
153
  request = Net::HTTP::Post.new(uri)
145
154
  request['Host'] = uri.host
146
155
  request['Content-Type'] = 'application/x-www-form-urlencoded'
147
- request.body = URI.encode_www_form(params) unless params.empty?
156
+ request.body = URI.encode_www_form(request_body)
148
157
 
149
158
  response = http.request(request)
150
159
 
@@ -153,8 +162,34 @@ module Axm
153
162
  response_json = JSON.parse(response.body)
154
163
 
155
164
  raise 'Invalid request' if response_json['error'] == 'invalid_request'
165
+ raise 'Invalid client ID or key ID' if response_json['error'] == 'invalid_client'
156
166
 
157
167
  [response_json, response.code]
158
168
  end
169
+
170
+ # Sends a POST request to the specified URI with given parameters.
171
+ #
172
+ # @param uri [String, URI] The endpoint URI.
173
+ # @param request_body [Hash] Parameters to include in the request body.
174
+ # @return [Hash, Integer] The HTTP response object and status code.
175
+ def post(path, request_body = {})
176
+ uri = URI("https://#{api_domain}/#{path}")
177
+
178
+ http = Net::HTTP.new(uri.host, uri.port)
179
+ http.use_ssl = uri.scheme == 'https'
180
+
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
+
186
+ request.body = request_body.to_json unless request_body.empty?
187
+
188
+ response = http.request(request)
189
+
190
+ response_body = JSON.parse(response.body)
191
+
192
+ [response_body, response.code]
193
+ end
159
194
  end
160
195
  end
data/lib/axm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Axm
4
- VERSION = "0.1.1"
4
+ VERSION = "1.0.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: axm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nick-f
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-14 00:00:00.000000000 Z
11
+ date: 2025-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -40,6 +40,8 @@ files:
40
40
  - Rakefile
41
41
  - lib/axm.rb
42
42
  - lib/axm/client.rb
43
+ - lib/axm/client/mdm_servers.rb
44
+ - lib/axm/client/organization_device_activities.rb
43
45
  - lib/axm/client/organization_devices.rb
44
46
  - lib/axm/secret.rb
45
47
  - lib/axm/version.rb
@@ -66,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
68
  - !ruby/object:Gem::Version
67
69
  version: '0'
68
70
  requirements: []
69
- rubygems_version: 3.4.19
71
+ rubygems_version: 3.5.22
70
72
  signing_key:
71
73
  specification_version: 4
72
74
  summary: Gem to interact with the Apple Business Manager/Apple School Manager API