mdm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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