1- """Credential fill rules shared by the sync and async clients and the tools ."""
1+ """Credential fill serialization shared by sync and async clients."""
22
33from typing import Any , Dict
44
55from pydantic import ValidationError
66
77from .client ._request import coerce_request
88from .exceptions import HyperbrowserError
9- from .models .credential import CredentialFillField , FillCredentialParams
10- from .models .secret import (
11- MAX_ONEPASSWORD_REFERENCE_BYTES ,
12- SECRET_ENV_NAME_PATTERN ,
13- SECRET_NAME_PATTERN ,
14- onepassword_reference ,
15- )
16- from .models .vault import MAX_VAULT_ENV_NAME_LENGTH
9+ from .models .credential import FillCredentialParams
1710
18- MAX_CREDENTIAL_FILL_FIELDS = 16
19- MAX_CREDENTIAL_NAME_LENGTH = 255
20- MAX_CREDENTIAL_SELECTOR_BYTES = 1024
21- MAX_CREDENTIAL_PAGE_ID_BYTES = 128
22- MAX_CREDENTIAL_URL_BYTES = 2048
23- # The server gives a fill about 18 seconds, so wait longer than that.
2411CREDENTIAL_FILL_REQUEST_TIMEOUT_SECONDS = 30
2512
2613
27- def _invalid (message : str ) -> HyperbrowserError :
28- return HyperbrowserError (message , code = "invalid_request" )
29-
30-
31- def _utf8_length (value : str ) -> int :
32- return len (value .encode ("utf-8" , "surrogatepass" ))
33-
34-
35- def _check_bytes (value : Any , name : str , limit : int ) -> None :
36- if value is not None and not 0 < _utf8_length (value ) <= limit :
37- raise _invalid (f"{ name } must be 1 to { limit } bytes when set" )
38-
39-
40- def _check_onepassword_field (vault : str , entry : CredentialFillField ) -> None :
41- if entry .field is not None :
42- raise _invalid (
43- "field names a vault entry; with one_password each field takes "
44- "ref (the 1Password field) instead"
45- )
46- if entry .ref is None :
47- raise _invalid (
48- f"The field for { entry .selector !r} needs ref: the 1Password field "
49- "to type"
50- )
51- size = _utf8_length (onepassword_reference (vault , entry .ref ))
52- if size > MAX_ONEPASSWORD_REFERENCE_BYTES :
53- raise _invalid (
54- f"The 1Password reference for { entry .selector !r} is { size } bytes; "
55- f"at most { MAX_ONEPASSWORD_REFERENCE_BYTES } are allowed"
56- )
57-
58-
5914def fill_credential_payload (params : Any ) -> Dict [str , Any ]:
60- """Check a fill request locally and return its JSON body.
61-
62- Raises ``HyperbrowserError`` with code ``invalid_request`` before any
63- request is sent.
64- """
15+ """Serialize aliases and frame targeting; the API validates fill policy."""
6516 try :
6617 request = coerce_request (params , FillCredentialParams )
6718 except ValidationError as error :
@@ -70,76 +21,11 @@ def fill_credential_payload(params: Any) -> Dict[str, Any]:
7021 f"{ problem ['msg' ]} "
7122 for problem in error .errors ()
7223 )
73- raise _invalid (f"Invalid credential fill request: { problems } " ) from None
24+ raise HyperbrowserError (
25+ f"Invalid credential fill request: { problems } " , code = "invalid_request"
26+ ) from None
7427 except TypeError as error :
75- raise _invalid (str (error )) from None
76-
77- sources = [request .credential , request .vault , request .one_password ]
78- if sum (source is not None for source in sources ) != 1 :
79- raise _invalid (
80- "Pass exactly one of credential (a secret attached to the session), "
81- "vault (a vault attached to the session) and one_password (1Password "
82- "fields read with an op_token attached to the session)"
83- )
84- if request .credential is not None and not (
85- request .credential .strip ()
86- and len (request .credential ) <= MAX_CREDENTIAL_NAME_LENGTH
87- ):
88- raise _invalid ("credential must name a secret attached to the session" )
89- if request .vault is not None and not SECRET_NAME_PATTERN .fullmatch (request .vault ):
90- raise _invalid (
91- f"Invalid vault name { request .vault !r} : names are 1-128 letters, "
92- "digits, '_' or '-', starting with a letter or digit"
93- )
94- if not 1 <= len (request .fields ) <= MAX_CREDENTIAL_FILL_FIELDS :
95- raise _invalid (
96- f"fields must have 1 to { MAX_CREDENTIAL_FILL_FIELDS } entries, "
97- f"got { len (request .fields )} "
98- )
99- selectors = set ()
100- for entry in request .fields :
101- if not entry .selector .strip ():
102- raise _invalid ("A field's selector is empty" )
103- if _utf8_length (entry .selector ) > MAX_CREDENTIAL_SELECTOR_BYTES :
104- raise _invalid (
105- f"The selector { entry .selector [:64 ]!r} is longer than "
106- f"{ MAX_CREDENTIAL_SELECTOR_BYTES } bytes"
107- )
108- if entry .selector in selectors :
109- raise _invalid (f"The selector { entry .selector !r} is listed more than once" )
110- selectors .add (entry .selector )
111- if request .one_password is not None :
112- _check_onepassword_field (request .one_password .vault , entry )
113- continue
114- if entry .ref is not None :
115- raise _invalid (
116- "ref names a 1Password field; pass it only with one_password"
117- )
118- if request .vault is None :
119- if entry .field is not None :
120- raise _invalid (
121- "field names a vault entry; leave it out when filling a "
122- "single credential"
123- )
124- elif entry .field is None :
125- raise _invalid (
126- f"The field for { entry .selector !r} needs field: the vault entry "
127- "to type"
128- )
129- elif (
130- not SECRET_ENV_NAME_PATTERN .fullmatch (entry .field )
131- or len (entry .field ) > MAX_VAULT_ENV_NAME_LENGTH
132- or entry .field == "__proto__"
133- ):
134- raise _invalid (
135- f"Invalid field { entry .field !r} : vault entries are environment "
136- "variable names (letters, digits and '_', not starting with a digit)"
137- )
138- if request .page_id is not None and request .page_url is not None :
139- raise _invalid ("Pass page_id or page_url, not both" )
140- _check_bytes (request .page_id , "page_id" , MAX_CREDENTIAL_PAGE_ID_BYTES )
141- _check_bytes (request .page_url , "page_url" , MAX_CREDENTIAL_URL_BYTES )
142- _check_bytes (request .frame_url , "frame_url" , MAX_CREDENTIAL_URL_BYTES )
28+ raise HyperbrowserError (str (error ), code = "invalid_request" ) from None
14329
14430 body = request .model_dump (by_alias = True , exclude_none = True )
14531 if request .frame_url is not None :
0 commit comments