blob: a0771e7b1d4a68213c8bae925208b294e2e9c949 [file] [log] [blame]
Matteo Scandolo6288d5a2017-08-08 13:05:26 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16
Andrea Campanellaedfdbca2017-02-01 17:33:47 -080017import requests
18import logging
19import json
20import sys
21from rest_framework.exceptions import APIException
22
23""" format of settings
24
25 ["settings"]
26 ["watershed"]
27 ["rating"]
28 ["categories"]
29 ["blocklist"]
30 ["allowlist"]
31
32 ["users"]
33 array
34 ["account_id"] - 58
35 ["reporting"] - False
36 ["name"] - Scott1
37 ["devices"]
38 ["settings"] -
39 ["watershed"]
40 ["rating"]
41 ["categories"]
42 ["blocklist"]
43 ["allowlist"]
44
45 ["devices"]
46 array
47 ["username"] - "Scott1" or "" if whole-house
48 ["uuid"] - empty
49 ["mac_address"] - mac address as hex digits in ascii
50 ["type"] - "laptop"
51 ["name"] - human readable name of device ("Scott's laptop")
52 ["settings"]
53 ["watershed"]
54 array
55 array
56 ["rating"]
57 ["category"]
58 ["rating"] - ["G" | "NONE"]
59 ["categories"] - list of categories set by rating
60 ["blocklist"] - []
61 ["allowlist"] - []
62"""
63
64class BBS_Failure(APIException):
65 status_code=400
66 def __init__(self, why="broadbandshield error", fields={}):
67 APIException.__init__(self, {"error": "BBS_Failure",
68 "specific_error": why,
69 "fields": fields})
70
71
72class BBS:
73 level_map = {"PG_13": "PG13",
74 "NONE": "OFF",
75 "ALL": "NONE",
76 None: "NONE"}
77
78 def __init__(self, username, password, bbs_hostname=None, bbs_port=None):
79 self.username = username
80 self.password = password
81
82 # XXX not tested on port 80
83 #self.bbs_hostname = "www.broadbandshield.com"
84 #self.bbs_port = 80
85
86 if not bbs_hostname:
87 bbs_hostname = "cordcompute01.onlab.us"
88 if not bbs_port:
89 bbs_port = 8018
90
91 self.bbs_hostname = bbs_hostname
92 self.bbs_port = int(bbs_port)
93
94 self.api = "http://%s:%d/api" % (self.bbs_hostname, self.bbs_port)
95 self.nic_update = "http://%s:%d/nic/update" % (self.bbs_hostname, self.bbs_port)
96
97 self.session = None
98 self.settings = None
99
100 def login(self):
101 self.session = requests.Session()
102 r = self.session.post(self.api + "/login", data = json.dumps({"email": self.username, "password": self.password}))
103 if (r.status_code != 200):
104 raise BBS_Failure("Failed to login (%d)" % r.status_code)
105
106 def get_account(self):
107 if not self.session:
108 self.login()
109
110 r = self.session.get(self.api + "/account")
111 if (r.status_code != 200):
112 raise BBS_Failure("Failed to get account settings (%d)" % r.status_code)
113 self.settings = r.json()
114
115 return self.settings
116
117 def post_account(self):
118 if not self.settings:
119 raise XOSProgrammingError("no settings to post")
120
121 r = self.session.post(self.api + "/account/settings", data= json.dumps(self.settings))
122 if (r.status_code != 200):
123 raise BBS_Failure("Failed to set account settings (%d)" % r.status_code)
124
125 def add_device(self, name, mac, type="tablet", username=""):
126 data = {"name": name, "mac_address": mac, "type": type, "username": username}
127 r = self.session.post(self.api + "/device", data = json.dumps(data))
128 if (r.status_code != 200):
129 raise BBS_Failure("Failed to add device (%d)" % r.status_code)
130
131 def delete_device(self, data):
132 r = self.session.delete(self.api + "/device", data = json.dumps(data))
133 if (r.status_code != 200):
134 raise BBS_Failure("Failed to delete device (%d)" % r.status_code)
135
136 def add_user(self, name, rating="NONE", categories=[]):
137 data = {"name": name, "settings": {"rating": rating, "categories": categories}}
138 r = self.session.post(self.api + "/users", data = json.dumps(data))
139 if (r.status_code != 200):
140 raise BBS_Failure("Failed to add user (%d)" % r.status_code)
141
142 def delete_user(self, data):
143 r = self.session.delete(self.api + "/users", data = json.dumps(data))
144 if (r.status_code != 200):
145 raise BBS_Failure("Failed to delete user (%d)" % r.status_code)
146
147 def clear_users_and_devices(self):
148 if not self.settings:
149 self.get_account()
150
151 for device in self.settings["devices"]:
152 self.delete_device(device)
153
154 for user in self.settings["users"]:
155 self.delete_user(user)
156
157 def get_whole_home_level(self):
158 if not self.settings:
159 self.get_account()
160
161 return self.settings["settings"]["rating"]
162
163 def sync(self, whole_home_level, users):
164 if not self.settings:
165 self.get_account()
166
167 veg_users = {}
168 for user in users:
169 user = user.copy()
170 user["level"] = self.level_map.get(user["level"], user["level"])
171 user["mac"] = user.get("mac", "")
172 veg_users[user["name"]] = user
173
174 whole_home_level = self.level_map.get(whole_home_level, whole_home_level)
175
176 if (whole_home_level != self.settings["settings"]["rating"]):
177 print "*** set whole_home", whole_home_level, "***"
178 self.settings["settings"]["rating"] = whole_home_level
179 self.post_account()
180
181 bbs_usernames = [bbs_user["name"] for bbs_user in self.settings["users"]]
182 bbs_devicenames = [bbs_device["name"] for bbs_device in self.settings["devices"]]
183
184 add_users = []
185 add_devices = []
186 delete_users = []
187 delete_devices = []
188
189 for bbs_user in self.settings["users"]:
190 bbs_username = bbs_user["name"]
191 if bbs_username in veg_users.keys():
192 veg_user = veg_users[bbs_username]
193 if bbs_user["settings"]["rating"] != veg_user["level"]:
194 print "set user", veg_user["name"], "rating", veg_user["level"]
195 #bbs_user["settings"]["rating"] = veg_user["level"]
196 # add can be used as an update
197 add_users.append(veg_user)
198 else:
199 delete_users.append(bbs_user)
200
201 for bbs_device in self.settings["devices"]:
202 bbs_devicename = bbs_device["name"]
203 if bbs_devicename in veg_users.keys():
204 veg_user = veg_users[bbs_devicename]
205 if bbs_device["mac_address"] != veg_user["mac"]:
206 print "set device", veg_user["name"], "mac", veg_user["mac"]
207 #bbs_device["mac_address"] = veg_user["mac"]
208 # add of a device can't be used as an update, as you'll end
209 # up with two of them.
210 delete_devices.append(bbs_device)
211 add_devices.append(veg_user)
212 else:
213 delete_devices.append(bbs_device)
214
215 for (username, user) in veg_users.iteritems():
216 if not username in bbs_usernames:
217 add_users.append(user)
218 if not username in bbs_devicenames:
219 add_devices.append(user)
220
221 for bbs_user in delete_users:
222 print "delete user", bbs_user["name"]
223 self.delete_user(bbs_user)
224
225 for bbs_device in delete_devices:
226 print "delete device", bbs_device["name"]
227 self.delete_device(bbs_device)
228
229 for veg_user in add_users:
230 print "add user", veg_user["name"], "level", veg_user["level"]
231 self.add_user(veg_user["name"], veg_user["level"])
232
233 for veg_user in add_devices:
234 print "add device", veg_user["name"], "mac", veg_user["mac"]
235 self.add_device(veg_user["name"], veg_user["mac"], "tablet", veg_user["name"])
236
237 def get_whole_home_rating(self):
238 return self.settings["settings"]["rating"]
239
240 def get_user(self, name):
241 for user in self.settings["users"]:
242 if user["name"]==name:
243 return user
244 return None
245
246 def get_device(self, name):
247 for device in self.settings["devices"]:
248 if device["name"]==name:
249 return device
250 return None
251
252 def dump(self):
253 if not self.settings:
254 self.get_account()
255
256 print "whole_home_rating:", self.settings["settings"]["rating"]
257 print "users:"
258 for user in self.settings["users"]:
259 print " user", user["name"], "rating", user["settings"]["rating"]
260
261 print "devices:"
262 for device in self.settings["devices"]:
263 print " device", device["name"], "user", device["username"], "rating", device["settings"]["rating"], "mac", device["mac_address"]
264
265 def associate(self, ip):
266 bbs_hostname = "cordcompute01.onlab.us"
267 r = requests.get(self.nic_update, params={"hostname": "onlab.us"}, headers={"X-Forwarded-For": ip}, auth=requests.auth.HTTPBasicAuth(self.username,self.password))
268 if (r.status_code != 200):
269 raise BBS_Failure("Failed to associate account with ip (%d)" % r.status_code)
270
271def dump():
272 bbs = BBS(sys.argv[2], sys.argv[3])
273 bbs.dump()
274
275def associate():
276 if len(sys.argv)<5:
277 print "you need to specify IP address"
278 sys.exit(-1)
279
280 bbs = BBS(sys.argv[2], sys.argv[3])
281 bbs.associate(sys.argv[4])
282
283def self_test():
284 bbs = BBS(sys.argv[2], sys.argv[3])
285
286 print "*** initial ***"
287 bbs.dump()
288
289 open("bbs.json","w").write(json.dumps(bbs.settings))
290
291 # a new BBS account will throw a 500 error if it has no rating
292 bbs.settings["settings"]["rating"] = "R"
293 #bbs.settings["settings"]["category"] = [u'PORNOGRAPHY', u'ADULT', u'ILLEGAL', u'WEAPONS', u'DRUGS', u'GAMBLING', u'CYBERBULLY', u'ANONYMIZERS', u'SUICIDE', u'MALWARE']
294 #bbs.settings["settings"]["blocklist"] = []
295 #bbs.settings["settings"]["allowlist"] = []
296 #for water in bbs.settings["settings"]["watershed"];
297 # water["categories"]=[]
298 # delete everything
299 bbs.post_account()
300 bbs.clear_users_and_devices()
301
302 print "*** cleared ***"
303 bbs.settings=None
304 bbs.dump()
305
306 users = [{"name": "Moms pc", "level": "R", "mac": "010203040506"},
307 {"name": "Dads pc", "level": "R", "mac": "010203040507"},
308 {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"},
309 {"name": "Jills iphone", "level": "G", "mac": "010203040509"}]
310
311 print "*** syncing mom-R, Dad-R, jack-PG, Jill-G, wholehome-PG-13 ***"
312
313 bbs.settings = None
314 bbs.sync("PG-13", users)
315
316 print "*** after sync ***"
317 bbs.settings=None
318 bbs.dump()
319 assert(bbs.get_whole_home_rating() == "PG-13")
320 assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R")
321 assert(bbs.get_user("Dads pc")["settings"]["rating"] == "R")
322 assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG")
323 assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "G")
324 assert(bbs.get_device("Moms pc")["mac_address"] == "010203040506")
325 assert(bbs.get_device("Dads pc")["mac_address"] == "010203040507")
326 assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508")
327 assert(bbs.get_device("Jills iphone")["mac_address"] == "010203040509")
328
329 print "*** update whole home level ***"
330 bbs.settings=None
331 bbs.get_account()
332 bbs.settings["settings"]["rating"] = "PG"
333 bbs.post_account()
334
335 print "*** after sync ***"
336 bbs.settings=None
337 bbs.dump()
338 assert(bbs.get_whole_home_rating() == "PG")
339 assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R")
340 assert(bbs.get_user("Dads pc")["settings"]["rating"] == "R")
341 assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG")
342 assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "G")
343 assert(bbs.get_device("Moms pc")["mac_address"] == "010203040506")
344 assert(bbs.get_device("Dads pc")["mac_address"] == "010203040507")
345 assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508")
346 assert(bbs.get_device("Jills iphone")["mac_address"] == "010203040509")
347
348 print "*** delete dad, change moms IP, change jills level to PG, change whole home to PG-13 ***"
349 users = [{"name": "Moms pc", "level": "R", "mac": "010203040511"},
350 {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"},
351 {"name": "Jills iphone", "level": "PG", "mac": "010203040509"}]
352
353 bbs.settings = None
354 bbs.sync("PG-13", users)
355
356 print "*** after sync ***"
357 bbs.settings=None
358 bbs.dump()
359 assert(bbs.get_whole_home_rating() == "PG-13")
360 assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R")
361 assert(bbs.get_user("Dads pc") == None)
362 assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG")
363 assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "PG")
364 assert(bbs.get_device("Moms pc")["mac_address"] == "010203040511")
365 assert(bbs.get_device("Dads pc") == None)
366 assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508")
367
368 print "add dad's laptop"
369 users = [{"name": "Moms pc", "level": "R", "mac": "010203040511"},
370 {"name": "Dads laptop", "level": "PG-13", "mac": "010203040512"},
371 {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"},
372 {"name": "Jills iphone", "level": "PG", "mac": "010203040509"}]
373
374 bbs.settings = None
375 bbs.sync("PG-13", users)
376
377 print "*** after sync ***"
378 bbs.settings=None
379 bbs.dump()
380 assert(bbs.get_whole_home_rating() == "PG-13")
381 assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R")
382 assert(bbs.get_user("Dads pc") == None)
383 assert(bbs.get_user("Dads laptop")["settings"]["rating"] == "PG-13")
384 assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG")
385 assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "PG")
386 assert(bbs.get_device("Moms pc")["mac_address"] == "010203040511")
387 assert(bbs.get_device("Dads pc") == None)
388 assert(bbs.get_device("Dads laptop")["mac_address"] == "010203040512")
389 assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508")
390
391 #bbs.add_user("tom", "G", [u'PORNOGRAPHY', u'ADULT', u'ILLEGAL', u'WEAPONS', u'DRUGS', u'GAMBLING', u'SOCIAL', u'CYBERBULLY', u'GAMES', u'ANONYMIZERS', u'SUICIDE', u'MALWARE'])
392 #bbs.add_device(name="tom's iphone", mac="010203040506", type="tablet", username="tom")
393
394def main():
395 if len(sys.argv)<4:
396 print "syntax: broadbandshield.py <operation> <email> <password>"
397 print " operation = [dump | selftest | assocate"
398 sys.exit(-1)
399
400 operation = sys.argv[1]
401
402 if operation=="dump":
403 dump()
404 elif operation=="selftest":
405 self_test()
406 elif operation=="associate":
407 associate()
408
409if __name__ == "__main__":
410 main()
411
412