mdm 0.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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/controllers/mdm/application_controller.rb +4 -0
  6. data/app/controllers/mdm/commands_controller.rb +60 -0
  7. data/app/controllers/mdm/enrollment/enrollment_controller.rb +47 -0
  8. data/app/controllers/mdm/management/accounts_controller.rb +15 -0
  9. data/app/controllers/mdm/management/devices_controller.rb +35 -0
  10. data/app/controllers/mdm/management/profiles_controller.rb +35 -0
  11. data/app/controllers/mdm/management/services_controller.rb +13 -0
  12. data/app/controllers/mdm/server_controller.rb +44 -0
  13. data/app/helpers/mdm/application_helper.rb +4 -0
  14. data/app/models/mdm/command.rb +23 -0
  15. data/app/models/mdm/cursor.rb +11 -0
  16. data/app/models/mdm/device.rb +39 -0
  17. data/app/models/mdm/payload.rb +158 -0
  18. data/app/models/mdm/profile.rb +14 -0
  19. data/app/views/layouts/mdm/application.html.erb +14 -0
  20. data/config/routes.rb +18 -0
  21. data/db/migrate/20150816150227_create_mdm_commands.rb +12 -0
  22. data/db/migrate/20150816150732_create_mdm_devices.rb +34 -0
  23. data/db/migrate/20150823153933_create_mdm_cursors.rb +10 -0
  24. data/db/migrate/20150827082737_create_mdm_profiles.rb +24 -0
  25. data/lib/mdm.rb +21 -0
  26. data/lib/mdm/configuration.rb +14 -0
  27. data/lib/mdm/engine.rb +5 -0
  28. data/lib/mdm/enrollment/client.rb +47 -0
  29. data/lib/mdm/enrollment/service/account.rb +32 -0
  30. data/lib/mdm/enrollment/service/assign_profile.rb +35 -0
  31. data/lib/mdm/enrollment/service/auth.rb +100 -0
  32. data/lib/mdm/enrollment/service/base.rb +67 -0
  33. data/lib/mdm/enrollment/service/create_profile.rb +69 -0
  34. data/lib/mdm/enrollment/service/devices.rb +92 -0
  35. data/lib/mdm/enrollment/service/profile.rb +62 -0
  36. data/lib/mdm/enrollment/service/sync.rb +43 -0
  37. data/lib/mdm/railtie.rb +22 -0
  38. data/lib/mdm/version.rb +3 -0
  39. data/lib/tasks/mdm_tasks.rake +4 -0
  40. data/test/controllers/mdm/commands_controller_test.rb +13 -0
  41. data/test/controllers/mdm/enrollment/accounts_controller_test.rb +13 -0
  42. data/test/controllers/mdm/enrollment/devices_controller_test.rb +13 -0
  43. data/test/controllers/mdm/enrollment/enrollment_controller_test.rb +13 -0
  44. data/test/controllers/mdm/enrollment/profiles_controller_test.rb +13 -0
  45. data/test/controllers/mdm/services_controller_test.rb +13 -0
  46. data/test/dummy/README.rdoc +28 -0
  47. data/test/dummy/Rakefile +6 -0
  48. data/test/dummy/app/assets/javascripts/application.js +13 -0
  49. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  50. data/test/dummy/app/controllers/application_controller.rb +5 -0
  51. data/test/dummy/app/helpers/application_helper.rb +2 -0
  52. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  53. data/test/dummy/bin/bundle +3 -0
  54. data/test/dummy/bin/rails +4 -0
  55. data/test/dummy/bin/rake +4 -0
  56. data/test/dummy/bin/setup +29 -0
  57. data/test/dummy/config.ru +4 -0
  58. data/test/dummy/config/application.rb +26 -0
  59. data/test/dummy/config/boot.rb +5 -0
  60. data/test/dummy/config/database.yml +25 -0
  61. data/test/dummy/config/environment.rb +5 -0
  62. data/test/dummy/config/environments/development.rb +41 -0
  63. data/test/dummy/config/environments/production.rb +79 -0
  64. data/test/dummy/config/environments/test.rb +42 -0
  65. data/test/dummy/config/initializers/assets.rb +11 -0
  66. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  68. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  69. data/test/dummy/config/initializers/inflections.rb +16 -0
  70. data/test/dummy/config/initializers/mime_types.rb +4 -0
  71. data/test/dummy/config/initializers/session_store.rb +3 -0
  72. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  73. data/test/dummy/config/locales/en.yml +23 -0
  74. data/test/dummy/config/routes.rb +4 -0
  75. data/test/dummy/config/secrets.yml +22 -0
  76. data/test/dummy/public/404.html +67 -0
  77. data/test/dummy/public/422.html +67 -0
  78. data/test/dummy/public/500.html +66 -0
  79. data/test/dummy/public/favicon.ico +0 -0
  80. data/test/fixtures/mdm/commands.yml +11 -0
  81. data/test/fixtures/mdm/cursors.yml +9 -0
  82. data/test/fixtures/mdm/devices.yml +25 -0
  83. data/test/fixtures/mdm/profiles.yml +35 -0
  84. data/test/integration/navigation_test.rb +9 -0
  85. data/test/mdm_test.rb +7 -0
  86. data/test/models/mdm/command_test.rb +9 -0
  87. data/test/models/mdm/cursor_test.rb +9 -0
  88. data/test/models/mdm/device_test.rb +9 -0
  89. data/test/models/mdm/profile_test.rb +9 -0
  90. data/test/test_helper.rb +20 -0
  91. metadata +254 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0e0daa1a33ac49616b63c831fcd2070282921e78
