security updates and some change on protobuf
This commit is contained in:
		
							parent
							
								
									1bc1497113
								
							
						
					
					
						commit
						350b611f1b
					
				
					 11 changed files with 131 additions and 77 deletions
				
			
		|  | @ -2,6 +2,7 @@ import os | |||
| import hashlib | ||||
| import time | ||||
| from typing import List | ||||
| import secrets | ||||
| 
 | ||||
| from playwright.sync_api import sync_playwright | ||||
| from jinja2 import Environment, FileSystemLoader, StrictUndefined | ||||
|  | @ -67,7 +68,7 @@ def render(post_context:dict) -> tuple[list[str], int]: | |||
|             # screen shot | ||||
|             time.sleep(0.1) | ||||
|             while True: | ||||
|                 filename = TMP + hashlib.sha512( str(time.time()).encode() ).hexdigest() + ".jpg" | ||||
|                 filename = TMP + hashlib.sha512( (str(time.time()) + secrets.token_urlsafe(nbytes=24)).encode() ).hexdigest() + ".jpg" | ||||
|                 filename = os.path.abspath(filename) | ||||
|                 page.screenshot(path=filename) | ||||
|                 fnlist.append(filename) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import time | ||||
| import hashlib | ||||
| import secrets | ||||
| import os | ||||
| 
 | ||||
| from PIL import Image, ImageDraw, ImageFont | ||||
|  | @ -33,7 +34,7 @@ def gen(context:dict) -> str | None: | |||
|               fill=(0, 0, 0)) | ||||
|      | ||||
|     # save | ||||
|     filename = TMP + hashlib.sha512( str(time.time()).encode() ).hexdigest() + ".jpg" | ||||
|     filename = TMP + hashlib.sha512( (str(time.time()) + secrets.token_urlsafe(nbytes=24)).encode() ).hexdigest() + ".jpg" | ||||
|     img.save(filename) | ||||
|     filename = os.path.abspath(filename) | ||||
|      | ||||
|  |  | |||
							
								
								
									
										18
									
								
								TODO
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								TODO
									
										
									
									
									
								
							|  | @ -1,18 +1,18 @@ | |||
| [ ] Vuln: XSS in PictureMaker.default : jinja2沒開模板轉義 | ||||
| [ ] Hash只用timestamp當作seed可能不夠(會撞) | ||||
| [ ] backend.utils.fileProcessor那邊,考慮改善寫法跟加強安全(尤其是考慮關閉管道) | ||||
| 
 | ||||
| [ ] 2FA : Merge from sljh niming | ||||
| [ ] api: ID查IGID,IGID反查ID,批量查詢 | ||||
| [ ] 隊列本地檔案存儲(持久化) (測試) | ||||
| [ ] 隊列重試機制 | ||||
| [ ] 改善Traceback的收集 | ||||
| [ ] api: 返回錯誤處理紀錄 | ||||
| [ ] 處理因為ig媒體畫面比例固定,但是使用者圖片畫面比例不固定導致的問題 | ||||
|     看要不要幫使用者的媒體填充畫面到正確的比例 | ||||
| [ ] api: ID查IGID,IGID反查ID | ||||
| [ ] api: 返回錯誤處理紀錄 | ||||
| [ ] Protobuf 重新定義 | ||||
| [ ] 改善Traceback的收集 | ||||
| [ ] 隊列本地檔案存儲 | ||||
| [好了一半,反正之後還可能擴展] Protobuf 重新定義 | ||||
| 
 | ||||
| [V] 本地儲存ID對IGID表 | ||||
| [V] Webp Support | ||||
| [V] GIF Support | ||||
| [V] 使用者文章太長,溢出換頁機制 | ||||
| [V] Hash只用timestamp當作seed可能不夠(會撞) | ||||
| [V] backend.utils.fileProcessor那邊,考慮改善寫法跟加強安全(尤其是考慮關閉管道) | ||||
| 
 | ||||
| [ ] 測試 | ||||
|  | @ -51,6 +51,11 @@ def BACKEND_queue() -> dict: | |||
|     return reply | ||||
| 
 | ||||
| 
 | ||||
| # search | ||||
| def BACKEND_search(aid:int | None, igid:str | None) -> dict | None: | ||||
|     return dbhelper.solo_article_fetcher(aid=aid, igid=igid) | ||||
| 
 | ||||
