xolo-server 1.0.1 → 2.0.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/data/client/xolo +182 -82
  3. data/lib/xolo/core/base_classes/title.rb +261 -20
  4. data/lib/xolo/core/base_classes/version.rb +53 -8
  5. data/lib/xolo/core/constants.rb +7 -3
  6. data/lib/xolo/core/security_cmd.rb +128 -0
  7. data/lib/xolo/core/version.rb +1 -1
  8. data/lib/xolo/core.rb +1 -0
  9. data/lib/xolo/server/app.rb +7 -0
  10. data/lib/xolo/server/configuration.rb +243 -37
  11. data/lib/xolo/server/constants.rb +10 -0
  12. data/lib/xolo/server/helpers/auth.rb +19 -2
  13. data/lib/xolo/server/helpers/autopkg.rb +170 -0
  14. data/lib/xolo/server/helpers/client_data.rb +91 -61
  15. data/lib/xolo/server/helpers/file_transfers.rb +412 -82
  16. data/lib/xolo/server/helpers/jamf_pro.rb +30 -7
  17. data/lib/xolo/server/helpers/log.rb +2 -0
  18. data/lib/xolo/server/helpers/maintenance.rb +1 -0
  19. data/lib/xolo/server/helpers/notification.rb +4 -3
  20. data/lib/xolo/server/helpers/pkg_signing.rb +16 -12
  21. data/lib/xolo/server/helpers/progress_streaming.rb +9 -12
  22. data/lib/xolo/server/helpers/subscriptions.rb +119 -0
  23. data/lib/xolo/server/helpers/titles.rb +27 -3
  24. data/lib/xolo/server/helpers/versions.rb +23 -11
  25. data/lib/xolo/server/mixins/changelog.rb +9 -16
  26. data/lib/xolo/server/mixins/title_jamf_access.rb +395 -383
  27. data/lib/xolo/server/mixins/title_ted_access.rb +29 -3
  28. data/lib/xolo/server/mixins/version_jamf_access.rb +185 -159
  29. data/lib/xolo/server/mixins/version_ted_access.rb +25 -0
  30. data/lib/xolo/server/object_locks.rb +2 -1
  31. data/lib/xolo/server/routes/auth.rb +2 -2
  32. data/lib/xolo/server/routes/jamf_pro.rb +11 -1
  33. data/lib/xolo/server/routes/maint.rb +2 -1
  34. data/lib/xolo/server/routes/subscriptions.rb +126 -0
  35. data/lib/xolo/server/routes/title_editor.rb +1 -1
  36. data/lib/xolo/server/routes/titles.rb +30 -17
  37. data/lib/xolo/server/routes/uploads.rb +0 -14
  38. data/lib/xolo/server/routes/versions.rb +22 -17
  39. data/lib/xolo/server/routes.rb +9 -0
  40. data/lib/xolo/server/title.rb +107 -78
  41. data/lib/xolo/server/tmp_overrides.rb +38 -0
  42. data/lib/xolo/server/version.rb +193 -15
  43. data/lib/xolo/server.rb +12 -0
  44. metadata +8 -9
@@ -27,7 +27,8 @@ module Xolo
27
27
  end
28
28
 
29
29
  # How many seconds are update locks valid for?
30
- OBJECT_LOCK_LIMIT = 60 * 60
30
+ # 15 min
31
+ OBJECT_LOCK_LIMIT = 60 * 15
31
32
 
32
33
  # The locks for titles and versions - when they are being created or updated
33
34
  # they appear in this data structure with an expiration timestamp.
@@ -16,7 +16,7 @@ module Xolo
16
16
 
17
17
  module Auth
18
18
 
19
- # This is how we 'mix in' modules to Sinatra servers:
19
+ # This is how we 'extend' modules to Sinatra servers:
20
20
  # We make them extentions here with
21
21
  # extend Sinatra::Extension (from sinatra-contrib)
22
22
  # and then 'register' them in the server with
@@ -33,7 +33,7 @@ module Xolo
33
33
  # Auth a Xolo Admin via Jamf API login
34
34
  # Must be a member of the Jamf Admin group
35
35
  # named in Xolo::Server.config.admin_jamf_group
36
- # before
36
+ # NOTE: This cannot be an API Client - must be a real user account
37
37
  ###################
38
38
  post '/auth/login' do
39
39
  request.body.rewind
@@ -16,7 +16,7 @@ module Xolo
16
16
 
17
17
  module JamfPro
