def _reg_cert(_config: IosAccountInfo, cert_req_id, name, cert_id, sn, type_str, expire): sid = "%s:%s" % (_config.account, name) orig = str_json( db_model.get("IosCertInfo:%s:%s" % (_config.account, name)) or '{}') obj = { "name": name, "app": _config.account, "cert_req_id": cert_req_id, "cert_id": cert_id, "sn": sn, "type_str": type_str, "expire": expire, "expire_str": date_time_str(expire), } if orig == obj: return cert_req_id db_model.set("IosCertInfo:%s:%s" % (_config.account, name), json_str(obj), ex=(expire - now()) // 1000) _info = IosCertInfo() _info.sid = sid _info.app = _config.account _info.cert_req_id = cert_req_id _info.cert_id = cert_id _info.sn = sn _info.type_str = type_str _info.name = name _info.create = now() _info.expire = datetime.datetime.utcfromtimestamp(expire // 1000) _info.save() Log("注册新的证书[%s][%s]" % (name, cert_req_id)) return cert_req_id
def download_process( _req: HttpRequest, download_id: str, timeout=3000, last: int = 0, ): if download_id not in __download_process: gevent.sleep(timeout / 1000) return { "code": 0, "progress": 0, "total": 1, } else: orig = last or __download_process[download_id] total = __download_total[download_id] if orig == total: return { "code": 0, "progress": total, "total": total, } expire = (now() + timeout) if timeout > 0 else (now() + 30 * 1000) while now() < expire: if __download_process[download_id] == orig: gevent.sleep(1) else: # 进度有变化就马上回去 break return { "code": 0, "progress": __download_process[download_id], "total": total, }
def _refresh_certs(): p = Popen("security find-identity -p codesigning -v", bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) expire = now() + 30000 while p.poll() is None: if now() < expire: gevent.sleep(1) expr = re.compile(r'\s*\d+\)\s*\S+\s+"([^"]+(\s+\(.+\)))"') for each in map(lambda x: x.decode("utf8").strip(), p.stdout.readlines()): result = expr.match(each) if not result: continue if "REVOKED" in each: Log("过期的证书[%s]" % each) continue # noinspection PyBroadException try: cert, _id = result.groups() _certs[cert] = _id.strip()[1:-2] _certs[cert[:-len(_id)]] = _id.strip()[1:-2] Log("有效的证书[%s]" % each) except: Log("跳过[%s]" % each)
def wait(): __pub = db_session.pubsub() __pub.subscribe("account:security:code") expire = now() + 1200 * 1000 while now() < expire: gevent.sleep(1) for data in message_from_topic(__pub, is_json=True, limit=1): return data raise Fail("超时")
def acquire(self, timeout=10000, delta=10): timeout = self.__timeout or timeout // 1000 or 1000 if db_session.set(self.__key, 1, ex=timeout, nx=False): self.__lock = True return expire = now() + timeout while now() < expire: if db_session.set(self.__key, 1, ex=timeout, nx=False): self.__lock = True return gevent.sleep(delta) raise Fail("[%s]锁竞争失败" % self.__key)
def _wait_code(info: IosAccountInfo, session: requests.Session, ts, save_now=False): """ 等待二次提交的需要 """ Log("等待[%s]二次验证" % info.account) # last = info.security_code expire = now() + 1200 * 1000 while now() < expire: gevent.sleep(1) for data in message_from_topic(__pub, is_json=True, limit=1): if data.get("ts") > ts: if data.get("code") and data.get("account") in { info.account, "*" }: rsp = session.post( "https://idmsa.apple.com/appleauth/auth/verify/phone/securitycode", json={ "securityCode": { "code": str(data.get("code")), }, "phoneNumber": { "id": 1, }, "mode": "sms", }) # Log("[%s] %s" % (rsp.status_code, rsp.json())) # rsp = session.post("https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode", json={ # "securityCode": {"code": data["code"]}, # }) if rsp.status_code != 200: Log("账号[%s]验证校验码[%s]失败[%s]" % (info.account, data.get("code"), rsp.status_code)) # if rsp.status_code != 204: # Log("账号[%s]验证校验码[%s]失败[%s]" % (info.account, data.get("code"), rsp.status_code)) # continue rsp = session.get( "https://idmsa.apple.com/appleauth/auth/2sv/trust") if rsp.status_code == 204: session.cookies.update( {"__expire": str(now() + 6 * 3600 * 1000)}) if save_now: info.cookie = json_str(session.cookies) info.save() Log("账号[%s]登录成功" % info.account) return session raise Fail("登录二次验证超时")
def __add_task(_user: UserInfo): _account = IosAccountInfo.objects.filter( account=_user.account).first() # type:IosAccountInfo _project = IosProjectInfo.objects.filter( project=_user.project).first() # type:IosProjectInfo _profile = IosProfileInfo.objects.filter( sid="%s:%s" % (_user.account, _user.project)).first() # type:IosProfileInfo Assert(_profile, "[%s][%s]证书无效" % (_project.project, _account.account)) Log("[%s]发布任务[%s]" % (_user.project, _user.account)) db_session.publish( "task:package", json_str({ "cert": "iPhone Developer: zhangming luo", "cert_p12": "", "mp_url": entry("/apple/download_mp?uuid=%s" % _user.uuid), "mp_md5": md5bytes(base64decode(_profile.profile)), "project": _project.project, "ipa_url": _asset_url("%s/orig.ipa" % _user.project), "ipa_md5": _project.md5sum, "ipa_new": "%s_%s.ipa" % (_account.team_id, _account.devices_num), "upload_url": entry("/apple/upload_ipa?project=%s&account=%s" % (_user.project, _user.account)), "ts": now(), }))
def mongo_get(key: str, model=None, active=True) -> Optional[Dict]: if model is not None: if not key.startswith(model + ":"): return None i = key.index(':') if i <= 0: return None model, _id = key[0:i], key[i + 1:] if active: if active is True: active = {"ts": now()} ret = mongo(model).find_one_and_update( {"_id": key}, {"$set": { "__active__": active }}) if ret is not None: ret = json.dumps(ret, separators=(',', ':'), sort_keys=True, ensure_ascii=False) Log("从mongodb[%s]激活[%s][%s]" % (model, key, ret)) db_model.set(key, ret) return ret else: return mongo(model).find_one({"_id": _id})
def is_login(self) -> ExFalse: if "myacinfo" not in self.cookie: return ExFalse("未登录") if int(self.cookie.get("__expire", 0)) < now(): # 登录信息已经过期需要刷新 return ExFalse("登录过期") if not self.team_id: return ExFalse("team_id缺失") return ExFalse.TRUE
def info(_req: HttpRequest, project: str, uuid: str = ""): _project = IosProjectInfo.objects.filter( project=project).first() # type: IosProjectInfo udid = "" if not _project: return {} ret = str_json(_project.comments) ready = False if uuid: _user = UserInfo.objects.filter(uuid=uuid).first() # type: UserInfo if _user: ready = True udid = _user.udid else: Log("上传的uuid无效[%s]" % uuid) else: uuid = _req.get_signed_cookie("uuid", "", salt="zhihu") _user = UserInfo.objects.filter(uuid=uuid).first() # type: UserInfo if _user: ready = True udid = _user.udid else: Log("cookie中的uuid无效[%s]" % uuid) if ready: ret.update({ "ready": True, }) _task = TaskInfo.objects.filter(uuid=uuid).first() # type:TaskInfo if _task: if _task.state == "fail" or _task.expire.timestamp() * 1000 < now( ): __add_task(_user) else: uuid = _newbee(_project) ret.update({ "ready": False, }) ret.update({ "uuid": uuid, }) rsp = JsonResponse({ "ret": 0, "result": ret, "download_id": random_str(32), }) rsp.set_signed_cookie("uuid", uuid, salt="zhihu", expires=3600 * 24) if udid: rsp.set_signed_cookie("udid", _user.udid, salt="zhihu", expires=300 * 3600 * 24) return rsp
def _get_cert(info: IosAccountInfo) -> IosCertInfo: cert = IosCertInfo.objects.filter( app=info.account, expire__gt=datetime.datetime.utcfromtimestamp(now() // 1000), type_str="development", ).first() # type: IosCertInfo if not cert: # todo: 生成证书 pass return Assert(cert, "缺少现成的开发[iOS App Development]证书[%s]" % info.account)
def security_code_sms(phone: str, sms: str): Assert("apple" in sms.lower(), "短信非验证码短信[%s]" % sms) code = re.compile(r"\d{4,6}").findall(sms) Assert(len(code), "短信非验证码短信[%s]" % sms) code = code[0] _account = IosAccountInfo.objects.filter( phone=phone).first() # type: IosAccountInfo publish_security_code(_account.account if _account else "*", code, now()) return { "succ": True, }
def _reg_device(device_id: str, udid: str, model: str, sn: str) -> str: # 需要缓存 _info = IosDeviceInfo() _info.udid = udid _info.device_id = device_id _info.model = model _info.sn = sn _info.create = now() _info.save() Log("注册新的设备[%s][%s][%s]" % (udid, device_id, sn)) return udid
def _reg_app(_config: IosAccountInfo, project: str, app_id_id: str, name: str, prefix: str, identifier: str): sid = "%s:%s" % (_config.account, project) _info = IosAppInfo() _info.sid = sid _info.app = _config.account _info.app_id_id = app_id_id _info.identifier = identifier _info.name = name _info.prefix = prefix _info.create = now() _info.save() Log("注册新的app[%s][%s][%s]" % (_config.account, app_id_id, identifier))
def _reg_cert(_config: IosAccountInfo, cert_req_id, name, cert_id, sn, type_str, expire): sid = "%s:%s" % (_config.account, cert_id) _info = IosCertInfo() _info.sid = sid _info.account = _config.account _info.cert_req_id = cert_req_id _info.cert_id = cert_id _info.sn = sn _info.type_str = type_str _info.name = name _info.create = now() _info.expire = datetime.datetime.utcfromtimestamp(expire // 1000) _info.save() Log("更新证书[%s][%s]" % (name, cert_req_id)) return cert_req_id
def _reg_device(account: str, device_id: str, udid: str, model: str, sn: str) -> str: # 需要缓存 _info = IosDeviceInfo() _info.sid = "%s:%s" % (account, udid) _info.account = account _info.udid = udid _info.device_id = device_id _info.model = model _info.sn = sn _info.create = now() _info.save() Log("注册新的设备[%s][%s][%s]" % (udid, device_id, sn)) _account = IosAccountInfo.objects.get(account=account) device_map = str_json(_account.devices) device_map[udid] = device_id if json_str(device_map) != _account.devices: _account.devices = json_str(device_map) _account.devices_num = len(device_map) _account.save() return udid
def is_login(self): return "myacinfo" in self.cookie and self.team_id and self.cookie and self.info.csrf_ts > now()
def __login(self): if self.csrf_ts > now(): return with Block("账号登录"): ret = requests.post( "https://developer.apple.com/services-account/QH65B2/account/getTeams", json={ "includeInMigrationTeams": 1, }, headers={ 'cookie': to_form_url(self.cookie, split=';'), }, timeout=3, verify=False).json() if not self.team_id else {} if ret.get("resultCode") == 0: self.teams = list(map(lambda x: x["teamId"], ret["teams"])) self.info.team_id = self.team_id = self.teams[0] self.info.teams = json_str(self.teams) self.info.save() else: # 重新登录 self.session = requests.session() self.session.headers["User-Agent"] = "Spaceship 2.117.1" rsp = self.session.get( "https://olympus.itunes.apple.com/v1/app/config?hostname=itunesconnect.apple.com" ).json() self.session.headers["X-Apple-Widget-Key"] = rsp[ "authServiceKey"] # self.session.headers["X-Apple-Widget-Key"] = "16452abf721961a1728885bef033f28e" self.session.headers["Accept"] = "application/json" rsp = self.session.post( "https://idmsa.apple.com/appleauth/auth/signin", json={ "accountName": self.account, "password": self.password, "rememberMe": True, }, timeout=3) self.session.headers["x-apple-id-session-id"] = rsp.headers[ "x-apple-id-session-id"] self.session.headers["scnt"] = rsp.headers["scnt"] if rsp.status_code == 409: # 二次验证 # noinspection PyUnusedLocal rsp = self.session.post( "https://idmsa.apple.com/appleauth/auth") # Log("===> https://idmsa.apple.com/appleauth/auth [%s] %s" % (rsp.status_code, rsp.json())) # 切手机验证码 rsp = self.session.put( "https://idmsa.apple.com/appleauth/auth/verify/phone", json={ "phoneNumber": { "id": 1 }, "mode": "sms", }) Assert(rsp.status_code == 200, "[%s]短信发送失败" % self.account) # Log("===> https://idmsa.apple.com/appleauth/auth/verify/phone [%s] %s" % (rsp.status_code, rsp.json())) _wait_code(self.info, self.session, now()) self.cookie.update(self.session.cookies) self.__expire = now() + 3600 * 1000 if not self.team_id: ret = requests.post( "https://developer.apple.com/services-account/QH65B2/account/getTeams", json={ "includeInMigrationTeams": 1, }, headers={ 'cookie': to_form_url(self.cookie, split=';'), }, timeout=3, verify=False).json() if ret["resultCode"] == 0: self.teams = list(map(lambda x: x["teamId"], ret["teams"])) self.info.team_id = self.team_id = self.teams[0] self.info.teams = json_str(self.teams) self.info.save() else: Log("[%s]获取team失败登出了" % self.account) self.__logout() Log("apple账号[%s:%s]登录完成了" % (self.account, self.team_id))
def post(self, title: str, url: str, data: Union[Dict, str] = None, is_json=True, log=True, cache: Union[bool, int] = False, csrf=False, json_api=True, method="POST", is_binary=False, ex_headers=None, status=200): if cache is True: expire = 3600 * 1000 else: expire = cache if not self.is_login: self.__login() start = now() rsp_str = "#NODATA#" try: if "teamId=" in url: if "teamId=%s" % self.team_id not in url: url = url.replace("teamId=", "teamId=%s" % self.team_id) if cache: rsp_str = _cache(url, data) or rsp_str headers = { 'cookie': to_form_url({"myacinfo": self.cookie["myacinfo"]}, split=';'), } if csrf: headers.update({ 'csrf': self.csrf, 'csrf_ts': self.csrf_ts, }) if ex_headers: headers.update(ex_headers) if rsp_str == "#NODATA#": if method.upper() == "GET": rsp = requests.get(url, params=data, headers=headers, timeout=3, verify=False) else: rsp = requests.post(url, data=data, headers=headers, timeout=3, verify=False) rsp_str = rsp.text if rsp.headers.get("csrf"): self.csrf = rsp.headers["csrf"] self.csrf_ts = int(rsp.headers["csrf_ts"]) Assert(rsp.status_code == status, "请求[%s]异常[%s]" % (title, rsp.status_code)) if json_api: _data = str_json_i(rsp_str) or {} if _data.get("resultCode") == 1100: self.__logout() raise Fail("登录[%s]过期了[%s][%s]" % (self.account, _data.get("resultString"), _data.get("userString"))) Assert( _data.get("resultCode") == 0, "请求业务[%s]失败[%s][%s]" % (title, _data.get("resultString"), _data.get("userString"))) if log: Log("apple请求[%s][%s]发送[%r]成功[%r]" % (title, now() - start, data, rsp_str)) if is_binary: rsp_str = base64(rsp.content) if cache: _set_cache(url, data, rsp_str, expire=expire) if is_json: return str_json(rsp_str) elif is_binary: return base64decode(rsp_str) else: return rsp_str except Exception as e: if log: Log("apple请求[%s][%s]发送[%r]失败[%r]" % (title, now() - start, data, rsp_str)) raise e
def security_code(account: str, code: str): publish_security_code(account, code, now()) return { "succ": True, }
def _shell_run(cmd: str, timeout=30000, succ_only=False, include_err=True, err_last=False, pwd=None, verbose=False, debug=False): if debug: verbose = True if verbose: if debug: Log("+ [%s] [%s]" % (os.path.abspath(pwd or os.path.curdir), cmd)) else: Log("+ [%s]" % cmd) p = Popen(cmd, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT if include_err and not err_last else subprocess.PIPE, shell=True, cwd=pwd) expire = now() + timeout buffer = [] buffer_err = [] while p.poll() is None: if now() < expire: time.sleep(0.1) else: p.kill() break if p.stdout.readable(): buffer.extend(p.stdout.readlines()) if include_err: if err_last: if p.stderr.readable(): buffer_err.extend(p.stderr.readlines()) if succ_only: if p.returncode == 0: pass else: raise Fail("命令[%s]执行失败" % cmd) if p.stdout and p.stdout.readable(): buffer.extend(p.stdout.readlines()) if p.stderr and p.stderr.readable(): buffer_err.extend(p.stderr.readlines()) # todo: 采用yield后会导致不执行... # if debug: # for each in map(lambda x: x.decode("utf8"), buffer): # type:str # Log("- %s" % each.rstrip("\r\n")) # yield each.rstrip("\r\n") # if err_last: # for each in map(lambda x: x.decode("utf8"), buffer_err): # type:str # Log("* %s" % each.rstrip("\r\n")) # yield each.rstrip("\r\n") # else: # for each in map(lambda x: x.decode("utf8"), buffer): # type:str # yield each.rstrip("\r\n") # if err_last: # for each in map(lambda x: x.decode("utf8"), buffer_err): # type:str # yield each.rstrip("\r\n") if debug: tmp = [] for each in map(lambda x: x.decode("utf8"), buffer): # type:str Log("- %s" % each.rstrip("\r\n")) tmp.append(each.rstrip("\r\n")) if err_last: for each in map(lambda x: x.decode("utf8"), buffer_err): # type:str Log("* %s" % each.rstrip("\r\n")) tmp.append(each.rstrip("\r\n")) return tmp else: tmp = [] for each in map(lambda x: x.decode("utf8"), buffer): # type:str tmp.append(each.rstrip("\r\n")) if err_last: for each in map(lambda x: x.decode("utf8"), buffer_err): # type:str tmp.append(each.rstrip("\r\n")) return tmp