| 
 | ||||
| # task: upload | ||||
| def upload(aid:int) -> Tuple[str, int]: | ||||
|     # check - visible | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| import time | ||||
| import os | ||||
| import io | ||||
| import secrets | ||||
| from typing import Tuple | ||||
| import subprocess | ||||
| 
 | ||||
| from hashlib import sha512 | ||||
| 
 | ||||
| from PIL import Image | ||||
| from pillow_heif import register_heif_opener | ||||
| import ffmpeg | ||||
|  | @ -58,14 +59,14 @@ def video_conventor(filename:str, oriFormat:str, binary:bytes) -> int: | |||
|                     crf=28, | ||||
|                     preset='medium' | ||||
|                 ) | ||||
|                 .run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True) | ||||
|                 .run_async() #(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True) | ||||
|             ) | ||||
|         else: | ||||
|             process:subprocess.Popen = ( | ||||
|                 ffmpeg | ||||
|                 .input(tmpfile, format=oriFormat) | ||||
|                 .output(filename, format='mp4') | ||||
|                 .run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True) | ||||
|                 .run_async() #(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True) | ||||
|             ) | ||||
|          | ||||
|         process.wait() | ||||
|  | @ -99,7 +100,7 @@ def file_saver(ftype:str, binary:bytes) -> Tuple[str, int]: | |||
|         return "Invalid file type", 1 | ||||
|      | ||||
|     # 如果不是 IG 本身支援的檔案 -> 轉檔 | ||||
|     filename = sha512( str(time.time()).encode() ).hexdigest() # 暫存檔名稱 | ||||
|     filename = sha512( (str(time.time()) + secrets.token_urlsafe(nbytes=24)).encode() ).hexdigest() # 暫存檔名稱 | ||||
|     opt = "" # output file name | ||||
|     if not ( ftype == "image/jpg" or ftype == "image/webp" or \ | ||||
|             ftype == "video/mp4" ): | ||||
|  |  | |||
|  | @ -9,14 +9,14 @@ service IGAPI { | |||
| 
 | ||||
|   rpc delete (Request) returns (Reply) {} | ||||
| 
 | ||||
|   rpc setting (Request) returns (Reply) {} | ||||
| 
 | ||||
|   rpc queue (Request) returns (Reply) {} | ||||
| 
 | ||||
|   rpc search (Request) returns (Reply) {} | ||||
| } | ||||
| 
 | ||||
| message Request { | ||||
|   int64 code = 1; | ||||
|   repeated string args = 2; | ||||
|   int64 id = 1; | ||||
|   string igid = 2; | ||||
| } | ||||
| 
 | ||||