4
+ data.tar.gz: 2d22d20fa023a6ee190349983b2c8646b0ce21dc
5
+ SHA512:
6
+ metadata.gz: 5fa6587ec44183147c65f74aa53231e9aed2876626020381a8e3de7a2f866aab2f09a46cd8be9229bacba45cfea5a5b274353c54cffec0126be1d710dc267d32
7
+ data.tar.gz: 2240aa24ebde87657890d8088e4999f1c8f5ca5c9a66704378b1152031a6fbe2636e85aa92abfcfa0ef58201230b7362947b75d80df530daa7b7aa33e25b10da
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Olivier Thierry
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ = Mdm
2
+
3
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Mdm'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,4 @@
1
+ module Mdm
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,60 @@
1
+ require_dependency "mdm/application_controller"
2
+
3
+ module Mdm
4
+ class CommandsController < ApplicationController
5
+
6
+ def index
7
+ render json: { commands: case params[:filter]
8
+ when 'completed'
9
+ Command.completed
10
+ when 'pending'
11
+ Command.pending
12
+ else
13
+ Command.all
14
+ end }
15
+ end
16
+
17
+ def create
18
+ @commands = create_commands(devices)
19
+ @commands.each { |command|
20
+ command.device.notify
21
+ }
22
+ render json: @commands
23
+ rescue NoMethodError
24
+ render json: { error: 'wrong command' }, status: :bad_request
25
+ rescue ArgumentError
26
+ render json: { error: 'bad arguments' }, status: :bad_request
27
+ rescue ActiveRecord::RecordNotFound
28
+ render nothing: true, status: :not_found
29
+ end
30
+
31
+ private
32
+
33
+ def create_commands(devices)
34
+ devices.map do |device|
35
+ uuid, payload = create_command_payload_for_device(device)
36
+ Command.create(
37
+ uuid: uuid,
38
+ payload: payload,
39
+ status: Command::Status::IDLE,
40
+ device: device
41
+ )
42
+ end
43
+ end
44
+
45
+ def create_command_payload_for_device(device)
46
+ Payload.send(
47
+ "payload_#{params[:command]}".to_sym,
48
+ *[device, params]
49
+ )
50
+ end
51
+
52
+ def devices
53
+ @devices ||= if params[:devices]
54
+ Device.where(id: params[:devices])
55
+ else
56
+ Device.all
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,47 @@
1
+ require_dependency "mdm/application_controller"
2
+
3
+ module Mdm
4
+ module Enrollment
5
+ class EnrollmentController < ApplicationController
6
+
7
+ def enroll
8
+ send_file Configuration.configuration_profile, {
9
+ disposition: 'attachment',
10
+ filename: 'Enroll.mobileconfig'
11
+ }
12
+ end
13
+
14
+ def checkin
15
+ if params[:MessageType] == 'TokenUpdate'
16
+ device_token_update
17
+ end
18
+
19
+ render nothing: true
20
+ end
21
+
22
+ private
23
+
24
+ def device_token_update
25
+ device = Mdm::Device.find_or_create_by(udid: params[:UDID])
26
+ device.update!(
27
+ unlock_token: params[:UnlockToken].try(:read),
28
+ push_token: params[:Token].hexastring,
29
+ push_magic: params[:PushMagic]
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ # TODO: move this somewhere
37
+ class StringIO
38
+
39
+ def hexastring
40
+ chars = []
41
+ each_byte { |byte|
42
+ chars << ("%02x" % byte)
43
+ }
44
+ return chars.join
45
+ end
46
+
47
+ end
@@ -0,0 +1,15 @@
1
+ require_dependency "mdm/application_controller"
2
+
3
+ module Mdm
4
+ class Enrollment::AccountsController < ApplicationController
5
+
6
+ def show
7
+ service = Enrollment::Account.new
8
+ service.params[:profile_uuid] = params.require(:profile_uuid)
9
+ service.start
10
+
11
+ render json: { account: service.result }
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ require_dependency "mdm/application_controller"
2
+
3
+ module Mdm
4
+ class Enrollment::DevicesController < ApplicationController
5
+
6
+ before_action :sync, only: :index
7
+
8
+ def index
9
+ @devices = Device.all
10
+ render json: { devices: @devices.map { |device|
11
+ device.as_json(except: [
12
+ :unlock_token,
13
+ :push_magic
14
+ ])
15
+ }
16
+ }
17
+ end
18
+
19
+ def show
20
+ @device = Device.find(params[:id])
21
+ render json: { device: @device }
22
+ end
23
+
24
+ private
25
+
26
+ def sync
27
+ service = Enrollment::Sync.new
28
+ service.start
29
+ rescue Enrollment::Sync::DevicesCursorNotFound
30
+ service = Enrollment::Devices.new
31
+ service.start
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ require_dependency "mdm/application_controller"
2
+
3
+ module Mdm
4
+ class Enrollment::ProfilesController < ApplicationController
5
+
6
+ def index
7
+ @profiles = Profile.all
8
+ render json: { profiles: @profiles }
9
+ end
10
+
11
+ def create
12
+ service = Enrollment::CreateProfile.new
13
+ service.params = params.except(:devices)
14
+
15
+ if params[:devices]
16
+ service.devices = Device.where(serial_number: params.require[:devices])
17
+ end
18
+
19
+ service.start
20
+ render json: service.result
21
+ end
22
+
23
+ def update
24
+ @profile = Mdm::Profile.find(params[:id])
25
+
26
+ service = Mdm::Enrollment::AssignProfile.new
27
+ service.profile = @profile
28
+ service.devices = Mdm::Device.where(serial_number: params.require(:devices))
29
+ service.start
30
+
31
+ render json: service.result
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ require_dependency "mdm/application_controller"
2
+
3
+ module Mdm
4
+ class Enrollment::ServicesController < ApplicationController
5
+
6
+ def index
7
+ render json: {
8
+ services: Enrollment::Service::Base.available_services
9
+ }
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ module Mdm
2
+ class ServerController < ApplicationController
3
+
4
+ def server
5
+ # Device is waiting for command
6
+ if params[:Status] == Command::Status::IDLE
7
+ return render nothing: true if device.commands.pending.empty?
8
+ return render text: device.commands.pending.first.payload
9
+ end
10
+
11
+ # Device is updating command
12
+ command.update(status: params[:Status])
13
+
14
+ if command.complete?
15
+ payload = Plist::parse_xml(command.payload).with_indifferent_access
16
+ # Use data from `DeviceInformation` request to update the device locally
17
+ if payload[:Command][:RequestType] == 'DeviceInformation'
18
+ device.refresh!(params[:QueryResponses])
19
+ end
20
+ end
21
+
22
+ render nothing: true
23
+ end
24
+
25
+ private
26
+
27
+ def device
28
+ @device ||= begin
29
+ device = Device.find_by_udid(params[:UDID])
30
+ return render nothing: true, status: :not_found unless device
31
+ device
32
+ end
33
+ end
34
+
35
+ def command
36
+ @command ||= begin
37
+ command = Command.find_by_uuid(params[:CommandUUID])
38
+ return render nothing: true, status: :not_found unless command
39
+ command
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,4 @@
1
+ module Mdm
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,23 @@
1
+ module Mdm
2
+ class Command < ActiveRecord::Base
3
+
4
+ module Status
5
+ IDLE, ACKNOWLEDGED = ["Idle", "Acknowledged"]
6
+ end
7
+
8
+ belongs_to :device, class_name: 'Mdm::Device', foreign_key: 'mdm_device_id'
9
+
10
+ scope :pending, -> { where(status: Status::IDLE) }
11
+ scope :completed, -> { where(status: Status::ACKNOWLEDGED) }
12
+
13
+ def complete?
14
+ status == Status::ACKNOWLEDGED
15
+ end
16
+
17
+ def self.available_commands
18
+ Payload.methods.select { |method|
19
+ method.to_s.start_with? 'payload_' }
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ module Mdm
2
+ class Cursor < ActiveRecord::Base
3
+
4
+ DEVICES, SYNC = ['devices', 'sync']
5
+
6
+ validates :service,
7
+ uniqueness: true,
8
+ inclusion: { in: [DEVICES, SYNC] }
9
+
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ module Mdm
2
+ class Device < ActiveRecord::Base
3
+
4
+ has_many :commands, foreign_key: 'mdm_device_id'
5
+
6
+ has_one :profile,
7
+ primary_key: :profile_uuid,
8
+ foreign_key: :profile_uuid
9
+
10
+ def notify
11
+ apn = Houston::Client.production
12
+ apn.certificate = File.read("#{Rails.root}/certs/push.pem")
13
+
14
+ notification = Houston::Notification.new(device: push_token)
15
+ notification.custom_data = {
16
+ mdm: push_magic
17
+ }
18
+
19
+ apn.push(notification)
20
+ end
21
+
22
+ def refresh!(data = {})
23
+ update!(
24
+ name: data[:DeviceName],
25
+ udid: data[:UDID],
26
+ serial_number: data[:SerialNumber],
27
+ device_model: data[:Model],
28
+ device_model_name: data[:ModelName],
29
+ os_version: data[:OSVersion],
30
+ build_version: data[:BuildVersion],
31
+ imei: data[:IMEI],
32
+ is_roaming: data[:IsRoaming],
33
+ is_supervised: data[:IsSupervised],
34
+ device_capacity: data[:DeviceCapacity],
35
+ available_capacity: data[:AvailableDeviceCapacity]
36
+ )
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,158 @@
1
+ module Mdm
2
+ class Payload
3
+
4
+ def self.payload_device_lock(device, request_params = {})
5
+ payload('DeviceLock')
6
+ end
7
+
8
+ def self.payload_device_info(device, request_params = {})
9
+ payload('DeviceInformation', {
10
+ Queries: [
11
+ "IsSupervised",
12
+ "AvailableDeviceCapacity",
13
+ "BluetoothMAC",
14
+ "BuildVersion",
15
+ "CarrierSettingsVersion",
16
+ "CurrentCarrierNetwork",
17
+ "CurrentMCC",
18
+ "CurrentMNC",
19
+ "DataRoamingEnabled",
20
+ "DeviceCapacity",
21
+ "DeviceName",
22
+ "ICCID",
23
+ "IMEI",
24
+ "IsRoaming",
25
+ "Model",
26
+ "ModelName",
27
+ "ModemFirmwareVersion",
28
+ "OSVersion",
29
+ "PhoneNumber",
30
+ "Product",
31
+ "ProductName",
32
+ "SIMCarrierNetwork",
33
+ "SIMMCC",
34
+ "SIMMNC",
35
+ "SerialNumber",
36
+ "UDID",
37
+ "WiFiMAC",
38
+ "UDID"
39
+ ]
40
+ })
41
+ end
42
+
43
+ def self.payload_install_app(device, request_params)
44
+ params = request_params.permit(
45
+ :Options,
46
+ :ManifestURL,
47
+ :ManagementFlags,
48
+ :Configuration,
49
+ :Attributes,
50
+ :ChangeManagementState
51
+ )
52
+
53
+ payload('InstallApplication', params)
54
+ end
55
+
56
+ def self.payload_uninstall_app(device, request_params)
57
+ params = request_params.permit(:Identifier)
58
+ Rails.logger.info "params: #{params.inspect}"
59
+ payload('RemoveApplication', params)
60
+ end
61
+
62
+ def self.payload_clear_password(device, request_params)
63
+ params = request_params.permit(:device_id, :Where)
64
+ device = Device.find(params[:device_id])
65
+ payload('ClearPasscode', params.merge({
66
+ UnlockToken: StringIO.new(device.unlock_token)
67
+ }))
68
+ end
69
+
70
+ def self.payload_voice_roaming(device, request_params)
71
+ payload('Settings', {
72
+ Settings: [
73
+ {
74
+ Item: 'VoiceRoaming',
75
+ Enabled: request_params.required(:Enabled) == "true"
76
+ }
77
+ ]
78
+ })
79
+ end
80
+
81
+ def self.payload_data_roaming(device, request_params)
82
+ payload('Settings', {
83
+ Settings: [
84
+ {
85
+ Item: 'DataRoaming',
86
+ Enabled: request_params.required(:Enabled) == "true"
87
+ }
88
+ ]
89
+ })
90
+ end
91
+
92
+ def self.payload_personal_hotspot(device, request_params)
93
+ payload('Settings', {
94
+ Settings: [
95
+ {
96
+ Item: 'PersonalHotspot',
97
+ Enabled: request_params.required(:Enabled) == "true"
98
+ }
99
+ ]
100
+ })
101
+ end
102
+
103
+ def self.payload_set_wallpaper(device, request_params)
104
+ payload('Settings', {
105
+ Settings: [
106
+ {
107
+ Item: 'Wallpaper',
108
+ Image: StringIO.new(request_params.require(:Image).read),
109
+ Where: request_params[:Where]
110
+ }
111
+ ]
112
+ })
113
+ end
114
+
115
+ def self.payload_set_app_settings(device, request_params)
116
+ payload('Settings', {
117
+ Settings: [
118
+ {
119
+ Item: 'ApplicationConfiguration',
120
+ Identifier: request_params.require(:Identifier),
121
+ Configuration: request_params[:Configuration]
122
+ }
123
+ ]
124
+ })
125
+ end
126
+
127
+ def self.payload_identify(device, request_params)
128
+ params = request_params.permit(:Identifier)
129
+ payload_set_app_settings(
130
+ device,
131
+ ActionController::Parameters.new(
132
+ {
133
+ Configuration: {
134
+ device: {
135
+ udid: device.udid,
136
+ serial_number: device.serial_number || 'N/A'
137
+ }
138
+ }
139
+ }.merge(params)
140
+ )
141
+ )
142
+ end
143
+
144
+ private
145
+
146
+ def self.payload(command, params = {})
147
+ uuid = SecureRandom.uuid
148
+
149
+ [
150
+ uuid,
151
+ {
152
+ CommandUUID: uuid,
153
+ Command: { RequestType: command }.merge(params)
154
+ }.to_plist.gsub("\n", '').gsub("\t", '')
155
+ ]
156
+ end
157
+ end
158
+ end