| |
| # Copyright 2017-present Open Networking Foundation |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| |
| import requests |
| import logging |
| import json |
| import sys |
| from rest_framework.exceptions import APIException |
| |
| """ format of settings |
| |
| ["settings"] |
| ["watershed"] |
| ["rating"] |
| ["categories"] |
| ["blocklist"] |
| ["allowlist"] |
| |
| ["users"] |
| array |
| ["account_id"] - 58 |
| ["reporting"] - False |
| ["name"] - Scott1 |
| ["devices"] |
| ["settings"] - |
| ["watershed"] |
| ["rating"] |
| ["categories"] |
| ["blocklist"] |
| ["allowlist"] |
| |
| ["devices"] |
| array |
| ["username"] - "Scott1" or "" if whole-house |
| ["uuid"] - empty |
| ["mac_address"] - mac address as hex digits in ascii |
| ["type"] - "laptop" |
| ["name"] - human readable name of device ("Scott's laptop") |
| ["settings"] |
| ["watershed"] |
| array |
| array |
| ["rating"] |
| ["category"] |
| ["rating"] - ["G" | "NONE"] |
| ["categories"] - list of categories set by rating |
| ["blocklist"] - [] |
| ["allowlist"] - [] |
| """ |
| |
| class BBS_Failure(APIException): |
| status_code=400 |
| def __init__(self, why="broadbandshield error", fields={}): |
| APIException.__init__(self, {"error": "BBS_Failure", |
| "specific_error": why, |
| "fields": fields}) |
| |
| |
| class BBS: |
| level_map = {"PG_13": "PG13", |
| "NONE": "OFF", |
| "ALL": "NONE", |
| None: "NONE"} |
| |
| def __init__(self, username, password, bbs_hostname=None, bbs_port=None): |
| self.username = username |
| self.password = password |
| |
| # XXX not tested on port 80 |
| #self.bbs_hostname = "www.broadbandshield.com" |
| #self.bbs_port = 80 |
| |
| if not bbs_hostname: |
| bbs_hostname = "cordcompute01.onlab.us" |
| if not bbs_port: |
| bbs_port = 8018 |
| |
| self.bbs_hostname = bbs_hostname |
| self.bbs_port = int(bbs_port) |
| |
| self.api = "http://%s:%d/api" % (self.bbs_hostname, self.bbs_port) |
| self.nic_update = "http://%s:%d/nic/update" % (self.bbs_hostname, self.bbs_port) |
| |
| self.session = None |
| self.settings = None |
| |
| def login(self): |
| self.session = requests.Session() |
| r = self.session.post(self.api + "/login", data = json.dumps({"email": self.username, "password": self.password})) |
| if (r.status_code != 200): |
| raise BBS_Failure("Failed to login (%d)" % r.status_code) |
| |
| def get_account(self): |
| if not self.session: |
| self.login() |
| |
| r = self.session.get(self.api + "/account") |
| if (r.status_code != 200): |
| raise BBS_Failure("Failed to get account settings (%d)" % r.status_code) |
| self.settings = r.json() |
| |
| return self.settings |
| |
| def post_account(self): |
| if not self.settings: |
| raise XOSProgrammingError("no settings to post") |
| |
| r = self.session.post(self.api + "/account/settings", data= json.dumps(self.settings)) |
| if (r.status_code != 200): |
| raise BBS_Failure("Failed to set account settings (%d)" % r.status_code) |
| |
| def add_device(self, name, mac, type="tablet", username=""): |
| data = {"name": name, "mac_address": mac, "type": type, "username": username} |
| r = self.session.post(self.api + "/device", data = json.dumps(data)) |
| if (r.status_code != 200): |
| raise BBS_Failure("Failed to add device (%d)" % r.status_code) |
| |
| def delete_device(self, data): |
| r = self.session.delete(self.api + "/device", data = json.dumps(data)) |
| if (r.status_code != 200): |
| raise BBS_Failure("Failed to delete device (%d)" % r.status_code) |
| |
| def add_user(self, name, rating="NONE", categories=[]): |
| data = {"name": name, "settings": {"rating": rating, "categories": categories}} |
| r = self.session.post(self.api + "/users", data = json.dumps(data)) |
| if (r.status_code != 200): |
| raise BBS_Failure("Failed to add user (%d)" % r.status_code) |
| |
| def delete_user(self, data): |
| r = self.session.delete(self.api + "/users", data = json.dumps(data)) |
| if (r.status_code != 200): |
| raise BBS_Failure("Failed to delete user (%d)" % r.status_code) |
| |
| def clear_users_and_devices(self): |
| if not self.settings: |
| self.get_account() |
| |
| for device in self.settings["devices"]: |
| self.delete_device(device) |
| |
| for user in self.settings["users"]: |
| self.delete_user(user) |
| |
| def get_whole_home_level(self): |
| if not self.settings: |
| self.get_account() |
| |
| return self.settings["settings"]["rating"] |
| |
| def sync(self, whole_home_level, users): |
| if not self.settings: |
| self.get_account() |
| |
| veg_users = {} |
| for user in users: |
| user = user.copy() |
| user["level"] = self.level_map.get(user["level"], user["level"]) |
| user["mac"] = user.get("mac", "") |
| veg_users[user["name"]] = user |
| |
| whole_home_level = self.level_map.get(whole_home_level, whole_home_level) |
| |
| if (whole_home_level != self.settings["settings"]["rating"]): |
| print "*** set whole_home", whole_home_level, "***" |
| self.settings["settings"]["rating"] = whole_home_level |
| self.post_account() |
| |
| bbs_usernames = [bbs_user["name"] for bbs_user in self.settings["users"]] |
| bbs_devicenames = [bbs_device["name"] for bbs_device in self.settings["devices"]] |
| |
| add_users = [] |
| add_devices = [] |
| delete_users = [] |
| delete_devices = [] |
| |
| for bbs_user in self.settings["users"]: |
| bbs_username = bbs_user["name"] |
| if bbs_username in veg_users.keys(): |
| veg_user = veg_users[bbs_username] |
| if bbs_user["settings"]["rating"] != veg_user["level"]: |
| print "set user", veg_user["name"], "rating", veg_user["level"] |
| #bbs_user["settings"]["rating"] = veg_user["level"] |
| # add can be used as an update |
| add_users.append(veg_user) |
| else: |
| delete_users.append(bbs_user) |
| |
| for bbs_device in self.settings["devices"]: |
| bbs_devicename = bbs_device["name"] |
| if bbs_devicename in veg_users.keys(): |
| veg_user = veg_users[bbs_devicename] |
| if bbs_device["mac_address"] != veg_user["mac"]: |
| print "set device", veg_user["name"], "mac", veg_user["mac"] |
| #bbs_device["mac_address"] = veg_user["mac"] |
| # add of a device can't be used as an update, as you'll end |
| # up with two of them. |
| delete_devices.append(bbs_device) |
| add_devices.append(veg_user) |
| else: |
| delete_devices.append(bbs_device) |
| |
| for (username, user) in veg_users.iteritems(): |
| if not username in bbs_usernames: |
| add_users.append(user) |
| if not username in bbs_devicenames: |
| add_devices.append(user) |
| |
| for bbs_user in delete_users: |
| print "delete user", bbs_user["name"] |
| self.delete_user(bbs_user) |
| |
| for bbs_device in delete_devices: |
| print "delete device", bbs_device["name"] |
| self.delete_device(bbs_device) |
| |
| for veg_user in add_users: |
| print "add user", veg_user["name"], "level", veg_user["level"] |
| self.add_user(veg_user["name"], veg_user["level"]) |
| |
| for veg_user in add_devices: |
| print "add device", veg_user["name"], "mac", veg_user["mac"] |
| self.add_device(veg_user["name"], veg_user["mac"], "tablet", veg_user["name"]) |
| |
| def get_whole_home_rating(self): |
| return self.settings["settings"]["rating"] |
| |
| def get_user(self, name): |
| for user in self.settings["users"]: |
| if user["name"]==name: |
| return user |
| return None |
| |
| def get_device(self, name): |
| for device in self.settings["devices"]: |
| if device["name"]==name: |
| return device |
| return None |
| |
| def dump(self): |
| if not self.settings: |
| self.get_account() |
| |
| print "whole_home_rating:", self.settings["settings"]["rating"] |
| print "users:" |
| for user in self.settings["users"]: |
| print " user", user["name"], "rating", user["settings"]["rating"] |
| |
| print "devices:" |
| for device in self.settings["devices"]: |
| print " device", device["name"], "user", device["username"], "rating", device["settings"]["rating"], "mac", device["mac_address"] |
| |
| def associate(self, ip): |
| bbs_hostname = "cordcompute01.onlab.us" |
| r = requests.get(self.nic_update, params={"hostname": "onlab.us"}, headers={"X-Forwarded-For": ip}, auth=requests.auth.HTTPBasicAuth(self.username,self.password)) |
| if (r.status_code != 200): |
| raise BBS_Failure("Failed to associate account with ip (%d)" % r.status_code) |
| |
| def dump(): |
| bbs = BBS(sys.argv[2], sys.argv[3]) |
| bbs.dump() |
| |
| def associate(): |
| if len(sys.argv)<5: |
| print "you need to specify IP address" |
| sys.exit(-1) |
| |
| bbs = BBS(sys.argv[2], sys.argv[3]) |
| bbs.associate(sys.argv[4]) |
| |
| def self_test(): |
| bbs = BBS(sys.argv[2], sys.argv[3]) |
| |
| print "*** initial ***" |
| bbs.dump() |
| |
| open("bbs.json","w").write(json.dumps(bbs.settings)) |
| |
| # a new BBS account will throw a 500 error if it has no rating |
| bbs.settings["settings"]["rating"] = "R" |
| #bbs.settings["settings"]["category"] = [u'PORNOGRAPHY', u'ADULT', u'ILLEGAL', u'WEAPONS', u'DRUGS', u'GAMBLING', u'CYBERBULLY', u'ANONYMIZERS', u'SUICIDE', u'MALWARE'] |
| #bbs.settings["settings"]["blocklist"] = [] |
| #bbs.settings["settings"]["allowlist"] = [] |
| #for water in bbs.settings["settings"]["watershed"]; |
| # water["categories"]=[] |
| # delete everything |
| bbs.post_account() |
| bbs.clear_users_and_devices() |
| |
| print "*** cleared ***" |
| bbs.settings=None |
| bbs.dump() |
| |
| users = [{"name": "Moms pc", "level": "R", "mac": "010203040506"}, |
| {"name": "Dads pc", "level": "R", "mac": "010203040507"}, |
| {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"}, |
| {"name": "Jills iphone", "level": "G", "mac": "010203040509"}] |
| |
| print "*** syncing mom-R, Dad-R, jack-PG, Jill-G, wholehome-PG-13 ***" |
| |
| bbs.settings = None |
| bbs.sync("PG-13", users) |
| |
| print "*** after sync ***" |
| bbs.settings=None |
| bbs.dump() |
| assert(bbs.get_whole_home_rating() == "PG-13") |
| assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R") |
| assert(bbs.get_user("Dads pc")["settings"]["rating"] == "R") |
| assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG") |
| assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "G") |
| assert(bbs.get_device("Moms pc")["mac_address"] == "010203040506") |
| assert(bbs.get_device("Dads pc")["mac_address"] == "010203040507") |
| assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508") |
| assert(bbs.get_device("Jills iphone")["mac_address"] == "010203040509") |
| |
| print "*** update whole home level ***" |
| bbs.settings=None |
| bbs.get_account() |
| bbs.settings["settings"]["rating"] = "PG" |
| bbs.post_account() |
| |
| print "*** after sync ***" |
| bbs.settings=None |
| bbs.dump() |
| assert(bbs.get_whole_home_rating() == "PG") |
| assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R") |
| assert(bbs.get_user("Dads pc")["settings"]["rating"] == "R") |
| assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG") |
| assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "G") |
| assert(bbs.get_device("Moms pc")["mac_address"] == "010203040506") |
| assert(bbs.get_device("Dads pc")["mac_address"] == "010203040507") |
| assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508") |
| assert(bbs.get_device("Jills iphone")["mac_address"] == "010203040509") |
| |
| print "*** delete dad, change moms IP, change jills level to PG, change whole home to PG-13 ***" |
| users = [{"name": "Moms pc", "level": "R", "mac": "010203040511"}, |
| {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"}, |
| {"name": "Jills iphone", "level": "PG", "mac": "010203040509"}] |
| |
| bbs.settings = None |
| bbs.sync("PG-13", users) |
| |
| print "*** after sync ***" |
| bbs.settings=None |
| bbs.dump() |
| assert(bbs.get_whole_home_rating() == "PG-13") |
| assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R") |
| assert(bbs.get_user("Dads pc") == None) |
| assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG") |
| assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "PG") |
| assert(bbs.get_device("Moms pc")["mac_address"] == "010203040511") |
| assert(bbs.get_device("Dads pc") == None) |
| assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508") |
| |
| print "add dad's laptop" |
| users = [{"name": "Moms pc", "level": "R", "mac": "010203040511"}, |
| {"name": "Dads laptop", "level": "PG-13", "mac": "010203040512"}, |
| {"name": "Jacks ipad", "level": "PG", "mac": "010203040508"}, |
| {"name": "Jills iphone", "level": "PG", "mac": "010203040509"}] |
| |
| bbs.settings = None |
| bbs.sync("PG-13", users) |
| |
| print "*** after sync ***" |
| bbs.settings=None |
| bbs.dump() |
| assert(bbs.get_whole_home_rating() == "PG-13") |
| assert(bbs.get_user("Moms pc")["settings"]["rating"] == "R") |
| assert(bbs.get_user("Dads pc") == None) |
| assert(bbs.get_user("Dads laptop")["settings"]["rating"] == "PG-13") |
| assert(bbs.get_user("Jacks ipad")["settings"]["rating"] == "PG") |
| assert(bbs.get_user("Jills iphone")["settings"]["rating"] == "PG") |
| assert(bbs.get_device("Moms pc")["mac_address"] == "010203040511") |
| assert(bbs.get_device("Dads pc") == None) |
| assert(bbs.get_device("Dads laptop")["mac_address"] == "010203040512") |
| assert(bbs.get_device("Jacks ipad")["mac_address"] == "010203040508") |
| |
| #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']) |
| #bbs.add_device(name="tom's iphone", mac="010203040506", type="tablet", username="tom") |
| |
| def main(): |
| if len(sys.argv)<4: |
| print "syntax: broadbandshield.py <operation> <email> <password>" |
| print " operation = [dump | selftest | assocate" |
| sys.exit(-1) |
| |
| operation = sys.argv[1] |
| |
| if operation=="dump": |
| dump() |
| elif operation=="selftest": |
| self_test() |
| elif operation=="associate": |
| associate() |
| |
| if __name__ == "__main__": |
| main() |
| |
| |