| message Reply { | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| # Generated by the protocol buffer compiler.  DO NOT EDIT! | ||||
| # NO CHECKED-IN PROTOBUF GENCODE | ||||
| # source: igapi.proto | ||||
| # Protobuf Python Version: 5.28.1 | ||||
| # Protobuf Python Version: 5.29.0 | ||||
| """Generated protocol buffer code.""" | ||||
| from google.protobuf import descriptor as _descriptor | ||||
| from google.protobuf import descriptor_pool as _descriptor_pool | ||||
|  | @ -12,8 +12,8 @@ from google.protobuf.internal import builder as _builder | |||
| _runtime_version.ValidateProtobufRuntimeVersion( | ||||
|     _runtime_version.Domain.PUBLIC, | ||||
|     5, | ||||
|     28, | ||||
|     1, | ||||
|     29, | ||||
|     0, | ||||
|     '', | ||||
|     'igapi.proto' | ||||
| ) | ||||
|  | @ -24,7 +24,7 @@ _sym_db = _symbol_database.Default() | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bigapi.proto\"%\n\x07Request\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x03\x12\x0c\n\x04\x61rgs\x18\x02 \x03(\t\"g\n\x05Reply\x12\x0b\n\x03\x65rr\x18\x01 \x01(\x03\x12\"\n\x06result\x18\x02 \x03(\x0b\x32\x12.Reply.ResultEntry\x1a-\n\x0bResultEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x32\xc0\x01\n\x05IGAPI\x12\x1b\n\x05login\x12\x08.Request\x1a\x06.Reply\"\x00\x12\"\n\x0c\x61\x63\x63ount_info\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1c\n\x06upload\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1c\n\x06\x64\x65lete\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1d\n\x07setting\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1b\n\x05queue\x12\x08.Request\x1a\x06.Reply\"\x00\x62\x06proto3') | ||||
| DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bigapi.proto\"#\n\x07Request\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04igid\x18\x02 \x01(\t\"g\n\x05Reply\x12\x0b\n\x03\x65rr\x18\x01 \x01(\x03\x12\"\n\x06result\x18\x02 \x03(\x0b\x32\x12.Reply.ResultEntry\x1a-\n\x0bResultEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x32\xbf\x01\n\x05IGAPI\x12\x1b\n\x05login\x12\x08.Request\x1a\x06.Reply\"\x00\x12\"\n\x0c\x61\x63\x63ount_info\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1c\n\x06upload\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1c\n\x06\x64\x65lete\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1b\n\x05queue\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1c\n\x06search\x12\x08.Request\x1a\x06.Reply\"\x00\x62\x06proto3') | ||||
| 
 | ||||
| _globals = globals() | ||||
| _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) | ||||
|  | @ -34,11 +34,11 @@ if not _descriptor._USE_C_DESCRIPTORS: | |||
|   _globals['_REPLY_RESULTENTRY']._loaded_options = None | ||||
|   _globals['_REPLY_RESULTENTRY']._serialized_options = b'8\001' | ||||
|   _globals['_REQUEST']._serialized_start=15 | ||||
|   _globals['_REQUEST']._serialized_end=52 | ||||
|   _globals['_REPLY']._serialized_start=54 | ||||
|   _globals['_REPLY']._serialized_end=157 | ||||
|   _globals['_REPLY_RESULTENTRY']._serialized_start=112 | ||||
|   _globals['_REPLY_RESULTENTRY']._serialized_end=157 | ||||
|   _globals['_IGAPI']._serialized_start=160 | ||||
|   _globals['_IGAPI']._serialized_end=352 | ||||
|   _globals['_REQUEST']._serialized_end=50 | ||||
|   _globals['_REPLY']._serialized_start=52 | ||||
|   _globals['_REPLY']._serialized_end=155 | ||||
|   _globals['_REPLY_RESULTENTRY']._serialized_start=110 | ||||
|   _globals['_REPLY_RESULTENTRY']._serialized_end=155 | ||||
|   _globals['_IGAPI']._serialized_start=158 | ||||
|   _globals['_IGAPI']._serialized_end=349 | ||||
| # @@protoc_insertion_point(module_scope) | ||||
|  |  | |||
							
								
								
									
										29
									
								
								frontend/grpc/protobuf/igapi_pb2.pyi
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								frontend/grpc/protobuf/igapi_pb2.pyi
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| from google.protobuf.internal import containers as _containers | ||||
| from google.protobuf import descriptor as _descriptor | ||||
| from google.protobuf import message as _message | ||||
| from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional | ||||
| 
 | ||||
| DESCRIPTOR: _descriptor.FileDescriptor | ||||
| 
 | ||||
| class Request(_message.Message): | ||||
|     __slots__ = ("id", "igid") | ||||
|     ID_FIELD_NUMBER: _ClassVar[int] | ||||
|     IGID_FIELD_NUMBER: _ClassVar[int] | ||||
|     id: int | ||||
|     igid: str | ||||
|     def __init__(self, id: _Optional[int] = ..., igid: _Optional[str] = ...) -> None: ... | ||||
| 
 | ||||
| class Reply(_message.Message): | ||||
|     __slots__ = ("err", "result") | ||||
|     class ResultEntry(_message.Message): | ||||
|         __slots__ = ("key", "value") | ||||
|         KEY_FIELD_NUMBER: _ClassVar[int] | ||||
|         VALUE_FIELD_NUMBER: _ClassVar[int] | ||||
|         key: str | ||||
|         value: str | ||||
|         def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... | ||||
|     ERR_FIELD_NUMBER: _ClassVar[int] | ||||
|     RESULT_FIELD_NUMBER: _ClassVar[int] | ||||
|     err: int | ||||
|     result: _containers.ScalarMap[str, str] | ||||
|     def __init__(self, err: _Optional[int] = ..., result: _Optional[_Mapping[str, str]] = ...) -> None: ... | ||||
|  | @ -3,9 +3,9 @@ | |||
| import grpc | ||||
| import warnings | ||||
| 
 | ||||
