async def delete(self, request, *args, **kwargs): jwt_token = request.headers.get( config.app.auth.config.authorization_header(), None) if jwt_token is None: raise Forbidden("missing authentication") if request.json is None or "refresh_token" not in request.json: raise Forbidden("missing refresh_token") try: decoded = decode( jwt_token, config.app.auth.config.secret(), algorithms=config.app.auth.config.algorithm(), ) except ExpiredSignatureError: raise Forbidden("token expired") if decoded is None or "name" not in decoded: raise Forbidden("malformed token") user = User.get_by_email(decoded["email"]) if user is None: raise Forbidden("malformed token") await delete_refresh_token(user.email, request.json["refresh_token"]) return response.json(None, status=204)
async def userCreate(request): session = request.ctx.session form = request.json authId = getattr(session, 'authId', None) if authId is None: raise Forbidden('anonymous') form['authorization'] = authId user = await User.get_or_none(authId=authId) if user is not None: raise Forbidden('exists') try: async with request.app.ctx.usermgrd.post('http://localhost/', json=form) as resp: data = await resp.json() if data['status'] != 'ok': request.ctx.logger.error(__name__ + '.create.usermgrd_error', reason=data['status']) raise ServerError('backend') except aiohttp.ClientConnectionError: request.ctx.logger.error(__name__ + '.create.usermgrd_connect_failure') raise ServiceUnavailable('backend') user = User(authId=authId, name=data['user']) user.password = data['password'] await user.save() request.ctx.logger.info(__name__ + '.create', user=user.name) return await makeUserResponse(user)
async def dialogue(request: Request) -> HTTPResponse: name, message, client_id = mzk.get_args(request, ('name|n', str, None, { 'max_length': 32 }), ('message|msg|m', str, None, { 'max_length': 512 }), ('client_id|id', str, None, { 'max_length': 64 })) if name is None: raise Forbidden('name parameter format error.') if message is None: raise Forbidden('message parameter format error.') bot = request.app.bots.get(name, None) bot_id = request.app.dict_cache['name'].get(name, 'unknown') if bot is None: raise Forbidden('Unknown bot name.') if message == '[el]#moca_bot_dict_count#': res = await request.app.mysql.execute_aio(core.GET_DICT_COUNT_QUERY, (bot_id, )) return json({ 'res_type': 'system', 'res_content': f'学習済みデータ数: {res[0][0]}' }) res_type, res_content = bot.dialogue(message) await request.app.mysql.execute_aio( core.INSERT_CHAT_LOG_QUERY, (mzk.get_remote_address(request), bot_id, message, res_content, res_type, client_id), True) return json({'res_type': res_type, 'res_content': res_content})
def do_protection(request: Request, scoped: t.Optional[str] = None) -> None: """This method does all of our auth checks and raises exceptions upon failure""" if not is_authenticated(request): raise Unauthorized("Who are you?") if not is_authorized(request, scoped): raise Forbidden("You are not allowed") if not is_pass_csrf(request): raise Forbidden("You CSRF thief!")
async def processRun(request, authenticatedUser): def substitute(s): return s.format(user=authenticatedUser.name) reqData = request.json actionToken = reqData.get('action', None) command = reqData.get('command', None) # client-defined extra data, forwarded to start notification extraData = reqData.get('extraData', None) token = reqData.get('token', None) isAction = bool(actionToken) isCommand = bool(command) user = None if isAction and isCommand: # cannot have both at the same time raise InvalidUsage('make_up_your_mind') elif isAction: action = await getAction(actionToken) args = action.arguments authId = args['user'] user = await User.get_or_none(authId=authId) command = list(map(substitute, args['command'])) elif isCommand: # use current user instead user = authenticatedUser if not user: # not connected to any authorized user raise Forbidden('forbidden') else: processes = perUserProcesses[authenticatedUser] if token in processes: raise InvalidUsage('process_exists') elif not token: raise InvalidUsage('missing_token') p = WebsocketProcess(token, user, broadcastFunc=partial(broadcast, authenticatedUser), command=command, extraData=extraData) try: await p.run() processes[token] = p return jsonResponse({'status': 'ok', 'token': token}) except asyncssh.misc.PermissionDenied: raise Forbidden('locked_out') except TermsNotAccepted: raise Forbidden('terms_of_service')
async def reset_user_database(request: Request) -> HTTPResponse: root_pass, api_key = get_args(request, 'root_pass', 'api_key') api_key_status = await request.app.moca_access.check_api_key( api_key, '-SM-', request) if api_key_status[0] != 0: raise Forbidden(api_key_status[1]) if not (isinstance(root_pass, str) and root_pass.isascii() and (8 <= len(root_pass) <= 32)): raise Forbidden('root password format error.') if not compare_digest(root_pass, app.moca_config.get('root_pass', '')): raise Forbidden('invalid root password.') await request.app.moca_users.reset_user_database() return text('success.')
async def submit(request, user): # quota, allow 50 mails/day oldest = now() - timedelta(days=1) sent = await SentEmail.filter(sent__gte=oldest).count() quota = 50 if sent >= quota: raise Forbidden('quota_reached') session = request.ctx.session values = dict(request.json) dryRun = values.get('dryRun') templateName = values.get('template') language = values.get('lang') or 'en' tpl = templates.get(templateName, {}).get(language, None) if not tpl: raise NotFound('template_not_found') subject = subjectTemplates[templateName][language] # input validation link = furl(values.get('link', None)) recipientName = values.get('recipientName', None) recipientAddress = values.get('recipientAddress', None) senderAddress = session.oauthInfo.get('email', None) senderName = session.oauthInfo.get('name', None) # we’re lazy and defer actual validation to the sending SMTP server if not recipientName or not recipientAddress: raise SanicException('recipient_invalid', status_code=400) if not senderName or not senderAddress: raise Forbidden('sender_invalid') if link.host != request.server_name: raise Forbidden('link_invalid') values['senderName'] = senderName message = tpl.format(**values) try: if not dryRun: email = await sendEmail(request.app.config, recipientName, recipientAddress, senderAddress, subject, message) entry = SentEmail(messageId=email['message-id'], sender=user, message=str(email)) await entry.save() except aiosmtplib.errors.SMTPException as e: if e.code == 501: raise SanicException('syntax_error', status_code=400) else: raise ServerError('error') return json(dict(status='ok', message=str(message)))
async def msg_delete(request: Request, message_id: int): request, user = await check_request(request, list(), True) msg = await Msg.find(msg_id=message_id) if msg.sender_id != user.id: raise Forbidden('You do not have permission to delete this message') await msg.update(is_deleted=True) return text('Success')
async def auth_server(session: aiohttp.ClientSession, username: str, password: str): """ 登录到 SDUT AuthServer(七天有效期) """ # 获取页面参数 async with session.get( 'http://authserver.sdut.edu.cn/authserver/login') as resp: text = await resp.text() cookies = resp.cookies soup = BeautifulSoup(text, 'html.parser') ipts = soup.form.find_all('input') data = { 'username': username, 'password': password, 'rememberMe': 'on', # 七天内记住我 } for ipt in ipts: if ipt.get('value'): data[ipt.get('name')] = ipt.get('value') JSESSIONID_auth = cookies.get('JSESSIONID_auth').value # 提交登录 # 山东理工大学统一登录平台有一处 Set-Cookie 错误,Python 没有对错误的格式进行兼容 # 手动处理第一次跳转,处理格式兼容 async with session.post( f'http://authserver.sdut.edu.cn/authserver/login;{JSESSIONID_auth}', data=data, allow_redirects=False) as resp: headers = resp.headers next_url = headers.get('Location') for key in headers: if key.lower() == 'set-cookie' and headers[key].startswith( 'CASTGC'): castgc = headers[key].split(';')[0][7:] session.cookie_jar.update_cookies( {'CASTGC': castgc}, URL('http://authserver.sdut.edu.cn/authserver')) break else: raise Unauthorized('获取 Cookie 失败,请检查用户名与密码。如果问题持续出现,请联系作者。') # 手动进行后续的跳转 async with session.get(next_url) as resp: text = await resp.text() url = str(resp.url) # 若页面跳转至首页,则说明登录成功 if url == 'http://authserver.sdut.edu.cn/authserver/index.do': return True # 若页面跳转回登录界面,则说明登录失败(用户名或密码错误) elif url == 'http://authserver.sdut.edu.cn/authserver/login': raise Unauthorized('用户名或密码错误') elif url == 'http://authserver.sdut.edu.cn/authserver/pcImproveInfo.do': raise Forbidden('需要修改初始密码后使用') else: print(url) raise ServerError('发生意料之外的错误,如果问题持续出现,请联系作者。')
async def delete_jogging_result(request, *args, **kwargs): user_from_token = retrieve_user(request, args, kwargs) if user_from_token is None: raise InvalidUsage("invalid parameter (maybe expired?)") try: jogging_id = int(request.path.split("/")[2]) except ValueError as e: raise InvalidUsage(e) if jogging_id < 0: raise InvalidUsage("invalid id") jog = JoggingResult.load_by_jogging_id(jogging_id) if jog is None: raise InvalidUsage("invalid id") user_id_from_token = retrieve_user(request, args, kwargs).user_id if user_id_from_token != jog.user_id: raise Forbidden("user can only access user jogs") jog.delete() return response.HTTPResponse(status=204)
async def notification_staff_route(request, *, req_args=None, req_body=None, query_params=None, requester=None, **kwargs): if "role_id" not in requester: raise Forbidden("Only staffs can receive notifications") staff_id = requester["id"] call_funcs = { "GET": noti_staff_retrieve, # "POST": noti_staff_create, "PUT": noti_staff_refresh, # "PATCH": visitor_update, # "DELETE": user_delete, } response = await call_funcs[request.method](request, req_args={ "staff_id": staff_id }, req_body=req_body, query_params=query_params, **kwargs) return json(response)
async def show_bot_dict(request: Request) -> HTTPResponse: check_root_pass(request) name, *_ = mzk.get_args( request, ('name|n', str, None, { 'max_length': 32 }), ) if name is None: raise Forbidden('name parameter format error.') bot_id = request.app.dict_cache['name'].get(name) if bot_id is None: raise Forbidden('unknown bot name.') res = await request.app.mysql.execute_aio(core.GET_BOT_DATA_QUERY, (bot_id, )) return json(res)
def decorator(request, *args, **kwargs): prefix, token = _get_token(request) if not token or token != valid_token: raise Forbidden("You don't have access to requested resource") return handler(request, *args, **kwargs)
async def msg_read(request: Request, message_id: int): request, user = await check_request(request, list(), True) msg = await Msg.find(msg_id=message_id, is_deleted=False) if user.id not in (msg.sender_id, msg.recipient_id): raise Forbidden('You do not have permission to view this message') return json(await msg.dump())
async def user_update_avatar(request: Request, user_id: int): user = (await check_request(request, list(), True))[1] if user.id != user_id: raise Forbidden('You do not have permission to update this avatar') upload_file = request.files.get('photo') if not upload_file: raise InvalidUsage('You need to upload a photo') if len(upload_file.body) > MAX_FILE_SIZE: raise InvalidUsage( f'Photo size must be less than {MAX_FILE_SIZE} bytes') if imghdr.what(None, upload_file.body) == 'jpeg': file_type = 'jpg' elif imghdr.what(None, upload_file.body) == 'png': file_type = 'png' elif imghdr.what(None, upload_file.body) == 'gif': file_type = 'gif' else: raise InvalidUsage('Photo must be .jpg, .png or .gif') upload = await Upload.save_upload(user_id=user_id, file_type=file_type) async with aiofiles.open(f'{UPLOAD_DIR}\\{upload.id}.{file_type}', 'wb') as f: await f.write(upload_file.body) f.close() await user.update(photo=upload.id) return text('Success')
async def extract_quiz_questions_from_quiz(quiz_id, requester=None, allow_readonly=False, query_params=None): quiz = await Quiz.get(id=quiz_id) quiz_question_ids = quiz["questions"] query_params = query_params or {} # Check if the requester is the Quiz's owner if requester and requester["id"] != quiz["creator_id"]: raise Forbidden("You are not allowed to perform this action") questions = [] if quiz_question_ids: quiz_questions = await QuizQuestion.get( in_column="id", in_values=quiz_question_ids, many=True, allow_readonly=allow_readonly, **query_params, ) questions_dict = { question["id"]: question for question in quiz_questions } questions = [ questions_dict[question_id] for question_id in quiz_question_ids ] return quiz_question_ids, questions_dict, questions
async def login_(req, login, password): """Authenticate a user, on success create a new JSON Web Token""" try: user = await RDB.get_user_by_login(login) except NotFoundError as exc: logger.log(45, "User not found (IP: %s)", req.ip) raise NotFound("User not found") from exc try: scrypt.decrypt(user.password, password, encoding=None) except scrypt.error: logger.log(45, "Wrong password for user %s (IP: %s)", user.name, req.ip) raise Forbidden("Wrong password") jwt = jwtlib.encode( { "iss": ClientType.WEBAPI.value, "sub": "webgames", "iat": datetime.utcnow(), "exp": datetime.utcnow() + JWT_EXPIRATION_TIME, "jti": str(uuid4()), "typ": ClientType.ADMIN.value if user.isadmin else ClientType.PLAYER.value, "uid": str(user.userid), "nic": user.name }, config.webapi.JWT_SECRET, algorithm='HS256') logger.info("User connected: %s", user.userid) return json({"token": jwt})
def raise_role_authorization_exception(target_role_id, action: str = None): # action = action or "perform this action" # if ROLES[target_role_id] == "admin": # raise Forbidden("Please contact the service provider" " to {}.".format(action)) action = action or "create" raise Forbidden("Only {} and upper are allowed to {} {} accounts.".format( ROLES[target_role_id - 1], action, ROLES[target_role_id]))
async def method_delete( self, request: Request, body: dict, session: DBSession, message_id: int, token: dict, *args, **kwargs, ) -> BaseHTTPResponse: """ Delete message by id :param request: :param body: :param session: :param message_id: :param token: :param args: :param kwargs: :return: """ try: db_message = delete_message(session, message_id=message_id) except DBMessageNotExists as e: raise NotFound(f"Message {message_id} not found") from e if token["sub"] not in (db_message.sender_id, db_message.recipient_id): raise Forbidden("user is not recipient or sender") try: session.commit_session() except (DBDataException, DBIntegrityException) as e: raise SanicDBException(str(e)) return await self.make_response_json(status=204)
async def method_get( self, request: Request, body: dict, session: DBSession, message_id: int, token: dict, *args, **kwargs, ) -> BaseHTTPResponse: """ Get message by id :param request: :param body: :param session: :param message_id: :param token: :param args: :param kwargs: :return: """ try: db_message = get_message_by_id(session, message_id=message_id) except DBMessageNotExists as e: raise NotFound(f"Message {message_id} not found") from e if token["sub"] not in (db_message.sender_id, db_message.recipient_id): raise Forbidden("user is not recipient or sender") response_model = ResponseMessageDto(db_message) return await self.make_response_json(status=200, body=response_model.dump())
async def sendmail(request, user_id, account): """ Send an email """ if account.name != user_id: raise Forbidden("You can't consult another person account.") data = request.json from_addr = mailutils.parse_email(account.address) all_addrs = [ mailutils.parse_email(a["address"]) for a in data["recipients"] ] tos = [ mailutils.parse_email(a["address"]) for a in data["recipients"] if a["type"] == "to" ] ccs = [ mailutils.parse_email(a["address"]) for a in data["recipients"] if a["type"] == "cc" ] attachments = data.get("attachments", []) msg = mailutils.make_msg(data["subject"], data["content"], from_addr, tos, ccs, attachments) result = await send_mail(account, msg, from_addr, all_addrs) return json(result)
async def unreads(request, user_id, account): if account.name != user_id: raise Forbidden("You can't consult another person account.") mbxs = await storage.get_unreads(account) return json(mbxs)
async def mailbox(request, mailbox_id, account): mailbox_to_return = await storage.get_mailbox(mailbox_id) if mailbox_to_return['account'] != account.name: raise Forbidden("You don't have permission to see this mailbox.") for m in mailbox_to_return['messages']: if isinstance(m['date'], datetime.datetime): # Also strange hack m['date'] = m['date'].isoformat() return json(mailbox_to_return)
async def get_locked_user_number(request: Request) -> HTTPResponse: api_key = get_args(request, 'api_key')[0] api_key_status = await request.app.moca_access.check_api_key( api_key, '-SM-', request) if api_key_status[0] != 0: raise Forbidden(api_key_status[1]) count = await request.app.moca_users.get_locked_user_number() return text(str(count))
async def get_user_phone_number(request: Request) -> HTTPResponse: api_key, userid_list = get_args(request, 'api_key', 'userid_list') api_key_status = await request.app.moca_access.check_api_key( api_key, '-SM-', request) if api_key_status[0] != 0: raise Forbidden(api_key_status[1]) data = await request.app.moca_users.get_user_phone_number(userid_list) return json(data)
async def ip_blacklist_filter(request: Request): """ Block all IPs in configs/ip_blacklist.json """ if request.app.ip_blacklist.is_in(mzk.get_remote_address(request)): raise Forbidden("Your access was blocked by ip filter.") else: pass # do nothing
async def search_users(request: Request) -> HTTPResponse: api_key, keywords = get_args(request, 'api_key', 'keywords') api_key_status = await request.app.moca_access.check_api_key( api_key, '-NR-', request) if api_key_status[0] != 0: raise Forbidden(api_key_status[1]) data = await request.app.moca_users.search_users(keywords) return json(data)
async def visitor_update(req, *, req_args, req_body, requester, **kwargs): visitor_id = req_args["id"] # Only the visitor himself can modify if requester["id"] != visitor_id: raise Forbidden("Only the visitor himself can modify.") return {"data": await Visitor.modify(req_args, req_body)}
async def get_chat_logs(request: Request) -> HTTPResponse: check_root_pass(request) name, *_ = mzk.get_args( request, ('name|n', str, None, { 'max_length': 32 }), ) if name is None: raise Forbidden('name parameter format error.') bot_id = request.app.dict_cache['name'].get(name) if bot_id is None: raise Forbidden('unknown bot name.') res = await request.app.mysql.execute_aio(core.GET_CHAT_LOGS_QUERY, (bot_id, )) return json([(item[0], item[1], item[2], item[3], str(item[4]), item[5], item[6]) for item in res])
async def update_user(request, *args, **kwargs): if request.json is None: raise InvalidUsage("invalid payload (empty payload not allowed)") try: requested_user_id = int(request.path.split("/")[2]) except ValueError as e: raise InvalidUsage(e) user_from_token = retrieve_user(request, args, kwargs) if user_from_token is None: raise InvalidUsage("invalid parameter (maybe expired?)") if ( "admin" not in user_from_token.scopes and "manager" not in user_from_token.scopes ): if requested_user_id != user_from_token.user_id: raise Forbidden(f"user can only update self") user = User.get_by_user_id(requested_user_id) if not user: raise InvalidUsage("invalid parameter") if ( "manager" in user_from_token.scopes and "admin" not in user_from_token.scopes and ("manager" in user.scopes or "admin" in user.scopes) ): if requested_user_id != user_from_token.user_id: raise Forbidden(f"manager can only update manager") if "password" in request.json: password = request.json["password"] if not password_validator(password): raise InvalidUsage("password does not match minimum requirements") user.update_password(encrypt(password)) if "email" in request.json: user.update_email(request.json["email"]) if "name" in request.json: user.update_name(request.json["name"]) user.save(modifying_user_id=user_from_token.user_id) return response.HTTPResponse(status=204)