Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
pip install zermeloapi
import zermeloapi
zermelo = zermeloapi.zermelo(school, username, password, teacher=False, version=3)
token = zermelo.get_token()
if u wand to u can give the school username and password again but u don't need to in that case u can use get_token(school,username,password)
access_token = zermelo.get_access_token()
again if u wand to u can give the school and username again but u don't need to in that case u can use get_access_token(school,username)
raw_schedule = zermelo.get_schedule()
u can give the week number and or year of u wand to else it uses the current year/week u can use get_schedule(week=07)
,get_schedule(year=2024)
orget_schedule(year=2024,week=07)
raw_schedule = zermelo.get_schedule()
u can give the week number and or year of u wand to else it uses the current year/week u can use get_schedule(week=07)
,get_schedule(year=2024)
orget_schedule(year=2024,week=07)
and u you can pass a rawshedule from get_raw_shedule()
sorted_schedule = zermelo.sort_schedule()
like before u can give the week and year but u can also give the schedule from get_schedule()
instead to sort it defaults to getting it self but if u give it uses that one u can do that by using it like this sort_schedule(schedule)
for the week and year it is the same syntax
readable = zermelo.readable_schedule()
again u can give it week and year with the same syntax as before also you can give it a sorted schedule (output from sort_schedule()
) by using readable_schedule(sorted_schedule)
zermelo.print_schedule()
yes u can use it like this zermelo.print_schedule(zermelo.readable_schedule())
but WHY if u wand that use it like that just print(zermelo.readable_schedule())
the only use is that it defaults to zermelo.readable_schedule()
# from calendar import weekday
import os.path
import requests
import json
import time
import base64
# from datetime import datetime
import datetime
class zermelo:
expires_in = 0
school = ''
username = ''
teacher = False
debug = False
TimeToAddToUtc = 0
def __init__(self, school, username,password=None, teacher=False, version=3,debug=False,linkcode=None):
self.school = school
self.username = username
self.teacher = teacher
self.debug = debug
self.version = 'v'+str(version)
self.TimeToAddToUtc = self.get_date()[2]
if linkcode == None and password != None:
self.token = self.get_tokenfromusrpsw(school=school,username=username,password=password)
else:
self.token = self.gettokenfromfile(linkcode=linkcode)
def updatetoken(self, linkcode):
self.token = self.gettokenfromfile(linkcode=linkcode)
def gettokenfromlinkcode(self, linkcode=None):
if linkcode == None:
linkcode = input("apitoken: ").replace(" ",'')
else:
linkcode = str(linkcode).replace(" ",'')
return json.loads(requests.post(f"https://ozhw.zportal.nl/api/v3/oauth/token?grant_type=authorization_code&code={linkcode}").text)["access_token"]
def get_tokenfromusrpsw(self, school=None, username=None, password=None):
if(school == None):
school = self.school
if (username == None):
username = self.username
if(password == None):
password = self.password
url = 'https://'+school+'.zportal.nl/api/'+self.version+'/oauth'
myobj = {'username': username, 'password': password, 'client_id': 'OAuthPage', 'redirect_uri': '/main/',
'scope': '', 'state': '4E252A', 'response_type': 'code', 'tenant': school}
x = requests.post(url, data=myobj,allow_redirects=False)
if self.debug:
print(x.text)
try:
respons = x.headers['Location']
except:
respons = x.text
# exit(0)
# exit(0)
start = respons.find("code=") + len("code=")
end = respons.find("&", start)
token = respons[start:end]
start = respons.find("tenant=") + len("tenant=")
end = respons.find("&", start)
school = respons[start:end]
start = respons.find("expires_in=") + len("expires_in=")
end = respons.find("&", start)
self.expires_in = respons[start:end]
start = respons.find("loginMethod=") + len("loginMethod=")
end = respons.find("&", start)
self.loginMethod = respons[start:end]
start = respons.find("interfaceVersion=") + len("interfaceVersion=")
end = respons.find("&", start)
self.interfaceVersion = respons[start:end]
if(school == None):
school = self.school
if(token == None):
token = self.get_token()
if self.debug:
print(token)
url = 'https://' + school+'.zportal.nl/api/'+self.version+'/oauth/token'
myobj = {'code': token, 'client_id': 'ZermeloPortal', 'client_secret': 42,
'grant_type': 'authorization_code', 'rememberMe': False}
if self.debug:
print("\n\n")
if self.debug:
print(myobj,url)
l = requests.post(url, data=myobj)
if self.debug:
print(l.text)
jl = json.loads(l.text)
token = jl['access_token']
return(token)
def gettokenfromfile(self, file="./token", linkcode=None):
if not os.path.exists(file):
self.settokentofile(linkcode=linkcode)
with open(file) as f:
filevalue = f.read()
return str(base64.b64decode(filevalue))[2:-1]
def settokentofile(self,token=None,file="./token",linkcode=None):
if token == None:
token = self.gettokenfromlinkcode(linkcode=linkcode)
file = str(base64.b64encode(bytes(token, "utf-8")))[2:-1]
with open("./token", "w") as f:
f.write(str(file))
def get_date(self):
now = datetime.datetime.now()
return (now.year,now.isocalendar()[1],int((datetime.datetime.fromtimestamp(now.timestamp()) - datetime.datetime.utcfromtimestamp(now.timestamp())).total_seconds() / 3600))
def get_raw_schedule(self, year:int=None, week:int=None, username:str=None,teacher:bool = False) -> dict:
time = self.get_date()
if year == None:
year = time[0]
if week == None:
week = time[1]
if username == None:
url = f"https://{self.school}.zportal.nl/api/v3/liveschedule?access_token={self.token}&{'teacher' if (self.teacher) else 'student'}={self.username}&week={year}{week:0>2}"
else:
start = str(datetime.datetime.strptime(f"{year}-{week}-0","%Y-%U-%w").timestamp())[0:-2]
end = str(datetime.datetime.strptime(f"{year}-{week}-6","%Y-%U-%w").timestamp())[0:-2]
url = f"https://{self.school}.zportal.nl/api/v3/appointments?access_token={self.token}&start={start}&end={end}&{'teachers' if (teacher) else 'possibleStudents'}={username}"
if self.debug:
print(url)
rawr = requests.get(url)
if self.debug:
print(rawr)
rl = json.loads(rawr.text)
return rl
def get_schedule(self,rawschedule:dict=None,year=None, week=None,username=None,teacher=None):
if rawschedule == None:
rawschedule = self.get_raw_schedule(year=year,week=week,username=username,teacher=teacher)
response = rawschedule["response"]
if username != None:
appointments = response["data"]
else:
data = response["data"][0]
appointments = data["appointments"]
return(appointments)
def sort_schedule(self, schedule=None, year=None, week=None,username=None,teacher=None):
if(schedule == None):
schedule = self.get_schedule(year=year, week=week,username=username,teacher=teacher)
pdate = 0
days = [[[], [], []]]
thisweek = {}
for les in schedule:
date = datetime.datetime.utcfromtimestamp(les["start"]).strftime('%Y%m%d')
hour = str(int(datetime.datetime.utcfromtimestamp(
les["start"]).strftime('%H')) + self.TimeToAddToUtc)
time = datetime.datetime.utcfromtimestamp(les["start"]).strftime('%Y-%m-%d ') + (hour if int(
hour) > 9 else ('0'+hour)) + datetime.datetime.utcfromtimestamp(les["start"]).strftime(':%M')
ehour = str(int(datetime.datetime.utcfromtimestamp(
les["end"]).strftime('%H')) + self.TimeToAddToUtc)
etime = datetime.datetime.utcfromtimestamp(les["end"]).strftime('%Y-%m-%d ') + (ehour if int(
ehour) > 9 else ('0'+ehour)) + datetime.datetime.utcfromtimestamp(les["end"]).strftime(':%M')
# print(les["status"])
if username != None:
code = 2002
if les["cancelled"] or not les["valid"]:
code = 4007
elif les["moved"]:
code = 3012
elif les["modified"]:
code = 3011
if not date in thisweek:
thisweek[date] = [[],[]]
if (not les["cancelled"] and les["valid"]):
thisweek[date][0].append([les["subjects"][0], time, etime,
str(les["locations"]), [{"code":code}], False])
else:
thisweek[date][1].append([les["subjects"][0], time, etime,
str(les["locations"]), [{"code":code}], False])
else:
if date != pdate:
days.append([[], [], []])
if self.debug:
print(les)#,les["appointmentType"])
# pass
for appointment in (les["actions"] if les["appointmentType"] == "choice" else [{'appointment': les,'status':les['status']}]):
app = appointment["appointment"]
if app != None and (len(appointment["status"]) > 0):
if (appointment["status"][0]["code"] < 4000 and appointment["status"][0]["code"] >= 2000):
days[-1][0].append([app["subjects"][0], time, etime,
str(app["locations"]), appointment["status"], app["online"],"",app["id"]])
elif (appointment["status"][0]["code"] == 1004):
days[-1][2].append([app["subjects"][0], time, etime,
str(app["locations"]), appointment["status"], app["online"],appointment["post"],app["id"]])
else:
days[-1][1].append([app["subjects"][0], time, etime,
str(app["locations"]), appointment["status"], app["online"],"",app["id"]])
pdate = date
if username != None:
thisweekdayssorted = [thisweek[i] for i in [str(c) for c in sorted([int(i) for i in thisweek.keys()])]]
days = []
for v in thisweekdayssorted:
part = []
for v2 in v:
part.append(sorted(v2,key = lambda x: int(x[1].replace(":","").replace("-","").replace(" ",""))))
days.append(part)
else:
days.pop(0)
return(days)
def enroll(self,les=None,lesid=0):
if les == None and lesid == 0:
return False
url = f"https://{self.school}.zportal.nl{les[6] if lesid == 0 else f'/api/v3/liveschedule/enrollment?enroll={lesid}'}&access_token={self.token}"
if self.debug:
print(url)
req = requests.post(url)
if self.debug:
print(req,req.text)
return True
def unenroll(self,les=None,lesid=0):
if les == None and lesid == 0:
return False
url = f"https://{self.school}.zportal.nl{str(les[6]).replace('&unenroll=','').replace('enroll','unenroll') if lesid == 0 else f'/api/v3/liveschedule/enrollment?unenroll={lesid}'}&access_token={self.token}"
if self.debug:
print(url)
req = requests.post(url)
if self.debug:
print(req,req.text)
return True
def readable_schedule(self, days=None, year=None, week=None,username=None,teacher=None):
result = ''
if(days == None):
days = self.sort_schedule(year=year, week=week,username=username,teacher=teacher)
daysofweek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
for i,day in enumerate(days):
if len(day[0]) > 0:
result += (daysofweek[i]+"\nstart: "+day[0][0][1]+"\tend: "+day[0][-1][2]+'\n')
else:
result += (f"{daysofweek[i]}\n")
for les in day[0]:
# if (les[4][0]["code"] < 4000 and les[4][0]["code"] >= 2000):
if True:
result += f"les: {les[0].ljust(8, ' ')} lokaal: {('📷'if(les[5])else (les[3][2:-2]if(len(les[3][2:-2]) > 0)else '----')).ljust(8, ' ')} start: {les[1]}\tend: {les[2]}\n"
pass
result += ("\n\n")
return result
def print_schedule(self, readable=None, days=None, year=None, week=None,username=None,teacher=None):
if(readable == None):
readable = self.readable_schedule(days=days, year=year, week=week,username=username,teacher=teacher)
print(readable)
FAQs
zermelo api
We found that zermeloapi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.