class PinkMgr(BaseMgr): module = Pink t_life = FromConf.load('TL_PWD_RESET') ve_life = FromConf.load('TL_EMAIL_VERIFICATION') @classmethod def verify_email(cls, email): token = pat.encode( exp=(g.now + cls.ve_life).timestamp(), payload={ 'old': Pink.query.get(g.pink_id) if g.pink_id else None, 'new': email }) sendgrid.send(to=email, template_name='email verification', email=email, token=token) @classmethod def forget_pwd(cls, name, email): pink = cls.model.query.filter_by(name=name).one() if pink and pink.email != email: return token = random_b85(k=20) redis.set(f'rst-{token}', pink.id, ex=cls.t_life.seconds) sendgrid.send(to=email, template_name='pwd reset', name=name, token=token) @staticmethod def reset_pwd(token, pwd, device_id): if not (pink_id := redis.get(f'rst-{token}')): raise InvalidCredential(type_=InvalidCredential.T.rst) lemon = PinkMgr(pink_id).set_pwd(pwd, device_id) redis.delete(token) return lemon
class MangoMgr(BaseMgr): model = Mango t_life = FromConf.load('TL_PIT_SUMBIT') @classmethod def _create(cls, pit, i): if mango := cls.model.query.filter_by(sha1=i['sha1']): raise FileExist(pit=mango.pit) if last := pit.mango: onedrive.delete(item_id=last.id)
class ProjMgr(BaseMgr): model = Proj shift_buffer = FromConf.load('PIT_SHIFT_BUFFER') def _upload(self): self.o.status = Proj.S.upload self.o.add_track(info=Proj.T.upload, now=g.now) def post_works(self, pit): # check if all pits in this department are done exists = Pit.query.join(Role). \ filter(Role.dep == pit.role.dep). \ filter(Role.proj_id == self.o.id). \ filter(Pit.status.notin_({Pit.S.fin, Pit.S.fin_p, Pit.S.dropped})). \ exists() if not db.session.query(exists).scalar(): return # alter start and due for the subsequent departments extended = pit.finish_at \ - _dep_graph.get_finish_time(cat=self.o.cat, base=self.o.start_at, dep=pit.role.dep) pits = Pit.query.join(Role). \ filter(Role.dep.in_(_dep_graph.get_dependents(cat=self.o.cat, dep=pit.role.dep))). \ filter(Role.proj_id == self.o.id). \ all() for pit_ in pits: pit_.status = Pit.S.working # sequence of the following block MUST NOT BE CHANGED if extended < -self.shift_buffer: # finished before 1 day prior to due pit_.add_track(info=Pit.T.shift, by=pit.id) pit_.start_at += extended pit_.due += extended elif extended.seconds > 0: # pit is extended pit_.add_track(info=Pit.T.cascade, by=pit.id) pit_.due += extended # check if proj can upload if not pits: # all pits in this dep are done + no further pits to fill = project can upload self._upload()
class PinkMgr(BaseMgr): model = Pink t_life = FromConf.load('TL_NEW_PINK') @classmethod def assign_token(cls, deps, amount): check_scopes(deps) deps_s = ','.join(deps) tokens = [random_b85(k=20) for __ in range(amount)] redis.mset({f'deps-{token}': deps_s for token in tokens}, ex=cls.t_life.seconds) return tokens @classmethod def sign_up(cls, name: str, pwd, qq: int, other: str, email_token: str, deps_token: str): if not (deps_s := redis.get(f'deps-{deps_token}')): raise InvalidCredential(type_=InvalidCredential.T.new) pink = cls.model(id=cls.gen_id(), name=name, email=None, qq=str(qq), other=other) pink.pwd = pwd pink.deps = deps_s.split(',') PinkMgr(pink).set_email(email_token) db.session.add(pink) sendgrid.send(to=pink.email, template_name='new pink', name=pink.name) return pink
import re import requests from bs4 import BeautifulSoup from models import Proj from core.errors import InvalidSource from core.singleton import redis from core.utils import FromConf _CN_SITE_URL = 'http://scp-wiki-cn.wikidot.com' _web_exp = FromConf.load('WEB_EXP') def fetch_web(url): if not (web_page_t := redis.get(f'web-{url}')): web_page = requests.get(f'{_CN_SITE_URL}/{url}') if web_page.status_code == 404: raise InvalidSource(rsn=InvalidSource.Rsn.web, url=url) web_page_t = web_page.text redis.set(f'web-{url}', web_page_t, ex=_web_exp) soup = BeautifulSoup(web_page_t, 'lxml') return soup.find('div', {'id': 'main-content'}) def fetch_title_by_url(url): if not re.match(r'^/[a-z]+(?:(?:-[a-z]+)+)?$', url): raise InvalidSource(rsn=InvalidSource.Rsn.inp)
class LemonMgr(BaseMgr): model = Lemon a_life = FromConf.load('TL_ACCESS_TOKEN') r_life = FromConf.load('TL_REFRESH_TOKEN') def __init__(self, obj_or_id): try: super().__init__(obj_or_id) if self.o.pink_id != g.pink_id: raise RecordNotFound(cls_=Lemon, id_='< - secret - >') except RecordNotFound: raise InvalidRefreshToken(rsn=InvalidRefreshToken.Rsn.non) @classmethod def _grant(cls, pink, device_id): pink.lemons.filter_by(device_id=device_id).delete() lemon = cls.model(id=cls.gen_id(), key=random_b85(k=40), pink=pink, device_id=device_id, ip=request.remote_addr, exp=g.now + cls.r_life, timestamp=g.now) db.session.add(lemon) return lemon @staticmethod def login(name, pwd, device_id): pink: Pink = Pink.query.filter_by(name=name).one() if not pink.active: raise AccountDeactivated() if not pink or not pink.check_pwd(pwd): raise InvalidCredential(type_=InvalidCredential.T.pwd) sqlogger.log('login', '') return LemonMgr._grant(pink, device_id) def grant_access_token(self, key, device_id): if self.o.key != key or self.o.device_id != device_id: raise InvalidRefreshToken(rsn=InvalidRefreshToken.Rsn.key) if self.o.ip != request.remote_addr \ and ip2loc.get_city(self.o.ip) != ip2loc.get_city(request.remote_addr): raise InvalidRefreshToken(rsn=InvalidRefreshToken.Rsn.ip) if self.o.expiration < g.now: self.revoke() raise InvalidRefreshToken(rsn=InvalidRefreshToken.Rsn.exp, at=self.o.expiration) # buffer last = redis.hget('last_access', g.pink_id) if last and g.now.timestamp() - last > 86400: self.o.expiration = g.now + self.r_life # assign token token = random_b85(k=20) with redis.pipeline(transaction=False) as p: p.hset('last_access', g.pink_id, g.now.timestamp()) p.set(token, g.pink_id, ex=self.a_life.seconds) p.execute() return token, g.now + self.a_life def revoke(self): db.session.delete(self.o) @staticmethod def revoke_all(): revoke_all_lemons(pink_or_id=g.pink_id)
import pickle from math import log2 from typing import Set from core.errors import WeekPwd from core.utils import FromConf common_pwd: Set[str] = set() with FromConf.load('PWDDB_PATH').open('rb') as f: common_pwd = pickle.load(f) def is_common_pwd(pwd: str) -> bool: return pwd in common_pwd # modified from # https://github.com/kolypto/py-password-strength/blob/master/password_strength/stats.py WEAK_BITS: int = 30 WEAK_MAX: float = 1 / 3 HARD_BITS: int = WEAK_BITS * 3 HARD_VAL: float = 0.950 K: float = -log2((1 - HARD_VAL) / (1 - WEAK_MAX)) / HARD_BITS def measure_strength(pwd: str) -> float: ''' Get password strength as a number normalized to range {0 .. 1}. Normalization is done in the following fashion: 1. If entropy_bits <= weak_bits -- linear in range{0.0 .. 0.33} (weak) 2. If entropy_bits <= weak_bits*2 -- almost linear in range{0.33 .. 0.66} (medium)