· 6 years ago · Feb 18, 2020, 09:34 AM
1
2
3class DPCRequest(GenericAPIView):
4 """Common class for DPC requests"""
5
6 def __init__(self):
7 super().__init__()
8 self.device = None
9 self.agent_type = None
10 self.device_datetime = None
11
12 @classmethod
13 def check_request(cls, request, required_fields):
14 """Check if the request contains all required field"""
15 for field in required_fields:
16 if field not in request.data:
17 raise ParseError('field {} is missing'.format(field))
18
19 @classmethod
20 def is_uuid(cls, string):
21 """Check if string is uuid"""
22 try:
23 uuid.UUID(string)
24 return True
25 except ValueError:
26 return False
27
28 def get_device(self, device_id):
29 """Get device from database"""
30 if self.is_uuid(device_id):
31 # ЗАМЕНИТЬ НА
32 self.device = Device.objects.filter(device_id=device_id).first()
33 else:
34 self.device = Device.objects.filter(reported_id=device_id).first()
35 if self.device is None:
36 raise NotFound("device not found")
37
38 # Check if the device is blacklisted
39 if self.device.device_status == DeviceStatus.blacklisted.name:
40 raise PermissionDenied('device is blacklisted')
41
42 def check_token(self, request):
43 """Check device auth token"""
44 if "HTTP_AUTHORIZATION" not in request.META:
45 send_log("", self.device.device_id, self.device.company_id, "Request: device token required")
46 raise PermissionDenied("device token required")
47
48 token = request.META.get("HTTP_AUTHORIZATION").split(" ")
49
50 if self.device.security is None or "token" not in self.device.security:
51 send_log("", self.device.device_id, self.device.company_id, "Request: enrollment required")
52 raise APIException("enrollment required")
53 elif len(token) != 2 or token[1] != self.device.security["token"]:
54 send_log("", self.device.device_id, self.device.company_id, "Request: token incorrect")
55 raise PermissionDenied("token incorrect")
56
57 def check_agent_version(self, request):
58 """Check DPC version"""
59 self.agent_type = request.META['HTTP_DEVICE_AGENT_TYPE'] \
60 if 'HTTP_DEVICE_AGENT_TYPE' in request.META else AgentType.common.name
61 if self.agent_type == AgentType.common.name and self.device.manufacturer and self.device.model and \
62 DeviceAgent.objects.filter(agent_type='.'.join((self.device.manufacturer, self.device.model))).exists():
63 self.agent_type = '.'.join((self.device.manufacturer, self.device.model))
64
65 device_agent = DeviceAgent.objects.filter(agent_type=self.agent_type).order_by('agent_code', 'uploaded').last()
66 if device_agent is None:
67 raise PermissionDenied('DPC uses an unsupported agent type')
68
69 version_code = 0
70 ignore_upgrade = False
71 if 'HTTP_DEVICE_AGENT_CODE' in request.META:
72 version_code = int(request.META['HTTP_DEVICE_AGENT_CODE'])
73 self.device.refresh_from_db()
74 self.device.custom_attrs['latest_agent'] = device_agent.agent_code <= version_code
75 Device.objects.filter(pk=self.device.pk). \
76 update(custom_attrs=self.device.custom_attrs, last_updated=datetime.datetime.now(tzutc()))
77 if 'HTTP_IGNORE_UPGRADE' in request.META:
78 ignore_upgrade = request.META['HTTP_IGNORE_UPGRADE'] == 'true'
79 if device_agent.threshold > version_code or (device_agent.agent_code > version_code and not ignore_upgrade):
80 return {
81 'message': 'Newer Device agent version available',
82 'detail': DeviceAgentSerializer(device_agent).data,
83 'key': device_agent.location
84 }
85 if self.agent_type == AgentType.common.name and self.device.manufacturer and self.device.model and \
86 Application.objects.filter(
87 device_type='.'.join((self.device.manufacturer, self.device.model))).exists():
88 self.agent_type = '.'.join((self.device.manufacturer, self.device.model))
89 return None
90
91 def check_company_license(self):
92 """Check the company license"""
93 company_license = None
94 if self.device.company_license_id is not None:
95 company_license = CompanyLicense.objects.filter(
96 company_license_id=self.device.company_license_id,
97 company_id=self.device.company_id).first()
98
99 if company_license is not None and \
100 (company_license.license_max_qty == 0 or
101 company_license.license_in_use > company_license.license_max_qty):
102 CompanyLicense.objects.filter(pk=company_license.pk). \
103 update(license_in_use=F('license_in_use') - 1, last_updated=datetime.datetime.now(tzutc()))
104 Device.objects.filter(pk=self.device.pk). \
105 update(company_license_id=None, last_updated=datetime.datetime.now(tzutc()))
106 company_license = None
107
108 if company_license is None:
109 company_license = CompanyLicense.objects.filter(
110 company_id=self.device.company_id,
111 license_in_use__lt=F('license_max_qty'),
112 license_max_qty__gt=0).order_by('license_expiry').first()
113
114 if company_license is not None:
115 self.device.company_license_id = company_license.company_license_id
116 company_license.license_in_use = company_license.license_in_use + 1
117 company_license.save()
118 else:
119 send_log("updateDevice", self.device.device_id, self.device.company_id, "Response: license exceeded")
120 raise PermissionDenied("license exceeded")
121
122 @classmethod
123 def in_geofence(cls, device_location, polygon_coords):
124 """Check if the device is inside the geofence"""
125
126 result = False
127 x = device_location[0]
128 y = device_location[1]
129 for i in range(len(polygon_coords)):
130 xp = polygon_coords[i][0]
131 yp = polygon_coords[i][1]
132 xp_prev = polygon_coords[i - 1][0]
133 yp_prev = polygon_coords[i - 1][1]
134 if (((yp <= y < yp_prev) or (yp_prev <= y < yp)) and
135 (x > (xp_prev - xp) * (y - yp) / (yp_prev - yp) + xp)):
136 result = not result
137 return result
138
139 def add_command(self, command_type, command_details):
140 """Create new command for the device"""
141 self.device.refresh_from_db()
142 commands = Command.objects.filter(pk__in=self.device.custom_attrs['commands'])
143 if command_type in (CommandType.reboot, CommandType.wipe, CommandType.shutdown, CommandType.geofence_violation)\
144 and commands.filter(command_type=command_type.name).exists():
145 return
146 if command_type in (CommandType.download_app, CommandType.remove_app) and commands.filter(
147 command_type=command_type.name, command_details__app_id=command_details['app_id']).exists():
148 return
149 command = Command(
150 device_id=self.device.pk,
151 company_id=self.device.company_id,
152 command_type=command_type.name,
153 command_details=command_details)
154 command.save()
155 self.device.refresh_from_db()
156 self.device.custom_attrs['commands'].append(str(command.pk))
157 Device.objects.filter(pk=self.device.pk). \
158 update(custom_attrs=self.device.custom_attrs, last_updated=datetime.datetime.now(tzutc()))
159 if command_type == CommandType.download_app:
160 # Write app statistics
161 app = Application.objects.filter(pk=command_details['app_id']).first()
162 if app is not None and app.app_category == AppCategory.customer_app.name:
163 aws_key = '{}{}/{}/{}/statistics_1.json'.format(
164 settings.AWS_APK_PATH,
165 self.device.company_id,
166 app.app_id_name,
167 app.app_version_code
168 )
169 bucket = boto3.resource(
170 's3',
171 aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
172 aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY)\
173 .Bucket(settings.AWS_BUCKET)
174
175 # Create json file if it does not exist
176 if not list(bucket.objects.filter(Prefix=aws_key)):
177 app_data = {
178 'devices_installed': [],
179 'devices_failed': [],
180 }
181 else:
182 with TemporaryFile() as temp_file:
183 bucket.download_fileobj(aws_key, temp_file)
184 temp_file.seek(0)
185 app_data = json.loads(temp_file.read().decode('utf-8'))
186
187 if str(self.device.pk) not in app_data['devices_failed']:
188 app_data['devices_failed'].append(str(self.device.pk))
189 if str(self.device.pk) in app_data['devices_installed']:
190 app_data['devices_installed'].remove(str(self.device.pk))
191 bucket.put_object(Body=json.dumps(app_data).encode(), Key=aws_key)
192
193 def update_profiles(self):
194 """True if profiles need to be updated"""
195
196 device_profiles = self.device.custom_attrs['profiles']
197 server_profiles = []
198
199 # Find new/updated profiles
200
201 # Check profiles of the device
202 profiles_query = Profile.objects.filter(
203 company_id=self.device.company_id,
204 latest_version=True,
205 devices__contains=str(self.device.device_id))
206
207 for profile in profiles_query:
208 server_profiles.append(str(profile.parent_id))
209 if str(profile.pk) not in device_profiles and self.allowed_profile(profile=profile):
210 return True
211
212 # Check profiles of the fleets
213 fleet = FleetTree.objects.filter(pk=self.device.fleet_id).first()
214 while fleet is not None:
215 profiles_query = Profile.objects.filter(
216 company_id=self.device.company_id,
217 latest_version=True,
218 fleets__contains=str(fleet.pk))
219
220 for profile in profiles_query:
221 server_profiles.append(str(profile.parent_id))
222 if str(profile.pk) not in device_profiles and self.allowed_profile(profile=profile):
223 return True
224
225 fleet = FleetTree.objects.filter(pk=fleet.parent_id).first()
226
227 # Find deleted/unassigned profiles
228
229 for profile_id in device_profiles:
230 if not Profile.objects.filter(pk=profile_id, parent_id__in=server_profiles).exists():
231 return True
232
233 return False
234
235 def get_profiles(self, only_new=True):
236 """
237 Retrieve profiles to apply
238 If device fleet has been changed then update the profiles
239 Otherwise check if some profiles have been updated or added since the last checkin or device update requests
240 """
241
242 if not only_new or self.update_profiles():
243 device_profiles = self.device.custom_attrs['profiles']
244 profiles_data = []
245
246 # Get profiles of the device
247 profiles_query = Profile.objects.filter(
248 company_id=self.device.company_id,
249 latest_version=True,
250 devices__contains=str(self.device.device_id)). \
251 order_by('-last_updated')
252
253 for profile in profiles_query:
254 if next((profile_search for profile_search in profiles_data
255 if profile_search['profile_id'] == str(profile.pk)), None) is not None:
256 continue
257 if self.allowed_profile(profile=profile):
258 policies = Policy.objects.filter(profile_id=profile.pk)
259 profile.policies = PolicySerializer(policies, many=True).data
260 profiles_data.insert(0, ProfileSerializer(profile).data)
261 else:
262 device_profile = Profile.objects.filter(pk__in=device_profiles, parent_id=profile.parent_id).first()
263 if device_profile is not None and \
264 next((profile_search for profile_search in profiles_data
265 if profile_search['profile_id'] == str(device_profile.pk)), None) is None:
266 policies = Policy.objects.filter(profile_id=device_profile.pk)
267 device_profile.policies = PolicySerializer(policies, many=True).data
268 profiles_data.insert(0, ProfileSerializer(device_profile).data)
269
270 # Get profiles of the fleets
271
272 fleet = FleetTree.objects.filter(pk=self.device.fleet_id).first()
273 while fleet is not None:
274 profiles_query = Profile.objects.filter(
275 company_id=self.device.company_id,
276 latest_version=True,
277 fleets__contains=str(fleet.pk)). \
278 order_by('-last_updated')
279
280 for profile in profiles_query:
281 if next((profile_search for profile_search in profiles_data
282 if profile_search['profile_id'] == str(profile.pk)), None) is not None:
283 continue
284 if self.allowed_profile(profile=profile):
285 policies = Policy.objects.filter(profile_id=profile.pk)
286 profile.policies = PolicySerializer(policies, many=True).data
287 profiles_data.insert(0, ProfileSerializer(profile).data)
288 else:
289 device_profile = Profile.objects.filter(
290 pk__in=device_profiles, parent_id=profile.parent_id).first()
291 if device_profile is not None and \
292 next((profile_search for profile_search in profiles_data
293 if profile_search['profile_id'] == str(device_profile.pk)), None) is None:
294 policies = Policy.objects.filter(profile_id=device_profile.pk)
295 device_profile.policies = PolicySerializer(policies, many=True).data
296 profiles_data.insert(0, ProfileSerializer(device_profile).data)
297
298 fleet = FleetTree.objects.filter(pk=fleet.parent_id).first()
299
300 return profiles_data
301
302 return None
303
304 def allowed_profile(self, profile):
305 """returns True (profile allowed) or False (profile disallowed)"""
306
307 # profile.datetime_offset == None -> device local datetime
308
309 if profile.datetime_offset is None:
310 current_datetime = self.device_datetime
311 else:
312 current_datetime = datetime.datetime.now(tzutc()) + \
313 datetime.timedelta(seconds=profile.datetime_offset['offset'])
314 begin_date = profile.begin_date
315 begin_time = profile.begin_time
316 end_time = profile.end_time
317 current_date = current_datetime.date()
318 current_time = current_datetime.time()
319
320 if begin_date is not None:
321 if current_date < begin_date:
322 return False
323
324 if current_date == begin_date and \
325 current_time > begin_time and \
326 (end_time is None or begin_time > end_time or current_time < end_time):
327 return True
328 if current_date > begin_date:
329 if end_time is None:
330 return True
331 if (begin_time < current_time < end_time) or \
332 (begin_time > end_time and
333 (current_time > begin_time or current_time < end_time)):
334 return True
335 else:
336 if begin_time is not None and end_time is not None:
337 if (begin_time < current_time < end_time) or \
338 (begin_time > end_time and
339 (current_time > begin_time or current_time < end_time)):
340 return True
341 else:
342 return True
343 return False
344
345
346
347
348
349
350
351
352
353
354class CheckInDeviceV2(DPCRequest):
355 """API URL: /v2/device/checkin/"""
356 authentication_classes = ()
357
358 def post(self, request):
359 """Check-in for devices using device commands queue"""
360 # ПРОВЕРЯЕМ ЗАПРОС, ЕСТЬ ЛИ ПОЛЯ ТАМ, КОТОРЫЕ ЕСТЬ В ПЕРЕДАННОМ СПИСКЕ
361 self.check_request(request=request, required_fields=['device_id', 'company_id', 'fleet_id'])
362 device_id = request.data["device_id"]
363 company_id = request.data["company_id"]
364 fleet_id = request.data["fleet_id"]
365 current_date_time = datetime.datetime.now(tzutc())
366
367 self.device_datetime = datetime.datetime.strptime(request.data['datetime'], "%Y-%m-%dT%H:%M:%S") \
368 if 'datetime' in request.data else current_date_time
369
370 # QUERY
371 # ПОЛУЧАЕМ ДЕВАЙС ИЗ БАЗЫ ФИЛЬТЕР ЗАПРОСОМ
372 # 1 ЗАПРОС В БАЗУ
373 self.get_device(device_id=device_id)
374 # ТУТ МЫ ПОЛУЧАЕМ ОБЪЕКТ ДЕВАЙСА
375 # ПОСЛЕ ЭТОГО МЕТОДА ОБЪЕКТ ДЕВАЙСА ЛЕЖИТ в self.device
376
377 # ПРОВЕРЯЕМ ТОКЕН В ЗАПРОСЕ
378 self.check_token(request=request)
379
380
381
382 self.check_company_license()
383
384 send_log("checkin_v2", self.device.device_id, self.device.company_id, "Request: " + str(request.data))
385
386 device_log(self.device.company_id, self.device.device_id, 'stat', 'AD', str(self.device.device_id))
387 if self.device.device_status != "active":
388 # QUERY
389 Device.objects.filter(pk=self.device.pk). \
390 update(device_status='active', last_updated=datetime.datetime.now(tzutc()))
391 device_log(self.device.company_id, self.device.device_id, 'info', 'admin', 'Device is active')
392
393 # QUERY
394 # Check if the device has latest version of Springdel device agent
395 check_response = self.check_agent_version(request=request)
396 if check_response:
397 return Response(check_response, status=status.HTTP_200_OK)
398
399 self.device.refresh_from_db()
400 # QUERY
401 if 'commands' not in self.device.custom_attrs:
402 self.device.custom_attrs['commands'] = []
403 Device.objects.filter(pk=self.device.pk). \
404 update(custom_attrs=self.device.custom_attrs, last_updated=datetime.datetime.now(tzutc()))
405 # QUERY
406 # Checks if the device fleet has been changed
407 fleet_changed = str(self.device.fleet_id) != fleet_id
408 if fleet_changed:
409 fleet_obj = FleetTree.objects.get(pk=self.device.fleet_id)
410 device_log(self.device.company_id, self.device.device_id, 'info', 'admin',
411 'Device fleet has been changed to ' + fleet_obj.fleet_name)
412 # QUERY
413 profiles_data = None
414 if not self.device.pause_mode:
415 profiles_data = self.get_profiles(only_new=not fleet_changed)
416 # QUERY
417 if 'latitude' in request.data and 'longitude' in request.data:
418 Device.objects.filter(pk=self.device.pk). \
419 update(latitude=request.data['latitude'], longitude=request.data['longitude'],
420 last_updated=datetime.datetime.now(tzutc()))
421 self.device.refresh_from_db()
422
423 if self.device.longitude is not None and self.device.latitude is not None:
424 device_in_fence = None
425 self.device.refresh_from_db()
426 for profile_id in self.device.custom_attrs['profiles']:
427 geofence_policies = Policy.objects.filter(
428 profile_id=profile_id,
429 policy_type=PolicyType.geofencing.name,
430 policy_settings__active=True)
431 if geofence_policies.exists():
432 device_in_fence = False
433
434 point = transform(Proj(init='epsg:4326'), Proj(init='epsg:3857'),
435 self.device.longitude, self.device.latitude)
436
437 for policy in geofence_policies:
438 fence = json.loads(policy.policy_settings['fence'])
439 polygon = fence['features'][0]['geometry']['coordinates'][0]
440 point = (point[0] + 40_000_000 * ((polygon[0][0] // 40_000_000) + (polygon[0][0] < 0)),
441 point[1])
442 for it in range(-1, 2):
443 device_in_fence = self.in_geofence((point[0] + 40_000_000 * it, point[1]), polygon)
444 if device_in_fence:
445 break
446 if device_in_fence:
447 break
448 if device_in_fence:
449 break
450 if device_in_fence is not None:
451 if not device_in_fence:
452 if 'in_fence' not in self.device.custom_attrs or self.device.custom_attrs['in_fence'] is not False:
453 device_log(self.device.company_id, self.device.device_id, 'info', 'admin',
454 'Device outside the geofence')
455 self.add_command(
456 command_type=CommandType.geofence_violation,
457 command_details={})
458 self.device.custom_attrs['in_fence'] = False
459 else:
460 if 'in_fence' in self.device.custom_attrs and self.device.custom_attrs['in_fence'] is False:
461 device_log(self.device.company_id, self.device.device_id, 'info', 'admin',
462 'Device inside the geofence')
463 self.device.custom_attrs['in_fence'] = True
464 else:
465 self.device.custom_attrs['in_fence'] = None
466 Device.objects.filter(pk=self.device.pk). \
467 update(custom_attrs=self.device.custom_attrs, last_updated=datetime.datetime.now(tzutc()))
468
469 # Receives all commands assigned to the device QUERY
470 commands_query = Command.objects.filter(pk__in=self.device.custom_attrs['commands']).order_by('last_updated')
471 commands = CommandSerializer(commands_query, many=True).data
472
473 Device.objects.filter(pk=self.device.pk). \
474 update(last_checkin=current_date_time, last_updated=datetime.datetime.now(tzutc()))
475
476 response_data = {
477 'fleet_id': str(self.device.fleet_id),
478 'commands': commands,
479 'profiles': profiles_data,
480 'device_check_in_period': self.device.company.device_checkin_period
481 }
482
483 send_log("checkin", self.device.device_id, self.device.company_id, "Response: " + str(response_data))
484
485 send_log("checkin", self.device.device_id, self.device.company_id, "Success")
486 if profiles_data:
487 send_log("profiles_sent", self.device.device_id, self.device.company_id, len(profiles_data))
488 device_log(self.device.company_id, self.device.device_id, 'stat', 'PI', len(profiles_data))
489
490 return Response(response_data, status=status.HTTP_200_OK)