18
18
 
19
- # This is how we 'mix in' modules to Sinatra servers
19
+ # This is how we 'extend' modules to Sinatra servers
20
20
  # for route definitions and similar things
21
21
  #
22
22
  # (things to be 'included' for use in route and view processing
@@ -80,6 +80,16 @@ module Xolo
80
80
  jcnx&.disconnect
81
81
  end
82
82
 
83
+ # A list of all currently available titles that can be subscribed to
84
+ ###############
85
+ get '/jamf/available-titles-for-subscription' do
86
+ log_debug "Jamf: Fetching titles available for subscription for #{session[:admin]}"
87
+ jcnx = jamf_cnx
88
+ body available_titles_for_subscription
89
+ ensure
90
+ jcnx&.disconnect
91
+ end
92
+
83
93
  end # Module
84
94
 
85
95
  end # Routes
@@ -16,7 +16,7 @@ module Xolo
16
16
 
17
17
  module Maint
18
18
 
19
- # This is how we 'mix in' modules to Sinatra servers:
19
+ # This is how we 'extend' modules to Sinatra servers:
20
20
  # We make them extentions here with
21
21
  # extend Sinatra::Extension (from sinatra-contrib)
22
22
  # and then 'register' them in the server with
@@ -52,6 +52,7 @@ module Xolo
52
52
  uptime: uptime_secs.pix_humanize_secs,
53
53
  uptime_secs: uptime_secs,
54
54
  app_env: Xolo::Server.app_env,
55
+ test_server: Xolo::Server.test_server?,
55
56
  data_dir: Xolo::Server::DATA_DIR,
56
57
  log_file: Xolo::Server::Log::LOG_FILE,
57
58
  log_level: Xolo::Server::Log::LEVELS[Xolo::Server.logger.level],
@@ -0,0 +1,126 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ # main module
10
+ module Xolo
11
+
12
+ # Server Module
13
+ module Server
14
+
15
+ module Routes
16
+
17
+ # See comments for Xolo::Server::Helpers::TitleEditor
18
+ #
19
+ module Subscriptions
20
+
21
+ # This is how we 'extend' modules to Sinatra servers
22
+ # for route definitions and similar things
23
+ #
24
+ # (things to be 'included' for use in route and view processing
25
+ # are mixed in by delcaring them to be helpers)
26
+ #
27
+ # We make them extentions here with
28
+ # extend Sinatra::Extension (from sinatra-contrib)
29
+ # and then 'register' them in the server with
30
+ # register Xolo::Server::<Module>
31
+ # Doing it this way allows us to split the code into a logical
32
+ # file structure, without re-opening the Sinatra::Base server app,
33
+ # and let xeitwork do the requiring of those files
34
+ extend Sinatra::Extension
35
+
36
+ # Module methods
37
+ #
38
+ ##############################
39
+ ##############################
40
+
41
+ # when this module is included
42
+ def self.included(includer)
43
+ Xolo.verbose_include includer, self
44
+ end
45
+
46
+ # when this module is extended
47
+ def self.extended(extender)
48
+ Xolo.verbose_extend extender, self
49
+ end
50
+
51
+ # Routes
52
+ #
53
+ ##############################
54
+ ##############################
55
+
56
+ # This endpoint receives Jamf Webhook PatchSoftwareTitleUpdated events
57
+ # from the Jamf Pro server, indicating that a subscribed title has a new version available.
58
+ #
59
+ # If the title is subscribed in Xolo, this creates a new xolo version for the title,
60
+ # and either notifies the contact email for the title, or uses autopkg to get an installer.
61
+ #
62
+ # The body will be JSON like this:
63
+ # {
64
+ # "event": {
65
+ # "jssID": integer,
66
+ # "lastUpdate": epoch,
67
+ # "latestVersion": "string",
68
+ # "name": "string",
69
+ # "reportUrls": [
70
+ # "string",
71
+ # "string"
72
+ # ]
73
+ # },
74
+ # "webhook": {
75
+ # "eventTimestamp": epoch,
76
+ # "id": integer,
77
+ # "name": "string",
78
+ # "webhookEvent": "PatchSoftwareTitleUpdated"
79
+ # }
80
+ # }
81
+ #
82
+ # Some real data:
83
+ # {
84
+ # "event": {
85
+ # "jssID": 145,
86
+ # "lastUpdate":1765926131000,
87
+ # "latestVersion": "1.0.3",
88
+ # "name": "Xolo Testing",
89
+ # "reportUrls": [
90
+ # "https://myjamf.mycompany.com:8443//patch.html?id=145&o=r"
91
+ # ]
92
+ # },
93
+ # "webhook": {
94
+ # "eventTimestamp":1765926428322,
95
+ # "id": 4,
96
+ # "name": "PatchSoftwareTitleUpdated",
97
+ # "webhookEvent": "PatchSoftwareTitleUpdated"
98
+ # }
99
+ # }
100
+ #
101
+ # NOTE: The above reportUrls is incorrect on modern Jamf Pro servers.
102
+ # the correct one would be https://myjamf.mycompany.com:8443/view/computers/patch/145?tab=report
103
+ #
104
+ ###############
105
+ post '/subscribed-title-updates' do
106
+ session[:admin] = Xolo::Server::WEBHOOK_HANDLER_ADMIN_USERNAME
107
+ request.body.rewind
108
+
109
+ # this happens in a thread so that we can return a 200 response to the
110
+ # webhook immediately, and do the processing asynchronously (which may
111
+ # involve time-consuming tasks like autopkg runs)
112
+ process_patch_title_updated_webhook(request.body.read)
113
+
114
+ # always return 200 to the webhook sender
115
+ status 200
116
+ resp = { status: 200, message: 'Webhook event received' }
117
+ body resp
118
+ end
119
+
120
+ end # Module
121
+
122
+ end # Routes
123
+
124
+ end # Server
125
+
126
+ end # module Xolo
@@ -18,7 +18,7 @@ module Xolo
18
18
  #
