11#!/usr/bin/env python3
22
3+ from asyncio import Future
34import asyncio
45import base64
56import codecs
67from dataclasses import dataclass
78from enum import Enum
89import json
910import logging
11+ import secrets
1012import signal
1113import struct
1214import sys
1315from typing import Optional
1416
15- from dbus_next .aio import MessageBus
1617from dbus_next import Variant
18+ from dbus_next .aio import MessageBus
19+ from dbus_next .constants import MessageType
20+ from dbus_next .message import Message
1721
1822logging .basicConfig (
1923 filename = "/tmp/credential_manager_shim.log" , encoding = "utf-8" , level = logging .DEBUG
2024)
2125
26+ APP_ID = "@APP_ID@"
2227DBUS_DOC_FILE = "@DBUS_DOC_FILE@"
2328
2429
@@ -70,6 +75,61 @@ def b64_decode(s) -> bytes:
7075 return base64 .urlsafe_b64decode (s + padding )
7176
7277
78+ class PortalRequest [T ]:
79+ def __init__ (self , token : str , fut : Future ):
80+ self .token : str = token
81+ self ._fut : Future = fut
82+
83+ async def wait (self ) -> T :
84+ return await self ._fut
85+
86+
87+ def create_portal_request_message_handler (bus : MessageBus ) -> PortalRequest :
88+ loop = asyncio .get_running_loop ()
89+ future = loop .create_future ()
90+ if not bus .connected or bus .unique_name is None :
91+ raise Exception ("Bus is not connected" )
92+ unique_name = bus .unique_name [1 :].replace ("." , "_" )
93+ token = secrets .token_hex (16 )
94+ object_path = f"/org/freedesktop/portal/desktop/request/{ unique_name } /{ token } "
95+
96+ def message_handler (msg : Message ):
97+ if future .done ():
98+ return False
99+
100+ message_matches = (
101+ msg .path == object_path
102+ and msg .message_type == MessageType .SIGNAL
103+ and msg .destination == bus .unique_name
104+ and msg .interface == "org.freedesktop.portal.Request"
105+ and msg .member == "Response"
106+ )
107+ if not message_matches :
108+ return False
109+
110+ [code , value ] = msg .body
111+ if code == 0 :
112+ future .set_result (value )
113+ elif code == 1 :
114+ future .set_exception (Exception ("Portal request cancelled" ))
115+ raise
116+ elif code == 2 and "error" in value :
117+ future .set_exception (
118+ Exception (f"Portal returned an error: { value ['error' ].value } " )
119+ )
120+ else :
121+ future .set_exception (Exception ("Portal returned an unknown error" ))
122+ return True
123+
124+ def when_done (_fut ):
125+ bus .remove_message_handler (message_handler )
126+
127+ future .add_done_callback (when_done )
128+ bus .add_message_handler (message_handler )
129+ logging .debug (f"Listening for { object_path } " )
130+ return PortalRequest (token , future )
131+
132+
73133class MajorType (Enum ):
74134 PositiveInteger = (0 ,)
75135 NegativeInteger = (1 ,)
@@ -111,7 +171,7 @@ def _read_value(self, buf):
111171 argument = struct .unpack (">Q" , buf [1 : 1 + argument_len ])[0 ]
112172 elif additional_info == 31 :
113173 # Indefinite length for types 2-5
114- argument = None
174+ argument : Optional [ int ] = None
115175 argument_len = 0
116176 match buf [0 ] >> 5 :
117177 case 0 :
@@ -291,23 +351,24 @@ def has_flag(self, flag):
291351
292352async def create_passkey (interface , options , origin , top_origin ):
293353 logging .debug ("Creating passkey" )
294- is_same_origin = origin == top_origin
295354 req_json = json .dumps (options )
296355 logging .debug (req_json )
356+ request_event = create_portal_request_message_handler (interface .bus )
297357 req = {
298- "type" : Variant ("s" , "publicKey" ),
299- "origin" : Variant ("s" , origin ),
300- "is_same_origin" : Variant ("b" , is_same_origin ),
301- "publicKey" : Variant ("a{sv}" , {"request_json" : Variant ("s" , req_json )}),
358+ "handle_token" : Variant ("s" , request_event .token ),
359+ "public_key" : Variant ("s" , req_json ),
302360 }
361+ if top_origin != origin :
362+ req ["top_origin" ] = Variant ("s" , top_origin )
303363 logging .debug ("Sending request to D-Bus API" )
304- rsp = await interface .call_create_credential (["" , req ])
305- if rsp ["type" ].value != "public-key" :
364+ _rsp = await interface .call_create_credential ("" , origin , "publicKey" , req )
365+ result = await request_event .wait ()
366+ if result ["type" ].value != "public-key" :
306367 raise Exception (
307- f"Invalid credential type received: expected 'public-key', received { rsp ['type' .value ] } "
368+ f"Invalid credential type received: expected 'public-key', received { result ['type' ] .value } "
308369 )
309370 response_json = json .loads (
310- rsp ["public_key" ].value ["registration_response_json" ].value
371+ result ["public_key" ].value ["registration_response_json" ].value
311372 )
312373 attestation = cbor_loads (b64_decode (response_json ["response" ]["attestationObject" ]))
313374 auth_data_view = attestation ["authData" ]
@@ -339,7 +400,7 @@ async def get_passkey(interface, options, origin, top_origin):
339400 rsp = await interface .call_get_credential (["" , req ])
340401 if rsp ["type" ].value != "public-key" :
341402 raise Exception (
342- f"Invalid credential type received: expected 'public-key', received { rsp ['type' .value ] } "
403+ f"Invalid credential type received: expected 'public-key', received { rsp ['type' ] .value } "
343404 )
344405
345406 response_json = json .loads (
@@ -356,16 +417,31 @@ async def run(cmd, options, origin, top_origin):
356417
357418 logging .info (os .getcwd ())
358419
420+ msg = Message (
421+ "org.freedesktop.portal.Desktop" ,
422+ "/org/freedesktop/portal/desktop" ,
423+ "org.freedesktop.host.portal.Registry" ,
424+ "Register" ,
425+ signature = "sa{sv}" ,
426+ body = [
427+ APP_ID ,
428+ {},
429+ ],
430+ )
431+ await bus .call (msg )
432+
359433 with open (DBUS_DOC_FILE , "r" ) as f :
360434 introspection = f .read ()
361435
362436 proxy_object = bus .get_proxy_object (
363- "xyz.iinuwa.credentialsd.Credentials " ,
364- "/xyz/iinuwa/credentialsd/Credentials " ,
437+ "org.freedesktop.portal.Desktop " ,
438+ "/org/freedesktop/portal/desktop " ,
365439 introspection ,
366440 )
367441
368- interface = proxy_object .get_interface ("xyz.iinuwa.credentialsd.Credentials1" )
442+ interface = proxy_object .get_interface (
443+ "org.freedesktop.portal.experimental.Credential"
444+ )
369445 logging .debug (f"Connected to interface at { interface .path } " )
370446
371447 if cmd == "create" :
@@ -398,6 +474,7 @@ async def run(cmd, options, origin, top_origin):
398474
399475quit = asyncio .Event ()
400476
477+
401478async def main ():
402479 logging .info ("starting credential_manager_shim" )
403480 while not quit .is_set ():
@@ -417,5 +494,6 @@ async def main():
417494 logging .debug ("Sent error message" )
418495 logging .info ("quitting credential_manager_shim" )
419496
420- signal .signal (signal .SIGTERM , lambda _ , __ : quit .set ())
497+
498+ signal .signal (signal .SIGTERM , lambda _ , __ : quit .set ())
421499asyncio .run (main ())
0 commit comments