diff --git a/Sources/GuideSphinx/Orchestrator/04_HowToUse.rst b/Sources/GuideSphinx/Orchestrator/04_HowToUse.rst index 55d371a5..c2e09808 100755 --- a/Sources/GuideSphinx/Orchestrator/04_HowToUse.rst +++ b/Sources/GuideSphinx/Orchestrator/04_HowToUse.rst @@ -21,6 +21,31 @@ # Call the orchestrator main def Orchestrator.Orchestrator(inGSettings=gSettings) +************************************************************ +Шаблоны функций веб-сервера (с использованием FastAPI) +************************************************************ + +.. code-block:: python + + # ПРИМЕР Если НЕ требуется авторизация пользователя (получить inAuthTokenStr) + from fastapi import Request + from fastapi.responses import JSONResponse, HTMLResponse + @app.post("/url/to/def",response_class=JSONResponse) + async def some_def(inRequest:Request): + l_input_dict = await inRequest.json() + if lValueStr == None or lValueStr == b"": lValueStr="" + else: lValueStr = lValueStr.decode("utf8") + + # ПРИМЕР Если требуется авторизация пользователя (получить inAuthTokenStr) + from fastapi import Request + from fastapi.responses import JSONResponse, HTMLResponse + from pyOpenRPA import Orchestrator + @app.post("/url/to/def",response_class=JSONResponse) + async def some_def(inRequest:Request, inAuthTokenStr:str=Depends(Orchestrator.WebAuthDefGet())): + l_input_dict = await inRequest.json() + if lValueStr == None or lValueStr == b"": lValueStr="" + else: lValueStr = lValueStr.decode("utf8") + ****************************** Конфигурационный файл config.py diff --git a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py index 94152b46..9fe63671 100755 --- a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py +++ b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py @@ -30,7 +30,7 @@ import io from starlette.responses import StreamingResponse from typing import Union from fastapi.responses import JSONResponse - +import asyncio # # # # # # # # # # # # # v 1.2.0 Functionallity @@ -132,13 +132,11 @@ def HiddenAgentDictGenerate(inAuthTokenStr): # Client: mGlobal.pyOpenRPA.ServerDataHashStr # Client: mGlobal.pyOpenRPA.ServerDataDict -import asyncio -from fastapi import Request -@app.post("/orpa/client/server-data",response_class=JSONResponse) + +@app.post("/orpa/client/server-data",response_class=JSONResponse, tags=["Client"]) #{"Method": "POST", "URL": "/orpa/client/server-data", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerData, "ResponseContentType": "application/json"}, -async def pyOpenRPA_ServerData(inRequest: Request): +async def pyOpenRPA_ServerData(inRequest: Request, inAuthTokenStr:str=Depends(__Orchestrator__.WebAuthDefGet())): inGSettings = __Orchestrator__.GSettingsGet() - print(f"START pyOpenRPA_ServerData") # Extract the hash value from request lValueStr = await inRequest.body() lValueStr= lValueStr.decode("utf8") @@ -146,11 +144,11 @@ async def pyOpenRPA_ServerData(inRequest: Request): lFlagDoGenerateBool = True while lFlagDoGenerateBool: lServerDataDict = { - "CPDict": None,#HiddenCPDictGenerate(inRequest=inRequest, inGSettings=inGSettings), - "RDPDict": None,#HiddenRDPDictGenerate(inRequest=inRequest, inGSettings=inGSettings), - "AgentDict": None,#HiddenAgentDictGenerate(inRequest=inRequest, inGSettings=inGSettings), + "CPDict": HiddenCPDictGenerate(inAuthTokenStr=inAuthTokenStr), + "RDPDict": HiddenRDPDictGenerate(inAuthTokenStr=inAuthTokenStr), + "AgentDict": HiddenAgentDictGenerate(inAuthTokenStr=inAuthTokenStr), "UserDict": {"UACClientDict": {}, "CWDPathStr": os.getcwd(), "VersionStr": inGSettings["VersionStr"]}, - } # inRequest.OpenRPA["DefUserRoleHierarchyGet"]() + } # Create JSON lServerDataDictJSONStr = json.dumps(lServerDataDict) # Generate hash @@ -162,12 +160,6 @@ async def pyOpenRPA_ServerData(inRequest: Request): await asyncio.sleep(inGSettings["Client"]["Session"]["ControlPanelRefreshIntervalSecFloat"]) # Return the result if Hash is changed lResult = {"HashStr": lServerDataHashStr, "ServerDataDict": lServerDataDict} - #inResponseDict = inRequest.OpenRPAResponseDict - # Send message back to client - #message = json.dumps(lResult) - # Write content as utf-8 data - #inResponseDict["Body"] = bytes(message, "utf8") - print(f"STOP pyOpenRPA_ServerData") return lResult # GET @@ -184,9 +176,12 @@ def pyOpenRPA_ServerJSInit(inRequest,inGSettings): # /pyOpenRPA/ServerLog return {"HashStr" , "ServerLogList": ["row 1", "row 2"]} # Client: mGlobal.pyOpenRPA.ServerLogListHashStr # Client: mGlobal.pyOpenRPA.ServerLogList -async def pyOpenRPA_ServerLog(inRequest,inGSDict): +@app.post("/orpa/client/server-log",response_class=JSONResponse, tags=["Client"]) +async def pyOpenRPA_ServerLog(inRequest: Request, inAuthTokenStr:str=Depends(__Orchestrator__.WebAuthDefGet())): + inGSDict = __Orchestrator__.GSettingsGet() # Extract the hash value from request - lValueStr = inRequest.body + lValueStr = await inRequest.body() + lValueStr= lValueStr.decode("utf8") # Generate ServerDataDict lFlagDoGenerateBool = True while lFlagDoGenerateBool: @@ -199,11 +194,6 @@ async def pyOpenRPA_ServerLog(inRequest,inGSDict): asyncio.sleep(inGSDict["Client"]["DumpLogListRefreshIntervalSecFloat"]) # Return the result if Hash is changed lResult = {"HashStr": lServerLogListHashStr, "ServerLogList": lServerLogList} - inResponseDict = inRequest.OpenRPAResponseDict - # Send message back to client - message = json.dumps(lResult) - # Write content as utf-8 data - inResponseDict["Body"] = bytes(message, "utf8") return lResult # Get thread list /orpa/threads diff --git a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py index f6f9c5ac..2ce216b2 100755 --- a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py +++ b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py @@ -1,5 +1,6 @@ from re import I import subprocess, json, psutil, time, os +import typing from pyOpenRPA.Tools import CrossOS if CrossOS.IS_WINDOWS_BOOL: import win32security #CrossOS if CrossOS.IS_LINUX_BOOL: from simplepam import authenticate #CrossOS @@ -42,7 +43,9 @@ import math import glob # search the files import urllib +from . import ServerSettings +from fastapi import FastAPI, Depends #Единый глобальный словарь (За основу взять из Settings.py) gSettingsDict = None @@ -1053,6 +1056,101 @@ def GSettingsKeyListValueOperatorPlus(inValue, inKeyList=None, inGSettings = Non # # # # # # # # # # # # # # # # # # # # # # # # OrchestratorWeb defs # # # # # # # # # # # # # # # # # # # # # # # +def WebUserLoginGet(inAuthTokenStr: str=None) -> str: + """L+,W+: Получить логин авторизованного пользователя. Если авторизация не производилась - вернуть None + + :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) + :type inAuthTokenStr: str, опционально + :return: Логин пользователя + :rtype: str + """ + if inAuthTokenStr is None: return None + inGS = GSettingsGet() # Get the global settings + return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("User", None) + +def WebUserDomainGet(inAuthTokenStr: str=None) -> str: + """L+,W+: Получить домен авторизованного пользователя. Если авторизация не производилась - вернуть None + + :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) + :type inAuthTokenStr: str, опционально + :return: Домен пользователя + :rtype: str + """ + + if inAuthTokenStr is None: return None + inGS = GSettingsGet() # Get the global settings + return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("Domain", None) + + +def WebUserInfoGet(inAuthTokenStr=None): + """L+,W+: Информация о пользователе, который отправил HTTP запрос. + + :param inRequest: Экземпляр HTTP request. Опционален, если сообщение фиксируется из под потока, который был инициирован запросом пользователя + :return: Сведения в формате {"DomainUpperStr": "PYOPENRPA", "UserNameUpperStr": "IVAN.MASLOV"} + """ + try: + lResultDict = { + "DomainUpperStr": WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper(), + "UserNameUpperStr": WebUserLoginGet(inAuthTokenStr=inAuthTokenStr).upper() + } + return lResultDict + except Exception as e: + return {"DomainUpperStr": None, "UserNameUpperStr": None} + +def WebUserIsSuperToken(inAuthTokenStr: str=None): + """L+,W+: [ИЗМЕНЕНИЕ В 1.3.1] Проверить, авторизован ли HTTP запрос с помощью супер токена (токен, который не истекает). + + :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) + :type inAuthTokenStr: str, опционально + :return: True - является супертокеном; False - не является супертокеном; None - авторизация не производилась + """ + if inAuthTokenStr is None: return None + inGSettings = GSettingsGet() # Get the global settings + lIsSuperTokenBool = False + # Get Flag is supertoken (True|False) + lIsSuperTokenBool = inGSettings.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("FlagDoNotExpire", False) + return lIsSuperTokenBool + +def WebUserUACHierarchyGet(inAuthTokenStr: str=None) -> dict: + """L+,W+: [ИЗМЕНЕНИЕ В 1.3.1] Вернуть словарь доступа UAC в отношении пользователя, который выполнил HTTP запрос inRequest + + :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) + :type inAuthTokenStr: str, опционально + :return: UAC словарь доступа или {}, что означает полный доступ + """ + lDomainUpperStr = WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper() + lUserUpperStr = WebUserLoginGet(inAuthTokenStr=inAuthTokenStr).upper() + if lUserUpperStr is None: return {} + else: return GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("RuleDomainUserDict", {}).get((lDomainUpperStr, lUserUpperStr), {}).get("RoleHierarchyAllowedDict", {}) + +def WebUserUACCheck(inAuthTokenStr:str=None, inKeyList:list=None) -> bool: + """L+,W+: Проверить UAC доступ списка ключей для пользователя + + :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) + :type inAuthTokenStr: str, опционально + :return: True - доступ имеется, False - доступа нет + :rtype: bool + """ + if inAuthTokenStr is None: return True # Если авторизации не происходило - супердоступ + lResult = True # Init flag + lRoleHierarchyDict = WebUserUACHierarchyGet(inAuthTokenStr=inAuthTokenStr) # get the Hierarchy + # Try to get value from key list + lKeyValue = lRoleHierarchyDict # Init the base + for lItem in inKeyList: + if type(lKeyValue) is dict: + if lItem in lKeyValue: # Has key + lKeyValue = lKeyValue[lItem] # Get the value and go to the next loop iteration + else: # Else branch - true or false + if len(lKeyValue)>0: # False - if Dict has some elements + lResult = False # Set the False Flag + else: + lResult = True # Set the True flag + break # Stop the loop + else: # Has element with no detalization - return True + lResult = True # Set the flag + break # Close the loop + return lResult # Return the result + def WebURLIndexChange(inURLIndexStr:str ="/"): """L+,W+: Изменить адрес главной страницы Оркестратора. По умолчанию '/' @@ -1315,107 +1413,31 @@ def WebRequestGet(): lCurrentThread = threading.current_thread() if hasattr(lCurrentThread, "request"): return lCurrentThread.request -from fastapi import FastAPI + def WebAppGet() -> FastAPI: """L+,W+: Вернуть экземпляр веб сервера fastapi.FastAPI (app). Подробнее про app см. https://fastapi.tiangolo.com/tutorial/first-steps/ """ return Server.app -def WebUserLoginGet(inAuthTokenStr: str=None) -> str: - """L+,W+: Получить логин авторизованного пользователя. Если авторизация не производилась - вернуть None - - :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) - :type inAuthTokenStr: str, опционально - :return: Логин пользователя - :rtype: str - """ - if inAuthTokenStr is None: return None - inGS = GSettingsGet() # Get the global settings - return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("User", None) - -def WebUserDomainGet(inAuthTokenStr: str=None) -> str: - """L+,W+: Получить домен авторизованного пользователя. Если авторизация не производилась - вернуть None - - :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) - :type inAuthTokenStr: str, опционально - :return: Домен пользователя - :rtype: str - """ - - if inAuthTokenStr is None: return None - inGS = GSettingsGet() # Get the global settings - return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("Domain", None) - - -def WebUserInfoGet(inAuthTokenStr=None): - """L+,W+: Информация о пользователе, который отправил HTTP запрос. - :param inRequest: Экземпляр HTTP request. Опционален, если сообщение фиксируется из под потока, который был инициирован запросом пользователя - :return: Сведения в формате {"DomainUpperStr": "PYOPENRPA", "UserNameUpperStr": "IVAN.MASLOV"} - """ - try: - lResultDict = { - "DomainUpperStr": WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper(), - "UserNameUpperStr": WebUserLoginGet(inAuthTokenStr=inAuthTokenStr).upper() - } - return lResultDict - except Exception as e: - return {"DomainUpperStr": None, "UserNameUpperStr": None} +def WebAuthDefGet(): + """Вернуть функцию авторизации пользователя. Функция может использоваться для доменной авторизации. -def WebUserIsSuperToken(inAuthTokenStr: str=None): - """L+,W+: [ИЗМЕНЕНИЕ В 1.3.1] Проверить, авторизован ли HTTP запрос с помощью супер токена (токен, который не истекает). + .. code-block:: python - :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) - :type inAuthTokenStr: str, опционально - :return: True - является супертокеном; False - не является супертокеном; None - авторизация не производилась - """ - if inAuthTokenStr is None: return None - inGSettings = GSettingsGet() # Get the global settings - lIsSuperTokenBool = False - # Get Flag is supertoken (True|False) - lIsSuperTokenBool = inGSettings.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("FlagDoNotExpire", False) - return lIsSuperTokenBool - -def WebUserUACHierarchyGet(inAuthTokenStr: str=None) -> dict: - """L+,W+: [ИЗМЕНЕНИЕ В 1.3.1] Вернуть словарь доступа UAC в отношении пользователя, который выполнил HTTP запрос inRequest - - :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) - :type inAuthTokenStr: str, опционально - :return: UAC словарь доступа или {}, что означает полный доступ - """ - lDomainUpperStr = WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper() - lUserUpperStr = WebUserLoginGet(inAuthTokenStr=inAuthTokenStr).upper() - if lUserUpperStr is None: return {} - else: return GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("RuleDomainUserDict", {}).get((lDomainUpperStr, lUserUpperStr), {}).get("RoleHierarchyAllowedDict", {}) - -def WebUserUACCheck(inAuthTokenStr:str=None, inKeyList:list=None) -> bool: - """L+,W+: Проверить UAC доступ списка ключей для пользователя - - :param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен) - :type inAuthTokenStr: str, опционально - :return: True - доступ имеется, False - доступа нет - :rtype: bool + # ПРИМЕР Если требуется авторизация пользователя (получить inAuthTokenStr) + from fastapi import Request + from fastapi.responses import JSONResponse, HTMLResponse + from pyOpenRPA import Orchestrator + @app.post("/url/to/def",response_class=JSONResponse) + async def some_def(inRequest:Request, inAuthTokenStr:str=Depends(Orchestrator.WebAuthDefGet())): + l_input_dict = await inRequest.json() + if lValueStr == None or lValueStr == b"": lValueStr="" + else: lValueStr = lValueStr.decode("utf8") + + :return: Функция авторизации + :rtype: def """ - if inAuthTokenStr is None: return True # Если авторизации не происходило - супердоступ - lResult = True # Init flag - lRoleHierarchyDict = WebUserUACHierarchyGet(inAuthTokenStr=inAuthTokenStr) # get the Hierarchy - # Try to get value from key list - lKeyValue = lRoleHierarchyDict # Init the base - for lItem in inKeyList: - if type(lKeyValue) is dict: - if lItem in lKeyValue: # Has key - lKeyValue = lKeyValue[lItem] # Get the value and go to the next loop iteration - else: # Else branch - true or false - if len(lKeyValue)>0: # False - if Dict has some elements - lResult = False # Set the False Flag - else: - lResult = True # Set the True flag - break # Stop the loop - else: # Has element with no detalization - return True - lResult = True # Set the flag - break # Close the loop - return lResult # Return the result - - + return ServerSettings.IdentifyAuthorize def StorageRobotExists(inRobotNameStr): """L+,W+: Проверить, существует ли ключ inRobotNameStr в хранилище пользовательской информации StorageDict (GSettings > StarageDict) diff --git a/changelog.md b/changelog.md index 73e68936..ff569341 100755 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,11 @@ STD - STUDIO RBT - ROBOT AGT - AGENT +[1.3.2] +- ОРКЕСТРАТОР +- - Поддержка многотысячной аудитории, одновременно работающей в панели управления (async server-data server-log with fastapi) +- - Новые функции для упрощенной работы с FastAPI: Orchestrator.WebAuthDefGet Orchestrator.WebAppGet + [1.3.1] - ОРКЕСТРАТОР - - минорные правки в дизайн