19
19
  module TitleEditor
20
20
 
21
- # This is how we 'mix in' modules to Sinatra servers
21
+ # This is how we 'extend' modules to Sinatra servers
22
22
  # for route definitions and similar things
23
23
  #
24
24
  # (things to be 'included' for use in route and view processing
@@ -16,7 +16,7 @@ module Xolo
16
16
 
17
17
  module Titles
18
18
 
19
- # This is how we 'mix in' modules to Sinatra servers
19
+ # This is how we 'extend' modules to Sinatra servers
20
20
  # for route definitions and similar things
21
21
  #
22
22
  # (things to be 'included' for use in route and view processing
@@ -48,11 +48,11 @@ module Xolo
48
48
  halt_on_existing_title title.title
49
49
 
50
50
  log_info "Admin #{session[:admin]} is creating title '#{title.title}'"
51
+ info = { status: 'created', title: title.title }
52
+ body info
53
+
51
54
  with_streaming do
52
55
  title.create
53
- # we don't need to update client data when titles are created
54
- # because they don't have any versions yet, so there's nothing a
55
- # client can do with them.
56
56
  end
57
57
  end
58
58
 
@@ -93,7 +93,8 @@ module Xolo
93
93
  log_info "Admin #{session[:admin]} is updating title '#{params[:title]}'"
94
94
  with_streaming do
95
95
  title.update new_data
96
- update_client_data
96
+ # Moved to title.update
97
+ # update_client_data
97
98
  end
98
99
  end
99
100
 
@@ -121,7 +122,8 @@ module Xolo
121
122
 
122
123
  with_streaming do
123
124
  title.release vers_to_release
124
- update_client_data
125
+ # moved to title.release
126
+ # update_client_data
125
127
  end
126
128
  end
127
129
 
@@ -142,7 +144,8 @@ module Xolo
142
144
 
143
145
  with_streaming do
144
146
  title.repair repair_versions: do_versions
145
- update_client_data
147
+ # moved to title.repair
148
+ # update_client_data
146
149
  end
147
150
  end
148
151
 
@@ -156,7 +159,8 @@ module Xolo
156
159
 
157
160
  with_streaming do
158
161
  title.delete
159
- update_client_data
162
+ # moved to title.delete
163
+ # update_client_data
160
164
  end
161
165
  end
162
166
 
@@ -240,13 +244,14 @@ module Xolo
240
244
  #################################
241
245
  get '/titles/:title/urls' do
242
246
  log_debug "Admin #{session[:admin]} is fetching GUI URLS for title '#{params[:title]}'"
247
+
243
248
  halt_on_missing_title params[:title]
244
249
  title = instantiate_title params[:title]