| from frontend.grpc.protobuf import igapi_pb2 as igapi__pb2 | ||||
| import frontend.grpc.protobuf.igapi_pb2 as igapi__pb2 | ||||
| 
 | ||||
| GRPC_GENERATED_VERSION = '1.68.0' | ||||
| GRPC_GENERATED_VERSION = '1.71.0' | ||||
| GRPC_VERSION = grpc.__version__ | ||||
| _version_not_supported = False | ||||
| 
 | ||||
|  | @ -54,13 +54,13 @@ class IGAPIStub(object): | |||
|                 request_serializer=igapi__pb2.Request.SerializeToString, | ||||
|                 response_deserializer=igapi__pb2.Reply.FromString, | ||||
|                 _registered_method=True) | ||||
|         self.setting = channel.unary_unary( | ||||
|                 '/IGAPI/setting', | ||||
|         self.queue = channel.unary_unary( | ||||
|                 '/IGAPI/queue', | ||||
|                 request_serializer=igapi__pb2.Request.SerializeToString, | ||||
|                 response_deserializer=igapi__pb2.Reply.FromString, | ||||
|                 _registered_method=True) | ||||
|         self.queue = channel.unary_unary( | ||||
|                 '/IGAPI/queue', | ||||
|         self.search = channel.unary_unary( | ||||
|                 '/IGAPI/search', | ||||
|                 request_serializer=igapi__pb2.Request.SerializeToString, | ||||
|                 response_deserializer=igapi__pb2.Reply.FromString, | ||||
|                 _registered_method=True) | ||||
|  | @ -93,13 +93,13 @@ class IGAPIServicer(object): | |||
|         context.set_details('Method not implemented!') | ||||
|         raise NotImplementedError('Method not implemented!') | ||||
| 
 | ||||
|     def setting(self, request, context): | ||||
|     def queue(self, request, context): | ||||
|         """Missing associated documentation comment in .proto file.""" | ||||
|         context.set_code(grpc.StatusCode.UNIMPLEMENTED) | ||||
|         context.set_details('Method not implemented!') | ||||
|         raise NotImplementedError('Method not implemented!') | ||||
| 
 | ||||
|     def queue(self, request, context): | ||||
|     def search(self, request, context): | ||||
|         """Missing associated documentation comment in .proto file.""" | ||||
|         context.set_code(grpc.StatusCode.UNIMPLEMENTED) | ||||
|         context.set_details('Method not implemented!') | ||||
|  | @ -128,13 +128,13 @@ def add_IGAPIServicer_to_server(servicer, server): | |||
|                     request_deserializer=igapi__pb2.Request.FromString, | ||||
|                     response_serializer=igapi__pb2.Reply.SerializeToString, | ||||
|             ), | ||||
|             'setting': grpc.unary_unary_rpc_method_handler( | ||||
|                     servicer.setting, | ||||
|             'queue': grpc.unary_unary_rpc_method_handler( | ||||
|                     servicer.queue, | ||||
|                     request_deserializer=igapi__pb2.Request.FromString, | ||||
|                     response_serializer=igapi__pb2.Reply.SerializeToString, | ||||
|             ), | ||||
|             'queue': grpc.unary_unary_rpc_method_handler( | ||||
|                     servicer.queue, | ||||
|             'search': grpc.unary_unary_rpc_method_handler( | ||||
|                     servicer.search, | ||||
|                     request_deserializer=igapi__pb2.Request.FromString, | ||||
|                     response_serializer=igapi__pb2.Reply.SerializeToString, | ||||
|             ), | ||||
|  | @ -257,33 +257,6 @@ class IGAPI(object): | |||
|             metadata, | ||||
|             _registered_method=True) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def setting(request, | ||||
|             target, | ||||
|             options=(), | ||||
|             channel_credentials=None, | ||||
|             call_credentials=None, | ||||
|             insecure=False, | ||||
|             compression=None, | ||||
|             wait_for_ready=None, | ||||
|             timeout=None, | ||||
|             metadata=None): | ||||
|         return grpc.experimental.unary_unary( | ||||
|             request, | ||||
|             target, | ||||
|             '/IGAPI/setting', | ||||
|             igapi__pb2.Request.SerializeToString, | ||||
|             igapi__pb2.Reply.FromString, | ||||
|             options, | ||||
|             channel_credentials, | ||||
|             insecure, | ||||
|             call_credentials, | ||||
|             compression, | ||||
|             wait_for_ready, | ||||
|             timeout, | ||||
|             metadata, | ||||
|             _registered_method=True) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def queue(request, | ||||
|             target, | ||||
|  | @ -310,3 +283,30 @@ class IGAPI(object): | |||
|             timeout, | ||||
|             metadata, | ||||
|             _registered_method=True) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def search(request, | ||||
|             target, | ||||
|             options=(), | ||||
|             channel_credentials=None, | ||||
|             call_credentials=None, | ||||
|             insecure=False, | ||||
|             compression=None, | ||||
|             wait_for_ready=None, | ||||
|             timeout=None, | ||||
|             metadata=None): | ||||
|         return grpc.experimental.unary_unary( | ||||
|             request, | ||||
|             target, | ||||
|             '/IGAPI/search', | ||||
|             igapi__pb2.Request.SerializeToString, | ||||
|             igapi__pb2.Reply.FromString, | ||||
|             options, | ||||
|             channel_credentials, | ||||
|             insecure, | ||||
|             call_credentials, | ||||
|             compression, | ||||
|             wait_for_ready, | ||||
|             timeout, | ||||
|             metadata, | ||||
|             _registered_method=True) | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ class IGAPI_Server(igapi_pb2_grpc.IGAPIServicer): | |||
|      | ||||
|     async def upload(self, request: Request, context) -> Reply: | ||||
|         grpclog.info("Request: upload") | ||||
|         aid = request.code | ||||
|         aid = request.id | ||||
|         res, err = api.upload(aid) | ||||
|         if err: | ||||
|             return Reply(err=1, result={"error":res}) | ||||
|  | @ -52,7 +52,7 @@ class IGAPI_Server(igapi_pb2_grpc.IGAPIServicer): | |||
|      | ||||
|     async def delete(self, request: Request, context) -> Reply: | ||||
|         grpclog.info("Request: delete") | ||||
|         aid = request.code | ||||
|         aid = request.id | ||||
|         res, err = api.delete(aid) | ||||
|         if err: | ||||
|             return Reply(err=1, result={"error":res}) | ||||
|  | @ -66,12 +66,29 @@ class IGAPI_Server(igapi_pb2_grpc.IGAPIServicer): | |||
|         return Reply(err=0, result=reply) | ||||
|      | ||||
|      | ||||
|     async def setting(self, request:Request, context) -> Reply: | ||||
|         # not done | ||||
|         grpclog.info("Request: setting") | ||||
|         return Reply(err=1, result={"error":"Not Done"}) | ||||
|     #async def setting(self, request:Request, context) -> Reply: | ||||
|     #    # not done | ||||
|     #    grpclog.info("Request: setting") | ||||
|     #    return Reply(err=1, result={"error":"Not Done"}) | ||||
|      | ||||
|     # get igid with article id | ||||
|     # search 重作 | ||||
|     async def search(self, request:Request, context) -> Reply: | ||||
|         grpclog.info("Request: search") | ||||
|         # search | ||||
|         query_type = "" | ||||
|         if len(request.igid) > 0: # if igid is exist, use it first | ||||
|             reply = api.BACKEND_search(aid=None, igid=request.igid) | ||||
|             query_type = "igid" | ||||
|         else: | ||||
|             reply = api.BACKEND_search(aid=request.id, igid=None) | ||||
|             query_type = "id" | ||||
|         # return | ||||
|         if reply is None: | ||||
|             return Reply(err=1, result={"error":"Not found"}) | ||||
|         else: | ||||
|             reply["id"] = str(reply["id"]) | ||||
|             reply["query"] = query_type | ||||
|             return Reply(err=0, result=reply) | ||||
| 
 | ||||
| 
 | ||||
| # start server | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ python-magic | |||
| 
 | ||||
| # frontend.grpc | ||||
| grpcio | ||||
| protobuf==5.28.3 | ||||
| protobuf==5.29.0 | ||||
| 
 | ||||
| # PictureMaker | ||||
| playwright | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue