Google homegraph API 返回 404 在同步请求上找不到实体 - 未收到初始同步意图

问题描述 投票:0回答:1

我在初始帐户链接和与 google home 同步以及与 homegraph API 同步时遇到问题。我无法获得检索有效令牌(我可以用它来查询其他 API)的初始同步意图。 HomeGraph API 范围对用户无效,因此我设置了一个服务帐户令牌创建者来向 homegraph API 发出请求;该服务帐户可以向主页图发出请求,但由于我从未收到初始同步意图,因此请求同步仅返回 {“error”:{“code”:404,“message”:“未找到请求的实体。”,“状态": "NOT_FOUND" } }

我的应用程序是用 python 编写的,使用 Flask 和 ngrok(用于开发)来隧道我的本地主机。

如上所述,我已经为用户完成了 OAuth2 流程(使用范围 'openid'、'https://www.googleapis.com/auth/userinfo.profile'、'https://www.googleapis.com/auth /用户信息.电子邮件')。我无法在此处使用 https://www.googleapis.com/auth/homegraph,因为此范围仅限于用户。 (空格以避免看起来像垃圾邮件)

相反,在处理对 google HomeGraph 的意图或请求时,我使用服务帐户。

我完成了 OAuth2 流程,然后可以使用令牌访问定义的用户范围。我完成了服务帐户身份验证流程,并且可以查询 HomeGraph API(我可以在云控制台中看到我的项目中的 API 接收请求并返回错误),但我尚未实现将用户帐户链接到服务。

我的服务不会在 GHA 中使用 Google 进行连接的流程末尾进行链接,不会将初始同步意图发送到我的履行端点,因此通过此服务链接的我的设备最初不会同步,并且任何请求都会与agent_user_id将返回404实体未找到。没有初始同步意图意味着没有与服务关联的 agent_user_id,并且没有设备可以与不存在的 agent_user_id 关联

请求同步功能:

def request_sync():
        creds = service_account.Credentials.from_service_account_file(
            'filepath',
            scopes=['https://www.googleapis.com/auth/homegraph']
        )

        auth_request = google.auth.transport.requests.Request()
        creds.refresh(auth_request)
        # Define the URL for the HomeGraph API endpoint
        url = "https://homegraph.googleapis.com/v1/devices:requestSync"

        # Define the headers for the request
        headers = {
            "Authorization": "Bearer " + creds.token,
            "Content-Type": "application/json"
        }
         # Generate a unique requestId
        request_id = str(uuid.uuid4())

        agent_user_id = session.get('agent_user_id')
        # Define the body of the request
        body = {
            "agentUserId": agent_user_id,
            "async": True  # Set this to False if you want a synchronous request
        }
            # Print the body of the request for debugging
        print("Request Body:")
        print(json.dumps(body, indent=4))

        # Send the POST request
        response = requests.post(url, headers=headers, json=body)

        # Check if the request was successful
        if response.status_code == 200:
            print("Sync request was successful.")
        else:
            print("Failed to send sync request: {}".format(response.content))
            
            # Print more detailed error information for debugging
            print("Response Status Code: {}".format(response.status_code))
            print("Response Text: {}".format(response.text))
            try:
                print("Response JSON: {}".format(json.dumps(response.json(), indent=4)))
            except ValueError:
                print("Response could not be parsed as JSON.")
            return response.text

来自履行 URL 的意图处理(未完全实现,在理顺之前先查看同步):

 @app.route('/', methods=['POST'])
    def handle_request():
        global current_scene  # Declare the variable as global
        global agent_user_id
        data = request.get_json()
        intent = data['inputs'][0]['intent']

        if intent == 'action.devices.SYNC':
            print(intent)
            redirect(url_for('sync'))

      

        return json.dumps({"status": "success"})

用户的 OAuth 2 流程:

@app.route('/callback')
    def callback():
        print('Callback route was called')
        global session
        # Specify the state when creating the flow in the callback so that it can
        # verified in the authorization server response.
        #Many flask functions as imported at top
        state = session['state']

        flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
            CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
        flow.redirect_uri = "https://903e-2607-fea8-ff01-7fe9-5c8f-a50d-dd11-2c8.ngrok-free.app/callback"

        # Use the authorization server's response to fetch the OAuth 2.0 tokens. (this url is the response from google in our authrisation function where it redirects to google.  Google then responds to this oauth2 callback so we are passing that authorisation response exactly to get the token for making authenticated requests)
        authorization_response = request.url
        print(state)
        flow.fetch_token(authorization_response=authorization_response)

        # Store credentials in the session.
        credentials = flow.credentials
        # Convert credentials to a dictionary and store it back in credentials
        credentials = credentials_to_dict(credentials)

        session['credentials'] = credentials
        # Set agent_user_id in session after OAuth 2.0 flow is complete.
       
        user_info = get_user_info(credentials['token'])
        session['agent_user_id'] = user_info['sub']  # unique id for signed in account
        print(session['agent_user_id'])
        return request_sync()



    @app.route('/authorize')
    def authorize():
        print('Authorize route was called')
        # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
        flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
            CLIENT_SECRETS_FILE, scopes=SCOPES)

        
        flow.redirect_uri = "https://903e-2607-fea8-ff01-7fe9-5c8f-a50d-dd11-2c8.ngrok-free.app/callback"
        #creates auth url with link to google and returns it as well as the included state to variables
        authorization_url, state = flow.authorization_url(
        
        access_type='offline',
       
        include_granted_scopes='true',
        prompt='consent')

        global session
        session['state'] = state
        print(state)
       
        return redirect(authorization_url)


    def credentials_to_dict(credentials):
        return {
            'token': credentials.token,
            'refresh_token': credentials.refresh_token,
            'token_uri': credentials.token_uri,
            'client_id': credentials.client_id,
            'client_secret': credentials.client_secret,
            'scopes': credentials.scopes
        }

同步意图处理片段:

#for handling google requests
    app = Flask(__name__)
    app.secret_key= secrets.token_hex()

    @app.route('/', methods=['POST'])
    def handle_request():
        global current_scene  # Declare the variable as global
        global agent_user_id
        data = request.get_json()
        intent = data['inputs'][0]['intent']

        if intent == 'action.devices.SYNC':
            print(intent)
            redirect(url_for('sync'))

以及同步意图响应是否被调用(当前未触发)

 @app.route('/sync')
    def sync():
        creds = service_account.Credentials.from_service_account_file(
            'filepath',
            scopes=['https://www.googleapis.com/auth/homegraph']
        )

        auth_request = google.auth.transport.requests.Request()
        creds.refresh(auth_request)
        # Define the URL for the HomeGraph API endpoint
        url = "https://homegraph.googleapis.com/v1/devices:sync"

        scenes = []
        with open('Scenes.txt', 'r') as file:
            for line in file:
                scene = json.loads(line.strip())  
                scenes.append(scene)

        # Generate a unique requestId
        request_id = str(uuid.uuid4())
        # Define the headers for the request
        headers = {
            "Authorization": "Bearer " + creds.token,
            "Content-Type": "application/json"
        }

        agent_user_id = session.get('agent_user_id')
        # Define the body of the response
        body = {
            "requestId": request_id,
            "payload": {
                "agentUserId": agent_user_id,
                "devices": scenes
            }
        }
        # Return the response
        requests.post(url, headers=headers, json=body) # Send the POST request
        print (body)
        return body

最后,错误:

Failed to send sync request: b'{\n  "error": {\n    "code": 404,\n    "message": "Requested entity was not found.",\n    "status": "NOT_FOUND"\n  }\n}\n'
Response Status Code: 404
Response Text: {
  "error": {
    "code": 404,
    "message": "Requested entity was not found.",
    "status": "NOT_FOUND"
  }
}

Response JSON: {
    "error": {
        "code": 404,
        "message": "Requested entity was not found.",
        "status": "NOT_FOUND"
    }
}

如果有人能帮助我理解我所缺少的东西,我将是一个非常快乐的开发者!谢谢:)

python google-cloud-platform google-api iot google-home
1个回答
0
投票

错误在于我使用谷歌进行身份验证的方法。 homegraph api 的身份验证和请求的工作方式与用户范围的 api 不同。我在第一种方法中不明白这的含义,最终将谷歌用户帐户身份验证访问流程(用于查询个人谷歌帐户信息)与使用我的授权服务帐户单独访问 homegraph api 混合在一起。

对于那些和我一样想尝试理解这个流程的人:

您需要实施特定于您的应用程序的用户帐户(创建和登录)。 Homegraph 将您正在构建的服务/应用程序视为具有自己帐户的自己的实体,并且 homegraph 会记录与您的服务下的用户帐户关联的 google 帐户(假设您正确完成了用于注册新“作品”的 oauth 流程)通过 google home 应用程序使用 google 服务/应用程序)。

通过服务帐户将您的谷歌帐户链接到您的服务/应用程序帐户的 Oauth 流程需要通过工作登录到您自己在服务/应用程序上创建的帐户(与谷歌无关 - 您自己的用户文件/数据库等)与谷歌主页中的谷歌部分。这将使用正确的参数在您的授权端点处启动流程,您可以使用代码和状态进行重定向,然后(重要)假设身份验证代码和状态在您的令牌端点匹配,生成令牌并返回以供谷歌用于身份验证使用您的应用程序请求,而不是每次都手动登录。

总而言之,homegraph api 将通过您的服务帐户与所有用户进行交互。假设登录成功并且正确生成/提供了一个令牌给google,google home应用程序会通过您的服务帐户(每个服务帐户)在homegraph中注册您的应用程序用户与您的google帐户(登录到google home应用程序的帐户)相关联仅限于访问自己的数据,因此所有服务、其关联的帐户/设备等对其帐户所有者而言都是独立且单一的,无论服务有多少用户,或用户链接到 google home 有多少服务)。

请注意,这需要事先在云控制台中使用适当的 api/服务帐户设置您的云项目和操作项目,并使用与您的服务帐户相关的帐户链接以及您的身份验证、令牌、有效的 URL 配置操作项目。如果使用则重定向 uri。

© www.soinside.com 2019 - 2024. All rights reserved.