245
- data = {
246
- ted_title_url: title.ted_title_url,
247
- jamf_installed_group_url: title.jamf_installed_group_url,
248
- jamf_frozen_group_url: title.jamf_frozen_group_url
249
- }
250
+
251
+ data = {}
252
+ data[:ted_title_url] = title.ted_title_url if title.managed?
253
+ data[:jamf_installed_group_url] = title.jamf_installed_group_url if title.jamf_installed_group_exist?
254
+ data[:jamf_frozen_group_url] = title.jamf_frozen_group_url if title.jamf_frozen_group_exist?
250
255
  data[:jamf_manual_install_released_policy_url] = title.jamf_manual_install_released_policy_url if title.jamf_manual_install_released_policy_exist?
251
256
 
252
257
  if title.uninstallable?
@@ -255,13 +260,11 @@ module Xolo
255
260
  data[:jamf_expire_policy_url] = title.jamf_expire_policy_url if title.expiration
256
261
  end
257
262
 
258
- if title.jamf_ted_title_active?
259
- data[:jamf_patch_title_url] = title.jamf_patch_title_url unless title.versions.empty?
263
+ if title.jamf_title_active?
264
+ data[:jamf_patch_title_url] = title.jamf_patch_title_url unless title.jamf_patch_title_id.pix_empty?
260
265
  data[:jamf_patch_ea_url] = title.jamf_patch_ea_url if title.version_script
261
266
  end
262
267
 
263
- data[:jamf_normal_ea_url] = title.jamf_normal_ea_url if title.version_script
264
-
265
268
  body data
266
269
  end
267
270
 
@@ -276,6 +279,16 @@ module Xolo
276
279
  body title.changelog
277
280
  end
278
281
 
282
+ # Does this xolo server support autopkg integration?
283
+ #
284
+ # @return [Array<Hash>] The changelog for a title
285
+ #################################
286
+ get '/titles/autopkg_enabled' do
287
+ log_debug "Admin #{session[:admin]} is checking if autopkg is enabled on this server"
288
+ data = { autopkg_enabled: autopkg_enabled? }
289
+ body data
290
+ end
291
+
279
292
  end # Titles
280
293
 
281
294
  end # Routes
@@ -62,20 +62,6 @@ module Xolo
62
62
  ##############################
63
63
  ##############################
64
64
 
65
- # # param with the uploaded file must be :file
66
- # ######################
67
- # post '/upload/ssvc-icon/:title' do
68
- # process_incoming_ssvc_icon
69
- # body({ result: :uploaded })
70
- # end
71
-
72
- # # param with the uploaded file must be :file
73
- # ######################
74
- # post '/upload/pkg/:title/:version' do
75
- # process_incoming_pkg
76
- # body({ result: :uploaded })
77
- # end
78
-
79
65
  # param with the uploaded file must be :file
80
66
  ######################
81
67
  post '/upload/test' do
@@ -16,7 +16,7 @@ module Xolo
16
16
 
17
17
  module Versions
18
18
 
19
- # This is how we 'mix in' modules to Sinatra servers
19
+ # This is how we 'extend' modules to Sinatra servers
20
20
  # for route definitions and similar things
21
21
  #
22
22
  # (things to be 'included' for use in route and view processing
@@ -58,9 +58,10 @@ module Xolo
58
58
  data[:min_os] = default_min_os if data[:min_os].pix_empty?
59
59
 
60
60
  log_debug "Incoming new version data: #{data}"
61
- log_debug "Incoming new version data: #{data.class}"
62
61
 
63
- vers = instantiate_version(data)
62
+ vers = instantiate_version(**data)
63
+ log_debug "Instantiated version object: #{vers.class} - #{vers.title} #{vers.version}"
64
+
64
65
  halt_on_existing_version vers.title, vers.version
65
66
 
66
67
  if vers.title_object.jamf_patch_ea_awaiting_acceptance? && !Xolo::Server.config.jamf_auto_accept_xolo_eas
@@ -76,7 +77,8 @@ module Xolo
76
77
  Xolo::Server.rw_lock(data[:title], data[:version]).with_write_lock do
77
78
  with_streaming do
78
79
  vers.create
79
- update_client_data
80
+ # moved to vers.create
81
+ # update_client_data
80
82
  end
81
83
  end
82
84
  end
@@ -89,9 +91,9 @@ module Xolo
89
91
 
90
92
  log_debug "Admin #{session[:admin]} is listing all versions for title '#{params[:title]}'"
91
93
  # body all_versions(params[:title])
92
- vers_ins = all_version_instances(params[:title])
94
+ vers_objs = all_version_objects(params[:title])
93
95
  # log_debug "vers_ins: #{vers_ins}"
94
- body vers_ins.map(&:to_h)
96
+ body vers_objs.map(&:to_h)
95
97
  end
96
98
 
97
99
  # get all the data for a single version
@@ -125,7 +127,8 @@ module Xolo
125
127
 
126
128
  with_streaming do
127
129
  vers.update new_data
128
- update_client_data
130
+ # moved to vers.update
131
+ # update_client_data
129
132
  end
130
133
  end
131
134
 
@@ -148,7 +151,8 @@ module Xolo
148
151
 
149
152
  with_streaming do
150
153
  vers.delete
151
- update_client_data
154
+ # moved to vers.delete
155
+ # update_client_data
152
156
  end
153
157
  end
154
158
 
@@ -156,7 +160,7 @@ module Xolo
156
160
  # param with the uploaded file must be :file
157
161
  ######################
158
162
  post '/titles/:title/versions/:version/pkg' do
159
- process_incoming_pkg
163
+ process_and_upload_uploaded_pkg
160
164
  body({ result: :uploaded })
161
165
  end
162
166
 
@@ -205,7 +209,8 @@ module Xolo
205
209
 
206
210
  with_streaming do
207
211
  vers.repair
208
- update_client_data
212
+ # moved to vers.repair
213
+ # update_client_data
209
214
  end
210
215
  end
211
216
 
@@ -239,13 +244,13 @@ module Xolo
239
244
 
240
245
  halt_on_missing_version params[:title], params[:version]
241
246
  vers = instantiate_version title: params[:title], version: params[:version]
242
- data = {
243
- ted_patch_url: vers.ted_patch_url,
244
- jamf_auto_install_policy_url: vers.jamf_auto_install_policy_url,
245
- jamf_manual_install_policy_url: vers.jamf_manual_install_policy_url,
246
- jamf_patch_policy_url: vers.jamf_patch_policy_url,
247
- jamf_package_url: vers.jamf_package_url
248
- }
247
+
248
+ data = {}
249
+ data[:ted_patch_url] = vers.ted_patch_url if vers.managed?
250
+ data[:jamf_auto_install_policy_url] = vers.jamf_auto_install_policy_url if vers.jamf_auto_install_policy_exist?
251
+ data[:jamf_manual_install_policy_url] = vers.jamf_manual_install_policy_url if vers.jamf_manual_install_policy_exist?
252
+ data[:jamf_patch_policy_url] = vers.jamf_patch_policy_url if vers.jamf_patch_policy_exist?
253
+ data[:jamf_package_url] = vers.jamf_package_url if vers.jamf_package_exist?
249
254
  data[:jamf_version_installed_group] = vers.jamf_installed_group_url if vers.jamf_installed_group_exist?
250
255
  data[:jamf_auto_reinstall_policy_url] = vers.jamf_auto_reinstall_policy_url if vers.jamf_auto_reinstall_policy_exist?
251
256
 
@@ -49,6 +49,14 @@ module Xolo
49
49
  log_debug "Checking if request path '#{request.path}' is in INTERNAL_ROUTES"
50
50
  break if Xolo::Server::Helpers::Auth::INTERNAL_ROUTES.include?(request.path) && valid_internal_auth_token?
51
51
 
52
+ # This route is used by Jamf Pro Webhooks to notify Xolo of subscribed title updates
53
+ log_debug "Checking if request path '#{request.path}' is PATCH_TITLE_UPDATED_WEBHOOK_ROUTE"
54
+ if request.path == Xolo::Server::Helpers::Auth::PATCH_TITLE_UPDATED_WEBHOOK_ROUTE
55
+ break if jamf_webhook_auth_token_ok?
56
+
57
+ halt 401, { status: 401, error: 'Valid Bearer Token Required' }
58
+ end
59
+
52
60
  # these routes are for server admins only, and require an authenticated session
53
61
  log_debug "Checking if request path '#{request.path}' is in SERVER_ADMIN_ROUTES"
54
62
  break if Xolo::Server::Helpers::Auth::SERVER_ADMIN_ROUTES.include?(request.path) && valid_server_admin?
@@ -149,3 +157,4 @@ require 'xolo/server/routes/title_editor'
149
157
  require 'xolo/server/routes/titles'
150
158
  require 'xolo/server/routes/uploads'
151
159
  require 'xolo/server/routes/versions'
160
+ require 'xolo/server/routes/subscriptions'